Bug 1830 fixed: maps completed operations to original operation types
[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 if (
332 user_info.get("domain_name")
333 and user_info["domain_name"] in self.user_domain_ro_list
334 ):
335 raise AuthconnConflictException(
336 "Cannot create a user in the read only domain {}".format(
337 user_info["domain_name"]
338 )
339 )
340
341 new_user = self.keystone.users.create(
342 user_info["username"],
343 password=user_info["password"],
344 domain=self._get_domain_id(
345 user_info.get("domain_name", self.user_domain_name_list[0])
346 ),
347 _admin=user_info["_admin"],
348 )
349 if "project_role_mappings" in user_info.keys():
350 for mapping in user_info["project_role_mappings"]:
351 self.assign_role_to_user(
352 new_user, mapping["project"], mapping["role"]
353 )
354 return {"username": new_user.name, "_id": new_user.id}
355 except Conflict as e:
356 # self.logger.exception("Error during user creation using keystone: {}".format(e))
357 raise AuthconnOperationException(e, http_code=HTTPStatus.CONFLICT)
358 except ClientException as e:
359 # self.logger.exception("Error during user creation using keystone: {}".format(e))
360 raise AuthconnOperationException(
361 "Error during user creation using Keystone: {}".format(e)
362 )
363
364 def update_user(self, user_info):
365 """
366 Change the user name and/or password.
367
368 :param user_info: user info modifications
369 :raises AuthconnOperationException: if change failed.
370 """
371 try:
372 user = user_info.get("_id") or user_info.get("username")
373 try:
374 user_obj = self.keystone.users.get(user)
375 except Exception:
376 user_obj = None
377 if not user_obj:
378 for user_domain in self.user_domain_name_list:
379 domain_id = self._get_domain_id(
380 user_domain, fail_if_not_found=False
381 )
382 if not domain_id:
383 continue
384 user_obj_list = self.keystone.users.list(
385 name=user, domain=domain_id
386 )
387 if user_obj_list:
388 user_obj = user_obj_list[0]
389 break
390 else: # user not found
391 raise AuthconnNotFoundException("User '{}' not found".format(user))
392
393 user_id = user_obj.id
394 domain_id = user_obj.domain_id
395 domain_name = self.domains_id2name.get(domain_id)
396
397 if domain_name in self.user_domain_ro_list:
398 if user_info.get("password") or user_info.get("username"):
399 raise AuthconnConflictException(
400 "Cannot update the user {} belonging to a read only domain {}".format(
401 user, domain_name
402 )
403 )
404
405 elif (
406 user_info.get("password")
407 or user_info.get("username")
408 or user_info.get("add_project_role_mappings")
409 or user_info.get("remove_project_role_mappings")
410 ):
411 # if user_index>0, it is an external domain, that should not be updated
412 ctime = (
413 user_obj._admin.get("created", 0)
414 if hasattr(user_obj, "_admin")
415 else 0
416 )
417 try:
418 self.keystone.users.update(
419 user_id,
420 password=user_info.get("password"),
421 name=user_info.get("username"),
422 _admin={"created": ctime, "modified": time.time()},
423 )
424 except Exception as e:
425 if user_info.get("username") or user_info.get("password"):
426 raise AuthconnOperationException(
427 "Error during username/password change: {}".format(str(e))
428 )
429 self.logger.error(
430 "Error during updating user profile: {}".format(str(e))
431 )
432
433 for mapping in user_info.get("remove_project_role_mappings", []):
434 self.remove_role_from_user(
435 user_obj, mapping["project"], mapping["role"]
436 )
437 for mapping in user_info.get("add_project_role_mappings", []):
438 self.assign_role_to_user(user_obj, mapping["project"], mapping["role"])
439 except ClientException as e:
440 # self.logger.exception("Error during user password/name update using keystone: {}".format(e))
441 raise AuthconnOperationException(
442 "Error during user update using Keystone: {}".format(e)
443 )
444
445 def delete_user(self, user_id):
446 """
447 Delete user.
448
449 :param user_id: user identifier.
450 :raises AuthconnOperationException: if user deletion failed.
451 """
452 try:
453 user_obj = self.keystone.users.get(user_id)
454 domain_id = user_obj.domain_id
455 domain_name = self.domains_id2name.get(domain_id)
456 if domain_name in self.user_domain_ro_list:
457 raise AuthconnConflictException(
458 "Cannot delete user {} belonging to a read only domain {}".format(
459 user_id, domain_name
460 )
461 )
462
463 result, detail = self.keystone.users.delete(user_id)
464 if result.status_code != 204:
465 raise ClientException("error {} {}".format(result.status_code, detail))
466 return True
467 except ClientException as e:
468 # self.logger.exception("Error during user deletion using keystone: {}".format(e))
469 raise AuthconnOperationException(
470 "Error during user deletion using Keystone: {}".format(e)
471 )
472
473 def get_user_list(self, filter_q=None):
474 """
475 Get user list.
476
477 :param filter_q: dictionary to filter user list by one or several
478 _id:
479 name (username is also admitted). If a user id is equal to the filter name, it is also provided
480 domain_id, domain_name
481 :return: returns a list of users.
482 """
483 try:
484 self._get_domains()
485 filter_name = filter_domain = None
486 if filter_q:
487 filter_name = filter_q.get("name") or filter_q.get("username")
488 if filter_q.get("domain_name"):
489 filter_domain = self._get_domain_id(
490 filter_q["domain_name"], fail_if_not_found=False
491 )
492 # If domain is not found, use the same name to obtain an empty list
493 filter_domain = filter_domain or filter_q["domain_name"]
494 if filter_q.get("domain_id"):
495 filter_domain = filter_q["domain_id"]
496
497 users = self.keystone.users.list(name=filter_name, domain=filter_domain)
498 # get users from user_domain_name_list[1:], because it will not be provided in case of LDAP
499 if filter_domain is None and len(self.user_domain_name_list) > 1:
500 for user_domain in self.user_domain_name_list[1:]:
501 domain_id = self._get_domain_id(
502 user_domain, fail_if_not_found=False
503 )
504 if not domain_id:
505 continue
506 # find if users of this domain are already provided. In this case ignore
507 for u in users:
508 if u.domain_id == domain_id:
509 break
510 else:
511 users += self.keystone.users.list(
512 name=filter_name, domain=domain_id
513 )
514
515 # if filter name matches a user id, provide it also
516 if filter_name:
517 try:
518 user_obj = self.keystone.users.get(filter_name)
519 if user_obj not in users:
520 users.append(user_obj)
521 except Exception:
522 pass
523
524 users = [
525 {
526 "username": user.name,
527 "_id": user.id,
528 "id": user.id,
529 "_admin": user.to_dict().get("_admin", {}), # TODO: REVISE
530 "domain_name": self.domains_id2name.get(user.domain_id),
531 }
532 for user in users
533 if user.name != self.admin_username
534 ]
535
536 if filter_q and filter_q.get("_id"):
537 users = [user for user in users if filter_q["_id"] == user["_id"]]
538
539 for user in users:
540 user["project_role_mappings"] = []
541 user["projects"] = []
542 projects = self.keystone.projects.list(user=user["_id"])
543 for project in projects:
544 user["projects"].append(project.name)
545
546 roles = self.keystone.roles.list(
547 user=user["_id"], project=project.id
548 )
549 for role in roles:
550 prm = {
551 "project": project.id,
552 "project_name": project.name,
553 "role_name": role.name,
554 "role": role.id,
555 }
556 user["project_role_mappings"].append(prm)
557
558 return users
559 except ClientException as e:
560 # self.logger.exception("Error during user listing using keystone: {}".format(e))
561 raise AuthconnOperationException(
562 "Error during user listing using Keystone: {}".format(e)
563 )
564
565 def get_role_list(self, filter_q=None):
566 """
567 Get role list.
568
569 :param filter_q: dictionary to filter role list by _id and/or name.
570 :return: returns the list of roles.
571 """
572 try:
573 filter_name = None
574 if filter_q:
575 filter_name = filter_q.get("name")
576 roles_list = self.keystone.roles.list(name=filter_name)
577
578 roles = [
579 {
580 "name": role.name,
581 "_id": role.id,
582 "_admin": role.to_dict().get("_admin", {}),
583 "permissions": role.to_dict().get("permissions", {}),
584 }
585 for role in roles_list
586 if role.name != "service"
587 ]
588
589 if filter_q and filter_q.get("_id"):
590 roles = [role for role in roles if filter_q["_id"] == role["_id"]]
591
592 return roles
593 except ClientException as e:
594 # self.logger.exception("Error during user role listing using keystone: {}".format(e))
595 raise AuthException(
596 "Error during user role listing using Keystone: {}".format(e),
597 http_code=HTTPStatus.UNAUTHORIZED,
598 )
599
600 def create_role(self, role_info):
601 """
602 Create a role.
603
604 :param role_info: full role info.
605 :raises AuthconnOperationException: if role creation failed.
606 """
607 try:
608 result = self.keystone.roles.create(
609 role_info["name"],
610 permissions=role_info.get("permissions"),
611 _admin=role_info.get("_admin"),
612 )
613 return result.id
614 except Conflict as ex:
615 raise AuthconnConflictException(str(ex))
616 except ClientException as e:
617 # self.logger.exception("Error during role creation using keystone: {}".format(e))
618 raise AuthconnOperationException(
619 "Error during role creation using Keystone: {}".format(e)
620 )
621
622 def delete_role(self, role_id):
623 """
624 Delete a role.
625
626 :param role_id: role identifier.
627 :raises AuthconnOperationException: if role deletion failed.
628 """
629 try:
630 result, detail = self.keystone.roles.delete(role_id)
631
632 if result.status_code != 204:
633 raise ClientException("error {} {}".format(result.status_code, detail))
634
635 return True
636 except ClientException as e:
637 # self.logger.exception("Error during role deletion using keystone: {}".format(e))
638 raise AuthconnOperationException(
639 "Error during role deletion using Keystone: {}".format(e)
640 )
641
642 def update_role(self, role_info):
643 """
644 Change the name of a role
645 :param role_info: full role info
646 :return: None
647 """
648 try:
649 rid = role_info["_id"]
650 if not is_valid_uuid(rid): # Is this required?
651 role_obj_list = self.keystone.roles.list(name=rid)
652 if not role_obj_list:
653 raise AuthconnNotFoundException("Role '{}' not found".format(rid))
654 rid = role_obj_list[0].id
655 self.keystone.roles.update(
656 rid,
657 name=role_info["name"],
658 permissions=role_info.get("permissions"),
659 _admin=role_info.get("_admin"),
660 )
661 except ClientException as e:
662 # self.logger.exception("Error during role update using keystone: {}".format(e))
663 raise AuthconnOperationException(
664 "Error during role updating using Keystone: {}".format(e)
665 )
666
667 def get_project_list(self, filter_q=None):
668 """
669 Get all the projects.
670
671 :param filter_q: dictionary to filter project list.
672 :return: list of projects
673 """
674 try:
675 self._get_domains()
676 filter_name = filter_domain = None
677 if filter_q:
678 filter_name = filter_q.get("name")
679 if filter_q.get("domain_name"):
680 filter_domain = self.domains_name2id.get(filter_q["domain_name"])
681 if filter_q.get("domain_id"):
682 filter_domain = filter_q["domain_id"]
683
684 projects = self.keystone.projects.list(
685 name=filter_name, domain=filter_domain
686 )
687
688 projects = [
689 {
690 "name": project.name,
691 "_id": project.id,
692 "_admin": project.to_dict().get("_admin", {}), # TODO: REVISE
693 "quotas": project.to_dict().get("quotas", {}), # TODO: REVISE
694 "domain_name": self.domains_id2name.get(project.domain_id),
695 }
696 for project in projects
697 ]
698
699 if filter_q and filter_q.get("_id"):
700 projects = [
701 project for project in projects if filter_q["_id"] == project["_id"]
702 ]
703
704 return projects
705 except ClientException as e:
706 # self.logger.exception("Error during user project listing using keystone: {}".format(e))
707 raise AuthException(
708 "Error during user project listing using Keystone: {}".format(e),
709 http_code=HTTPStatus.UNAUTHORIZED,
710 )
711
712 def create_project(self, project_info):
713 """
714 Create a project.
715
716 :param project_info: full project info.
717 :return: the internal id of the created project
718 :raises AuthconnOperationException: if project creation failed.
719 """
720 try:
721 result = self.keystone.projects.create(
722 project_info["name"],
723 domain=self._get_domain_id(
724 project_info.get("domain_name", self.project_domain_name_list[0])
725 ),
726 _admin=project_info["_admin"],
727 quotas=project_info.get("quotas", {}),
728 )
729 return result.id
730 except ClientException as e:
731 # self.logger.exception("Error during project creation using keystone: {}".format(e))
732 raise AuthconnOperationException(
733 "Error during project creation using Keystone: {}".format(e)
734 )
735
736 def delete_project(self, project_id):
737 """
738 Delete a project.
739
740 :param project_id: project identifier.
741 :raises AuthconnOperationException: if project deletion failed.
742 """
743 try:
744 # projects = self.keystone.projects.list()
745 # project_obj = [project for project in projects if project.id == project_id][0]
746 # result, _ = self.keystone.projects.delete(project_obj)
747
748 result, detail = self.keystone.projects.delete(project_id)
749 if result.status_code != 204:
750 raise ClientException("error {} {}".format(result.status_code, detail))
751
752 return True
753 except ClientException as e:
754 # self.logger.exception("Error during project deletion using keystone: {}".format(e))
755 raise AuthconnOperationException(
756 "Error during project deletion using Keystone: {}".format(e)
757 )
758
759 def update_project(self, project_id, project_info):
760 """
761 Change the name of a project
762 :param project_id: project to be changed
763 :param project_info: full project info
764 :return: None
765 """
766 try:
767 self.keystone.projects.update(
768 project_id,
769 name=project_info["name"],
770 _admin=project_info["_admin"],
771 quotas=project_info.get("quotas", {}),
772 )
773 except ClientException as e:
774 # self.logger.exception("Error during project update using keystone: {}".format(e))
775 raise AuthconnOperationException(
776 "Error during project update using Keystone: {}".format(e)
777 )
778
779 def assign_role_to_user(self, user_obj, project, role):
780 """
781 Assigning a role to a user in a project.
782
783 :param user_obj: user object, obtained with keystone.users.get or list.
784 :param project: project name.
785 :param role: role name.
786 :raises AuthconnOperationException: if role assignment failed.
787 """
788 try:
789 try:
790 project_obj = self.keystone.projects.get(project)
791 except Exception:
792 project_obj_list = self.keystone.projects.list(name=project)
793 if not project_obj_list:
794 raise AuthconnNotFoundException(
795 "Project '{}' not found".format(project)
796 )
797 project_obj = project_obj_list[0]
798
799 try:
800 role_obj = self.keystone.roles.get(role)
801 except Exception:
802 role_obj_list = self.keystone.roles.list(name=role)
803 if not role_obj_list:
804 raise AuthconnNotFoundException("Role '{}' not found".format(role))
805 role_obj = role_obj_list[0]
806
807 self.keystone.roles.grant(role_obj, user=user_obj, project=project_obj)
808 except ClientException as e:
809 # self.logger.exception("Error during user role assignment using keystone: {}".format(e))
810 raise AuthconnOperationException(
811 "Error during role '{}' assignment to user '{}' and project '{}' using "
812 "Keystone: {}".format(role, user_obj.name, project, e)
813 )
814
815 def remove_role_from_user(self, user_obj, project, role):
816 """
817 Remove a role from a user in a project.
818
819 :param user_obj: user object, obtained with keystone.users.get or list.
820 :param project: project name or id.
821 :param role: role name or id.
822
823 :raises AuthconnOperationException: if role assignment revocation failed.
824 """
825 try:
826 try:
827 project_obj = self.keystone.projects.get(project)
828 except Exception:
829 project_obj_list = self.keystone.projects.list(name=project)
830 if not project_obj_list:
831 raise AuthconnNotFoundException(
832 "Project '{}' not found".format(project)
833 )
834 project_obj = project_obj_list[0]
835
836 try:
837 role_obj = self.keystone.roles.get(role)
838 except Exception:
839 role_obj_list = self.keystone.roles.list(name=role)
840 if not role_obj_list:
841 raise AuthconnNotFoundException("Role '{}' not found".format(role))
842 role_obj = role_obj_list[0]
843
844 self.keystone.roles.revoke(role_obj, user=user_obj, project=project_obj)
845 except ClientException as e:
846 # self.logger.exception("Error during user role revocation using keystone: {}".format(e))
847 raise AuthconnOperationException(
848 "Error during role '{}' revocation to user '{}' and project '{}' using "
849 "Keystone: {}".format(role, user_obj.name, project, e)
850 )