1 # -*- coding: utf-8 -*-
3 # Copyright 2018 Whitestack, LLC
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
9 # http://www.apache.org/licenses/LICENSE-2.0
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
17 # For those usages not covered by the Apache License, Version 2.0 please
18 # contact: esousa@whitestack.com or glavado@whitestack.com
22 AuthconnKeystone implements implements the connector for
23 Openstack Keystone and leverages the RBAC model, to bring
28 __author__
= "Eduardo Sousa <esousa@whitestack.com>, " \
29 "Pedro de la Cruz Ramos <pdelacruzramos@altran.com>"
30 __date__
= "$27-jul-2018 23:59:59$"
32 from osm_nbi
.authconn
import Authconn
, AuthException
, AuthconnOperationException
, AuthconnNotFoundException
, \
33 AuthconnConflictException
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
47 class AuthconnKeystone(Authconn
):
48 def __init__(self
, config
, db
, role_permissions
):
49 Authconn
.__init
__(self
, config
, db
, role_permissions
)
51 self
.logger
= logging
.getLogger("nbi.authenticator.keystone")
52 self
.domains_id2name
= {}
53 self
.domains_name2id
= {}
55 self
.auth_url
= config
.get("auth_url")
56 if config
.get("auth_url"):
57 validate_input(self
.auth_url
, http_schema
)
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")]
66 self
.user_domain_name_list
= [x
if not x
.endswith(":ro") else x
[:-3] for x
in self
.user_domain_name_list
]
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")
79 # Waiting for Keystone to be up
82 while available
is None:
85 result
= requests
.get(self
.auth_url
)
86 available
= True if result
.status_code
== 200 else None
90 raise AuthException("Keystone not available after 300s timeout")
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
)
101 def authenticate(self
, credentials
, token_info
=None):
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,
115 project_id: scoped_token project_id,
116 project_name: scoped_token project_name,
117 expires: epoch time when it expires,
124 if credentials
.get("project_domain_name"):
125 project_domain_name_list
= (credentials
["project_domain_name"], )
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"], )
131 user_domain_name_list
= self
.user_domain_name_list
133 for index
, project_domain_name
in enumerate(project_domain_name_list
):
134 user_domain_name
= user_domain_name_list
[index
]
136 if credentials
.get("username"):
137 if is_valid_uuid(credentials
["username"]):
138 user_id
= credentials
["username"]
140 username
= credentials
["username"]
142 # get an unscoped token firstly
143 unscoped_token
= self
.keystone
.get_raw_token_from_identity_service(
144 auth_url
=self
.auth_url
,
147 password
=credentials
.get("password"),
148 user_domain_name
=user_domain_name
,
149 project_domain_name
=project_domain_name
)
151 unscoped_token
= self
.keystone
.tokens
.validate(token
=token_info
.get("_id"))
153 raise AuthException("Provide credentials: username/password or Authorization Bearer token",
154 http_code
=HTTPStatus
.UNAUTHORIZED
)
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"])
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
164 if is_valid_uuid(credentials
["project_id"]):
165 project_id
= credentials
["project_id"]
167 project_name
= credentials
["project_id"]
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"])
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()
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
)
198 def validate_token(self
, token
):
200 Check if the token is valid.
202 :param token: token id to be validated
203 :return: dictionary with information associated with the token:
206 "project_id": project_id,
208 "roles": list with dict containing {name, id}
209 If the token is not valid an exception is raised.
215 token_info
= self
.keystone
.tokens
.validate(token
=token
)
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()
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
)
234 def revoke_token(self
, token
):
238 :param token: token to be revoked
241 self
.logger
.info("Revoking token: " + token
)
242 self
.keystone
.tokens
.revoke_token(token
=token
)
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
)
250 def _get_domain_id(self
, domain_name
, fail_if_not_found
=True):
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
257 domain_id
= self
.domains_name2id
.get(domain_name
)
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
264 if not domain_id
and fail_if_not_found
:
265 raise AuthconnNotFoundException("Domain {} cannot be found".format(domain_name
))
268 def _get_domains(self
):
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
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
}
281 def create_user(self
, user_info
):
285 :param user_info: full user info.
286 :raises AuthconnOperationException: if user creation failed.
287 :return: returns the id of the user in keystone.
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"]))
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
))
310 def update_user(self
, user_info
):
312 Change the user name and/or password.
314 :param user_info: user info modifications
315 :raises AuthconnOperationException: if change failed.
318 user
= user_info
.get("_id") or user_info
.get("username")
320 user_obj
= self
.keystone
.users
.get(user
)
324 for user_domain
in self
.user_domain_name_list
:
325 domain_id
= self
._get
_domain
_id
(user_domain
, fail_if_not_found
=False)
328 user_obj_list
= self
.keystone
.users
.list(name
=user
, domain
=domain_id
)
330 user_obj
= user_obj_list
[0]
332 else: # user not found
333 raise AuthconnNotFoundException("User '{}' not found".format(user
))
335 user_id
= user_obj
.id
336 domain_id
= user_obj
.domain_id
337 domain_name
= self
.domains_id2name
.get(domain_id
)
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
))
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 self
.keystone
.users
.update(user_id
, password
=user_info
.get("password"), name
=user_info
.get("username"),
349 _admin
={"created": ctime
, "modified": time
.time()})
351 for mapping
in user_info
.get("remove_project_role_mappings", []):
352 self
.remove_role_from_user(user_obj
, mapping
["project"], mapping
["role"])
353 for mapping
in user_info
.get("add_project_role_mappings", []):
354 self
.assign_role_to_user(user_obj
, mapping
["project"], mapping
["role"])
355 except ClientException
as e
:
356 # self.logger.exception("Error during user password/name update using keystone: {}".format(e))
357 raise AuthconnOperationException("Error during user update using Keystone: {}".format(e
))
359 def delete_user(self
, user_id
):
363 :param user_id: user identifier.
364 :raises AuthconnOperationException: if user deletion failed.
367 user_obj
= self
.keystone
.users
.get(user_id
)
368 domain_id
= user_obj
.domain_id
369 domain_name
= self
.domains_id2name
.get(domain_id
)
370 if domain_name
in self
.user_domain_ro_list
:
371 raise AuthconnConflictException("Cannot delete user {} belonging to a read only domain {}".
372 format(user_id
, domain_name
))
374 result
, detail
= self
.keystone
.users
.delete(user_id
)
375 if result
.status_code
!= 204:
376 raise ClientException("error {} {}".format(result
.status_code
, detail
))
378 except ClientException
as e
:
379 # self.logger.exception("Error during user deletion using keystone: {}".format(e))
380 raise AuthconnOperationException("Error during user deletion using Keystone: {}".format(e
))
382 def get_user_list(self
, filter_q
=None):
386 :param filter_q: dictionary to filter user list by one or several
388 name (username is also admitted). If a user id is equal to the filter name, it is also provided
389 domain_id, domain_name
390 :return: returns a list of users.
394 filter_name
= filter_domain
= None
396 filter_name
= filter_q
.get("name") or filter_q
.get("username")
397 if filter_q
.get("domain_name"):
398 filter_domain
= self
._get
_domain
_id
(filter_q
["domain_name"], fail_if_not_found
=False)
399 # If domain is not found, use the same name to obtain an empty list
400 filter_domain
= filter_domain
or filter_q
["domain_name"]
401 if filter_q
.get("domain_id"):
402 filter_domain
= filter_q
["domain_id"]
404 users
= self
.keystone
.users
.list(name
=filter_name
, domain
=filter_domain
)
405 # get users from user_domain_name_list[1:], because it will not be provided in case of LDAP
406 if filter_domain
is None and len(self
.user_domain_name_list
) > 1:
407 for user_domain
in self
.user_domain_name_list
[1:]:
408 domain_id
= self
._get
_domain
_id
(user_domain
, fail_if_not_found
=False)
411 # find if users of this domain are already provided. In this case ignore
413 if u
.domain_id
== domain_id
:
416 users
+= self
.keystone
.users
.list(name
=filter_name
, domain
=domain_id
)
418 # if filter name matches a user id, provide it also
421 user_obj
= self
.keystone
.users
.get(filter_name
)
422 if user_obj
not in users
:
423 users
.append(user_obj
)
428 "username": user
.name
,
431 "_admin": user
.to_dict().get("_admin", {}), # TODO: REVISE
432 "domain_name": self
.domains_id2name
.get(user
.domain_id
)
433 } for user
in users
if user
.name
!= self
.admin_username
]
435 if filter_q
and filter_q
.get("_id"):
436 users
= [user
for user
in users
if filter_q
["_id"] == user
["_id"]]
439 user
["project_role_mappings"] = []
440 user
["projects"] = []
441 projects
= self
.keystone
.projects
.list(user
=user
["_id"])
442 for project
in projects
:
443 user
["projects"].append(project
.name
)
445 roles
= self
.keystone
.roles
.list(user
=user
["_id"], project
=project
.id)
448 "project": project
.id,
449 "project_name": project
.name
,
450 "role_name": role
.name
,
453 user
["project_role_mappings"].append(prm
)
456 except ClientException
as e
:
457 # self.logger.exception("Error during user listing using keystone: {}".format(e))
458 raise AuthconnOperationException("Error during user listing using Keystone: {}".format(e
))
460 def get_role_list(self
, filter_q
=None):
464 :param filter_q: dictionary to filter role list by _id and/or name.
465 :return: returns the list of roles.
470 filter_name
= filter_q
.get("name")
471 roles_list
= self
.keystone
.roles
.list(name
=filter_name
)
476 "_admin": role
.to_dict().get("_admin", {}),
477 "permissions": role
.to_dict().get("permissions", {})
478 } for role
in roles_list
if role
.name
!= "service"]
480 if filter_q
and filter_q
.get("_id"):
481 roles
= [role
for role
in roles
if filter_q
["_id"] == role
["_id"]]
484 except ClientException
as e
:
485 # self.logger.exception("Error during user role listing using keystone: {}".format(e))
486 raise AuthException("Error during user role listing using Keystone: {}".format(e
),
487 http_code
=HTTPStatus
.UNAUTHORIZED
)
489 def create_role(self
, role_info
):
493 :param role_info: full role info.
494 :raises AuthconnOperationException: if role creation failed.
497 result
= self
.keystone
.roles
.create(role_info
["name"], permissions
=role_info
.get("permissions"),
498 _admin
=role_info
.get("_admin"))
500 except Conflict
as ex
:
501 raise AuthconnConflictException(str(ex
))
502 except ClientException
as e
:
503 # self.logger.exception("Error during role creation using keystone: {}".format(e))
504 raise AuthconnOperationException("Error during role creation using Keystone: {}".format(e
))
506 def delete_role(self
, role_id
):
510 :param role_id: role identifier.
511 :raises AuthconnOperationException: if role deletion failed.
514 result
, detail
= self
.keystone
.roles
.delete(role_id
)
516 if result
.status_code
!= 204:
517 raise ClientException("error {} {}".format(result
.status_code
, detail
))
520 except ClientException
as e
:
521 # self.logger.exception("Error during role deletion using keystone: {}".format(e))
522 raise AuthconnOperationException("Error during role deletion using Keystone: {}".format(e
))
524 def update_role(self
, role_info
):
526 Change the name of a role
527 :param role_info: full role info
531 rid
= role_info
["_id"]
532 if not is_valid_uuid(rid
): # Is this required?
533 role_obj_list
= self
.keystone
.roles
.list(name
=rid
)
534 if not role_obj_list
:
535 raise AuthconnNotFoundException("Role '{}' not found".format(rid
))
536 rid
= role_obj_list
[0].id
537 self
.keystone
.roles
.update(rid
, name
=role_info
["name"], permissions
=role_info
.get("permissions"),
538 _admin
=role_info
.get("_admin"))
539 except ClientException
as e
:
540 # self.logger.exception("Error during role update using keystone: {}".format(e))
541 raise AuthconnOperationException("Error during role updating using Keystone: {}".format(e
))
543 def get_project_list(self
, filter_q
=None):
545 Get all the projects.
547 :param filter_q: dictionary to filter project list.
548 :return: list of projects
552 filter_name
= filter_domain
= None
554 filter_name
= filter_q
.get("name")
555 if filter_q
.get("domain_name"):
556 filter_domain
= self
.domains_name2id
.get(filter_q
["domain_name"])
557 if filter_q
.get("domain_id"):
558 filter_domain
= filter_q
["domain_id"]
560 projects
= self
.keystone
.projects
.list(name
=filter_name
, domain
=filter_domain
)
563 "name": project
.name
,
565 "_admin": project
.to_dict().get("_admin", {}), # TODO: REVISE
566 "quotas": project
.to_dict().get("quotas", {}), # TODO: REVISE
567 "domain_name": self
.domains_id2name
.get(project
.domain_id
)
568 } for project
in projects
]
570 if filter_q
and filter_q
.get("_id"):
571 projects
= [project
for project
in projects
572 if filter_q
["_id"] == project
["_id"]]
575 except ClientException
as e
:
576 # self.logger.exception("Error during user project listing using keystone: {}".format(e))
577 raise AuthException("Error during user project listing using Keystone: {}".format(e
),
578 http_code
=HTTPStatus
.UNAUTHORIZED
)
580 def create_project(self
, project_info
):
584 :param project_info: full project info.
585 :return: the internal id of the created project
586 :raises AuthconnOperationException: if project creation failed.
589 result
= self
.keystone
.projects
.create(
590 project_info
["name"],
591 domain
=self
._get
_domain
_id
(project_info
.get("domain_name", self
.project_domain_name_list
[0])),
592 _admin
=project_info
["_admin"],
593 quotas
=project_info
.get("quotas", {})
596 except ClientException
as e
:
597 # self.logger.exception("Error during project creation using keystone: {}".format(e))
598 raise AuthconnOperationException("Error during project creation using Keystone: {}".format(e
))
600 def delete_project(self
, project_id
):
604 :param project_id: project identifier.
605 :raises AuthconnOperationException: if project deletion failed.
608 # projects = self.keystone.projects.list()
609 # project_obj = [project for project in projects if project.id == project_id][0]
610 # result, _ = self.keystone.projects.delete(project_obj)
612 result
, detail
= self
.keystone
.projects
.delete(project_id
)
613 if result
.status_code
!= 204:
614 raise ClientException("error {} {}".format(result
.status_code
, detail
))
617 except ClientException
as e
:
618 # self.logger.exception("Error during project deletion using keystone: {}".format(e))
619 raise AuthconnOperationException("Error during project deletion using Keystone: {}".format(e
))
621 def update_project(self
, project_id
, project_info
):
623 Change the name of a project
624 :param project_id: project to be changed
625 :param project_info: full project info
629 self
.keystone
.projects
.update(project_id
, name
=project_info
["name"],
630 _admin
=project_info
["_admin"],
631 quotas
=project_info
.get("quotas", {})
633 except ClientException
as e
:
634 # self.logger.exception("Error during project update using keystone: {}".format(e))
635 raise AuthconnOperationException("Error during project update using Keystone: {}".format(e
))
637 def assign_role_to_user(self
, user_obj
, project
, role
):
639 Assigning a role to a user in a project.
641 :param user_obj: user object, obtained with keystone.users.get or list.
642 :param project: project name.
643 :param role: role name.
644 :raises AuthconnOperationException: if role assignment failed.
648 project_obj
= self
.keystone
.projects
.get(project
)
650 project_obj_list
= self
.keystone
.projects
.list(name
=project
)
651 if not project_obj_list
:
652 raise AuthconnNotFoundException("Project '{}' not found".format(project
))
653 project_obj
= project_obj_list
[0]
656 role_obj
= self
.keystone
.roles
.get(role
)
658 role_obj_list
= self
.keystone
.roles
.list(name
=role
)
659 if not role_obj_list
:
660 raise AuthconnNotFoundException("Role '{}' not found".format(role
))
661 role_obj
= role_obj_list
[0]
663 self
.keystone
.roles
.grant(role_obj
, user
=user_obj
, project
=project_obj
)
664 except ClientException
as e
:
665 # self.logger.exception("Error during user role assignment using keystone: {}".format(e))
666 raise AuthconnOperationException("Error during role '{}' assignment to user '{}' and project '{}' using "
667 "Keystone: {}".format(role
, user_obj
.name
, project
, e
))
669 def remove_role_from_user(self
, user_obj
, project
, role
):
671 Remove a role from a user in a project.
673 :param user_obj: user object, obtained with keystone.users.get or list.
674 :param project: project name or id.
675 :param role: role name or id.
677 :raises AuthconnOperationException: if role assignment revocation failed.
681 project_obj
= self
.keystone
.projects
.get(project
)
683 project_obj_list
= self
.keystone
.projects
.list(name
=project
)
684 if not project_obj_list
:
685 raise AuthconnNotFoundException("Project '{}' not found".format(project
))
686 project_obj
= project_obj_list
[0]
689 role_obj
= self
.keystone
.roles
.get(role
)
691 role_obj_list
= self
.keystone
.roles
.list(name
=role
)
692 if not role_obj_list
:
693 raise AuthconnNotFoundException("Role '{}' not found".format(role
))
694 role_obj
= role_obj_list
[0]
696 self
.keystone
.roles
.revoke(role_obj
, user
=user_obj
, project
=project_obj
)
697 except ClientException
as e
:
698 # self.logger.exception("Error during user role revocation using keystone: {}".format(e))
699 raise AuthconnOperationException("Error during role '{}' revocation to user '{}' and project '{}' using "
700 "Keystone: {}".format(role
, user_obj
.name
, project
, e
))