blob: d039f84247d66bde3936f8d11b88c4e7a6fab8fa [file] [log] [blame]
delacruzramoceb8baf2019-06-21 14:25:38 +02001# -*- 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"""
23AuthconnInternal implements implements the connector for
24OSM Internal Authentication Backend and leverages the RBAC model
25"""
26
garciadeblas4568a372021-03-24 09:19:48 +010027__author__ = (
28 "Pedro de la Cruz Ramos <pdelacruzramos@altran.com>, "
29 "Alfonso Tierno <alfonso.tiernosepulveda@telefoncia.com"
30)
delacruzramoceb8baf2019-06-21 14:25:38 +020031__date__ = "$06-jun-2019 11:16:08$"
32
tierno5ec768a2020-03-31 09:46:44 +000033import logging
34import re
35
garciadeblasf2af4a12023-01-24 16:56:54 +010036from osm_nbi.authconn import (
37 Authconn,
38 AuthException,
39 AuthconnConflictException,
40) # , AuthconnOperationException
delacruzramoceb8baf2019-06-21 14:25:38 +020041from osm_common.dbbase import DbException
tierno23acf402019-08-28 13:36:34 +000042from osm_nbi.base_topic import BaseTopic
tierno5ec768a2020-03-31 09:46:44 +000043from osm_nbi.validation import is_valid_uuid
delacruzramoad682a52019-12-10 16:26:34 +010044from time import time, sleep
delacruzramoceb8baf2019-06-21 14:25:38 +020045from http import HTTPStatus
46from uuid import uuid4
47from hashlib import sha256
48from copy import deepcopy
49from random import choice as random_choice
50
51
52class AuthconnInternal(Authconn):
garciadeblas4568a372021-03-24 09:19:48 +010053 token_time_window = 2 # seconds
54 token_delay = 1 # seconds to wait upon second request within time window
delacruzramoceb8baf2019-06-21 14:25:38 +020055
K Sai Kiran7ddb0732020-10-30 11:14:44 +053056 users_collection = "users"
57 roles_collection = "roles"
58 projects_collection = "projects"
59 tokens_collection = "tokens"
60
tierno9e87a7f2020-03-23 09:24:10 +000061 def __init__(self, config, db, role_permissions):
62 Authconn.__init__(self, config, db, role_permissions)
delacruzramoceb8baf2019-06-21 14:25:38 +020063 self.logger = logging.getLogger("nbi.authenticator.internal")
64
delacruzramoceb8baf2019-06-21 14:25:38 +020065 self.db = db
delacruzramoad682a52019-12-10 16:26:34 +010066 # self.msg = msg
67 # self.token_cache = token_cache
delacruzramoceb8baf2019-06-21 14:25:38 +020068
69 # To be Confirmed
delacruzramoceb8baf2019-06-21 14:25:38 +020070 self.sess = None
71
delacruzramoceb8baf2019-06-21 14:25:38 +020072 def validate_token(self, token):
73 """
74 Check if the token is valid.
75
76 :param token: token to validate
77 :return: dictionary with information associated with the token:
78 "_id": token id
79 "project_id": project id
80 "project_name": project name
81 "user_id": user id
82 "username": user name
83 "roles": list with dict containing {name, id}
84 "expires": expiration date
85 If the token is not valid an exception is raised.
86 """
87
88 try:
89 if not token:
garciadeblas4568a372021-03-24 09:19:48 +010090 raise AuthException(
91 "Needed a token or Authorization HTTP header",
92 http_code=HTTPStatus.UNAUTHORIZED,
93 )
delacruzramoceb8baf2019-06-21 14:25:38 +020094
delacruzramoceb8baf2019-06-21 14:25:38 +020095 now = time()
delacruzramoceb8baf2019-06-21 14:25:38 +020096
97 # get from database if not in cache
delacruzramoad682a52019-12-10 16:26:34 +010098 # if not token_info:
K Sai Kiran7ddb0732020-10-30 11:14:44 +053099 token_info = self.db.get_one(self.tokens_collection, {"_id": token})
delacruzramoad682a52019-12-10 16:26:34 +0100100 if token_info["expires"] < now:
garciadeblas4568a372021-03-24 09:19:48 +0100101 raise AuthException(
102 "Expired Token or Authorization HTTP header",
103 http_code=HTTPStatus.UNAUTHORIZED,
104 )
delacruzramoceb8baf2019-06-21 14:25:38 +0200105
tierno701018c2019-06-25 11:13:14 +0000106 return token_info
delacruzramoceb8baf2019-06-21 14:25:38 +0200107
108 except DbException as e:
109 if e.http_code == HTTPStatus.NOT_FOUND:
garciadeblas4568a372021-03-24 09:19:48 +0100110 raise AuthException(
111 "Invalid Token or Authorization HTTP header",
112 http_code=HTTPStatus.UNAUTHORIZED,
113 )
delacruzramoceb8baf2019-06-21 14:25:38 +0200114 else:
115 raise
116 except AuthException:
tiernoe1eb3b22019-08-26 15:59:24 +0000117 raise
delacruzramoceb8baf2019-06-21 14:25:38 +0200118 except Exception:
garciadeblas4568a372021-03-24 09:19:48 +0100119 self.logger.exception(
120 "Error during token validation using internal backend"
121 )
122 raise AuthException(
123 "Error during token validation using internal backend",
124 http_code=HTTPStatus.UNAUTHORIZED,
125 )
delacruzramoceb8baf2019-06-21 14:25:38 +0200126
127 def revoke_token(self, token):
128 """
129 Invalidate a token.
130
131 :param token: token to be revoked
132 """
133 try:
delacruzramoad682a52019-12-10 16:26:34 +0100134 # self.token_cache.pop(token, None)
K Sai Kiran7ddb0732020-10-30 11:14:44 +0530135 self.db.del_one(self.tokens_collection, {"_id": token})
delacruzramoceb8baf2019-06-21 14:25:38 +0200136 return True
137 except DbException as e:
138 if e.http_code == HTTPStatus.NOT_FOUND:
garciadeblas4568a372021-03-24 09:19:48 +0100139 raise AuthException(
140 "Token '{}' not found".format(token), http_code=HTTPStatus.NOT_FOUND
141 )
delacruzramoceb8baf2019-06-21 14:25:38 +0200142 else:
143 # raise
delacruzramoad682a52019-12-10 16:26:34 +0100144 exmsg = "Error during token revocation using internal backend"
145 self.logger.exception(exmsg)
146 raise AuthException(exmsg, http_code=HTTPStatus.UNAUTHORIZED)
delacruzramoceb8baf2019-06-21 14:25:38 +0200147
K Sai Kiran7ddb0732020-10-30 11:14:44 +0530148 def validate_user(self, user, password):
149 """
150 Validate username and password via appropriate backend.
151 :param user: username of the user.
152 :param password: password to be validated.
153 """
garciadeblas4568a372021-03-24 09:19:48 +0100154 user_rows = self.db.get_list(
155 self.users_collection, {BaseTopic.id_field("users", user): user}
156 )
K Sai Kiran7ddb0732020-10-30 11:14:44 +0530157 user_content = None
158 if user_rows:
159 user_content = user_rows[0]
160 salt = user_content["_admin"]["salt"]
garciadeblas4568a372021-03-24 09:19:48 +0100161 shadow_password = sha256(
162 password.encode("utf-8") + salt.encode("utf-8")
163 ).hexdigest()
K Sai Kiran7ddb0732020-10-30 11:14:44 +0530164 if shadow_password != user_content["password"]:
165 user_content = None
166 return user_content
167
tierno6486f742020-02-13 16:30:14 +0000168 def authenticate(self, credentials, token_info=None):
delacruzramoceb8baf2019-06-21 14:25:38 +0200169 """
tierno701018c2019-06-25 11:13:14 +0000170 Authenticate a user using username/password or previous token_info plus project; its creates a new token
delacruzramoceb8baf2019-06-21 14:25:38 +0200171
tierno6486f742020-02-13 16:30:14 +0000172 :param credentials: dictionary that contains:
173 username: name, id or None
174 password: password or None
175 project_id: name, id, or None. If None first found project will be used to get an scope token
176 other items are allowed and ignored
tierno701018c2019-06-25 11:13:14 +0000177 :param token_info: previous token_info to obtain authorization
delacruzramoceb8baf2019-06-21 14:25:38 +0200178 :return: the scoped token info or raises an exception. The token is a dictionary with:
179 _id: token string id,
180 username: username,
181 project_id: scoped_token project_id,
182 project_name: scoped_token project_name,
183 expires: epoch time when it expires,
184 """
185
186 now = time()
187 user_content = None
tierno6486f742020-02-13 16:30:14 +0000188 user = credentials.get("username")
189 password = credentials.get("password")
190 project = credentials.get("project_id")
delacruzramoceb8baf2019-06-21 14:25:38 +0200191
delacruzramo01b15d32019-07-02 14:37:47 +0200192 # Try using username/password
193 if user:
K Sai Kiran7ddb0732020-10-30 11:14:44 +0530194 user_content = self.validate_user(user, password)
delacruzramo01b15d32019-07-02 14:37:47 +0200195 if not user_content:
garciadeblas4568a372021-03-24 09:19:48 +0100196 raise AuthException(
197 "Invalid username/password", http_code=HTTPStatus.UNAUTHORIZED
198 )
K Sai Kiran7ddb0732020-10-30 11:14:44 +0530199 if not user_content.get("_admin", None):
garciadeblas4568a372021-03-24 09:19:48 +0100200 raise AuthException(
201 "No default project for this user.",
202 http_code=HTTPStatus.UNAUTHORIZED,
203 )
delacruzramo01b15d32019-07-02 14:37:47 +0200204 elif token_info:
garciadeblas4568a372021-03-24 09:19:48 +0100205 user_rows = self.db.get_list(
206 self.users_collection, {"username": token_info["username"]}
207 )
delacruzramo01b15d32019-07-02 14:37:47 +0200208 if user_rows:
209 user_content = user_rows[0]
delacruzramoceb8baf2019-06-21 14:25:38 +0200210 else:
delacruzramo01b15d32019-07-02 14:37:47 +0200211 raise AuthException("Invalid token", http_code=HTTPStatus.UNAUTHORIZED)
212 else:
garciadeblas4568a372021-03-24 09:19:48 +0100213 raise AuthException(
214 "Provide credentials: username/password or Authorization Bearer token",
215 http_code=HTTPStatus.UNAUTHORIZED,
216 )
delacruzramoad682a52019-12-10 16:26:34 +0100217 # Delay upon second request within time window
garciadeblas4568a372021-03-24 09:19:48 +0100218 if (
219 now - user_content["_admin"].get("last_token_time", 0)
220 < self.token_time_window
221 ):
delacruzramoad682a52019-12-10 16:26:34 +0100222 sleep(self.token_delay)
223 # user_content["_admin"]["last_token_time"] = now
224 # self.db.replace("users", user_content["_id"], user_content) # might cause race conditions
garciadeblas4568a372021-03-24 09:19:48 +0100225 self.db.set_one(
226 self.users_collection,
227 {"_id": user_content["_id"]},
228 {"_admin.last_token_time": now},
229 )
delacruzramoad682a52019-12-10 16:26:34 +0100230
garciadeblas4568a372021-03-24 09:19:48 +0100231 token_id = "".join(
232 random_choice(
233 "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
234 )
235 for _ in range(0, 32)
236 )
delacruzramoceb8baf2019-06-21 14:25:38 +0200237
delacruzramo01b15d32019-07-02 14:37:47 +0200238 # projects = user_content.get("projects", [])
239 prm_list = user_content.get("project_role_mappings", [])
delacruzramoceb8baf2019-06-21 14:25:38 +0200240
delacruzramo01b15d32019-07-02 14:37:47 +0200241 if not project:
242 project = prm_list[0]["project"] if prm_list else None
243 if not project:
garciadeblas4568a372021-03-24 09:19:48 +0100244 raise AuthException(
245 "can't find a default project for this user",
246 http_code=HTTPStatus.UNAUTHORIZED,
247 )
tierno701018c2019-06-25 11:13:14 +0000248
delacruzramo01b15d32019-07-02 14:37:47 +0200249 projects = [prm["project"] for prm in prm_list]
delacruzramoceb8baf2019-06-21 14:25:38 +0200250
garciadeblas4568a372021-03-24 09:19:48 +0100251 proj = self.db.get_one(
252 self.projects_collection, {BaseTopic.id_field("projects", project): project}
253 )
delacruzramo01b15d32019-07-02 14:37:47 +0200254 project_name = proj["name"]
255 project_id = proj["_id"]
256 if project_name not in projects and project_id not in projects:
garciadeblas4568a372021-03-24 09:19:48 +0100257 raise AuthException(
258 "project {} not allowed for this user".format(project),
259 http_code=HTTPStatus.UNAUTHORIZED,
260 )
tierno701018c2019-06-25 11:13:14 +0000261
delacruzramo01b15d32019-07-02 14:37:47 +0200262 # TODO remove admin, this vill be used by roles RBAC
263 if project_name == "admin":
264 token_admin = True
265 else:
266 token_admin = proj.get("admin", False)
delacruzramoceb8baf2019-06-21 14:25:38 +0200267
delacruzramo01b15d32019-07-02 14:37:47 +0200268 # add token roles
269 roles = []
270 roles_list = []
271 for prm in prm_list:
272 if prm["project"] in [project_id, project_name]:
garciadeblas4568a372021-03-24 09:19:48 +0100273 role = self.db.get_one(
274 self.roles_collection,
275 {BaseTopic.id_field("roles", prm["role"]): prm["role"]},
276 )
delacruzramo01b15d32019-07-02 14:37:47 +0200277 rid = role["_id"]
278 if rid not in roles:
279 rnm = role["name"]
280 roles.append(rid)
281 roles_list.append({"name": rnm, "id": rid})
282 if not roles_list:
garciadeblas4568a372021-03-24 09:19:48 +0100283 rid = self.db.get_one(self.roles_collection, {"name": "project_admin"})[
284 "_id"
285 ]
delacruzramo01b15d32019-07-02 14:37:47 +0200286 roles_list = [{"name": "project_admin", "id": rid}]
delacruzramoceb8baf2019-06-21 14:25:38 +0200287
garciadeblas4568a372021-03-24 09:19:48 +0100288 new_token = {
289 "issued_at": now,
290 "expires": now + 3600,
291 "_id": token_id,
292 "id": token_id,
293 "project_id": proj["_id"],
294 "project_name": proj["name"],
295 "username": user_content["username"],
296 "user_id": user_content["_id"],
297 "admin": token_admin,
298 "roles": roles_list,
299 }
delacruzramoceb8baf2019-06-21 14:25:38 +0200300
K Sai Kiran7ddb0732020-10-30 11:14:44 +0530301 self.db.create(self.tokens_collection, new_token)
delacruzramo01b15d32019-07-02 14:37:47 +0200302 return deepcopy(new_token)
303
304 def get_role_list(self, filter_q={}):
delacruzramoceb8baf2019-06-21 14:25:38 +0200305 """
306 Get role list.
307
308 :return: returns the list of roles.
309 """
K Sai Kiran7ddb0732020-10-30 11:14:44 +0530310 return self.db.get_list(self.roles_collection, filter_q)
delacruzramoceb8baf2019-06-21 14:25:38 +0200311
delacruzramo01b15d32019-07-02 14:37:47 +0200312 def create_role(self, role_info):
delacruzramoceb8baf2019-06-21 14:25:38 +0200313 """
314 Create a role.
315
delacruzramo01b15d32019-07-02 14:37:47 +0200316 :param role_info: full role info.
317 :return: returns the role id.
delacruzramoceb8baf2019-06-21 14:25:38 +0200318 :raises AuthconnOperationException: if role creation failed.
319 """
delacruzramoceb8baf2019-06-21 14:25:38 +0200320 # TODO: Check that role name does not exist ?
delacruzramo01b15d32019-07-02 14:37:47 +0200321 rid = str(uuid4())
322 role_info["_id"] = rid
K Sai Kiran7ddb0732020-10-30 11:14:44 +0530323 rid = self.db.create(self.roles_collection, role_info)
delacruzramo01b15d32019-07-02 14:37:47 +0200324 return rid
delacruzramoceb8baf2019-06-21 14:25:38 +0200325
326 def delete_role(self, role_id):
327 """
328 Delete a role.
329
330 :param role_id: role identifier.
331 :raises AuthconnOperationException: if role deletion failed.
332 """
K Sai Kiran7ddb0732020-10-30 11:14:44 +0530333 rc = self.db.del_one(self.roles_collection, {"_id": role_id})
334 self.db.del_list(self.tokens_collection, {"roles.id": role_id})
delacruzramoad682a52019-12-10 16:26:34 +0100335 return rc
delacruzramo01b15d32019-07-02 14:37:47 +0200336
337 def update_role(self, role_info):
338 """
339 Update a role.
340
341 :param role_info: full role info.
342 :return: returns the role name and id.
343 :raises AuthconnOperationException: if user creation failed.
344 """
345 rid = role_info["_id"]
K Sai Kiran7ddb0732020-10-30 11:14:44 +0530346 self.db.set_one(self.roles_collection, {"_id": rid}, role_info)
delacruzramo01b15d32019-07-02 14:37:47 +0200347 return {"_id": rid, "name": role_info["name"]}
348
349 def create_user(self, user_info):
350 """
351 Create a user.
352
353 :param user_info: full user info.
354 :return: returns the username and id of the user.
355 """
356 BaseTopic.format_on_new(user_info, make_public=False)
357 salt = uuid4().hex
358 user_info["_admin"]["salt"] = salt
selvi.ja9a1fc82022-04-04 06:54:30 +0000359 present = time()
360 if not user_info["username"] == "admin":
361 if self.config.get("pwd_expiry_check"):
362 user_info["_admin"]["modified_time"] = present
363 user_info["_admin"]["expire_time"] = present
delacruzramo01b15d32019-07-02 14:37:47 +0200364 if "password" in user_info:
garciadeblas4568a372021-03-24 09:19:48 +0100365 user_info["password"] = sha256(
366 user_info["password"].encode("utf-8") + salt.encode("utf-8")
367 ).hexdigest()
delacruzramo01b15d32019-07-02 14:37:47 +0200368 # "projects" are not stored any more
369 if "projects" in user_info:
370 del user_info["projects"]
K Sai Kiran7ddb0732020-10-30 11:14:44 +0530371 self.db.create(self.users_collection, user_info)
delacruzramo01b15d32019-07-02 14:37:47 +0200372 return {"username": user_info["username"], "_id": user_info["_id"]}
373
374 def update_user(self, user_info):
375 """
376 Change the user name and/or password.
377
378 :param user_info: user info modifications
379 """
380 uid = user_info["_id"]
selvi.ja9a1fc82022-04-04 06:54:30 +0000381 old_pwd = user_info.get("old_password")
garciadeblas4568a372021-03-24 09:19:48 +0100382 user_data = self.db.get_one(
383 self.users_collection, {BaseTopic.id_field("users", uid): uid}
384 )
selvi.ja9a1fc82022-04-04 06:54:30 +0000385 if old_pwd:
386 salt = user_data["_admin"]["salt"]
garciadeblasf2af4a12023-01-24 16:56:54 +0100387 shadow_password = sha256(
388 old_pwd.encode("utf-8") + salt.encode("utf-8")
389 ).hexdigest()
selvi.ja9a1fc82022-04-04 06:54:30 +0000390 if shadow_password != user_data["password"]:
391 raise AuthconnConflictException(
garciadeblasf2af4a12023-01-24 16:56:54 +0100392 "Incorrect password", http_code=HTTPStatus.CONFLICT
selvi.ja9a1fc82022-04-04 06:54:30 +0000393 )
delacruzramo01b15d32019-07-02 14:37:47 +0200394 BaseTopic.format_on_edit(user_data, user_info)
395 # User Name
396 usnm = user_info.get("username")
397 if usnm:
398 user_data["username"] = usnm
399 # If password is given and is not already encripted
400 pswd = user_info.get("password")
garciadeblas4568a372021-03-24 09:19:48 +0100401 if pswd and (
402 len(pswd) != 64 or not re.match("[a-fA-F0-9]*", pswd)
403 ): # TODO: Improve check?
delacruzramo01b15d32019-07-02 14:37:47 +0200404 salt = uuid4().hex
405 if "_admin" not in user_data:
406 user_data["_admin"] = {}
407 user_data["_admin"]["salt"] = salt
garciadeblas4568a372021-03-24 09:19:48 +0100408 user_data["password"] = sha256(
409 pswd.encode("utf-8") + salt.encode("utf-8")
410 ).hexdigest()
selvi.ja9a1fc82022-04-04 06:54:30 +0000411 if not user_data["username"] == "admin":
412 if self.config.get("pwd_expiry_check"):
413 present = time()
414 if self.config.get("days"):
415 expire = present + 86400 * self.config.get("days")
416 user_data["_admin"]["modified_time"] = present
417 user_data["_admin"]["expire_time"] = expire
delacruzramo01b15d32019-07-02 14:37:47 +0200418 # Project-Role Mappings
419 # TODO: Check that user_info NEVER includes "project_role_mappings"
420 if "project_role_mappings" not in user_data:
421 user_data["project_role_mappings"] = []
422 for prm in user_info.get("add_project_role_mappings", []):
423 user_data["project_role_mappings"].append(prm)
424 for prm in user_info.get("remove_project_role_mappings", []):
425 for pidf in ["project", "project_name"]:
426 for ridf in ["role", "role_name"]:
427 try:
garciadeblas4568a372021-03-24 09:19:48 +0100428 user_data["project_role_mappings"].remove(
429 {"role": prm[ridf], "project": prm[pidf]}
430 )
delacruzramo01b15d32019-07-02 14:37:47 +0200431 except KeyError:
432 pass
433 except ValueError:
434 pass
delacruzramo3d6881c2019-12-04 13:42:26 +0100435 idf = BaseTopic.id_field("users", uid)
K Sai Kiran7ddb0732020-10-30 11:14:44 +0530436 self.db.set_one(self.users_collection, {idf: uid}, user_data)
delacruzramo3d6881c2019-12-04 13:42:26 +0100437 if user_info.get("remove_project_role_mappings"):
delacruzramoad682a52019-12-10 16:26:34 +0100438 idf = "user_id" if idf == "_id" else idf
K Sai Kiran7ddb0732020-10-30 11:14:44 +0530439 self.db.del_list(self.tokens_collection, {idf: uid})
delacruzramo01b15d32019-07-02 14:37:47 +0200440
441 def delete_user(self, user_id):
442 """
443 Delete user.
444
445 :param user_id: user identifier.
446 :raises AuthconnOperationException: if user deletion failed.
447 """
K Sai Kiran7ddb0732020-10-30 11:14:44 +0530448 self.db.del_one(self.users_collection, {"_id": user_id})
449 self.db.del_list(self.tokens_collection, {"user_id": user_id})
delacruzramoceb8baf2019-06-21 14:25:38 +0200450 return True
delacruzramo01b15d32019-07-02 14:37:47 +0200451
452 def get_user_list(self, filter_q=None):
453 """
454 Get user list.
455
tierno5ec768a2020-03-31 09:46:44 +0000456 :param filter_q: dictionary to filter user list by:
457 name (username is also admitted). If a user id is equal to the filter name, it is also provided
458 other
delacruzramo01b15d32019-07-02 14:37:47 +0200459 :return: returns a list of users.
460 """
461 filt = filter_q or {}
tierno5ec768a2020-03-31 09:46:44 +0000462 if "name" in filt: # backward compatibility
463 filt["username"] = filt.pop("name")
464 if filt.get("username") and is_valid_uuid(filt["username"]):
465 # username cannot be a uuid. If this is the case, change from username to _id
466 filt["_id"] = filt.pop("username")
K Sai Kiran7ddb0732020-10-30 11:14:44 +0530467 users = self.db.get_list(self.users_collection, filt)
tierno1546f2a2019-08-20 15:38:11 +0000468 project_id_name = {}
469 role_id_name = {}
delacruzramo01b15d32019-07-02 14:37:47 +0200470 for user in users:
tierno1546f2a2019-08-20 15:38:11 +0000471 prms = user.get("project_role_mappings")
472 projects = user.get("projects")
473 if prms:
474 projects = []
475 # add project_name and role_name. Generate projects for backward compatibility
delacruzramo01b15d32019-07-02 14:37:47 +0200476 for prm in prms:
tierno1546f2a2019-08-20 15:38:11 +0000477 project_id = prm["project"]
478 if project_id not in project_id_name:
garciadeblas4568a372021-03-24 09:19:48 +0100479 pr = self.db.get_one(
480 self.projects_collection,
481 {BaseTopic.id_field("projects", project_id): project_id},
482 fail_on_empty=False,
483 )
tierno1546f2a2019-08-20 15:38:11 +0000484 project_id_name[project_id] = pr["name"] if pr else None
485 prm["project_name"] = project_id_name[project_id]
486 if prm["project_name"] not in projects:
487 projects.append(prm["project_name"])
488
489 role_id = prm["role"]
490 if role_id not in role_id_name:
garciadeblas4568a372021-03-24 09:19:48 +0100491 role = self.db.get_one(
492 self.roles_collection,
493 {BaseTopic.id_field("roles", role_id): role_id},
494 fail_on_empty=False,
495 )
tierno1546f2a2019-08-20 15:38:11 +0000496 role_id_name[role_id] = role["name"] if role else None
497 prm["role_name"] = role_id_name[role_id]
498 user["projects"] = projects # for backward compatibility
499 elif projects:
500 # user created with an old version. Create a project_role mapping with role project_admin
501 user["project_role_mappings"] = []
garciadeblas4568a372021-03-24 09:19:48 +0100502 role = self.db.get_one(
503 self.roles_collection,
504 {BaseTopic.id_field("roles", "project_admin"): "project_admin"},
505 )
tierno1546f2a2019-08-20 15:38:11 +0000506 for p_id_name in projects:
garciadeblas4568a372021-03-24 09:19:48 +0100507 pr = self.db.get_one(
508 self.projects_collection,
509 {BaseTopic.id_field("projects", p_id_name): p_id_name},
510 )
511 prm = {
512 "project": pr["_id"],
513 "project_name": pr["name"],
514 "role_name": "project_admin",
515 "role": role["_id"],
516 }
tierno1546f2a2019-08-20 15:38:11 +0000517 user["project_role_mappings"].append(prm)
518 else:
519 user["projects"] = []
520 user["project_role_mappings"] = []
521
delacruzramo01b15d32019-07-02 14:37:47 +0200522 return users
523
524 def get_project_list(self, filter_q={}):
525 """
526 Get role list.
527
528 :return: returns the list of projects.
529 """
K Sai Kiran7ddb0732020-10-30 11:14:44 +0530530 return self.db.get_list(self.projects_collection, filter_q)
delacruzramo01b15d32019-07-02 14:37:47 +0200531
532 def create_project(self, project_info):
533 """
534 Create a project.
535
536 :param project: full project info.
537 :return: the internal id of the created project
538 :raises AuthconnOperationException: if project creation failed.
539 """
K Sai Kiran7ddb0732020-10-30 11:14:44 +0530540 pid = self.db.create(self.projects_collection, project_info)
delacruzramo01b15d32019-07-02 14:37:47 +0200541 return pid
542
543 def delete_project(self, project_id):
544 """
545 Delete a project.
546
547 :param project_id: project identifier.
548 :raises AuthconnOperationException: if project deletion failed.
549 """
delacruzramoad682a52019-12-10 16:26:34 +0100550 idf = BaseTopic.id_field("projects", project_id)
K Sai Kiran7ddb0732020-10-30 11:14:44 +0530551 r = self.db.del_one(self.projects_collection, {idf: project_id})
delacruzramoad682a52019-12-10 16:26:34 +0100552 idf = "project_id" if idf == "_id" else "project_name"
K Sai Kiran7ddb0732020-10-30 11:14:44 +0530553 self.db.del_list(self.tokens_collection, {idf: project_id})
delacruzramo01b15d32019-07-02 14:37:47 +0200554 return r
555
556 def update_project(self, project_id, project_info):
557 """
558 Change the name of a project
559
560 :param project_id: project to be changed
561 :param project_info: full project info
562 :return: None
563 :raises AuthconnOperationException: if project update failed.
564 """
garciadeblas4568a372021-03-24 09:19:48 +0100565 self.db.set_one(
566 self.projects_collection,
567 {BaseTopic.id_field("projects", project_id): project_id},
568 project_info,
569 )