Code Coverage

Cobertura Coverage Report > osm_nbi >

authconn_keystone.py

Trend

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 0             if (
332                 user_info.get("domain_name")
333                 and user_info["domain_name"] in self.user_domain_ro_list
334             ):
335 0                 raise AuthconnConflictException(
336                     "Cannot create a user in the read only domain {}".format(
337                         user_info["domain_name"]
338                     )
339                 )
340
341 0             new_user = self.keystone.users.create(
342                 user_info["username"],
343                 password=user_info["password"],
344                 domain=self._get_domain_id(
345                     user_info.get("domain_name", self.user_domain_name_list[0])
346                 ),
347                 _admin=user_info["_admin"],
348             )
349 0             if "project_role_mappings" in user_info.keys():
350 0                 for mapping in user_info["project_role_mappings"]:
351 0                     self.assign_role_to_user(
352                         new_user, mapping["project"], mapping["role"]
353                     )
354 0             return {"username": new_user.name, "_id": new_user.id}
355 0         except Conflict as e:
356             # self.logger.exception("Error during user creation using keystone: {}".format(e))
357 0             raise AuthconnOperationException(e, http_code=HTTPStatus.CONFLICT)
358 0         except ClientException as e:
359             # self.logger.exception("Error during user creation using keystone: {}".format(e))
360 0             raise AuthconnOperationException(
361                 "Error during user creation using Keystone: {}".format(e)
362             )
363
364 1     def update_user(self, user_info):
365         """
366         Change the user name and/or password.
367
368         :param user_info: user info modifications
369         :raises AuthconnOperationException: if change failed.
370         """
371 0         try:
372 0             user = user_info.get("_id") or user_info.get("username")
373 0             try:
374 0                 user_obj = self.keystone.users.get(user)
375 0             except Exception:
376 0                 user_obj = None
377 0             if not user_obj:
378 0                 for user_domain in self.user_domain_name_list:
379 0                     domain_id = self._get_domain_id(
380                         user_domain, fail_if_not_found=False
381                     )
382 0                     if not domain_id:
383 0                         continue
384 0                     user_obj_list = self.keystone.users.list(
385                         name=user, domain=domain_id
386                     )
387 0                     if user_obj_list:
388 0                         user_obj = user_obj_list[0]
389 0                         break
390                 else:  # user not found
391 0                     raise AuthconnNotFoundException("User '{}' not found".format(user))
392
393 0             user_id = user_obj.id
394 0             domain_id = user_obj.domain_id
395 0             domain_name = self.domains_id2name.get(domain_id)
396
397 0             if domain_name in self.user_domain_ro_list:
398 0                 if user_info.get("password") or user_info.get("username"):
399 0                     raise AuthconnConflictException(
400                         "Cannot update the user {} belonging to a read only domain {}".format(
401                             user, domain_name
402                         )
403                     )
404
405 0             elif (
406                 user_info.get("password")
407                 or user_info.get("username")
408                 or user_info.get("add_project_role_mappings")
409                 or user_info.get("remove_project_role_mappings")
410             ):
411                 # if user_index>0, it is an external domain, that should not be updated
412 0                 ctime = (
413                     user_obj._admin.get("created", 0)
414                     if hasattr(user_obj, "_admin")
415                     else 0
416                 )
417 0                 try:
418 0                     self.keystone.users.update(
419                         user_id,
420                         password=user_info.get("password"),
421                         name=user_info.get("username"),
422                         _admin={"created": ctime, "modified": time.time()},
423                     )
424 0                 except Exception as e:
425 0                     if user_info.get("username") or user_info.get("password"):
426 0                         raise AuthconnOperationException(
427                             "Error during username/password change: {}".format(str(e))
428                         )
429 0                     self.logger.error(
430                         "Error during updating user profile: {}".format(str(e))
431                     )
432
433 0             for mapping in user_info.get("remove_project_role_mappings", []):
434 0                 self.remove_role_from_user(
435                     user_obj, mapping["project"], mapping["role"]
436                 )
437 0             for mapping in user_info.get("add_project_role_mappings", []):
438 0                 self.assign_role_to_user(user_obj, mapping["project"], mapping["role"])
439 0         except ClientException as e:
440             # self.logger.exception("Error during user password/name update using keystone: {}".format(e))
441 0             raise AuthconnOperationException(
442                 "Error during user update using Keystone: {}".format(e)
443             )
444
445 1     def delete_user(self, user_id):
446         """
447         Delete user.
448
449         :param user_id: user identifier.
450         :raises AuthconnOperationException: if user deletion failed.
451         """
452 0         try:
453 0             user_obj = self.keystone.users.get(user_id)
454 0             domain_id = user_obj.domain_id
455 0             domain_name = self.domains_id2name.get(domain_id)
456 0             if domain_name in self.user_domain_ro_list:
457 0                 raise AuthconnConflictException(
458                     "Cannot delete user {} belonging to a read only domain {}".format(
459                         user_id, domain_name
460                     )
461                 )
462
463 0             result, detail = self.keystone.users.delete(user_id)
464 0             if result.status_code != 204:
465 0                 raise ClientException("error {} {}".format(result.status_code, detail))
466 0             return True
467 0         except ClientException as e:
468             # self.logger.exception("Error during user deletion using keystone: {}".format(e))
469 0             raise AuthconnOperationException(
470                 "Error during user deletion using Keystone: {}".format(e)
471             )
472
473 1     def get_user_list(self, filter_q=None):
474         """
475         Get user list.
476
477         :param filter_q: dictionary to filter user list by one or several
478             _id:
479             name (username is also admitted).  If a user id is equal to the filter name, it is also provided
480             domain_id, domain_name
481         :return: returns a list of users.
482         """
483 0         try:
484 0             self._get_domains()
485 0             filter_name = filter_domain = None
486 0             if filter_q:
487 0                 filter_name = filter_q.get("name") or filter_q.get("username")
488 0                 if filter_q.get("domain_name"):
489 0                     filter_domain = self._get_domain_id(
490                         filter_q["domain_name"], fail_if_not_found=False
491                     )
492                     # If domain is not found, use the same name to obtain an empty list
493 0                     filter_domain = filter_domain or filter_q["domain_name"]
494 0                 if filter_q.get("domain_id"):
495 0                     filter_domain = filter_q["domain_id"]
496
497 0             users = self.keystone.users.list(name=filter_name, domain=filter_domain)
498             # get users from user_domain_name_list[1:], because it will not be provided in case of LDAP
499 0             if filter_domain is None and len(self.user_domain_name_list) > 1:
500 0                 for user_domain in self.user_domain_name_list[1:]:
501 0                     domain_id = self._get_domain_id(
502                         user_domain, fail_if_not_found=False
503                     )
504 0                     if not domain_id:
505 0                         continue
506                     # find if users of this domain are already provided. In this case ignore
507 0                     for u in users:
508 0                         if u.domain_id == domain_id:
509 0                             break
510                     else:
511 0                         users += self.keystone.users.list(
512                             name=filter_name, domain=domain_id
513                         )
514
515             # if filter name matches a user id, provide it also
516 0             if filter_name:
517 0                 try:
518 0                     user_obj = self.keystone.users.get(filter_name)
519 0                     if user_obj not in users:
520 0                         users.append(user_obj)
521 0                 except Exception:
522 0                     pass
523
524 0             users = [
525                 {
526                     "username": user.name,
527                     "_id": user.id,
528                     "id": user.id,
529                     "_admin": user.to_dict().get("_admin", {}),  # TODO: REVISE
530                     "domain_name": self.domains_id2name.get(user.domain_id),
531                 }
532                 for user in users
533                 if user.name != self.admin_username
534             ]
535
536 0             if filter_q and filter_q.get("_id"):
537 0                 users = [user for user in users if filter_q["_id"] == user["_id"]]
538
539 0             for user in users:
540 0                 user["project_role_mappings"] = []
541 0                 user["projects"] = []
542 0                 projects = self.keystone.projects.list(user=user["_id"])
543 0                 for project in projects:
544 0                     user["projects"].append(project.name)
545
546 0                     roles = self.keystone.roles.list(
547                         user=user["_id"], project=project.id
548                     )
549 0                     for role in roles:
550 0                         prm = {
551                             "project": project.id,
552                             "project_name": project.name,
553                             "role_name": role.name,
554                             "role": role.id,
555                         }
556 0                         user["project_role_mappings"].append(prm)
557
558 0             return users
559 0         except ClientException as e:
560             # self.logger.exception("Error during user listing using keystone: {}".format(e))
561 0             raise AuthconnOperationException(
562                 "Error during user listing using Keystone: {}".format(e)
563             )
564
565 1     def get_role_list(self, filter_q=None):
566         """
567         Get role list.
568
569         :param filter_q: dictionary to filter role list by _id and/or name.
570         :return: returns the list of roles.
571         """
572 0         try:
573 0             filter_name = None
574 0             if filter_q:
575 0                 filter_name = filter_q.get("name")
576 0             roles_list = self.keystone.roles.list(name=filter_name)
577
578 0             roles = [
579                 {
580                     "name": role.name,
581                     "_id": role.id,
582                     "_admin": role.to_dict().get("_admin", {}),
583                     "permissions": role.to_dict().get("permissions", {}),
584                 }
585                 for role in roles_list
586                 if role.name != "service"
587             ]
588
589 0             if filter_q and filter_q.get("_id"):
590 0                 roles = [role for role in roles if filter_q["_id"] == role["_id"]]
591
592 0             return roles
593 0         except ClientException as e:
594             # self.logger.exception("Error during user role listing using keystone: {}".format(e))
595 0             raise AuthException(
596                 "Error during user role listing using Keystone: {}".format(e),
597                 http_code=HTTPStatus.UNAUTHORIZED,
598             )
599
600 1     def create_role(self, role_info):
601         """
602         Create a role.
603
604         :param role_info: full role info.
605         :raises AuthconnOperationException: if role creation failed.
606         """
607 0         try:
608 0             result = self.keystone.roles.create(
609                 role_info["name"],
610                 permissions=role_info.get("permissions"),
611                 _admin=role_info.get("_admin"),
612             )
613 0             return result.id
614 0         except Conflict as ex:
615 0             raise AuthconnConflictException(str(ex))
616 0         except ClientException as e:
617             # self.logger.exception("Error during role creation using keystone: {}".format(e))
618 0             raise AuthconnOperationException(
619                 "Error during role creation using Keystone: {}".format(e)
620             )
621
622 1     def delete_role(self, role_id):
623         """
624         Delete a role.
625
626         :param role_id: role identifier.
627         :raises AuthconnOperationException: if role deletion failed.
628         """
629 0         try:
630 0             result, detail = self.keystone.roles.delete(role_id)
631
632 0             if result.status_code != 204:
633 0                 raise ClientException("error {} {}".format(result.status_code, detail))
634
635 0             return True
636 0         except ClientException as e:
637             # self.logger.exception("Error during role deletion using keystone: {}".format(e))
638 0             raise AuthconnOperationException(
639                 "Error during role deletion using Keystone: {}".format(e)
640             )
641
642 1     def update_role(self, role_info):
643         """
644         Change the name of a role
645         :param role_info: full role info
646         :return: None
647         """
648 0         try:
649 0             rid = role_info["_id"]
650 0             if not is_valid_uuid(rid):  # Is this required?
651 0                 role_obj_list = self.keystone.roles.list(name=rid)
652 0                 if not role_obj_list:
653 0                     raise AuthconnNotFoundException("Role '{}' not found".format(rid))
654 0                 rid = role_obj_list[0].id
655 0             self.keystone.roles.update(
656                 rid,
657                 name=role_info["name"],
658                 permissions=role_info.get("permissions"),
659                 _admin=role_info.get("_admin"),
660             )
661 0         except ClientException as e:
662             # self.logger.exception("Error during role update using keystone: {}".format(e))
663 0             raise AuthconnOperationException(
664                 "Error during role updating using Keystone: {}".format(e)
665             )
666
667 1     def get_project_list(self, filter_q=None):
668         """
669         Get all the projects.
670
671         :param filter_q: dictionary to filter project list.
672         :return: list of projects
673         """
674 0         try:
675 0             self._get_domains()
676 0             filter_name = filter_domain = None
677 0             if filter_q:
678 0                 filter_name = filter_q.get("name")
679 0                 if filter_q.get("domain_name"):
680 0                     filter_domain = self.domains_name2id.get(filter_q["domain_name"])
681 0                 if filter_q.get("domain_id"):
682 0                     filter_domain = filter_q["domain_id"]
683
684 0             projects = self.keystone.projects.list(
685                 name=filter_name, domain=filter_domain
686             )
687
688 0             projects = [
689                 {
690                     "name": project.name,
691                     "_id": project.id,
692                     "_admin": project.to_dict().get("_admin", {}),  # TODO: REVISE
693                     "quotas": project.to_dict().get("quotas", {}),  # TODO: REVISE
694                     "domain_name": self.domains_id2name.get(project.domain_id),
695                 }
696                 for project in projects
697             ]
698
699 0             if filter_q and filter_q.get("_id"):
700 0                 projects = [
701                     project for project in projects if filter_q["_id"] == project["_id"]
702                 ]
703
704 0             return projects
705 0         except ClientException as e:
706             # self.logger.exception("Error during user project listing using keystone: {}".format(e))
707 0             raise AuthException(
708                 "Error during user project listing using Keystone: {}".format(e),
709                 http_code=HTTPStatus.UNAUTHORIZED,
710             )
711
712 1     def create_project(self, project_info):
713         """
714         Create a project.
715
716         :param project_info: full project info.
717         :return: the internal id of the created project
718         :raises AuthconnOperationException: if project creation failed.
719         """
720 0         try:
721 0             result = self.keystone.projects.create(
722                 project_info["name"],
723                 domain=self._get_domain_id(
724                     project_info.get("domain_name", self.project_domain_name_list[0])
725                 ),
726                 _admin=project_info["_admin"],
727                 quotas=project_info.get("quotas", {}),
728             )
729 0             return result.id
730 0         except ClientException as e:
731             # self.logger.exception("Error during project creation using keystone: {}".format(e))
732 0             raise AuthconnOperationException(
733                 "Error during project creation using Keystone: {}".format(e)
734             )
735
736 1     def delete_project(self, project_id):
737         """
738         Delete a project.
739
740         :param project_id: project identifier.
741         :raises AuthconnOperationException: if project deletion failed.
742         """
743 0         try:
744             # projects = self.keystone.projects.list()
745             # project_obj = [project for project in projects if project.id == project_id][0]
746             # result, _ = self.keystone.projects.delete(project_obj)
747
748 0             result, detail = self.keystone.projects.delete(project_id)
749 0             if result.status_code != 204:
750 0                 raise ClientException("error {} {}".format(result.status_code, detail))
751
752 0             return True
753 0         except ClientException as e:
754             # self.logger.exception("Error during project deletion using keystone: {}".format(e))
755 0             raise AuthconnOperationException(
756                 "Error during project deletion using Keystone: {}".format(e)
757             )
758
759 1     def update_project(self, project_id, project_info):
760         """
761         Change the name of a project
762         :param project_id: project to be changed
763         :param project_info: full project info
764         :return: None
765         """
766 0         try:
767 0             self.keystone.projects.update(
768                 project_id,
769                 name=project_info["name"],
770                 _admin=project_info["_admin"],
771                 quotas=project_info.get("quotas", {}),
772             )
773 0         except ClientException as e:
774             # self.logger.exception("Error during project update using keystone: {}".format(e))
775 0             raise AuthconnOperationException(
776                 "Error during project update using Keystone: {}".format(e)
777             )
778
779 1     def assign_role_to_user(self, user_obj, project, role):
780         """
781         Assigning a role to a user in a project.
782
783         :param user_obj: user object, obtained with keystone.users.get or list.
784         :param project: project name.
785         :param role: role name.
786         :raises AuthconnOperationException: if role assignment failed.
787         """
788 0         try:
789 0             try:
790 0                 project_obj = self.keystone.projects.get(project)
791 0             except Exception:
792 0                 project_obj_list = self.keystone.projects.list(name=project)
793 0                 if not project_obj_list:
794 0                     raise AuthconnNotFoundException(
795                         "Project '{}' not found".format(project)
796                     )
797 0                 project_obj = project_obj_list[0]
798
799 0             try:
800 0                 role_obj = self.keystone.roles.get(role)
801 0             except Exception:
802 0                 role_obj_list = self.keystone.roles.list(name=role)
803 0                 if not role_obj_list:
804 0                     raise AuthconnNotFoundException("Role '{}' not found".format(role))
805 0                 role_obj = role_obj_list[0]
806
807 0             self.keystone.roles.grant(role_obj, user=user_obj, project=project_obj)
808 0         except ClientException as e:
809             # self.logger.exception("Error during user role assignment using keystone: {}".format(e))
810 0             raise AuthconnOperationException(
811                 "Error during role '{}' assignment to user '{}' and project '{}' using "
812                 "Keystone: {}".format(role, user_obj.name, project, e)
813             )
814
815 1     def remove_role_from_user(self, user_obj, project, role):
816         """
817         Remove a role from a user in a project.
818
819         :param user_obj: user object, obtained with keystone.users.get or list.
820         :param project: project name or id.
821         :param role: role name or id.
822
823         :raises AuthconnOperationException: if role assignment revocation failed.
824         """
825 0         try:
826 0             try:
827 0                 project_obj = self.keystone.projects.get(project)
828 0             except Exception:
829 0                 project_obj_list = self.keystone.projects.list(name=project)
830 0                 if not project_obj_list:
831 0                     raise AuthconnNotFoundException(
832                         "Project '{}' not found".format(project)
833                     )
834 0                 project_obj = project_obj_list[0]
835
836 0             try:
837 0                 role_obj = self.keystone.roles.get(role)
838 0             except Exception:
839 0                 role_obj_list = self.keystone.roles.list(name=role)
840 0                 if not role_obj_list:
841 0                     raise AuthconnNotFoundException("Role '{}' not found".format(role))
842 0                 role_obj = role_obj_list[0]
843
844 0             self.keystone.roles.revoke(role_obj, user=user_obj, project=project_obj)
845 0         except ClientException as e:
846             # self.logger.exception("Error during user role revocation using keystone: {}".format(e))
847 0             raise AuthconnOperationException(
848                 "Error during role '{}' revocation to user '{}' and project '{}' using "
849                 "Keystone: {}".format(role, user_obj.name, project, e)
850             )