Bug 1830 fixed: maps completed operations to original operation types
[osm/NBI.git] / osm_nbi / authconn_internal.py
1 # -*- coding: utf-8 -*-
2
3 # Copyright 2018 Telefonica S.A.
4 # Copyright 2018 ALTRAN InnovaciĆ³n S.L.
5 #
6 # Licensed under the Apache License, Version 2.0 (the "License"); you may
7 # not use this file except in compliance with the License. You may obtain
8 # a copy of the License at
9 #
10 # http://www.apache.org/licenses/LICENSE-2.0
11 #
12 # Unless required by applicable law or agreed to in writing, software
13 # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
14 # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
15 # License for the specific language governing permissions and limitations
16 # under the License.
17 #
18 # For those usages not covered by the Apache License, Version 2.0 please
19 # contact: esousa@whitestack.com or glavado@whitestack.com
20 ##
21
22 """
23 AuthconnInternal implements implements the connector for
24 OSM Internal Authentication Backend and leverages the RBAC model
25 """
26
27 __author__ = (
28 "Pedro de la Cruz Ramos <pdelacruzramos@altran.com>, "
29 "Alfonso Tierno <alfonso.tiernosepulveda@telefoncia.com"
30 )
31 __date__ = "$06-jun-2019 11:16:08$"
32
33 import logging
34 import re
35
36 from osm_nbi.authconn import (
37 Authconn,
38 AuthException,
39 AuthconnConflictException,
40 ) # , AuthconnOperationException
41 from osm_common.dbbase import DbException
42 from osm_nbi.base_topic import BaseTopic
43 from osm_nbi.utils import cef_event, cef_event_builder
44 from osm_nbi.validation import is_valid_uuid
45 from time import time, sleep
46 from http import HTTPStatus
47 from uuid import uuid4
48 from hashlib import sha256
49 from copy import deepcopy
50 from random import choice as random_choice
51
52
53 class AuthconnInternal(Authconn):
54 token_time_window = 2 # seconds
55 token_delay = 1 # seconds to wait upon second request within time window
56
57 users_collection = "users"
58 roles_collection = "roles"
59 projects_collection = "projects"
60 tokens_collection = "tokens"
61
62 def __init__(self, config, db, role_permissions):
63 Authconn.__init__(self, config, db, role_permissions)
64 self.logger = logging.getLogger("nbi.authenticator.internal")
65
66 self.db = db
67 # self.msg = msg
68 # self.token_cache = token_cache
69
70 # To be Confirmed
71 self.sess = None
72 self.cef_logger = cef_event_builder(config)
73
74 def validate_token(self, token):
75 """
76 Check if the token is valid.
77
78 :param token: token to validate
79 :return: dictionary with information associated with the token:
80 "_id": token id
81 "project_id": project id
82 "project_name": project name
83 "user_id": user id
84 "username": user name
85 "roles": list with dict containing {name, id}
86 "expires": expiration date
87 If the token is not valid an exception is raised.
88 """
89
90 try:
91 if not token:
92 raise AuthException(
93 "Needed a token or Authorization HTTP header",
94 http_code=HTTPStatus.UNAUTHORIZED,
95 )
96
97 now = time()
98
99 # get from database if not in cache
100 # if not token_info:
101 token_info = self.db.get_one(self.tokens_collection, {"_id": token})
102 if token_info["expires"] < now:
103 raise AuthException(
104 "Expired Token or Authorization HTTP header",
105 http_code=HTTPStatus.UNAUTHORIZED,
106 )
107
108 return token_info
109
110 except DbException as e:
111 if e.http_code == HTTPStatus.NOT_FOUND:
112 raise AuthException(
113 "Invalid Token or Authorization HTTP header",
114 http_code=HTTPStatus.UNAUTHORIZED,
115 )
116 else:
117 raise
118 except AuthException:
119 raise
120 except Exception:
121 self.logger.exception(
122 "Error during token validation using internal backend"
123 )
124 raise AuthException(
125 "Error during token validation using internal backend",
126 http_code=HTTPStatus.UNAUTHORIZED,
127 )
128
129 def revoke_token(self, token):
130 """
131 Invalidate a token.
132
133 :param token: token to be revoked
134 """
135 try:
136 # self.token_cache.pop(token, None)
137 self.db.del_one(self.tokens_collection, {"_id": token})
138 return True
139 except DbException as e:
140 if e.http_code == HTTPStatus.NOT_FOUND:
141 raise AuthException(
142 "Token '{}' not found".format(token), http_code=HTTPStatus.NOT_FOUND
143 )
144 else:
145 # raise
146 exmsg = "Error during token revocation using internal backend"
147 self.logger.exception(exmsg)
148 raise AuthException(exmsg, http_code=HTTPStatus.UNAUTHORIZED)
149
150 def validate_user(self, user, password):
151 """
152 Validate username and password via appropriate backend.
153 :param user: username of the user.
154 :param password: password to be validated.
155 """
156 user_rows = self.db.get_list(
157 self.users_collection, {BaseTopic.id_field("users", user): user}
158 )
159 now = time()
160 user_content = None
161 if user:
162 user_rows = self.db.get_list(
163 self.users_collection,
164 {BaseTopic.id_field(self.users_collection, user): user},
165 )
166 if user_rows:
167 user_content = user_rows[0]
168 # Updating user_status for every system_admin id role login
169 mapped_roles = user_content.get("project_role_mappings")
170 for role in mapped_roles:
171 role_id = role.get("role")
172 role_assigned = self.db.get_one(
173 self.roles_collection,
174 {BaseTopic.id_field(self.roles_collection, role_id): role_id},
175 )
176
177 if role_assigned.get("permissions")["admin"]:
178 if role_assigned.get("permissions")["default"]:
179 if self.config.get("user_management"):
180 filt = {}
181 users = self.db.get_list(self.users_collection, filt)
182 for user_info in users:
183 if not user_info.get("username") == "admin":
184 if not user_info.get("_admin").get(
185 "account_expire_time"
186 ):
187 expire = now + 86400 * self.config.get(
188 "account_expire_days"
189 )
190 self.db.set_one(
191 self.users_collection,
192 {"_id": user_info["_id"]},
193 {"_admin.account_expire_time": expire},
194 )
195 else:
196 if now > user_info.get("_admin").get(
197 "account_expire_time"
198 ):
199 self.db.set_one(
200 self.users_collection,
201 {"_id": user_info["_id"]},
202 {"_admin.user_status": "expired"},
203 )
204 break
205
206 # To add "admin" user_status key while upgrading osm setup with feature enabled
207 if user_content.get("username") == "admin":
208 if self.config.get("user_management"):
209 self.db.set_one(
210 self.users_collection,
211 {"_id": user_content["_id"]},
212 {"_admin.user_status": "always-active"},
213 )
214
215 if not user_content.get("username") == "admin":
216 if self.config.get("user_management"):
217 if not user_content.get("_admin").get("account_expire_time"):
218 account_expire_time = now + 86400 * self.config.get(
219 "account_expire_days"
220 )
221 self.db.set_one(
222 self.users_collection,
223 {"_id": user_content["_id"]},
224 {"_admin.account_expire_time": account_expire_time},
225 )
226 else:
227 account_expire_time = user_content.get("_admin").get(
228 "account_expire_time"
229 )
230
231 if now > account_expire_time:
232 self.db.set_one(
233 self.users_collection,
234 {"_id": user_content["_id"]},
235 {"_admin.user_status": "expired"},
236 )
237 raise AuthException(
238 "Account expired", http_code=HTTPStatus.UNAUTHORIZED
239 )
240
241 if user_content.get("_admin").get("user_status") == "locked":
242 raise AuthException(
243 "Failed to login as the account is locked due to MANY FAILED ATTEMPTS"
244 )
245 elif user_content.get("_admin").get("user_status") == "expired":
246 raise AuthException(
247 "Failed to login as the account is expired"
248 )
249
250 salt = user_content["_admin"]["salt"]
251 shadow_password = sha256(
252 password.encode("utf-8") + salt.encode("utf-8")
253 ).hexdigest()
254 if shadow_password != user_content["password"]:
255 count = 1
256 if user_content.get("_admin").get("retry_count") >= 0:
257 count += user_content.get("_admin").get("retry_count")
258 self.db.set_one(
259 self.users_collection,
260 {"_id": user_content["_id"]},
261 {"_admin.retry_count": count},
262 )
263 self.logger.debug(
264 "Failed Authentications count: {}".format(count)
265 )
266
267 if user_content.get("username") == "admin":
268 user_content = None
269 else:
270 if not self.config.get("user_management"):
271 user_content = None
272 else:
273 if (
274 user_content.get("_admin").get("retry_count")
275 >= self.config["max_pwd_attempt"] - 1
276 ):
277 self.db.set_one(
278 self.users_collection,
279 {"_id": user_content["_id"]},
280 {"_admin.user_status": "locked"},
281 )
282 raise AuthException(
283 "Failed to login as the account is locked due to MANY FAILED ATTEMPTS"
284 )
285 else:
286 user_content = None
287 return user_content
288
289 def authenticate(self, credentials, token_info=None):
290 """
291 Authenticate a user using username/password or previous token_info plus project; its creates a new token
292
293 :param credentials: dictionary that contains:
294 username: name, id or None
295 password: password or None
296 project_id: name, id, or None. If None first found project will be used to get an scope token
297 other items are allowed and ignored
298 :param token_info: previous token_info to obtain authorization
299 :return: the scoped token info or raises an exception. The token is a dictionary with:
300 _id: token string id,
301 username: username,
302 project_id: scoped_token project_id,
303 project_name: scoped_token project_name,
304 expires: epoch time when it expires,
305 """
306
307 now = time()
308 user_content = None
309 user = credentials.get("username")
310 password = credentials.get("password")
311 project = credentials.get("project_id")
312
313 # Try using username/password
314 if user:
315 user_content = self.validate_user(user, password)
316 if not user_content:
317 cef_event(
318 self.cef_logger,
319 {
320 "name": "User login",
321 "sourceUserName": user,
322 "message": "Invalid username/password Project={} Outcome=Failure".format(
323 project
324 ),
325 "severity": "3",
326 },
327 )
328 self.logger.exception("{}".format(self.cef_logger))
329 raise AuthException(
330 "Invalid username/password", http_code=HTTPStatus.UNAUTHORIZED
331 )
332 if not user_content.get("_admin", None):
333 raise AuthException(
334 "No default project for this user.",
335 http_code=HTTPStatus.UNAUTHORIZED,
336 )
337 elif token_info:
338 user_rows = self.db.get_list(
339 self.users_collection, {"username": token_info["username"]}
340 )
341 if user_rows:
342 user_content = user_rows[0]
343 else:
344 raise AuthException("Invalid token", http_code=HTTPStatus.UNAUTHORIZED)
345 else:
346 raise AuthException(
347 "Provide credentials: username/password or Authorization Bearer token",
348 http_code=HTTPStatus.UNAUTHORIZED,
349 )
350 # Delay upon second request within time window
351 if (
352 now - user_content["_admin"].get("last_token_time", 0)
353 < self.token_time_window
354 ):
355 sleep(self.token_delay)
356 # user_content["_admin"]["last_token_time"] = now
357 # self.db.replace("users", user_content["_id"], user_content) # might cause race conditions
358 user_data = {
359 "_admin.last_token_time": now,
360 "_admin.retry_count": 0,
361 }
362 self.db.set_one(
363 self.users_collection,
364 {"_id": user_content["_id"]},
365 user_data,
366 )
367
368 token_id = "".join(
369 random_choice(
370 "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
371 )
372 for _ in range(0, 32)
373 )
374
375 # projects = user_content.get("projects", [])
376 prm_list = user_content.get("project_role_mappings", [])
377
378 if not project:
379 project = prm_list[0]["project"] if prm_list else None
380 if not project:
381 raise AuthException(
382 "can't find a default project for this user",
383 http_code=HTTPStatus.UNAUTHORIZED,
384 )
385
386 projects = [prm["project"] for prm in prm_list]
387
388 proj = self.db.get_one(
389 self.projects_collection, {BaseTopic.id_field("projects", project): project}
390 )
391 project_name = proj["name"]
392 project_id = proj["_id"]
393 if project_name not in projects and project_id not in projects:
394 raise AuthException(
395 "project {} not allowed for this user".format(project),
396 http_code=HTTPStatus.UNAUTHORIZED,
397 )
398
399 # TODO remove admin, this vill be used by roles RBAC
400 if project_name == "admin":
401 token_admin = True
402 else:
403 token_admin = proj.get("admin", False)
404
405 # add token roles
406 roles = []
407 roles_list = []
408 for prm in prm_list:
409 if prm["project"] in [project_id, project_name]:
410 role = self.db.get_one(
411 self.roles_collection,
412 {BaseTopic.id_field("roles", prm["role"]): prm["role"]},
413 )
414 rid = role["_id"]
415 if rid not in roles:
416 rnm = role["name"]
417 roles.append(rid)
418 roles_list.append({"name": rnm, "id": rid})
419 if not roles_list:
420 rid = self.db.get_one(self.roles_collection, {"name": "project_admin"})[
421 "_id"
422 ]
423 roles_list = [{"name": "project_admin", "id": rid}]
424
425 login_count = user_content.get("_admin").get("retry_count")
426 last_token_time = user_content.get("_admin").get("last_token_time")
427
428 admin_show = False
429 user_show = False
430 if self.config.get("user_management"):
431 for role in roles_list:
432 role_id = role.get("id")
433 permission = self.db.get_one(
434 self.roles_collection,
435 {BaseTopic.id_field(self.roles_collection, role_id): role_id},
436 )
437 if permission.get("permissions")["admin"]:
438 if permission.get("permissions")["default"]:
439 admin_show = True
440 break
441 else:
442 user_show = True
443 new_token = {
444 "issued_at": now,
445 "expires": now + 3600,
446 "_id": token_id,
447 "id": token_id,
448 "project_id": proj["_id"],
449 "project_name": proj["name"],
450 "username": user_content["username"],
451 "user_id": user_content["_id"],
452 "admin": token_admin,
453 "roles": roles_list,
454 "login_count": login_count,
455 "last_login": last_token_time,
456 "admin_show": admin_show,
457 "user_show": user_show,
458 }
459
460 self.db.create(self.tokens_collection, new_token)
461 return deepcopy(new_token)
462
463 def get_role_list(self, filter_q={}):
464 """
465 Get role list.
466
467 :return: returns the list of roles.
468 """
469 return self.db.get_list(self.roles_collection, filter_q)
470
471 def create_role(self, role_info):
472 """
473 Create a role.
474
475 :param role_info: full role info.
476 :return: returns the role id.
477 :raises AuthconnOperationException: if role creation failed.
478 """
479 # TODO: Check that role name does not exist ?
480 rid = str(uuid4())
481 role_info["_id"] = rid
482 rid = self.db.create(self.roles_collection, role_info)
483 return rid
484
485 def delete_role(self, role_id):
486 """
487 Delete a role.
488
489 :param role_id: role identifier.
490 :raises AuthconnOperationException: if role deletion failed.
491 """
492 rc = self.db.del_one(self.roles_collection, {"_id": role_id})
493 self.db.del_list(self.tokens_collection, {"roles.id": role_id})
494 return rc
495
496 def update_role(self, role_info):
497 """
498 Update a role.
499
500 :param role_info: full role info.
501 :return: returns the role name and id.
502 :raises AuthconnOperationException: if user creation failed.
503 """
504 rid = role_info["_id"]
505 self.db.set_one(self.roles_collection, {"_id": rid}, role_info)
506 return {"_id": rid, "name": role_info["name"]}
507
508 def create_user(self, user_info):
509 """
510 Create a user.
511
512 :param user_info: full user info.
513 :return: returns the username and id of the user.
514 """
515 BaseTopic.format_on_new(user_info, make_public=False)
516 salt = uuid4().hex
517 user_info["_admin"]["salt"] = salt
518 user_info["_admin"]["user_status"] = "active"
519 present = time()
520 if not user_info["username"] == "admin":
521 if self.config.get("user_management"):
522 user_info["_admin"]["modified"] = present
523 user_info["_admin"]["password_expire_time"] = present
524 account_expire_time = present + 86400 * self.config.get(
525 "account_expire_days"
526 )
527 user_info["_admin"]["account_expire_time"] = account_expire_time
528
529 user_info["_admin"]["retry_count"] = 0
530 user_info["_admin"]["last_token_time"] = present
531 if "password" in user_info:
532 user_info["password"] = sha256(
533 user_info["password"].encode("utf-8") + salt.encode("utf-8")
534 ).hexdigest()
535 user_info["_admin"]["password_history"] = {salt: user_info["password"]}
536 # "projects" are not stored any more
537 if "projects" in user_info:
538 del user_info["projects"]
539 self.db.create(self.users_collection, user_info)
540 return {"username": user_info["username"], "_id": user_info["_id"]}
541
542 def update_user(self, user_info):
543 """
544 Change the user name and/or password.
545
546 :param user_info: user info modifications
547 """
548 uid = user_info["_id"]
549 old_pwd = user_info.get("old_password")
550 unlock = user_info.get("unlock")
551 renew = user_info.get("renew")
552 permission_id = user_info.get("system_admin_id")
553
554 user_data = self.db.get_one(
555 self.users_collection, {BaseTopic.id_field("users", uid): uid}
556 )
557 if old_pwd:
558 salt = user_data["_admin"]["salt"]
559 shadow_password = sha256(
560 old_pwd.encode("utf-8") + salt.encode("utf-8")
561 ).hexdigest()
562 if shadow_password != user_data["password"]:
563 raise AuthconnConflictException(
564 "Incorrect password", http_code=HTTPStatus.CONFLICT
565 )
566 # Unlocking the user
567 if unlock:
568 system_user = None
569 unlock_state = False
570 if not permission_id:
571 raise AuthconnConflictException(
572 "system_admin_id is the required field to unlock the user",
573 http_code=HTTPStatus.CONFLICT,
574 )
575 else:
576 system_user = self.db.get_one(
577 self.users_collection,
578 {
579 BaseTopic.id_field(
580 self.users_collection, permission_id
581 ): permission_id
582 },
583 )
584 mapped_roles = system_user.get("project_role_mappings")
585 for role in mapped_roles:
586 role_id = role.get("role")
587 role_assigned = self.db.get_one(
588 self.roles_collection,
589 {BaseTopic.id_field(self.roles_collection, role_id): role_id},
590 )
591 if role_assigned.get("permissions")["admin"]:
592 if role_assigned.get("permissions")["default"]:
593 user_data["_admin"]["retry_count"] = 0
594 user_data["_admin"]["user_status"] = "active"
595 unlock_state = True
596 break
597 if not unlock_state:
598 raise AuthconnConflictException(
599 "User '{}' does not have the privilege to unlock the user".format(
600 permission_id
601 ),
602 http_code=HTTPStatus.CONFLICT,
603 )
604 # Renewing the user
605 if renew:
606 system_user = None
607 renew_state = False
608 if not permission_id:
609 raise AuthconnConflictException(
610 "system_admin_id is the required field to renew the user",
611 http_code=HTTPStatus.CONFLICT,
612 )
613 else:
614 system_user = self.db.get_one(
615 self.users_collection,
616 {
617 BaseTopic.id_field(
618 self.users_collection, permission_id
619 ): permission_id
620 },
621 )
622 mapped_roles = system_user.get("project_role_mappings")
623 for role in mapped_roles:
624 role_id = role.get("role")
625 role_assigned = self.db.get_one(
626 self.roles_collection,
627 {BaseTopic.id_field(self.roles_collection, role_id): role_id},
628 )
629 if role_assigned.get("permissions")["admin"]:
630 if role_assigned.get("permissions")["default"]:
631 present = time()
632 account_expire = (
633 present + 86400 * self.config["account_expire_days"]
634 )
635 user_data["_admin"]["modified"] = present
636 user_data["_admin"]["account_expire_time"] = account_expire
637 user_data["_admin"]["user_status"] = "active"
638 renew_state = True
639 break
640 if not renew_state:
641 raise AuthconnConflictException(
642 "User '{}' does not have the privilege to renew the user".format(
643 permission_id
644 ),
645 http_code=HTTPStatus.CONFLICT,
646 )
647 BaseTopic.format_on_edit(user_data, user_info)
648 # User Name
649 usnm = user_info.get("username")
650 if usnm:
651 user_data["username"] = usnm
652 # If password is given and is not already encripted
653 pswd = user_info.get("password")
654 if pswd and (
655 len(pswd) != 64 or not re.match("[a-fA-F0-9]*", pswd)
656 ): # TODO: Improve check?
657 cef_event(
658 self.cef_logger,
659 {
660 "name": "Change Password",
661 "sourceUserName": user_data["username"],
662 "message": "Changing Password for user, Outcome=Success",
663 "severity": "2",
664 },
665 )
666 self.logger.info("{}".format(self.cef_logger))
667 salt = uuid4().hex
668 if "_admin" not in user_data:
669 user_data["_admin"] = {}
670 if user_data.get("_admin").get("password_history"):
671 old_pwds = user_data.get("_admin").get("password_history")
672 else:
673 old_pwds = {}
674 for k, v in old_pwds.items():
675 shadow_password = sha256(
676 pswd.encode("utf-8") + k.encode("utf-8")
677 ).hexdigest()
678 if v == shadow_password:
679 raise AuthconnConflictException(
680 "Password is used before", http_code=HTTPStatus.CONFLICT
681 )
682 user_data["_admin"]["salt"] = salt
683 user_data["password"] = sha256(
684 pswd.encode("utf-8") + salt.encode("utf-8")
685 ).hexdigest()
686 if len(old_pwds) >= 3:
687 old_pwds.pop(list(old_pwds.keys())[0])
688 old_pwds.update({salt: user_data["password"]})
689 user_data["_admin"]["password_history"] = old_pwds
690 if not user_data["username"] == "admin":
691 if self.config.get("user_management"):
692 present = time()
693 if self.config.get("pwd_expire_days"):
694 expire = present + 86400 * self.config.get("pwd_expire_days")
695 user_data["_admin"]["modified"] = present
696 user_data["_admin"]["password_expire_time"] = expire
697 # Project-Role Mappings
698 # TODO: Check that user_info NEVER includes "project_role_mappings"
699 if "project_role_mappings" not in user_data:
700 user_data["project_role_mappings"] = []
701 for prm in user_info.get("add_project_role_mappings", []):
702 user_data["project_role_mappings"].append(prm)
703 for prm in user_info.get("remove_project_role_mappings", []):
704 for pidf in ["project", "project_name"]:
705 for ridf in ["role", "role_name"]:
706 try:
707 user_data["project_role_mappings"].remove(
708 {"role": prm[ridf], "project": prm[pidf]}
709 )
710 except KeyError:
711 pass
712 except ValueError:
713 pass
714 idf = BaseTopic.id_field("users", uid)
715 self.db.set_one(self.users_collection, {idf: uid}, user_data)
716 if user_info.get("remove_project_role_mappings"):
717 idf = "user_id" if idf == "_id" else idf
718 self.db.del_list(self.tokens_collection, {idf: uid})
719
720 def delete_user(self, user_id):
721 """
722 Delete user.
723
724 :param user_id: user identifier.
725 :raises AuthconnOperationException: if user deletion failed.
726 """
727 self.db.del_one(self.users_collection, {"_id": user_id})
728 self.db.del_list(self.tokens_collection, {"user_id": user_id})
729 return True
730
731 def get_user_list(self, filter_q=None):
732 """
733 Get user list.
734
735 :param filter_q: dictionary to filter user list by:
736 name (username is also admitted). If a user id is equal to the filter name, it is also provided
737 other
738 :return: returns a list of users.
739 """
740 filt = filter_q or {}
741 if "name" in filt: # backward compatibility
742 filt["username"] = filt.pop("name")
743 if filt.get("username") and is_valid_uuid(filt["username"]):
744 # username cannot be a uuid. If this is the case, change from username to _id
745 filt["_id"] = filt.pop("username")
746 users = self.db.get_list(self.users_collection, filt)
747 project_id_name = {}
748 role_id_name = {}
749 for user in users:
750 prms = user.get("project_role_mappings")
751 projects = user.get("projects")
752 if prms:
753 projects = []
754 # add project_name and role_name. Generate projects for backward compatibility
755 for prm in prms:
756 project_id = prm["project"]
757 if project_id not in project_id_name:
758 pr = self.db.get_one(
759 self.projects_collection,
760 {BaseTopic.id_field("projects", project_id): project_id},
761 fail_on_empty=False,
762 )
763 project_id_name[project_id] = pr["name"] if pr else None
764 prm["project_name"] = project_id_name[project_id]
765 if prm["project_name"] not in projects:
766 projects.append(prm["project_name"])
767
768 role_id = prm["role"]
769 if role_id not in role_id_name:
770 role = self.db.get_one(
771 self.roles_collection,
772 {BaseTopic.id_field("roles", role_id): role_id},
773 fail_on_empty=False,
774 )
775 role_id_name[role_id] = role["name"] if role else None
776 prm["role_name"] = role_id_name[role_id]
777 user["projects"] = projects # for backward compatibility
778 elif projects:
779 # user created with an old version. Create a project_role mapping with role project_admin
780 user["project_role_mappings"] = []
781 role = self.db.get_one(
782 self.roles_collection,
783 {BaseTopic.id_field("roles", "project_admin"): "project_admin"},
784 )
785 for p_id_name in projects:
786 pr = self.db.get_one(
787 self.projects_collection,
788 {BaseTopic.id_field("projects", p_id_name): p_id_name},
789 )
790 prm = {
791 "project": pr["_id"],
792 "project_name": pr["name"],
793 "role_name": "project_admin",
794 "role": role["_id"],
795 }
796 user["project_role_mappings"].append(prm)
797 else:
798 user["projects"] = []
799 user["project_role_mappings"] = []
800
801 return users
802
803 def get_project_list(self, filter_q={}):
804 """
805 Get role list.
806
807 :return: returns the list of projects.
808 """
809 return self.db.get_list(self.projects_collection, filter_q)
810
811 def create_project(self, project_info):
812 """
813 Create a project.
814
815 :param project: full project info.
816 :return: the internal id of the created project
817 :raises AuthconnOperationException: if project creation failed.
818 """
819 pid = self.db.create(self.projects_collection, project_info)
820 return pid
821
822 def delete_project(self, project_id):
823 """
824 Delete a project.
825
826 :param project_id: project identifier.
827 :raises AuthconnOperationException: if project deletion failed.
828 """
829 idf = BaseTopic.id_field("projects", project_id)
830 r = self.db.del_one(self.projects_collection, {idf: project_id})
831 idf = "project_id" if idf == "_id" else "project_name"
832 self.db.del_list(self.tokens_collection, {idf: project_id})
833 return r
834
835 def update_project(self, project_id, project_info):
836 """
837 Change the name of a project
838
839 :param project_id: project to be changed
840 :param project_info: full project info
841 :return: None
842 :raises AuthconnOperationException: if project update failed.
843 """
844 self.db.set_one(
845 self.projects_collection,
846 {BaseTopic.id_field("projects", project_id): project_id},
847 project_info,
848 )