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