Feature5950: Management of quotas in VIM Account
[osm/NBI.git] / osm_nbi / authconn_keystone.py
1 # -*- coding: utf-8 -*-
2
3 # Copyright 2018 Whitestack, LLC
4 #
5 # Licensed under the Apache License, Version 2.0 (the "License"); you may
6 # not use this file except in compliance with the License. You may obtain
7 # a copy of the License at
8 #
9 # http://www.apache.org/licenses/LICENSE-2.0
10 #
11 # Unless required by applicable law or agreed to in writing, software
12 # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13 # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
14 # License for the specific language governing permissions and limitations
15 # under the License.
16 #
17 # For those usages not covered by the Apache License, Version 2.0 please
18 # contact: esousa@whitestack.com or glavado@whitestack.com
19 ##
20
21 """
22 AuthconnKeystone implements implements the connector for
23 Openstack Keystone and leverages the RBAC model, to bring
24 it for OSM.
25 """
26
27
28 __author__ = (
29 "Eduardo Sousa <esousa@whitestack.com>, "
30 "Pedro de la Cruz Ramos <pdelacruzramos@altran.com>"
31 )
32 __date__ = "$27-jul-2018 23:59:59$"
33
34 from osm_nbi.authconn import (
35 Authconn,
36 AuthException,
37 AuthconnOperationException,
38 AuthconnNotFoundException,
39 AuthconnConflictException,
40 )
41
42 import logging
43 import requests
44 import time
45 from keystoneauth1 import session
46 from keystoneauth1.identity import v3
47 from keystoneauth1.exceptions.base import ClientException
48 from keystoneauth1.exceptions.http import Conflict
49 from keystoneclient.v3 import client
50 from http import HTTPStatus
51 from osm_nbi.validation import is_valid_uuid, validate_input, http_schema
52
53
54 class AuthconnKeystone(Authconn):
55 def __init__(self, config, db, role_permissions):
56 Authconn.__init__(self, config, db, role_permissions)
57
58 self.logger = logging.getLogger("nbi.authenticator.keystone")
59 self.domains_id2name = {}
60 self.domains_name2id = {}
61
62 self.auth_url = config.get("auth_url")
63 if config.get("auth_url"):
64 validate_input(self.auth_url, http_schema)
65 else:
66 self.auth_url = "http://{0}:{1}/v3".format(
67 config.get("auth_host", "keystone"), config.get("auth_port", "5000")
68 )
69 self.user_domain_name_list = config.get("user_domain_name", "default")
70 self.user_domain_name_list = self.user_domain_name_list.split(",")
71 # read only domain list
72 self.user_domain_ro_list = [
73 x[:-3] for x in self.user_domain_name_list if x.endswith(":ro")
74 ]
75 # remove the ":ro"
76 self.user_domain_name_list = [
77 x if not x.endswith(":ro") else x[:-3] for x in self.user_domain_name_list
78 ]
79
80 self.admin_project = config.get("service_project", "service")
81 self.admin_username = config.get("service_username", "nbi")
82 self.admin_password = config.get("service_password", "nbi")
83 self.project_domain_name_list = config.get("project_domain_name", "default")
84 self.project_domain_name_list = self.project_domain_name_list.split(",")
85 if len(self.user_domain_name_list) != len(self.project_domain_name_list):
86 raise ValueError(
87 "Invalid configuration parameter fo authenticate. 'project_domain_name' and "
88 "'user_domain_name' must be a comma-separated list with the same size. Revise "
89 "configuration or/and 'OSMNBI_AUTHENTICATION_PROJECT_DOMAIN_NAME', "
90 "'OSMNBI_AUTHENTICATION_USER_DOMAIN_NAME' Variables"
91 )
92
93 # Waiting for Keystone to be up
94 available = None
95 counter = 300
96 while available is None:
97 time.sleep(1)
98 try:
99 result = requests.get(self.auth_url)
100 available = True if result.status_code == 200 else None
101 except Exception:
102 counter -= 1
103 if counter == 0:
104 raise AuthException("Keystone not available after 300s timeout")
105
106 self.auth = v3.Password(
107 user_domain_name=self.user_domain_name_list[0],
108 username=self.admin_username,
109 password=self.admin_password,
110 project_domain_name=self.project_domain_name_list[0],
111 project_name=self.admin_project,
112 auth_url=self.auth_url,
113 )
114 self.sess = session.Session(auth=self.auth)
115 self.keystone = client.Client(
116 session=self.sess, endpoint_override=self.auth_url
117 )
118
119 def authenticate(self, credentials, token_info=None):
120 """
121 Authenticate a user using username/password or token_info, plus project
122 :param credentials: dictionary that contains:
123 username: name, id or None
124 password: password or None
125 project_id: name, id, or None. If None first found project will be used to get an scope token
126 project_domain_name: (Optional) To use a concrete domain for the project
127 user_domain_name: (Optional) To use a concrete domain for the project
128 other items are allowed and ignored
129 :param token_info: previous token_info to obtain authorization
130 :return: the scoped token info or raises an exception. The token is a dictionary with:
131 _id: token string id,
132 username: username,
133 project_id: scoped_token project_id,
134 project_name: scoped_token project_name,
135 expires: epoch time when it expires,
136
137 """
138 username = None
139 user_id = None
140 project_id = None
141 project_name = None
142 if credentials.get("project_domain_name"):
143 project_domain_name_list = (credentials["project_domain_name"],)
144 else:
145 project_domain_name_list = self.project_domain_name_list
146 if credentials.get("user_domain_name"):
147 user_domain_name_list = (credentials["user_domain_name"],)
148 else:
149 user_domain_name_list = self.user_domain_name_list
150
151 for index, project_domain_name in enumerate(project_domain_name_list):
152 user_domain_name = user_domain_name_list[index]
153 try:
154 if credentials.get("username"):
155 if is_valid_uuid(credentials["username"]):
156 user_id = credentials["username"]
157 else:
158 username = credentials["username"]
159
160 # get an unscoped token firstly
161 unscoped_token = self.keystone.get_raw_token_from_identity_service(
162 auth_url=self.auth_url,
163 user_id=user_id,
164 username=username,
165 password=credentials.get("password"),
166 user_domain_name=user_domain_name,
167 project_domain_name=project_domain_name,
168 )
169 elif token_info:
170 unscoped_token = self.keystone.tokens.validate(
171 token=token_info.get("_id")
172 )
173 else:
174 raise AuthException(
175 "Provide credentials: username/password or Authorization Bearer token",
176 http_code=HTTPStatus.UNAUTHORIZED,
177 )
178
179 if not credentials.get("project_id"):
180 # get first project for the user
181 project_list = self.keystone.projects.list(
182 user=unscoped_token["user"]["id"]
183 )
184 if not project_list:
185 raise AuthException(
186 "The user {} has not any project and cannot be used for authentication".format(
187 credentials.get("username")
188 ),
189 http_code=HTTPStatus.UNAUTHORIZED,
190 )
191 project_id = project_list[0].id
192 else:
193 if is_valid_uuid(credentials["project_id"]):
194 project_id = credentials["project_id"]
195 else:
196 project_name = credentials["project_id"]
197
198 scoped_token = self.keystone.get_raw_token_from_identity_service(
199 auth_url=self.auth_url,
200 project_name=project_name,
201 project_id=project_id,
202 user_domain_name=user_domain_name,
203 project_domain_name=project_domain_name,
204 token=unscoped_token["auth_token"],
205 )
206
207 auth_token = {
208 "_id": scoped_token.auth_token,
209 "id": scoped_token.auth_token,
210 "user_id": scoped_token.user_id,
211 "username": scoped_token.username,
212 "project_id": scoped_token.project_id,
213 "project_name": scoped_token.project_name,
214 "project_domain_name": scoped_token.project_domain_name,
215 "user_domain_name": scoped_token.user_domain_name,
216 "expires": scoped_token.expires.timestamp(),
217 "issued_at": scoped_token.issued.timestamp(),
218 }
219
220 return auth_token
221 except ClientException as e:
222 if (
223 index >= len(user_domain_name_list) - 1
224 or index >= len(project_domain_name_list) - 1
225 ):
226 # if last try, launch exception
227 # self.logger.exception("Error during user authentication using keystone: {}".format(e))
228 raise AuthException(
229 "Error during user authentication using Keystone: {}".format(e),
230 http_code=HTTPStatus.UNAUTHORIZED,
231 )
232
233 def validate_token(self, token):
234 """
235 Check if the token is valid.
236
237 :param token: token id to be validated
238 :return: dictionary with information associated with the token:
239 "expires":
240 "_id": token_id,
241 "project_id": project_id,
242 "username": ,
243 "roles": list with dict containing {name, id}
244 If the token is not valid an exception is raised.
245 """
246 if not token:
247 return
248
249 try:
250 token_info = self.keystone.tokens.validate(token=token)
251 ses = {
252 "_id": token_info["auth_token"],
253 "id": token_info["auth_token"],
254 "project_id": token_info["project"]["id"],
255 "project_name": token_info["project"]["name"],
256 "user_id": token_info["user"]["id"],
257 "username": token_info["user"]["name"],
258 "roles": token_info["roles"],
259 "expires": token_info.expires.timestamp(),
260 "issued_at": token_info.issued.timestamp(),
261 }
262
263 return ses
264 except ClientException as e:
265 # self.logger.exception("Error during token validation using keystone: {}".format(e))
266 raise AuthException(
267 "Error during token validation using Keystone: {}".format(e),
268 http_code=HTTPStatus.UNAUTHORIZED,
269 )
270
271 def revoke_token(self, token):
272 """
273 Invalidate a token.
274
275 :param token: token to be revoked
276 """
277 try:
278 self.logger.info("Revoking token: " + token)
279 self.keystone.tokens.revoke_token(token=token)
280
281 return True
282 except ClientException as e:
283 # self.logger.exception("Error during token revocation using keystone: {}".format(e))
284 raise AuthException(
285 "Error during token revocation using Keystone: {}".format(e),
286 http_code=HTTPStatus.UNAUTHORIZED,
287 )
288
289 def _get_domain_id(self, domain_name, fail_if_not_found=True):
290 """
291 Get the domain id from the domain_name
292 :param domain_name: Can be the name or id
293 :param fail_if_not_found: If False it returns None instead of raising an exception if not found
294 :return: str or None/exception if domain is not found
295 """
296 domain_id = self.domains_name2id.get(domain_name)
297 if not domain_id:
298 self._get_domains()
299 domain_id = self.domains_name2id.get(domain_name)
300 if not domain_id and domain_name in self.domains_id2name:
301 # domain_name is already an id
302 return domain_name
303 if not domain_id and fail_if_not_found:
304 raise AuthconnNotFoundException(
305 "Domain {} cannot be found".format(domain_name)
306 )
307 return domain_id
308
309 def _get_domains(self):
310 """
311 Obtain a dictionary with domain_id to domain_name, stored at self.domains_id2name
312 and from domain_name to domain_id, sored at self.domains_name2id
313 :return: None. Exceptions are ignored
314 """
315 try:
316 domains = self.keystone.domains.list()
317 self.domains_id2name = {x.id: x.name for x in domains}
318 self.domains_name2id = {x.name: x.id for x in domains}
319 except Exception:
320 pass
321
322 def create_user(self, user_info):
323 """
324 Create a user.
325
326 :param user_info: full user info.
327 :raises AuthconnOperationException: if user creation failed.
328 :return: returns the id of the user in keystone.
329 """
330 try:
331
332 if (
333 user_info.get("domain_name")
334 and user_info["domain_name"] in self.user_domain_ro_list
335 ):
336 raise AuthconnConflictException(
337 "Cannot create a user in the read only domain {}".format(
338 user_info["domain_name"]
339 )
340 )
341
342 new_user = self.keystone.users.create(
343 user_info["username"],
344 password=user_info["password"],
345 domain=self._get_domain_id(
346 user_info.get("domain_name", self.user_domain_name_list[0])
347 ),
348 _admin=user_info["_admin"],
349 )
350 if "project_role_mappings" in user_info.keys():
351 for mapping in user_info["project_role_mappings"]:
352 self.assign_role_to_user(
353 new_user, mapping["project"], mapping["role"]
354 )
355 return {"username": new_user.name, "_id": new_user.id}
356 except Conflict as e:
357 # self.logger.exception("Error during user creation using keystone: {}".format(e))
358 raise AuthconnOperationException(e, http_code=HTTPStatus.CONFLICT)
359 except ClientException as e:
360 # self.logger.exception("Error during user creation using keystone: {}".format(e))
361 raise AuthconnOperationException(
362 "Error during user creation using Keystone: {}".format(e)
363 )
364
365 def update_user(self, user_info):
366 """
367 Change the user name and/or password.
368
369 :param user_info: user info modifications
370 :raises AuthconnOperationException: if change failed.
371 """
372 try:
373 user = user_info.get("_id") or user_info.get("username")
374 try:
375 user_obj = self.keystone.users.get(user)
376 except Exception:
377 user_obj = None
378 if not user_obj:
379 for user_domain in self.user_domain_name_list:
380 domain_id = self._get_domain_id(
381 user_domain, fail_if_not_found=False
382 )
383 if not domain_id:
384 continue
385 user_obj_list = self.keystone.users.list(
386 name=user, domain=domain_id
387 )
388 if user_obj_list:
389 user_obj = user_obj_list[0]
390 break
391 else: # user not found
392 raise AuthconnNotFoundException("User '{}' not found".format(user))
393
394 user_id = user_obj.id
395 domain_id = user_obj.domain_id
396 domain_name = self.domains_id2name.get(domain_id)
397
398 if domain_name in self.user_domain_ro_list:
399 if user_info.get("password") or user_info.get("username"):
400 raise AuthconnConflictException(
401 "Cannot update the user {} belonging to a read only domain {}".format(
402 user, domain_name
403 )
404 )
405
406 elif (
407 user_info.get("password")
408 or user_info.get("username")
409 or user_info.get("add_project_role_mappings")
410 or user_info.get("remove_project_role_mappings")
411 ):
412 # if user_index>0, it is an external domain, that should not be updated
413 ctime = (
414 user_obj._admin.get("created", 0)
415 if hasattr(user_obj, "_admin")
416 else 0
417 )
418 try:
419 self.keystone.users.update(
420 user_id,
421 password=user_info.get("password"),
422 name=user_info.get("username"),
423 _admin={"created": ctime, "modified": time.time()},
424 )
425 except Exception as e:
426 if user_info.get("username") or user_info.get("password"):
427 raise AuthconnOperationException(
428 "Error during username/password change: {}".format(str(e))
429 )
430 self.logger.error(
431 "Error during updating user profile: {}".format(str(e))
432 )
433
434 for mapping in user_info.get("remove_project_role_mappings", []):
435 self.remove_role_from_user(
436 user_obj, mapping["project"], mapping["role"]
437 )
438 for mapping in user_info.get("add_project_role_mappings", []):
439 self.assign_role_to_user(user_obj, mapping["project"], mapping["role"])
440 except ClientException as e:
441 # self.logger.exception("Error during user password/name update using keystone: {}".format(e))
442 raise AuthconnOperationException(
443 "Error during user update using Keystone: {}".format(e)
444 )
445
446 def delete_user(self, user_id):
447 """
448 Delete user.
449
450 :param user_id: user identifier.
451 :raises AuthconnOperationException: if user deletion failed.
452 """
453 try:
454 user_obj = self.keystone.users.get(user_id)
455 domain_id = user_obj.domain_id
456 domain_name = self.domains_id2name.get(domain_id)
457 if domain_name in self.user_domain_ro_list:
458 raise AuthconnConflictException(
459 "Cannot delete user {} belonging to a read only domain {}".format(
460 user_id, domain_name
461 )
462 )
463
464 result, detail = self.keystone.users.delete(user_id)
465 if result.status_code != 204:
466 raise ClientException("error {} {}".format(result.status_code, detail))
467 return True
468 except ClientException as e:
469 # self.logger.exception("Error during user deletion using keystone: {}".format(e))
470 raise AuthconnOperationException(
471 "Error during user deletion using Keystone: {}".format(e)
472 )
473
474 def get_user_list(self, filter_q=None):
475 """
476 Get user list.
477
478 :param filter_q: dictionary to filter user list by one or several
479 _id:
480 name (username is also admitted). If a user id is equal to the filter name, it is also provided
481 domain_id, domain_name
482 :return: returns a list of users.
483 """
484 try:
485 self._get_domains()
486 filter_name = filter_domain = None
487 if filter_q:
488 filter_name = filter_q.get("name") or filter_q.get("username")
489 if filter_q.get("domain_name"):
490 filter_domain = self._get_domain_id(
491 filter_q["domain_name"], fail_if_not_found=False
492 )
493 # If domain is not found, use the same name to obtain an empty list
494 filter_domain = filter_domain or filter_q["domain_name"]
495 if filter_q.get("domain_id"):
496 filter_domain = filter_q["domain_id"]
497
498 users = self.keystone.users.list(name=filter_name, domain=filter_domain)
499 # get users from user_domain_name_list[1:], because it will not be provided in case of LDAP
500 if filter_domain is None and len(self.user_domain_name_list) > 1:
501 for user_domain in self.user_domain_name_list[1:]:
502 domain_id = self._get_domain_id(
503 user_domain, fail_if_not_found=False
504 )
505 if not domain_id:
506 continue
507 # find if users of this domain are already provided. In this case ignore
508 for u in users:
509 if u.domain_id == domain_id:
510 break
511 else:
512 users += self.keystone.users.list(
513 name=filter_name, domain=domain_id
514 )
515
516 # if filter name matches a user id, provide it also
517 if filter_name:
518 try:
519 user_obj = self.keystone.users.get(filter_name)
520 if user_obj not in users:
521 users.append(user_obj)
522 except Exception:
523 pass
524
525 users = [
526 {
527 "username": user.name,
528 "_id": user.id,
529 "id": user.id,
530 "_admin": user.to_dict().get("_admin", {}), # TODO: REVISE
531 "domain_name": self.domains_id2name.get(user.domain_id),
532 }
533 for user in users
534 if user.name != self.admin_username
535 ]
536
537 if filter_q and filter_q.get("_id"):
538 users = [user for user in users if filter_q["_id"] == user["_id"]]
539
540 for user in users:
541 user["project_role_mappings"] = []
542 user["projects"] = []
543 projects = self.keystone.projects.list(user=user["_id"])
544 for project in projects:
545 user["projects"].append(project.name)
546
547 roles = self.keystone.roles.list(
548 user=user["_id"], project=project.id
549 )
550 for role in roles:
551 prm = {
552 "project": project.id,
553 "project_name": project.name,
554 "role_name": role.name,
555 "role": role.id,
556 }
557 user["project_role_mappings"].append(prm)
558
559 return users
560 except ClientException as e:
561 # self.logger.exception("Error during user listing using keystone: {}".format(e))
562 raise AuthconnOperationException(
563 "Error during user listing using Keystone: {}".format(e)
564 )
565
566 def get_role_list(self, filter_q=None):
567 """
568 Get role list.
569
570 :param filter_q: dictionary to filter role list by _id and/or name.
571 :return: returns the list of roles.
572 """
573 try:
574 filter_name = None
575 if filter_q:
576 filter_name = filter_q.get("name")
577 roles_list = self.keystone.roles.list(name=filter_name)
578
579 roles = [
580 {
581 "name": role.name,
582 "_id": role.id,
583 "_admin": role.to_dict().get("_admin", {}),
584 "permissions": role.to_dict().get("permissions", {}),
585 }
586 for role in roles_list
587 if role.name != "service"
588 ]
589
590 if filter_q and filter_q.get("_id"):
591 roles = [role for role in roles if filter_q["_id"] == role["_id"]]
592
593 return roles
594 except ClientException as e:
595 # self.logger.exception("Error during user role listing using keystone: {}".format(e))
596 raise AuthException(
597 "Error during user role listing using Keystone: {}".format(e),
598 http_code=HTTPStatus.UNAUTHORIZED,
599 )
600
601 def create_role(self, role_info):
602 """
603 Create a role.
604
605 :param role_info: full role info.
606 :raises AuthconnOperationException: if role creation failed.
607 """
608 try:
609 result = self.keystone.roles.create(
610 role_info["name"],
611 permissions=role_info.get("permissions"),
612 _admin=role_info.get("_admin"),
613 )
614 return result.id
615 except Conflict as ex:
616 raise AuthconnConflictException(str(ex))
617 except ClientException as e:
618 # self.logger.exception("Error during role creation using keystone: {}".format(e))
619 raise AuthconnOperationException(
620 "Error during role creation using Keystone: {}".format(e)
621 )
622
623 def delete_role(self, role_id):
624 """
625 Delete a role.
626
627 :param role_id: role identifier.
628 :raises AuthconnOperationException: if role deletion failed.
629 """
630 try:
631 result, detail = self.keystone.roles.delete(role_id)
632
633 if result.status_code != 204:
634 raise ClientException("error {} {}".format(result.status_code, detail))
635
636 return True
637 except ClientException as e:
638 # self.logger.exception("Error during role deletion using keystone: {}".format(e))
639 raise AuthconnOperationException(
640 "Error during role deletion using Keystone: {}".format(e)
641 )
642
643 def update_role(self, role_info):
644 """
645 Change the name of a role
646 :param role_info: full role info
647 :return: None
648 """
649 try:
650 rid = role_info["_id"]
651 if not is_valid_uuid(rid): # Is this required?
652 role_obj_list = self.keystone.roles.list(name=rid)
653 if not role_obj_list:
654 raise AuthconnNotFoundException("Role '{}' not found".format(rid))
655 rid = role_obj_list[0].id
656 self.keystone.roles.update(
657 rid,
658 name=role_info["name"],
659 permissions=role_info.get("permissions"),
660 _admin=role_info.get("_admin"),
661 )
662 except ClientException as e:
663 # self.logger.exception("Error during role update using keystone: {}".format(e))
664 raise AuthconnOperationException(
665 "Error during role updating using Keystone: {}".format(e)
666 )
667
668 def get_project_list(self, filter_q=None):
669 """
670 Get all the projects.
671
672 :param filter_q: dictionary to filter project list.
673 :return: list of projects
674 """
675 try:
676 self._get_domains()
677 filter_name = filter_domain = None
678 if filter_q:
679 filter_name = filter_q.get("name")
680 if filter_q.get("domain_name"):
681 filter_domain = self.domains_name2id.get(filter_q["domain_name"])
682 if filter_q.get("domain_id"):
683 filter_domain = filter_q["domain_id"]
684
685 projects = self.keystone.projects.list(
686 name=filter_name, domain=filter_domain
687 )
688
689 projects = [
690 {
691 "name": project.name,
692 "_id": project.id,
693 "_admin": project.to_dict().get("_admin", {}), # TODO: REVISE
694 "quotas": project.to_dict().get("quotas", {}), # TODO: REVISE
695 "domain_name": self.domains_id2name.get(project.domain_id),
696 }
697 for project in projects
698 ]
699
700 if filter_q and filter_q.get("_id"):
701 projects = [
702 project for project in projects if filter_q["_id"] == project["_id"]
703 ]
704
705 return projects
706 except ClientException as e:
707 # self.logger.exception("Error during user project listing using keystone: {}".format(e))
708 raise AuthException(
709 "Error during user project listing using Keystone: {}".format(e),
710 http_code=HTTPStatus.UNAUTHORIZED,
711 )
712
713 def create_project(self, project_info):
714 """
715 Create a project.
716
717 :param project_info: full project info.
718 :return: the internal id of the created project
719 :raises AuthconnOperationException: if project creation failed.
720 """
721 try:
722 result = self.keystone.projects.create(
723 project_info["name"],
724 domain=self._get_domain_id(
725 project_info.get("domain_name", self.project_domain_name_list[0])
726 ),
727 _admin=project_info["_admin"],
728 quotas=project_info.get("quotas", {}),
729 )
730 return result.id
731 except ClientException as e:
732 # self.logger.exception("Error during project creation using keystone: {}".format(e))
733 raise AuthconnOperationException(
734 "Error during project creation using Keystone: {}".format(e)
735 )
736
737 def delete_project(self, project_id):
738 """
739 Delete a project.
740
741 :param project_id: project identifier.
742 :raises AuthconnOperationException: if project deletion failed.
743 """
744 try:
745 # projects = self.keystone.projects.list()
746 # project_obj = [project for project in projects if project.id == project_id][0]
747 # result, _ = self.keystone.projects.delete(project_obj)
748
749 result, detail = self.keystone.projects.delete(project_id)
750 if result.status_code != 204:
751 raise ClientException("error {} {}".format(result.status_code, detail))
752
753 return True
754 except ClientException as e:
755 # self.logger.exception("Error during project deletion using keystone: {}".format(e))
756 raise AuthconnOperationException(
757 "Error during project deletion using Keystone: {}".format(e)
758 )
759
760 def update_project(self, project_id, project_info):
761 """
762 Change the name of a project
763 :param project_id: project to be changed
764 :param project_info: full project info
765 :return: None
766 """
767 try:
768 self.keystone.projects.update(
769 project_id,
770 name=project_info["name"],
771 _admin=project_info["_admin"],
772 quotas=project_info.get("quotas", {}),
773 )
774 except ClientException as e:
775 # self.logger.exception("Error during project update using keystone: {}".format(e))
776 raise AuthconnOperationException(
777 "Error during project update using Keystone: {}".format(e)
778 )
779
780 def assign_role_to_user(self, user_obj, project, role):
781 """
782 Assigning a role to a user in a project.
783
784 :param user_obj: user object, obtained with keystone.users.get or list.
785 :param project: project name.
786 :param role: role name.
787 :raises AuthconnOperationException: if role assignment failed.
788 """
789 try:
790 try:
791 project_obj = self.keystone.projects.get(project)
792 except Exception:
793 project_obj_list = self.keystone.projects.list(name=project)
794 if not project_obj_list:
795 raise AuthconnNotFoundException(
796 "Project '{}' not found".format(project)
797 )
798 project_obj = project_obj_list[0]
799
800 try:
801 role_obj = self.keystone.roles.get(role)
802 except Exception:
803 role_obj_list = self.keystone.roles.list(name=role)
804 if not role_obj_list:
805 raise AuthconnNotFoundException("Role '{}' not found".format(role))
806 role_obj = role_obj_list[0]
807
808 self.keystone.roles.grant(role_obj, user=user_obj, project=project_obj)
809 except ClientException as e:
810 # self.logger.exception("Error during user role assignment using keystone: {}".format(e))
811 raise AuthconnOperationException(
812 "Error during role '{}' assignment to user '{}' and project '{}' using "
813 "Keystone: {}".format(role, user_obj.name, project, e)
814 )
815
816 def remove_role_from_user(self, user_obj, project, role):
817 """
818 Remove a role from a user in a project.
819
820 :param user_obj: user object, obtained with keystone.users.get or list.
821 :param project: project name or id.
822 :param role: role name or id.
823
824 :raises AuthconnOperationException: if role assignment revocation failed.
825 """
826 try:
827 try:
828 project_obj = self.keystone.projects.get(project)
829 except Exception:
830 project_obj_list = self.keystone.projects.list(name=project)
831 if not project_obj_list:
832 raise AuthconnNotFoundException(
833 "Project '{}' not found".format(project)
834 )
835 project_obj = project_obj_list[0]
836
837 try:
838 role_obj = self.keystone.roles.get(role)
839 except Exception:
840 role_obj_list = self.keystone.roles.list(name=role)
841 if not role_obj_list:
842 raise AuthconnNotFoundException("Role '{}' not found".format(role))
843 role_obj = role_obj_list[0]
844
845 self.keystone.roles.revoke(role_obj, user=user_obj, project=project_obj)
846 except ClientException as e:
847 # self.logger.exception("Error during user role revocation using keystone: {}".format(e))
848 raise AuthconnOperationException(
849 "Error during role '{}' revocation to user '{}' and project '{}' using "
850 "Keystone: {}".format(role, user_obj.name, project, e)
851 )