fix 1042 add timeout parameter to ns-action ns-scale
[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__ = "Eduardo Sousa <esousa@whitestack.com>, " \
29 "Pedro de la Cruz Ramos <pdelacruzramos@altran.com>"
30 __date__ = "$27-jul-2018 23:59:59$"
31
32 from osm_nbi.authconn import Authconn, AuthException, AuthconnOperationException, AuthconnNotFoundException, \
33 AuthconnConflictException
34
35 import logging
36 import requests
37 import time
38 from keystoneauth1 import session
39 from keystoneauth1.identity import v3
40 from keystoneauth1.exceptions.base import ClientException
41 from keystoneauth1.exceptions.http import Conflict
42 from keystoneclient.v3 import client
43 from http import HTTPStatus
44 from osm_nbi.validation import is_valid_uuid
45
46
47 class AuthconnKeystone(Authconn):
48 def __init__(self, config, db, role_permissions):
49 Authconn.__init__(self, config, db, role_permissions)
50
51 self.logger = logging.getLogger("nbi.authenticator.keystone")
52 self.domains_id2name = {}
53 self.domains_name2id = {}
54
55 self.auth_url = "http://{0}:{1}/v3".format(config.get("auth_url", "keystone"), config.get("auth_port", "5000"))
56 self.user_domain_name_list = config.get("user_domain_name", "default")
57 self.user_domain_name_list = self.user_domain_name_list.split(",")
58 # read only domain list
59 self.user_domain_ro_list = [x[:-3] for x in self.user_domain_name_list if x.endswith(":ro")]
60 # remove the ":ro"
61 self.user_domain_name_list = [x if not x.endswith(":ro") else x[:-3] for x in self.user_domain_name_list]
62
63 self.admin_project = config.get("service_project", "service")
64 self.admin_username = config.get("service_username", "nbi")
65 self.admin_password = config.get("service_password", "nbi")
66 self.project_domain_name_list = config.get("project_domain_name", "default")
67 self.project_domain_name_list = self.project_domain_name_list.split(",")
68 if len(self.user_domain_name_list) != len(self.project_domain_name_list):
69 raise ValueError("Invalid configuration parameter fo authenticate. 'project_domain_name' and "
70 "'user_domain_name' must be a comma-separated list with the same size. Revise "
71 "configuration or/and 'OSMNBI_AUTHENTICATION_PROJECT_DOMAIN_NAME', "
72 "'OSMNBI_AUTHENTICATION_USER_DOMAIN_NAME' Variables")
73
74 # Waiting for Keystone to be up
75 available = None
76 counter = 300
77 while available is None:
78 time.sleep(1)
79 try:
80 result = requests.get(self.auth_url)
81 available = True if result.status_code == 200 else None
82 except Exception:
83 counter -= 1
84 if counter == 0:
85 raise AuthException("Keystone not available after 300s timeout")
86
87 self.auth = v3.Password(user_domain_name=self.user_domain_name_list[0],
88 username=self.admin_username,
89 password=self.admin_password,
90 project_domain_name=self.project_domain_name_list[0],
91 project_name=self.admin_project,
92 auth_url=self.auth_url)
93 self.sess = session.Session(auth=self.auth)
94 self.keystone = client.Client(session=self.sess)
95
96 def authenticate(self, credentials, token_info=None):
97 """
98 Authenticate a user using username/password or token_info, plus project
99 :param credentials: dictionary that contains:
100 username: name, id or None
101 password: password or None
102 project_id: name, id, or None. If None first found project will be used to get an scope token
103 project_domain_name: (Optional) To use a concrete domain for the project
104 user_domain_name: (Optional) To use a concrete domain for the project
105 other items are allowed and ignored
106 :param token_info: previous token_info to obtain authorization
107 :return: the scoped token info or raises an exception. The token is a dictionary with:
108 _id: token string id,
109 username: username,
110 project_id: scoped_token project_id,
111 project_name: scoped_token project_name,
112 expires: epoch time when it expires,
113
114 """
115 username = None
116 user_id = None
117 project_id = None
118 project_name = None
119 if credentials.get("project_domain_name"):
120 project_domain_name_list = (credentials["project_domain_name"], )
121 else:
122 project_domain_name_list = self.project_domain_name_list
123 if credentials.get("user_domain_name"):
124 user_domain_name_list = (credentials["user_domain_name"], )
125 else:
126 user_domain_name_list = self.user_domain_name_list
127
128 for index, project_domain_name in enumerate(project_domain_name_list):
129 user_domain_name = user_domain_name_list[index]
130 try:
131 if credentials.get("username"):
132 if is_valid_uuid(credentials["username"]):
133 user_id = credentials["username"]
134 else:
135 username = credentials["username"]
136
137 # get an unscoped token firstly
138 unscoped_token = self.keystone.get_raw_token_from_identity_service(
139 auth_url=self.auth_url,
140 user_id=user_id,
141 username=username,
142 password=credentials.get("password"),
143 user_domain_name=user_domain_name,
144 project_domain_name=project_domain_name)
145 elif token_info:
146 unscoped_token = self.keystone.tokens.validate(token=token_info.get("_id"))
147 else:
148 raise AuthException("Provide credentials: username/password or Authorization Bearer token",
149 http_code=HTTPStatus.UNAUTHORIZED)
150
151 if not credentials.get("project_id"):
152 # get first project for the user
153 project_list = self.keystone.projects.list(user=unscoped_token["user"]["id"])
154 if not project_list:
155 raise AuthException("The user {} has not any project and cannot be used for authentication".
156 format(credentials.get("username")), http_code=HTTPStatus.UNAUTHORIZED)
157 project_id = project_list[0].id
158 else:
159 if is_valid_uuid(credentials["project_id"]):
160 project_id = credentials["project_id"]
161 else:
162 project_name = credentials["project_id"]
163
164 scoped_token = self.keystone.get_raw_token_from_identity_service(
165 auth_url=self.auth_url,
166 project_name=project_name,
167 project_id=project_id,
168 user_domain_name=user_domain_name,
169 project_domain_name=project_domain_name,
170 token=unscoped_token["auth_token"])
171
172 auth_token = {
173 "_id": scoped_token.auth_token,
174 "id": scoped_token.auth_token,
175 "user_id": scoped_token.user_id,
176 "username": scoped_token.username,
177 "project_id": scoped_token.project_id,
178 "project_name": scoped_token.project_name,
179 "project_domain_name": scoped_token.project_domain_name,
180 "user_domain_name": scoped_token.user_domain_name,
181 "expires": scoped_token.expires.timestamp(),
182 "issued_at": scoped_token.issued.timestamp()
183 }
184
185 return auth_token
186 except ClientException as e:
187 if index >= len(user_domain_name_list)-1 or index >= len(project_domain_name_list)-1:
188 # if last try, launch exception
189 # self.logger.exception("Error during user authentication using keystone: {}".format(e))
190 raise AuthException("Error during user authentication using Keystone: {}".format(e),
191 http_code=HTTPStatus.UNAUTHORIZED)
192
193 def validate_token(self, token):
194 """
195 Check if the token is valid.
196
197 :param token: token id to be validated
198 :return: dictionary with information associated with the token:
199 "expires":
200 "_id": token_id,
201 "project_id": project_id,
202 "username": ,
203 "roles": list with dict containing {name, id}
204 If the token is not valid an exception is raised.
205 """
206 if not token:
207 return
208
209 try:
210 token_info = self.keystone.tokens.validate(token=token)
211 ses = {
212 "_id": token_info["auth_token"],
213 "id": token_info["auth_token"],
214 "project_id": token_info["project"]["id"],
215 "project_name": token_info["project"]["name"],
216 "user_id": token_info["user"]["id"],
217 "username": token_info["user"]["name"],
218 "roles": token_info["roles"],
219 "expires": token_info.expires.timestamp(),
220 "issued_at": token_info.issued.timestamp()
221 }
222
223 return ses
224 except ClientException as e:
225 # self.logger.exception("Error during token validation using keystone: {}".format(e))
226 raise AuthException("Error during token validation using Keystone: {}".format(e),
227 http_code=HTTPStatus.UNAUTHORIZED)
228
229 def revoke_token(self, token):
230 """
231 Invalidate a token.
232
233 :param token: token to be revoked
234 """
235 try:
236 self.logger.info("Revoking token: " + token)
237 self.keystone.tokens.revoke_token(token=token)
238
239 return True
240 except ClientException as e:
241 # self.logger.exception("Error during token revocation using keystone: {}".format(e))
242 raise AuthException("Error during token revocation using Keystone: {}".format(e),
243 http_code=HTTPStatus.UNAUTHORIZED)
244
245 def _get_domain_id(self, domain_name, fail_if_not_found=True):
246 """
247 Get the domain id from the domain_name
248 :param domain_name: Can be the name or id
249 :param fail_if_not_found: If False it returns None instead of raising an exception if not found
250 :return: str or None/exception if domain is not found
251 """
252 domain_id = self.domains_name2id.get(domain_name)
253 if not domain_id:
254 self._get_domains()
255 domain_id = self.domains_name2id.get(domain_name)
256 if not domain_id and domain_name in self.domains_id2name:
257 # domain_name is already an id
258 return domain_name
259 if not domain_id and fail_if_not_found:
260 raise AuthconnNotFoundException("Domain {} cannot be found".format(domain_name))
261 return domain_id
262
263 def _get_domains(self):
264 """
265 Obtain a dictionary with domain_id to domain_name, stored at self.domains_id2name
266 and from domain_name to domain_id, sored at self.domains_name2id
267 :return: None. Exceptions are ignored
268 """
269 try:
270 domains = self.keystone.domains.list()
271 self.domains_id2name = {x.id: x.name for x in domains}
272 self.domains_name2id = {x.name: x.id for x in domains}
273 except Exception:
274 pass
275
276 def create_user(self, user_info):
277 """
278 Create a user.
279
280 :param user_info: full user info.
281 :raises AuthconnOperationException: if user creation failed.
282 :return: returns the id of the user in keystone.
283 """
284 try:
285
286 if user_info.get("domain_name") and user_info["domain_name"] in self.user_domain_ro_list:
287 raise AuthconnConflictException("Cannot create a user in the read only domain {}".
288 format(user_info["domain_name"]))
289
290 new_user = self.keystone.users.create(
291 user_info["username"], password=user_info["password"],
292 domain=self._get_domain_id(user_info.get("domain_name", self.user_domain_name_list[0])),
293 _admin=user_info["_admin"])
294 if "project_role_mappings" in user_info.keys():
295 for mapping in user_info["project_role_mappings"]:
296 self.assign_role_to_user(new_user, mapping["project"], mapping["role"])
297 return {"username": new_user.name, "_id": new_user.id}
298 except Conflict as e:
299 # self.logger.exception("Error during user creation using keystone: {}".format(e))
300 raise AuthconnOperationException(e, http_code=HTTPStatus.CONFLICT)
301 except ClientException as e:
302 # self.logger.exception("Error during user creation using keystone: {}".format(e))
303 raise AuthconnOperationException("Error during user creation using Keystone: {}".format(e))
304
305 def update_user(self, user_info):
306 """
307 Change the user name and/or password.
308
309 :param user_info: user info modifications
310 :raises AuthconnOperationException: if change failed.
311 """
312 try:
313 user = user_info.get("_id") or user_info.get("username")
314 try:
315 user_obj = self.keystone.users.get(user)
316 except Exception:
317 user_obj = None
318 if not user_obj:
319 for user_domain in self.user_domain_name_list:
320 domain_id = self._get_domain_id(user_domain, fail_if_not_found=False)
321 if not domain_id:
322 continue
323 user_obj_list = self.keystone.users.list(name=user, domain=domain_id)
324 if user_obj_list:
325 user_obj = user_obj_list[0]
326 break
327 else: # user not found
328 raise AuthconnNotFoundException("User '{}' not found".format(user))
329
330 user_id = user_obj.id
331 domain_id = user_obj.domain_id
332 domain_name = self.domains_id2name.get(domain_id)
333
334 if domain_name in self.user_domain_ro_list:
335 if user_info.get("password") or user_info.get("username"):
336 raise AuthconnConflictException("Cannot update the user {} belonging to a read only domain {}".
337 format(user, domain_name))
338
339 elif user_info.get("password") or user_info.get("username") \
340 or user_info.get("add_project_role_mappings") or user_info.get("remove_project_role_mappings"):
341 # if user_index>0, it is an external domain, that should not be updated
342 ctime = user_obj._admin.get("created", 0) if hasattr(user_obj, "_admin") else 0
343 self.keystone.users.update(user_id, password=user_info.get("password"), name=user_info.get("username"),
344 _admin={"created": ctime, "modified": time.time()})
345
346 for mapping in user_info.get("remove_project_role_mappings", []):
347 self.remove_role_from_user(user_obj, mapping["project"], mapping["role"])
348 for mapping in user_info.get("add_project_role_mappings", []):
349 self.assign_role_to_user(user_obj, mapping["project"], mapping["role"])
350 except ClientException as e:
351 # self.logger.exception("Error during user password/name update using keystone: {}".format(e))
352 raise AuthconnOperationException("Error during user update using Keystone: {}".format(e))
353
354 def delete_user(self, user_id):
355 """
356 Delete user.
357
358 :param user_id: user identifier.
359 :raises AuthconnOperationException: if user deletion failed.
360 """
361 try:
362 user_obj = self.keystone.users.get(user_id)
363 domain_id = user_obj.domain_id
364 domain_name = self.domains_id2name.get(domain_id)
365 if domain_name in self.user_domain_ro_list:
366 raise AuthconnConflictException("Cannot delete user {} belonging to a read only domain {}".
367 format(user_id, domain_name))
368
369 result, detail = self.keystone.users.delete(user_id)
370 if result.status_code != 204:
371 raise ClientException("error {} {}".format(result.status_code, detail))
372 return True
373 except ClientException as e:
374 # self.logger.exception("Error during user deletion using keystone: {}".format(e))
375 raise AuthconnOperationException("Error during user deletion using Keystone: {}".format(e))
376
377 def get_user_list(self, filter_q=None):
378 """
379 Get user list.
380
381 :param filter_q: dictionary to filter user list by one or several
382 _id:
383 name (username is also admitted). If a user id is equal to the filter name, it is also provided
384 domain_id, domain_name
385 :return: returns a list of users.
386 """
387 try:
388 self._get_domains()
389 filter_name = filter_domain = None
390 if filter_q:
391 filter_name = filter_q.get("name") or filter_q.get("username")
392 if filter_q.get("domain_name"):
393 filter_domain = self._get_domain_id(filter_q["domain_name"], fail_if_not_found=False)
394 # If domain is not found, use the same name to obtain an empty list
395 filter_domain = filter_domain or filter_q["domain_name"]
396 if filter_q.get("domain_id"):
397 filter_domain = filter_q["domain_id"]
398
399 users = self.keystone.users.list(name=filter_name, domain=filter_domain)
400 # get users from user_domain_name_list[1:], because it will not be provided in case of LDAP
401 if filter_domain is None and len(self.user_domain_name_list) > 1:
402 for user_domain in self.user_domain_name_list[1:]:
403 domain_id = self._get_domain_id(user_domain, fail_if_not_found=False)
404 if not domain_id:
405 continue
406 # find if users of this domain are already provided. In this case ignore
407 for u in users:
408 if u.domain_id == domain_id:
409 break
410 else:
411 users += self.keystone.users.list(name=filter_name, domain=domain_id)
412
413 # if filter name matches a user id, provide it also
414 if filter_name:
415 try:
416 user_obj = self.keystone.users.get(filter_name)
417 if user_obj not in users:
418 users.append(user_obj)
419 except Exception:
420 pass
421
422 users = [{
423 "username": user.name,
424 "_id": user.id,
425 "id": user.id,
426 "_admin": user.to_dict().get("_admin", {}), # TODO: REVISE
427 "domain_name": self.domains_id2name.get(user.domain_id)
428 } for user in users if user.name != self.admin_username]
429
430 if filter_q and filter_q.get("_id"):
431 users = [user for user in users if filter_q["_id"] == user["_id"]]
432
433 for user in users:
434 user["project_role_mappings"] = []
435 user["projects"] = []
436 projects = self.keystone.projects.list(user=user["_id"])
437 for project in projects:
438 user["projects"].append(project.name)
439
440 roles = self.keystone.roles.list(user=user["_id"], project=project.id)
441 for role in roles:
442 prm = {
443 "project": project.id,
444 "project_name": project.name,
445 "role_name": role.name,
446 "role": role.id,
447 }
448 user["project_role_mappings"].append(prm)
449
450 return users
451 except ClientException as e:
452 # self.logger.exception("Error during user listing using keystone: {}".format(e))
453 raise AuthconnOperationException("Error during user listing using Keystone: {}".format(e))
454
455 def get_role_list(self, filter_q=None):
456 """
457 Get role list.
458
459 :param filter_q: dictionary to filter role list by _id and/or name.
460 :return: returns the list of roles.
461 """
462 try:
463 filter_name = None
464 if filter_q:
465 filter_name = filter_q.get("name")
466 roles_list = self.keystone.roles.list(name=filter_name)
467
468 roles = [{
469 "name": role.name,
470 "_id": role.id,
471 "_admin": role.to_dict().get("_admin", {}),
472 "permissions": role.to_dict().get("permissions", {})
473 } for role in roles_list if role.name != "service"]
474
475 if filter_q and filter_q.get("_id"):
476 roles = [role for role in roles if filter_q["_id"] == role["_id"]]
477
478 return roles
479 except ClientException as e:
480 # self.logger.exception("Error during user role listing using keystone: {}".format(e))
481 raise AuthException("Error during user role listing using Keystone: {}".format(e),
482 http_code=HTTPStatus.UNAUTHORIZED)
483
484 def create_role(self, role_info):
485 """
486 Create a role.
487
488 :param role_info: full role info.
489 :raises AuthconnOperationException: if role creation failed.
490 """
491 try:
492 result = self.keystone.roles.create(role_info["name"], permissions=role_info.get("permissions"),
493 _admin=role_info.get("_admin"))
494 return result.id
495 except Conflict as ex:
496 raise AuthconnConflictException(str(ex))
497 except ClientException as e:
498 # self.logger.exception("Error during role creation using keystone: {}".format(e))
499 raise AuthconnOperationException("Error during role creation using Keystone: {}".format(e))
500
501 def delete_role(self, role_id):
502 """
503 Delete a role.
504
505 :param role_id: role identifier.
506 :raises AuthconnOperationException: if role deletion failed.
507 """
508 try:
509 result, detail = self.keystone.roles.delete(role_id)
510
511 if result.status_code != 204:
512 raise ClientException("error {} {}".format(result.status_code, detail))
513
514 return True
515 except ClientException as e:
516 # self.logger.exception("Error during role deletion using keystone: {}".format(e))
517 raise AuthconnOperationException("Error during role deletion using Keystone: {}".format(e))
518
519 def update_role(self, role_info):
520 """
521 Change the name of a role
522 :param role_info: full role info
523 :return: None
524 """
525 try:
526 rid = role_info["_id"]
527 if not is_valid_uuid(rid): # Is this required?
528 role_obj_list = self.keystone.roles.list(name=rid)
529 if not role_obj_list:
530 raise AuthconnNotFoundException("Role '{}' not found".format(rid))
531 rid = role_obj_list[0].id
532 self.keystone.roles.update(rid, name=role_info["name"], permissions=role_info.get("permissions"),
533 _admin=role_info.get("_admin"))
534 except ClientException as e:
535 # self.logger.exception("Error during role update using keystone: {}".format(e))
536 raise AuthconnOperationException("Error during role updating using Keystone: {}".format(e))
537
538 def get_project_list(self, filter_q=None):
539 """
540 Get all the projects.
541
542 :param filter_q: dictionary to filter project list.
543 :return: list of projects
544 """
545 try:
546 self._get_domains()
547 filter_name = filter_domain = None
548 if filter_q:
549 filter_name = filter_q.get("name")
550 if filter_q.get("domain_name"):
551 filter_domain = self.domains_name2id.get(filter_q["domain_name"])
552 if filter_q.get("domain_id"):
553 filter_domain = filter_q["domain_id"]
554
555 projects = self.keystone.projects.list(name=filter_name, domain=filter_domain)
556
557 projects = [{
558 "name": project.name,
559 "_id": project.id,
560 "_admin": project.to_dict().get("_admin", {}), # TODO: REVISE
561 "quotas": project.to_dict().get("quotas", {}), # TODO: REVISE
562 "domain_name": self.domains_id2name.get(project.domain_id)
563 } for project in projects]
564
565 if filter_q and filter_q.get("_id"):
566 projects = [project for project in projects
567 if filter_q["_id"] == project["_id"]]
568
569 return projects
570 except ClientException as e:
571 # self.logger.exception("Error during user project listing using keystone: {}".format(e))
572 raise AuthException("Error during user project listing using Keystone: {}".format(e),
573 http_code=HTTPStatus.UNAUTHORIZED)
574
575 def create_project(self, project_info):
576 """
577 Create a project.
578
579 :param project_info: full project info.
580 :return: the internal id of the created project
581 :raises AuthconnOperationException: if project creation failed.
582 """
583 try:
584 result = self.keystone.projects.create(
585 project_info["name"],
586 domain=self._get_domain_id(project_info.get("domain_name", self.project_domain_name_list[0])),
587 _admin=project_info["_admin"],
588 quotas=project_info.get("quotas", {})
589 )
590 return result.id
591 except ClientException as e:
592 # self.logger.exception("Error during project creation using keystone: {}".format(e))
593 raise AuthconnOperationException("Error during project creation using Keystone: {}".format(e))
594
595 def delete_project(self, project_id):
596 """
597 Delete a project.
598
599 :param project_id: project identifier.
600 :raises AuthconnOperationException: if project deletion failed.
601 """
602 try:
603 # projects = self.keystone.projects.list()
604 # project_obj = [project for project in projects if project.id == project_id][0]
605 # result, _ = self.keystone.projects.delete(project_obj)
606
607 result, detail = self.keystone.projects.delete(project_id)
608 if result.status_code != 204:
609 raise ClientException("error {} {}".format(result.status_code, detail))
610
611 return True
612 except ClientException as e:
613 # self.logger.exception("Error during project deletion using keystone: {}".format(e))
614 raise AuthconnOperationException("Error during project deletion using Keystone: {}".format(e))
615
616 def update_project(self, project_id, project_info):
617 """
618 Change the name of a project
619 :param project_id: project to be changed
620 :param project_info: full project info
621 :return: None
622 """
623 try:
624 self.keystone.projects.update(project_id, name=project_info["name"],
625 _admin=project_info["_admin"],
626 quotas=project_info.get("quotas", {})
627 )
628 except ClientException as e:
629 # self.logger.exception("Error during project update using keystone: {}".format(e))
630 raise AuthconnOperationException("Error during project update using Keystone: {}".format(e))
631
632 def assign_role_to_user(self, user_obj, project, role):
633 """
634 Assigning a role to a user in a project.
635
636 :param user_obj: user object, obtained with keystone.users.get or list.
637 :param project: project name.
638 :param role: role name.
639 :raises AuthconnOperationException: if role assignment failed.
640 """
641 try:
642 try:
643 project_obj = self.keystone.projects.get(project)
644 except Exception:
645 project_obj_list = self.keystone.projects.list(name=project)
646 if not project_obj_list:
647 raise AuthconnNotFoundException("Project '{}' not found".format(project))
648 project_obj = project_obj_list[0]
649
650 try:
651 role_obj = self.keystone.roles.get(role)
652 except Exception:
653 role_obj_list = self.keystone.roles.list(name=role)
654 if not role_obj_list:
655 raise AuthconnNotFoundException("Role '{}' not found".format(role))
656 role_obj = role_obj_list[0]
657
658 self.keystone.roles.grant(role_obj, user=user_obj, project=project_obj)
659 except ClientException as e:
660 # self.logger.exception("Error during user role assignment using keystone: {}".format(e))
661 raise AuthconnOperationException("Error during role '{}' assignment to user '{}' and project '{}' using "
662 "Keystone: {}".format(role, user_obj.name, project, e))
663
664 def remove_role_from_user(self, user_obj, project, role):
665 """
666 Remove a role from a user in a project.
667
668 :param user_obj: user object, obtained with keystone.users.get or list.
669 :param project: project name or id.
670 :param role: role name or id.
671
672 :raises AuthconnOperationException: if role assignment revocation failed.
673 """
674 try:
675 try:
676 project_obj = self.keystone.projects.get(project)
677 except Exception:
678 project_obj_list = self.keystone.projects.list(name=project)
679 if not project_obj_list:
680 raise AuthconnNotFoundException("Project '{}' not found".format(project))
681 project_obj = project_obj_list[0]
682
683 try:
684 role_obj = self.keystone.roles.get(role)
685 except Exception:
686 role_obj_list = self.keystone.roles.list(name=role)
687 if not role_obj_list:
688 raise AuthconnNotFoundException("Role '{}' not found".format(role))
689 role_obj = role_obj_list[0]
690
691 self.keystone.roles.revoke(role_obj, user=user_obj, project=project_obj)
692 except ClientException as e:
693 # self.logger.exception("Error during user role revocation using keystone: {}".format(e))
694 raise AuthconnOperationException("Error during role '{}' revocation to user '{}' and project '{}' using "
695 "Keystone: {}".format(role, user_obj.name, project, e))