Code Coverage

Cobertura Coverage Report > osm_nbi >

authconn_keystone.py

Trend

Classes100%
 
Lines10%
   
Conditionals100%
 

File Coverage summary

NameClassesLinesConditionals
authconn_keystone.py
100%
1/1
10%
35/338
100%
0/0

Coverage Breakdown by Class

NameLinesConditionals
authconn_keystone.py
10%
35/338
N/A

Source

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 1 """
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 1 __author__ = "Eduardo Sousa <esousa@whitestack.com>, " \
29              "Pedro de la Cruz Ramos <pdelacruzramos@altran.com>"
30 1 __date__ = "$27-jul-2018 23:59:59$"
31
32 1 from osm_nbi.authconn import Authconn, AuthException, AuthconnOperationException, AuthconnNotFoundException, \
33     AuthconnConflictException
34
35 1 import logging
36 1 import requests
37 1 import time
38 1 from keystoneauth1 import session
39 1 from keystoneauth1.identity import v3
40 1 from keystoneauth1.exceptions.base import ClientException
41 1 from keystoneauth1.exceptions.http import Conflict
42 1 from keystoneclient.v3 import client
43 1 from http import HTTPStatus
44 1 from osm_nbi.validation import is_valid_uuid, validate_input, http_schema
45
46
47 1 class AuthconnKeystone(Authconn):
48 1     def __init__(self, config, db, role_permissions):
49 0         Authconn.__init__(self, config, db, role_permissions)
50
51 0         self.logger = logging.getLogger("nbi.authenticator.keystone")
52 0         self.domains_id2name = {}
53 0         self.domains_name2id = {}
54
55 0         self.auth_url = config.get("auth_url")
56 0         if config.get("auth_url"):
57 0             validate_input(self.auth_url, http_schema)
58         else:
59 0             self.auth_url = "http://{0}:{1}/v3".format(config.get("auth_host", "keystone"),
60                                                        config.get("auth_port", "5000"))
61 0         self.user_domain_name_list = config.get("user_domain_name", "default")
62 0         self.user_domain_name_list = self.user_domain_name_list.split(",")
63         # read only domain list
64 0         self.user_domain_ro_list = [x[:-3] for x in self.user_domain_name_list if x.endswith(":ro")]
65         # remove the ":ro"
66 0         self.user_domain_name_list = [x if not x.endswith(":ro") else x[:-3] for x in self.user_domain_name_list]
67
68 0         self.admin_project = config.get("service_project", "service")
69 0         self.admin_username = config.get("service_username", "nbi")
70 0         self.admin_password = config.get("service_password", "nbi")
71 0         self.project_domain_name_list = config.get("project_domain_name", "default")
72 0         self.project_domain_name_list = self.project_domain_name_list.split(",")
73 0         if len(self.user_domain_name_list) != len(self.project_domain_name_list):
74 0             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 0         available = None
81 0         counter = 300
82 0         while available is None:
83 0             time.sleep(1)
84 0             try:
85 0                 result = requests.get(self.auth_url)
86 0                 available = True if result.status_code == 200 else None
87 0             except Exception:
88 0                 counter -= 1
89 0                 if counter == 0:
90 0                     raise AuthException("Keystone not available after 300s timeout")
91
92 0         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 0         self.sess = session.Session(auth=self.auth)
99 0         self.keystone = client.Client(session=self.sess, endpoint_override=self.auth_url)
100
101 1     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 0         username = None
121 0         user_id = None
122 0         project_id = None
123 0         project_name = None
124 0         if credentials.get("project_domain_name"):
125 0             project_domain_name_list = (credentials["project_domain_name"], )
126         else:
127 0             project_domain_name_list = self.project_domain_name_list
128 0         if credentials.get("user_domain_name"):
129 0             user_domain_name_list = (credentials["user_domain_name"], )
130         else:
131 0             user_domain_name_list = self.user_domain_name_list
132
133 0         for index, project_domain_name in enumerate(project_domain_name_list):
134 0             user_domain_name = user_domain_name_list[index]
135 0             try:
136 0                 if credentials.get("username"):
137 0                     if is_valid_uuid(credentials["username"]):
138 0                         user_id = credentials["username"]
139                     else:
140 0                         username = credentials["username"]
141
142                     # get an unscoped token firstly
143 0                     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 0                 elif token_info:
151 0                     unscoped_token = self.keystone.tokens.validate(token=token_info.get("_id"))
152                 else:
153 0                     raise AuthException("Provide credentials: username/password or Authorization Bearer token",
154                                         http_code=HTTPStatus.UNAUTHORIZED)
155
156 0                 if not credentials.get("project_id"):
157                     # get first project for the user
158 0                     project_list = self.keystone.projects.list(user=unscoped_token["user"]["id"])
159 0                     if not project_list:
160 0                         raise AuthException("The user {} has not any project and cannot be used for authentication".
161                                             format(credentials.get("username")), http_code=HTTPStatus.UNAUTHORIZED)
162 0                     project_id = project_list[0].id
163                 else:
164 0                     if is_valid_uuid(credentials["project_id"]):
165 0                         project_id = credentials["project_id"]
166                     else:
167 0                         project_name = credentials["project_id"]
168
169 0                 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 0                 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 0                 return auth_token
191 0             except ClientException as e:
192 0                 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 0                     raise AuthException("Error during user authentication using Keystone: {}".format(e),
196                                         http_code=HTTPStatus.UNAUTHORIZED)
197
198 1     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 0         if not token:
212 0             return
213
214 0         try:
215 0             token_info = self.keystone.tokens.validate(token=token)
216 0             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 0             return ses
229 0         except ClientException as e:
230             # self.logger.exception("Error during token validation using keystone: {}".format(e))
231 0             raise AuthException("Error during token validation using Keystone: {}".format(e),
232                                 http_code=HTTPStatus.UNAUTHORIZED)
233
234 1     def revoke_token(self, token):
235         """
236         Invalidate a token.
237
238         :param token: token to be revoked
239         """
240 0         try:
241 0             self.logger.info("Revoking token: " + token)
242 0             self.keystone.tokens.revoke_token(token=token)
243
244 0             return True
245 0         except ClientException as e:
246             # self.logger.exception("Error during token revocation using keystone: {}".format(e))
247 0             raise AuthException("Error during token revocation using Keystone: {}".format(e),
248                                 http_code=HTTPStatus.UNAUTHORIZED)
249
250 1     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 0         domain_id = self.domains_name2id.get(domain_name)
258 0         if not domain_id:
259 0             self._get_domains()
260 0             domain_id = self.domains_name2id.get(domain_name)
261 0         if not domain_id and domain_name in self.domains_id2name:
262             # domain_name is already an id
263 0             return domain_name
264 0         if not domain_id and fail_if_not_found:
265 0             raise AuthconnNotFoundException("Domain {} cannot be found".format(domain_name))
266 0         return domain_id
267
268 1     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 0         try:
275 0             domains = self.keystone.domains.list()
276 0             self.domains_id2name = {x.id: x.name for x in domains}
277 0             self.domains_name2id = {x.name: x.id for x in domains}
278 0         except Exception:
279 0             pass
280
281 1     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 0         try:
290
291 0             if user_info.get("domain_name") and user_info["domain_name"] in self.user_domain_ro_list:
292 0                 raise AuthconnConflictException("Cannot create a user in the read only domain {}".
293                                                 format(user_info["domain_name"]))
294
295 0             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 0             if "project_role_mappings" in user_info.keys():
300 0                 for mapping in user_info["project_role_mappings"]:
301 0                     self.assign_role_to_user(new_user, mapping["project"], mapping["role"])
302 0             return {"username": new_user.name, "_id": new_user.id}
303 0         except Conflict as e:
304             # self.logger.exception("Error during user creation using keystone: {}".format(e))
305 0             raise AuthconnOperationException(e, http_code=HTTPStatus.CONFLICT)
306 0         except ClientException as e:
307             # self.logger.exception("Error during user creation using keystone: {}".format(e))
308 0             raise AuthconnOperationException("Error during user creation using Keystone: {}".format(e))
309
310 1     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 0         try:
318 0             user = user_info.get("_id") or user_info.get("username")
319 0             try:
320 0                 user_obj = self.keystone.users.get(user)
321 0             except Exception:
322 0                 user_obj = None
323 0             if not user_obj:
324 0                 for user_domain in self.user_domain_name_list:
325 0                     domain_id = self._get_domain_id(user_domain, fail_if_not_found=False)
326 0                     if not domain_id:
327 0                         continue
328 0                     user_obj_list = self.keystone.users.list(name=user, domain=domain_id)
329 0                     if user_obj_list:
330 0                         user_obj = user_obj_list[0]
331 0                         break
332                 else:   # user not found
333 0                     raise AuthconnNotFoundException("User '{}' not found".format(user))
334
335 0             user_id = user_obj.id
336 0             domain_id = user_obj.domain_id
337 0             domain_name = self.domains_id2name.get(domain_id)
338
339 0             if domain_name in self.user_domain_ro_list:
340 0                 if user_info.get("password") or user_info.get("username"):
341 0                     raise AuthconnConflictException("Cannot update the user {} belonging to a read only domain {}".
342                                                     format(user, domain_name))
343
344 0             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 0                 ctime = user_obj._admin.get("created", 0) if hasattr(user_obj, "_admin") else 0
348 0                 try:
349 0                     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 0                 except Exception as e:
353 0                     if user_info.get("username") or user_info.get("password"):
354 0                         raise AuthconnOperationException("Error during username/password change: {}".format(str(e)))
355 0                     self.logger.error("Error during updating user profile: {}".format(str(e)))
356
357 0             for mapping in user_info.get("remove_project_role_mappings", []):
358 0                 self.remove_role_from_user(user_obj, mapping["project"], mapping["role"])
359 0             for mapping in user_info.get("add_project_role_mappings", []):
360 0                 self.assign_role_to_user(user_obj, mapping["project"], mapping["role"])
361 0         except ClientException as e:
362             # self.logger.exception("Error during user password/name update using keystone: {}".format(e))
363 0             raise AuthconnOperationException("Error during user update using Keystone: {}".format(e))
364
365 1     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 0         try:
373 0             user_obj = self.keystone.users.get(user_id)
374 0             domain_id = user_obj.domain_id
375 0             domain_name = self.domains_id2name.get(domain_id)
376 0             if domain_name in self.user_domain_ro_list:
377 0                 raise AuthconnConflictException("Cannot delete user {} belonging to a read only domain {}".
378                                                 format(user_id, domain_name))
379
380 0             result, detail = self.keystone.users.delete(user_id)
381 0             if result.status_code != 204:
382 0                 raise ClientException("error {} {}".format(result.status_code, detail))
383 0             return True
384 0         except ClientException as e:
385             # self.logger.exception("Error during user deletion using keystone: {}".format(e))
386 0             raise AuthconnOperationException("Error during user deletion using Keystone: {}".format(e))
387
388 1     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 0         try:
399 0             self._get_domains()
400 0             filter_name = filter_domain = None
401 0             if filter_q:
402 0                 filter_name = filter_q.get("name") or filter_q.get("username")
403 0                 if filter_q.get("domain_name"):
404 0                     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 0                     filter_domain = filter_domain or filter_q["domain_name"]
407 0                 if filter_q.get("domain_id"):
408 0                     filter_domain = filter_q["domain_id"]
409
410 0             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 0             if filter_domain is None and len(self.user_domain_name_list) > 1:
413 0                 for user_domain in self.user_domain_name_list[1:]:
414 0                     domain_id = self._get_domain_id(user_domain, fail_if_not_found=False)
415 0                     if not domain_id:
416 0                         continue
417                     # find if users of this domain are already provided. In this case ignore
418 0                     for u in users:
419 0                         if u.domain_id == domain_id:
420 0                             break
421                     else:
422 0                         users += self.keystone.users.list(name=filter_name, domain=domain_id)
423
424             # if filter name matches a user id, provide it also
425 0             if filter_name:
426 0                 try:
427 0                     user_obj = self.keystone.users.get(filter_name)
428 0                     if user_obj not in users:
429 0                         users.append(user_obj)
430 0                 except Exception:
431 0                     pass
432
433 0             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 0             if filter_q and filter_q.get("_id"):
442 0                 users = [user for user in users if filter_q["_id"] == user["_id"]]
443
444 0             for user in users:
445 0                 user["project_role_mappings"] = []
446 0                 user["projects"] = []
447 0                 projects = self.keystone.projects.list(user=user["_id"])
448 0                 for project in projects:
449 0                     user["projects"].append(project.name)
450
451 0                     roles = self.keystone.roles.list(user=user["_id"], project=project.id)
452 0                     for role in roles:
453 0                         prm = {
454                             "project": project.id,
455                             "project_name": project.name,
456                             "role_name": role.name,
457                             "role": role.id,
458                         }
459 0                         user["project_role_mappings"].append(prm)
460
461 0             return users
462 0         except ClientException as e:
463             # self.logger.exception("Error during user listing using keystone: {}".format(e))
464 0             raise AuthconnOperationException("Error during user listing using Keystone: {}".format(e))
465
466 1     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 0         try:
474 0             filter_name = None
475 0             if filter_q:
476 0                 filter_name = filter_q.get("name")
477 0             roles_list = self.keystone.roles.list(name=filter_name)
478
479 0             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 0             if filter_q and filter_q.get("_id"):
487 0                 roles = [role for role in roles if filter_q["_id"] == role["_id"]]
488
489 0             return roles
490 0         except ClientException as e:
491             # self.logger.exception("Error during user role listing using keystone: {}".format(e))
492 0             raise AuthException("Error during user role listing using Keystone: {}".format(e),
493                                 http_code=HTTPStatus.UNAUTHORIZED)
494
495 1     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 0         try:
503 0             result = self.keystone.roles.create(role_info["name"], permissions=role_info.get("permissions"),
504                                                 _admin=role_info.get("_admin"))
505 0             return result.id
506 0         except Conflict as ex:
507 0             raise AuthconnConflictException(str(ex))
508 0         except ClientException as e:
509             # self.logger.exception("Error during role creation using keystone: {}".format(e))
510 0             raise AuthconnOperationException("Error during role creation using Keystone: {}".format(e))
511
512 1     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 0         try:
520 0             result, detail = self.keystone.roles.delete(role_id)
521
522 0             if result.status_code != 204:
523 0                 raise ClientException("error {} {}".format(result.status_code, detail))
524
525 0             return True
526 0         except ClientException as e:
527             # self.logger.exception("Error during role deletion using keystone: {}".format(e))
528 0             raise AuthconnOperationException("Error during role deletion using Keystone: {}".format(e))
529
530 1     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 0         try:
537 0             rid = role_info["_id"]
538 0             if not is_valid_uuid(rid):   # Is this required?
539 0                 role_obj_list = self.keystone.roles.list(name=rid)
540 0                 if not role_obj_list:
541 0                     raise AuthconnNotFoundException("Role '{}' not found".format(rid))
542 0                 rid = role_obj_list[0].id
543 0             self.keystone.roles.update(rid, name=role_info["name"], permissions=role_info.get("permissions"),
544                                        _admin=role_info.get("_admin"))
545 0         except ClientException as e:
546             # self.logger.exception("Error during role update using keystone: {}".format(e))
547 0             raise AuthconnOperationException("Error during role updating using Keystone: {}".format(e))
548
549 1     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 0         try:
557 0             self._get_domains()
558 0             filter_name = filter_domain = None
559 0             if filter_q:
560 0                 filter_name = filter_q.get("name")
561 0                 if filter_q.get("domain_name"):
562 0                     filter_domain = self.domains_name2id.get(filter_q["domain_name"])
563 0                 if filter_q.get("domain_id"):
564 0                     filter_domain = filter_q["domain_id"]
565
566 0             projects = self.keystone.projects.list(name=filter_name, domain=filter_domain)
567
568 0             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 0             if filter_q and filter_q.get("_id"):
577 0                 projects = [project for project in projects
578                             if filter_q["_id"] == project["_id"]]
579
580 0             return projects
581 0         except ClientException as e:
582             # self.logger.exception("Error during user project listing using keystone: {}".format(e))
583 0             raise AuthException("Error during user project listing using Keystone: {}".format(e),
584                                 http_code=HTTPStatus.UNAUTHORIZED)
585
586 1     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 0         try:
595 0             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 0             return result.id
602 0         except ClientException as e:
603             # self.logger.exception("Error during project creation using keystone: {}".format(e))
604 0             raise AuthconnOperationException("Error during project creation using Keystone: {}".format(e))
605
606 1     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 0         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 0             result, detail = self.keystone.projects.delete(project_id)
619 0             if result.status_code != 204:
620 0                 raise ClientException("error {} {}".format(result.status_code, detail))
621
622 0             return True
623 0         except ClientException as e:
624             # self.logger.exception("Error during project deletion using keystone: {}".format(e))
625 0             raise AuthconnOperationException("Error during project deletion using Keystone: {}".format(e))
626
627 1     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 0         try:
635 0             self.keystone.projects.update(project_id, name=project_info["name"],
636                                           _admin=project_info["_admin"],
637                                           quotas=project_info.get("quotas", {})
638                                           )
639 0         except ClientException as e:
640             # self.logger.exception("Error during project update using keystone: {}".format(e))
641 0             raise AuthconnOperationException("Error during project update using Keystone: {}".format(e))
642
643 1     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 0         try:
653 0             try:
654 0                 project_obj = self.keystone.projects.get(project)
655 0             except Exception:
656 0                 project_obj_list = self.keystone.projects.list(name=project)
657 0                 if not project_obj_list:
658 0                     raise AuthconnNotFoundException("Project '{}' not found".format(project))
659 0                 project_obj = project_obj_list[0]
660
661 0             try:
662 0                 role_obj = self.keystone.roles.get(role)
663 0             except Exception:
664 0                 role_obj_list = self.keystone.roles.list(name=role)
665 0                 if not role_obj_list:
666 0                     raise AuthconnNotFoundException("Role '{}' not found".format(role))
667 0                 role_obj = role_obj_list[0]
668
669 0             self.keystone.roles.grant(role_obj, user=user_obj, project=project_obj)
670 0         except ClientException as e:
671             # self.logger.exception("Error during user role assignment using keystone: {}".format(e))
672 0             raise AuthconnOperationException("Error during role '{}' assignment to user '{}' and project '{}' using "
673                                              "Keystone: {}".format(role, user_obj.name, project, e))
674
675 1     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 0         try:
686 0             try:
687 0                 project_obj = self.keystone.projects.get(project)
688 0             except Exception:
689 0                 project_obj_list = self.keystone.projects.list(name=project)
690 0                 if not project_obj_list:
691 0                     raise AuthconnNotFoundException("Project '{}' not found".format(project))
692 0                 project_obj = project_obj_list[0]
693
694 0             try:
695 0                 role_obj = self.keystone.roles.get(role)
696 0             except Exception:
697 0                 role_obj_list = self.keystone.roles.list(name=role)
698 0                 if not role_obj_list:
699 0                     raise AuthconnNotFoundException("Role '{}' not found".format(role))
700 0                 role_obj = role_obj_list[0]
701
702 0             self.keystone.roles.revoke(role_obj, user=user_obj, project=project_obj)
703 0         except ClientException as e:
704             # self.logger.exception("Error during user role revocation using keystone: {}".format(e))
705 0             raise AuthconnOperationException("Error during role '{}' revocation to user '{}' and project '{}' using "
706                                              "Keystone: {}".format(role, user_obj.name, project, e))