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