blob: 99d18e4ce256bc4f04370f186124da13de0be99a [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
selvi.ja9a1fc82022-04-04 06:54:30 +000036from osm_nbi.authconn import Authconn, AuthException, AuthconnConflictException # , AuthconnOperationException
delacruzramoceb8baf2019-06-21 14:25:38 +020037from osm_common.dbbase import DbException
tierno23acf402019-08-28 13:36:34 +000038from osm_nbi.base_topic import BaseTopic
tierno5ec768a2020-03-31 09:46:44 +000039from osm_nbi.validation import is_valid_uuid
delacruzramoad682a52019-12-10 16:26:34 +010040from time import time, sleep
delacruzramoceb8baf2019-06-21 14:25:38 +020041from http import HTTPStatus
42from uuid import uuid4
43from hashlib import sha256
44from copy import deepcopy
45from random import choice as random_choice
46
47
48class AuthconnInternal(Authconn):
garciadeblas4568a372021-03-24 09:19:48 +010049 token_time_window = 2 # seconds
50 token_delay = 1 # seconds to wait upon second request within time window
delacruzramoceb8baf2019-06-21 14:25:38 +020051
K Sai Kiran7ddb0732020-10-30 11:14:44 +053052 users_collection = "users"
53 roles_collection = "roles"
54 projects_collection = "projects"
55 tokens_collection = "tokens"
56
tierno9e87a7f2020-03-23 09:24:10 +000057 def __init__(self, config, db, role_permissions):
58 Authconn.__init__(self, config, db, role_permissions)
delacruzramoceb8baf2019-06-21 14:25:38 +020059 self.logger = logging.getLogger("nbi.authenticator.internal")
60
delacruzramoceb8baf2019-06-21 14:25:38 +020061 self.db = db
delacruzramoad682a52019-12-10 16:26:34 +010062 # self.msg = msg
63 # self.token_cache = token_cache
delacruzramoceb8baf2019-06-21 14:25:38 +020064
65 # To be Confirmed
delacruzramoceb8baf2019-06-21 14:25:38 +020066 self.sess = None
67
delacruzramoceb8baf2019-06-21 14:25:38 +020068 def validate_token(self, token):
69 """
70 Check if the token is valid.
71
72 :param token: token to validate
73 :return: dictionary with information associated with the token:
74 "_id": token id
75 "project_id": project id
76 "project_name": project name
77 "user_id": user id
78 "username": user name
79 "roles": list with dict containing {name, id}
80 "expires": expiration date
81 If the token is not valid an exception is raised.
82 """
83
84 try:
85 if not token:
garciadeblas4568a372021-03-24 09:19:48 +010086 raise AuthException(
87 "Needed a token or Authorization HTTP header",
88 http_code=HTTPStatus.UNAUTHORIZED,
89 )
delacruzramoceb8baf2019-06-21 14:25:38 +020090
delacruzramoceb8baf2019-06-21 14:25:38 +020091 now = time()
delacruzramoceb8baf2019-06-21 14:25:38 +020092
93 # get from database if not in cache
delacruzramoad682a52019-12-10 16:26:34 +010094 # if not token_info:
K Sai Kiran7ddb0732020-10-30 11:14:44 +053095 token_info = self.db.get_one(self.tokens_collection, {"_id": token})
delacruzramoad682a52019-12-10 16:26:34 +010096 if token_info["expires"] < now:
garciadeblas4568a372021-03-24 09:19:48 +010097 raise AuthException(
98 "Expired Token or Authorization HTTP header",
99 http_code=HTTPStatus.UNAUTHORIZED,
100 )
delacruzramoceb8baf2019-06-21 14:25:38 +0200101
tierno701018c2019-06-25 11:13:14 +0000102 return token_info
delacruzramoceb8baf2019-06-21 14:25:38 +0200103
104 except DbException as e:
105 if e.http_code == HTTPStatus.NOT_FOUND:
garciadeblas4568a372021-03-24 09:19:48 +0100106 raise AuthException(
107 "Invalid Token or Authorization HTTP header",
108 http_code=HTTPStatus.UNAUTHORIZED,
109 )
delacruzramoceb8baf2019-06-21 14:25:38 +0200110 else:
111 raise
112 except AuthException:
tiernoe1eb3b22019-08-26 15:59:24 +0000113 raise
delacruzramoceb8baf2019-06-21 14:25:38 +0200114 except Exception:
garciadeblas4568a372021-03-24 09:19:48 +0100115 self.logger.exception(
116 "Error during token validation using internal backend"
117 )
118 raise AuthException(
119 "Error during token validation using internal backend",
120 http_code=HTTPStatus.UNAUTHORIZED,
121 )
delacruzramoceb8baf2019-06-21 14:25:38 +0200122
123 def revoke_token(self, token):
124 """
125 Invalidate a token.
126
127 :param token: token to be revoked
128 """
129 try:
delacruzramoad682a52019-12-10 16:26:34 +0100130 # self.token_cache.pop(token, None)
K Sai Kiran7ddb0732020-10-30 11:14:44 +0530131 self.db.del_one(self.tokens_collection, {"_id": token})
delacruzramoceb8baf2019-06-21 14:25:38 +0200132 return True
133 except DbException as e:
134 if e.http_code == HTTPStatus.NOT_FOUND:
garciadeblas4568a372021-03-24 09:19:48 +0100135 raise AuthException(
136 "Token '{}' not found".format(token), http_code=HTTPStatus.NOT_FOUND
137 )
delacruzramoceb8baf2019-06-21 14:25:38 +0200138 else:
139 # raise
delacruzramoad682a52019-12-10 16:26:34 +0100140 exmsg = "Error during token revocation using internal backend"
141 self.logger.exception(exmsg)
142 raise AuthException(exmsg, http_code=HTTPStatus.UNAUTHORIZED)
delacruzramoceb8baf2019-06-21 14:25:38 +0200143
K Sai Kiran7ddb0732020-10-30 11:14:44 +0530144 def validate_user(self, user, password):
145 """
146 Validate username and password via appropriate backend.
147 :param user: username of the user.
148 :param password: password to be validated.
149 """
garciadeblas4568a372021-03-24 09:19:48 +0100150 user_rows = self.db.get_list(
151 self.users_collection, {BaseTopic.id_field("users", user): user}
152 )
K Sai Kiran7ddb0732020-10-30 11:14:44 +0530153 user_content = None
154 if user_rows:
155 user_content = user_rows[0]
156 salt = user_content["_admin"]["salt"]
garciadeblas4568a372021-03-24 09:19:48 +0100157 shadow_password = sha256(
158 password.encode("utf-8") + salt.encode("utf-8")
159 ).hexdigest()
K Sai Kiran7ddb0732020-10-30 11:14:44 +0530160 if shadow_password != user_content["password"]:
161 user_content = None
162 return user_content
163
tierno6486f742020-02-13 16:30:14 +0000164 def authenticate(self, credentials, token_info=None):
delacruzramoceb8baf2019-06-21 14:25:38 +0200165 """
tierno701018c2019-06-25 11:13:14 +0000166 Authenticate a user using username/password or previous token_info plus project; its creates a new token
delacruzramoceb8baf2019-06-21 14:25:38 +0200167
tierno6486f742020-02-13 16:30:14 +0000168 :param credentials: dictionary that contains:
169 username: name, id or None
170 password: password or None
171 project_id: name, id, or None. If None first found project will be used to get an scope token
172 other items are allowed and ignored
tierno701018c2019-06-25 11:13:14 +0000173 :param token_info: previous token_info to obtain authorization
delacruzramoceb8baf2019-06-21 14:25:38 +0200174 :return: the scoped token info or raises an exception. The token is a dictionary with:
175 _id: token string id,
176 username: username,
177 project_id: scoped_token project_id,
178 project_name: scoped_token project_name,
179 expires: epoch time when it expires,
180 """
181
182 now = time()
183 user_content = None
tierno6486f742020-02-13 16:30:14 +0000184 user = credentials.get("username")
185 password = credentials.get("password")
186 project = credentials.get("project_id")
delacruzramoceb8baf2019-06-21 14:25:38 +0200187
delacruzramo01b15d32019-07-02 14:37:47 +0200188 # Try using username/password
189 if user:
K Sai Kiran7ddb0732020-10-30 11:14:44 +0530190 user_content = self.validate_user(user, password)
delacruzramo01b15d32019-07-02 14:37:47 +0200191 if not user_content:
garciadeblas4568a372021-03-24 09:19:48 +0100192 raise AuthException(
193 "Invalid username/password", http_code=HTTPStatus.UNAUTHORIZED
194 )
K Sai Kiran7ddb0732020-10-30 11:14:44 +0530195 if not user_content.get("_admin", None):
garciadeblas4568a372021-03-24 09:19:48 +0100196 raise AuthException(
197 "No default project for this user.",
198 http_code=HTTPStatus.UNAUTHORIZED,
199 )
delacruzramo01b15d32019-07-02 14:37:47 +0200200 elif token_info:
garciadeblas4568a372021-03-24 09:19:48 +0100201 user_rows = self.db.get_list(
202 self.users_collection, {"username": token_info["username"]}
203 )
delacruzramo01b15d32019-07-02 14:37:47 +0200204 if user_rows:
205 user_content = user_rows[0]
delacruzramoceb8baf2019-06-21 14:25:38 +0200206 else:
delacruzramo01b15d32019-07-02 14:37:47 +0200207 raise AuthException("Invalid token", http_code=HTTPStatus.UNAUTHORIZED)
208 else:
garciadeblas4568a372021-03-24 09:19:48 +0100209 raise AuthException(
210 "Provide credentials: username/password or Authorization Bearer token",
211 http_code=HTTPStatus.UNAUTHORIZED,
212 )
delacruzramoad682a52019-12-10 16:26:34 +0100213 # Delay upon second request within time window
garciadeblas4568a372021-03-24 09:19:48 +0100214 if (
215 now - user_content["_admin"].get("last_token_time", 0)
216 < self.token_time_window
217 ):
delacruzramoad682a52019-12-10 16:26:34 +0100218 sleep(self.token_delay)
219 # user_content["_admin"]["last_token_time"] = now
220 # self.db.replace("users", user_content["_id"], user_content) # might cause race conditions
garciadeblas4568a372021-03-24 09:19:48 +0100221 self.db.set_one(
222 self.users_collection,
223 {"_id": user_content["_id"]},
224 {"_admin.last_token_time": now},
225 )
delacruzramoad682a52019-12-10 16:26:34 +0100226
garciadeblas4568a372021-03-24 09:19:48 +0100227 token_id = "".join(
228 random_choice(
229 "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
230 )
231 for _ in range(0, 32)
232 )
delacruzramoceb8baf2019-06-21 14:25:38 +0200233
delacruzramo01b15d32019-07-02 14:37:47 +0200234 # projects = user_content.get("projects", [])
235 prm_list = user_content.get("project_role_mappings", [])
delacruzramoceb8baf2019-06-21 14:25:38 +0200236
delacruzramo01b15d32019-07-02 14:37:47 +0200237 if not project:
238 project = prm_list[0]["project"] if prm_list else None
239 if not project:
garciadeblas4568a372021-03-24 09:19:48 +0100240 raise AuthException(
241 "can't find a default project for this user",
242 http_code=HTTPStatus.UNAUTHORIZED,
243 )
tierno701018c2019-06-25 11:13:14 +0000244
delacruzramo01b15d32019-07-02 14:37:47 +0200245 projects = [prm["project"] for prm in prm_list]
delacruzramoceb8baf2019-06-21 14:25:38 +0200246
garciadeblas4568a372021-03-24 09:19:48 +0100247 proj = self.db.get_one(
248 self.projects_collection, {BaseTopic.id_field("projects", project): project}
249 )
delacruzramo01b15d32019-07-02 14:37:47 +0200250 project_name = proj["name"]
251 project_id = proj["_id"]
252 if project_name not in projects and project_id not in projects:
garciadeblas4568a372021-03-24 09:19:48 +0100253 raise AuthException(
254 "project {} not allowed for this user".format(project),
255 http_code=HTTPStatus.UNAUTHORIZED,
256 )
tierno701018c2019-06-25 11:13:14 +0000257
delacruzramo01b15d32019-07-02 14:37:47 +0200258 # TODO remove admin, this vill be used by roles RBAC
259 if project_name == "admin":
260 token_admin = True
261 else:
262 token_admin = proj.get("admin", False)
delacruzramoceb8baf2019-06-21 14:25:38 +0200263
delacruzramo01b15d32019-07-02 14:37:47 +0200264 # add token roles
265 roles = []
266 roles_list = []
267 for prm in prm_list:
268 if prm["project"] in [project_id, project_name]:
garciadeblas4568a372021-03-24 09:19:48 +0100269 role = self.db.get_one(
270 self.roles_collection,
271 {BaseTopic.id_field("roles", prm["role"]): prm["role"]},
272 )
delacruzramo01b15d32019-07-02 14:37:47 +0200273 rid = role["_id"]
274 if rid not in roles:
275 rnm = role["name"]
276 roles.append(rid)
277 roles_list.append({"name": rnm, "id": rid})
278 if not roles_list:
garciadeblas4568a372021-03-24 09:19:48 +0100279 rid = self.db.get_one(self.roles_collection, {"name": "project_admin"})[
280 "_id"
281 ]
delacruzramo01b15d32019-07-02 14:37:47 +0200282 roles_list = [{"name": "project_admin", "id": rid}]
delacruzramoceb8baf2019-06-21 14:25:38 +0200283
garciadeblas4568a372021-03-24 09:19:48 +0100284 new_token = {
285 "issued_at": now,
286 "expires": now + 3600,
287 "_id": token_id,
288 "id": token_id,
289 "project_id": proj["_id"],
290 "project_name": proj["name"],
291 "username": user_content["username"],
292 "user_id": user_content["_id"],
293 "admin": token_admin,
294 "roles": roles_list,
295 }
delacruzramoceb8baf2019-06-21 14:25:38 +0200296
K Sai Kiran7ddb0732020-10-30 11:14:44 +0530297 self.db.create(self.tokens_collection, new_token)
delacruzramo01b15d32019-07-02 14:37:47 +0200298 return deepcopy(new_token)
299
300 def get_role_list(self, filter_q={}):
delacruzramoceb8baf2019-06-21 14:25:38 +0200301 """
302 Get role list.
303
304 :return: returns the list of roles.
305 """
K Sai Kiran7ddb0732020-10-30 11:14:44 +0530306 return self.db.get_list(self.roles_collection, filter_q)
delacruzramoceb8baf2019-06-21 14:25:38 +0200307
delacruzramo01b15d32019-07-02 14:37:47 +0200308 def create_role(self, role_info):
delacruzramoceb8baf2019-06-21 14:25:38 +0200309 """
310 Create a role.
311
delacruzramo01b15d32019-07-02 14:37:47 +0200312 :param role_info: full role info.
313 :return: returns the role id.
delacruzramoceb8baf2019-06-21 14:25:38 +0200314 :raises AuthconnOperationException: if role creation failed.
315 """
delacruzramoceb8baf2019-06-21 14:25:38 +0200316 # TODO: Check that role name does not exist ?
delacruzramo01b15d32019-07-02 14:37:47 +0200317 rid = str(uuid4())
318 role_info["_id"] = rid
K Sai Kiran7ddb0732020-10-30 11:14:44 +0530319 rid = self.db.create(self.roles_collection, role_info)
delacruzramo01b15d32019-07-02 14:37:47 +0200320 return rid
delacruzramoceb8baf2019-06-21 14:25:38 +0200321
322 def delete_role(self, role_id):
323 """
324 Delete a role.
325
326 :param role_id: role identifier.
327 :raises AuthconnOperationException: if role deletion failed.
328 """
K Sai Kiran7ddb0732020-10-30 11:14:44 +0530329 rc = self.db.del_one(self.roles_collection, {"_id": role_id})
330 self.db.del_list(self.tokens_collection, {"roles.id": role_id})
delacruzramoad682a52019-12-10 16:26:34 +0100331 return rc
delacruzramo01b15d32019-07-02 14:37:47 +0200332
333 def update_role(self, role_info):
334 """
335 Update a role.
336
337 :param role_info: full role info.
338 :return: returns the role name and id.
339 :raises AuthconnOperationException: if user creation failed.
340 """
341 rid = role_info["_id"]
K Sai Kiran7ddb0732020-10-30 11:14:44 +0530342 self.db.set_one(self.roles_collection, {"_id": rid}, role_info)
delacruzramo01b15d32019-07-02 14:37:47 +0200343 return {"_id": rid, "name": role_info["name"]}
344
345 def create_user(self, user_info):
346 """
347 Create a user.
348
349 :param user_info: full user info.
350 :return: returns the username and id of the user.
351 """
352 BaseTopic.format_on_new(user_info, make_public=False)
353 salt = uuid4().hex
354 user_info["_admin"]["salt"] = salt
selvi.ja9a1fc82022-04-04 06:54:30 +0000355 present = time()
356 if not user_info["username"] == "admin":
357 if self.config.get("pwd_expiry_check"):
358 user_info["_admin"]["modified_time"] = present
359 user_info["_admin"]["expire_time"] = present
delacruzramo01b15d32019-07-02 14:37:47 +0200360 if "password" in user_info:
garciadeblas4568a372021-03-24 09:19:48 +0100361 user_info["password"] = sha256(
362 user_info["password"].encode("utf-8") + salt.encode("utf-8")
363 ).hexdigest()
delacruzramo01b15d32019-07-02 14:37:47 +0200364 # "projects" are not stored any more
365 if "projects" in user_info:
366 del user_info["projects"]
K Sai Kiran7ddb0732020-10-30 11:14:44 +0530367 self.db.create(self.users_collection, user_info)
delacruzramo01b15d32019-07-02 14:37:47 +0200368 return {"username": user_info["username"], "_id": user_info["_id"]}
369
370 def update_user(self, user_info):
371 """
372 Change the user name and/or password.
373
374 :param user_info: user info modifications
375 """
376 uid = user_info["_id"]
selvi.ja9a1fc82022-04-04 06:54:30 +0000377 old_pwd = user_info.get("old_password")
garciadeblas4568a372021-03-24 09:19:48 +0100378 user_data = self.db.get_one(
379 self.users_collection, {BaseTopic.id_field("users", uid): uid}
380 )
selvi.ja9a1fc82022-04-04 06:54:30 +0000381 if old_pwd:
382 salt = user_data["_admin"]["salt"]
383 shadow_password = sha256(old_pwd.encode('utf-8') + salt.encode('utf-8')).hexdigest()
384 if shadow_password != user_data["password"]:
385 raise AuthconnConflictException(
386 "Incorrect password",
387 http_code=HTTPStatus.CONFLICT
388 )
delacruzramo01b15d32019-07-02 14:37:47 +0200389 BaseTopic.format_on_edit(user_data, user_info)
390 # User Name
391 usnm = user_info.get("username")
392 if usnm:
393 user_data["username"] = usnm
394 # If password is given and is not already encripted
395 pswd = user_info.get("password")
garciadeblas4568a372021-03-24 09:19:48 +0100396 if pswd and (
397 len(pswd) != 64 or not re.match("[a-fA-F0-9]*", pswd)
398 ): # TODO: Improve check?
delacruzramo01b15d32019-07-02 14:37:47 +0200399 salt = uuid4().hex
400 if "_admin" not in user_data:
401 user_data["_admin"] = {}
402 user_data["_admin"]["salt"] = salt
garciadeblas4568a372021-03-24 09:19:48 +0100403 user_data["password"] = sha256(
404 pswd.encode("utf-8") + salt.encode("utf-8")
405 ).hexdigest()
selvi.ja9a1fc82022-04-04 06:54:30 +0000406 if not user_data["username"] == "admin":
407 if self.config.get("pwd_expiry_check"):
408 present = time()
409 if self.config.get("days"):
410 expire = present + 86400 * self.config.get("days")
411 user_data["_admin"]["modified_time"] = present
412 user_data["_admin"]["expire_time"] = expire
delacruzramo01b15d32019-07-02 14:37:47 +0200413 # Project-Role Mappings
414 # TODO: Check that user_info NEVER includes "project_role_mappings"
415 if "project_role_mappings" not in user_data:
416 user_data["project_role_mappings"] = []
417 for prm in user_info.get("add_project_role_mappings", []):
418 user_data["project_role_mappings"].append(prm)
419 for prm in user_info.get("remove_project_role_mappings", []):
420 for pidf in ["project", "project_name"]:
421 for ridf in ["role", "role_name"]:
422 try:
garciadeblas4568a372021-03-24 09:19:48 +0100423 user_data["project_role_mappings"].remove(
424 {"role": prm[ridf], "project": prm[pidf]}
425 )
delacruzramo01b15d32019-07-02 14:37:47 +0200426 except KeyError:
427 pass
428 except ValueError:
429 pass
delacruzramo3d6881c2019-12-04 13:42:26 +0100430 idf = BaseTopic.id_field("users", uid)
K Sai Kiran7ddb0732020-10-30 11:14:44 +0530431 self.db.set_one(self.users_collection, {idf: uid}, user_data)
delacruzramo3d6881c2019-12-04 13:42:26 +0100432 if user_info.get("remove_project_role_mappings"):
delacruzramoad682a52019-12-10 16:26:34 +0100433 idf = "user_id" if idf == "_id" else idf
K Sai Kiran7ddb0732020-10-30 11:14:44 +0530434 self.db.del_list(self.tokens_collection, {idf: uid})
delacruzramo01b15d32019-07-02 14:37:47 +0200435
436 def delete_user(self, user_id):
437 """
438 Delete user.
439
440 :param user_id: user identifier.
441 :raises AuthconnOperationException: if user deletion failed.
442 """
K Sai Kiran7ddb0732020-10-30 11:14:44 +0530443 self.db.del_one(self.users_collection, {"_id": user_id})
444 self.db.del_list(self.tokens_collection, {"user_id": user_id})
delacruzramoceb8baf2019-06-21 14:25:38 +0200445 return True
delacruzramo01b15d32019-07-02 14:37:47 +0200446
447 def get_user_list(self, filter_q=None):
448 """
449 Get user list.
450
tierno5ec768a2020-03-31 09:46:44 +0000451 :param filter_q: dictionary to filter user list by:
452 name (username is also admitted). If a user id is equal to the filter name, it is also provided
453 other
delacruzramo01b15d32019-07-02 14:37:47 +0200454 :return: returns a list of users.
455 """
456 filt = filter_q or {}
tierno5ec768a2020-03-31 09:46:44 +0000457 if "name" in filt: # backward compatibility
458 filt["username"] = filt.pop("name")
459 if filt.get("username") and is_valid_uuid(filt["username"]):
460 # username cannot be a uuid. If this is the case, change from username to _id
461 filt["_id"] = filt.pop("username")
K Sai Kiran7ddb0732020-10-30 11:14:44 +0530462 users = self.db.get_list(self.users_collection, filt)
tierno1546f2a2019-08-20 15:38:11 +0000463 project_id_name = {}
464 role_id_name = {}
delacruzramo01b15d32019-07-02 14:37:47 +0200465 for user in users:
tierno1546f2a2019-08-20 15:38:11 +0000466 prms = user.get("project_role_mappings")
467 projects = user.get("projects")
468 if prms:
469 projects = []
470 # add project_name and role_name. Generate projects for backward compatibility
delacruzramo01b15d32019-07-02 14:37:47 +0200471 for prm in prms:
tierno1546f2a2019-08-20 15:38:11 +0000472 project_id = prm["project"]
473 if project_id not in project_id_name:
garciadeblas4568a372021-03-24 09:19:48 +0100474 pr = self.db.get_one(
475 self.projects_collection,
476 {BaseTopic.id_field("projects", project_id): project_id},
477 fail_on_empty=False,
478 )
tierno1546f2a2019-08-20 15:38:11 +0000479 project_id_name[project_id] = pr["name"] if pr else None
480 prm["project_name"] = project_id_name[project_id]
481 if prm["project_name"] not in projects:
482 projects.append(prm["project_name"])
483
484 role_id = prm["role"]
485 if role_id not in role_id_name:
garciadeblas4568a372021-03-24 09:19:48 +0100486 role = self.db.get_one(
487 self.roles_collection,
488 {BaseTopic.id_field("roles", role_id): role_id},
489 fail_on_empty=False,
490 )
tierno1546f2a2019-08-20 15:38:11 +0000491 role_id_name[role_id] = role["name"] if role else None
492 prm["role_name"] = role_id_name[role_id]
493 user["projects"] = projects # for backward compatibility
494 elif projects:
495 # user created with an old version. Create a project_role mapping with role project_admin
496 user["project_role_mappings"] = []
garciadeblas4568a372021-03-24 09:19:48 +0100497 role = self.db.get_one(
498 self.roles_collection,
499 {BaseTopic.id_field("roles", "project_admin"): "project_admin"},
500 )
tierno1546f2a2019-08-20 15:38:11 +0000501 for p_id_name in projects:
garciadeblas4568a372021-03-24 09:19:48 +0100502 pr = self.db.get_one(
503 self.projects_collection,
504 {BaseTopic.id_field("projects", p_id_name): p_id_name},
505 )
506 prm = {
507 "project": pr["_id"],
508 "project_name": pr["name"],
509 "role_name": "project_admin",
510 "role": role["_id"],
511 }
tierno1546f2a2019-08-20 15:38:11 +0000512 user["project_role_mappings"].append(prm)
513 else:
514 user["projects"] = []
515 user["project_role_mappings"] = []
516
delacruzramo01b15d32019-07-02 14:37:47 +0200517 return users
518
519 def get_project_list(self, filter_q={}):
520 """
521 Get role list.
522
523 :return: returns the list of projects.
524 """
K Sai Kiran7ddb0732020-10-30 11:14:44 +0530525 return self.db.get_list(self.projects_collection, filter_q)
delacruzramo01b15d32019-07-02 14:37:47 +0200526
527 def create_project(self, project_info):
528 """
529 Create a project.
530
531 :param project: full project info.
532 :return: the internal id of the created project
533 :raises AuthconnOperationException: if project creation failed.
534 """
K Sai Kiran7ddb0732020-10-30 11:14:44 +0530535 pid = self.db.create(self.projects_collection, project_info)
delacruzramo01b15d32019-07-02 14:37:47 +0200536 return pid
537
538 def delete_project(self, project_id):
539 """
540 Delete a project.
541
542 :param project_id: project identifier.
543 :raises AuthconnOperationException: if project deletion failed.
544 """
delacruzramoad682a52019-12-10 16:26:34 +0100545 idf = BaseTopic.id_field("projects", project_id)
K Sai Kiran7ddb0732020-10-30 11:14:44 +0530546 r = self.db.del_one(self.projects_collection, {idf: project_id})
delacruzramoad682a52019-12-10 16:26:34 +0100547 idf = "project_id" if idf == "_id" else "project_name"
K Sai Kiran7ddb0732020-10-30 11:14:44 +0530548 self.db.del_list(self.tokens_collection, {idf: project_id})
delacruzramo01b15d32019-07-02 14:37:47 +0200549 return r
550
551 def update_project(self, project_id, project_info):
552 """
553 Change the name of a project
554
555 :param project_id: project to be changed
556 :param project_info: full project info
557 :return: None
558 :raises AuthconnOperationException: if project update failed.
559 """
garciadeblas4568a372021-03-24 09:19:48 +0100560 self.db.set_one(
561 self.projects_collection,
562 {BaseTopic.id_field("projects", project_id): project_id},
563 project_info,
564 )