blob: 3f495d864243a6552352bea78e1b65932a62ecc2 [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
elumalai7802ff82023-04-24 20:38:32 +053043from osm_nbi.utils import cef_event, cef_event_builder
tierno5ec768a2020-03-31 09:46:44 +000044from osm_nbi.validation import is_valid_uuid
delacruzramoad682a52019-12-10 16:26:34 +010045from time import time, sleep
delacruzramoceb8baf2019-06-21 14:25:38 +020046from http import HTTPStatus
47from uuid import uuid4
48from hashlib import sha256
49from copy import deepcopy
50from random import choice as random_choice
51
52
53class AuthconnInternal(Authconn):
garciadeblas4568a372021-03-24 09:19:48 +010054 token_time_window = 2 # seconds
55 token_delay = 1 # seconds to wait upon second request within time window
delacruzramoceb8baf2019-06-21 14:25:38 +020056
K Sai Kiran7ddb0732020-10-30 11:14:44 +053057 users_collection = "users"
58 roles_collection = "roles"
59 projects_collection = "projects"
60 tokens_collection = "tokens"
61
tierno9e87a7f2020-03-23 09:24:10 +000062 def __init__(self, config, db, role_permissions):
63 Authconn.__init__(self, config, db, role_permissions)
delacruzramoceb8baf2019-06-21 14:25:38 +020064 self.logger = logging.getLogger("nbi.authenticator.internal")
65
delacruzramoceb8baf2019-06-21 14:25:38 +020066 self.db = db
delacruzramoad682a52019-12-10 16:26:34 +010067 # self.msg = msg
68 # self.token_cache = token_cache
delacruzramoceb8baf2019-06-21 14:25:38 +020069
70 # To be Confirmed
delacruzramoceb8baf2019-06-21 14:25:38 +020071 self.sess = None
elumalai7802ff82023-04-24 20:38:32 +053072 self.cef_logger = cef_event_builder(config)
delacruzramoceb8baf2019-06-21 14:25:38 +020073
delacruzramoceb8baf2019-06-21 14:25:38 +020074 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:
garciadeblas4568a372021-03-24 09:19:48 +010092 raise AuthException(
93 "Needed a token or Authorization HTTP header",
94 http_code=HTTPStatus.UNAUTHORIZED,
95 )
delacruzramoceb8baf2019-06-21 14:25:38 +020096
delacruzramoceb8baf2019-06-21 14:25:38 +020097 now = time()
delacruzramoceb8baf2019-06-21 14:25:38 +020098
99 # get from database if not in cache
delacruzramoad682a52019-12-10 16:26:34 +0100100 # if not token_info:
K Sai Kiran7ddb0732020-10-30 11:14:44 +0530101 token_info = self.db.get_one(self.tokens_collection, {"_id": token})
delacruzramoad682a52019-12-10 16:26:34 +0100102 if token_info["expires"] < now:
garciadeblas4568a372021-03-24 09:19:48 +0100103 raise AuthException(
104 "Expired Token or Authorization HTTP header",
105 http_code=HTTPStatus.UNAUTHORIZED,
106 )
delacruzramoceb8baf2019-06-21 14:25:38 +0200107
tierno701018c2019-06-25 11:13:14 +0000108 return token_info
delacruzramoceb8baf2019-06-21 14:25:38 +0200109
110 except DbException as e:
111 if e.http_code == HTTPStatus.NOT_FOUND:
garciadeblas4568a372021-03-24 09:19:48 +0100112 raise AuthException(
113 "Invalid Token or Authorization HTTP header",
114 http_code=HTTPStatus.UNAUTHORIZED,
115 )
delacruzramoceb8baf2019-06-21 14:25:38 +0200116 else:
117 raise
118 except AuthException:
tiernoe1eb3b22019-08-26 15:59:24 +0000119 raise
delacruzramoceb8baf2019-06-21 14:25:38 +0200120 except Exception:
garciadeblas4568a372021-03-24 09:19:48 +0100121 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 )
delacruzramoceb8baf2019-06-21 14:25:38 +0200128
129 def revoke_token(self, token):
130 """
131 Invalidate a token.
132
133 :param token: token to be revoked
134 """
135 try:
delacruzramoad682a52019-12-10 16:26:34 +0100136 # self.token_cache.pop(token, None)
K Sai Kiran7ddb0732020-10-30 11:14:44 +0530137 self.db.del_one(self.tokens_collection, {"_id": token})
delacruzramoceb8baf2019-06-21 14:25:38 +0200138 return True
139 except DbException as e:
140 if e.http_code == HTTPStatus.NOT_FOUND:
garciadeblas4568a372021-03-24 09:19:48 +0100141 raise AuthException(
142 "Token '{}' not found".format(token), http_code=HTTPStatus.NOT_FOUND
143 )
delacruzramoceb8baf2019-06-21 14:25:38 +0200144 else:
145 # raise
delacruzramoad682a52019-12-10 16:26:34 +0100146 exmsg = "Error during token revocation using internal backend"
147 self.logger.exception(exmsg)
148 raise AuthException(exmsg, http_code=HTTPStatus.UNAUTHORIZED)
delacruzramoceb8baf2019-06-21 14:25:38 +0200149
K Sai Kiran7ddb0732020-10-30 11:14:44 +0530150 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 """
garciadeblas4568a372021-03-24 09:19:48 +0100156 user_rows = self.db.get_list(
157 self.users_collection, {BaseTopic.id_field("users", user): user}
158 )
K Sai Kiran7ddb0732020-10-30 11:14:44 +0530159 user_content = None
160 if user_rows:
161 user_content = user_rows[0]
162 salt = user_content["_admin"]["salt"]
garciadeblas4568a372021-03-24 09:19:48 +0100163 shadow_password = sha256(
164 password.encode("utf-8") + salt.encode("utf-8")
165 ).hexdigest()
K Sai Kiran7ddb0732020-10-30 11:14:44 +0530166 if shadow_password != user_content["password"]:
167 user_content = None
168 return user_content
169
tierno6486f742020-02-13 16:30:14 +0000170 def authenticate(self, credentials, token_info=None):
delacruzramoceb8baf2019-06-21 14:25:38 +0200171 """
tierno701018c2019-06-25 11:13:14 +0000172 Authenticate a user using username/password or previous token_info plus project; its creates a new token
delacruzramoceb8baf2019-06-21 14:25:38 +0200173
tierno6486f742020-02-13 16:30:14 +0000174 :param credentials: dictionary that contains:
175 username: name, id or None
176 password: password or None
177 project_id: name, id, or None. If None first found project will be used to get an scope token
178 other items are allowed and ignored
tierno701018c2019-06-25 11:13:14 +0000179 :param token_info: previous token_info to obtain authorization
delacruzramoceb8baf2019-06-21 14:25:38 +0200180 :return: the scoped token info or raises an exception. The token is a dictionary with:
181 _id: token string id,
182 username: username,
183 project_id: scoped_token project_id,
184 project_name: scoped_token project_name,
185 expires: epoch time when it expires,
186 """
187
188 now = time()
189 user_content = None
tierno6486f742020-02-13 16:30:14 +0000190 user = credentials.get("username")
191 password = credentials.get("password")
192 project = credentials.get("project_id")
delacruzramoceb8baf2019-06-21 14:25:38 +0200193
delacruzramo01b15d32019-07-02 14:37:47 +0200194 # Try using username/password
195 if user:
K Sai Kiran7ddb0732020-10-30 11:14:44 +0530196 user_content = self.validate_user(user, password)
delacruzramo01b15d32019-07-02 14:37:47 +0200197 if not user_content:
elumalai7802ff82023-04-24 20:38:32 +0530198 cef_event(
199 self.cef_logger,
200 {
201 "name": "User login",
202 "sourceUserName": user,
203 "message": "Invalid username/password Project={} Outcome=Failure".format(
204 project
205 ),
206 "severity": "3",
207 },
208 )
209 self.logger.exception("{}".format(self.cef_logger))
garciadeblas4568a372021-03-24 09:19:48 +0100210 raise AuthException(
211 "Invalid username/password", http_code=HTTPStatus.UNAUTHORIZED
212 )
K Sai Kiran7ddb0732020-10-30 11:14:44 +0530213 if not user_content.get("_admin", None):
garciadeblas4568a372021-03-24 09:19:48 +0100214 raise AuthException(
215 "No default project for this user.",
216 http_code=HTTPStatus.UNAUTHORIZED,
217 )
delacruzramo01b15d32019-07-02 14:37:47 +0200218 elif token_info:
garciadeblas4568a372021-03-24 09:19:48 +0100219 user_rows = self.db.get_list(
220 self.users_collection, {"username": token_info["username"]}
221 )
delacruzramo01b15d32019-07-02 14:37:47 +0200222 if user_rows:
223 user_content = user_rows[0]
delacruzramoceb8baf2019-06-21 14:25:38 +0200224 else:
delacruzramo01b15d32019-07-02 14:37:47 +0200225 raise AuthException("Invalid token", http_code=HTTPStatus.UNAUTHORIZED)
226 else:
garciadeblas4568a372021-03-24 09:19:48 +0100227 raise AuthException(
228 "Provide credentials: username/password or Authorization Bearer token",
229 http_code=HTTPStatus.UNAUTHORIZED,
230 )
delacruzramoad682a52019-12-10 16:26:34 +0100231 # Delay upon second request within time window
garciadeblas4568a372021-03-24 09:19:48 +0100232 if (
233 now - user_content["_admin"].get("last_token_time", 0)
234 < self.token_time_window
235 ):
delacruzramoad682a52019-12-10 16:26:34 +0100236 sleep(self.token_delay)
237 # user_content["_admin"]["last_token_time"] = now
238 # self.db.replace("users", user_content["_id"], user_content) # might cause race conditions
garciadeblas4568a372021-03-24 09:19:48 +0100239 self.db.set_one(
240 self.users_collection,
241 {"_id": user_content["_id"]},
242 {"_admin.last_token_time": now},
243 )
delacruzramoad682a52019-12-10 16:26:34 +0100244
garciadeblas4568a372021-03-24 09:19:48 +0100245 token_id = "".join(
246 random_choice(
247 "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
248 )
249 for _ in range(0, 32)
250 )
delacruzramoceb8baf2019-06-21 14:25:38 +0200251
delacruzramo01b15d32019-07-02 14:37:47 +0200252 # projects = user_content.get("projects", [])
253 prm_list = user_content.get("project_role_mappings", [])
delacruzramoceb8baf2019-06-21 14:25:38 +0200254
delacruzramo01b15d32019-07-02 14:37:47 +0200255 if not project:
256 project = prm_list[0]["project"] if prm_list else None
257 if not project:
garciadeblas4568a372021-03-24 09:19:48 +0100258 raise AuthException(
259 "can't find a default project for this user",
260 http_code=HTTPStatus.UNAUTHORIZED,
261 )
tierno701018c2019-06-25 11:13:14 +0000262
delacruzramo01b15d32019-07-02 14:37:47 +0200263 projects = [prm["project"] for prm in prm_list]
delacruzramoceb8baf2019-06-21 14:25:38 +0200264
garciadeblas4568a372021-03-24 09:19:48 +0100265 proj = self.db.get_one(
266 self.projects_collection, {BaseTopic.id_field("projects", project): project}
267 )
delacruzramo01b15d32019-07-02 14:37:47 +0200268 project_name = proj["name"]
269 project_id = proj["_id"]
270 if project_name not in projects and project_id not in projects:
garciadeblas4568a372021-03-24 09:19:48 +0100271 raise AuthException(
272 "project {} not allowed for this user".format(project),
273 http_code=HTTPStatus.UNAUTHORIZED,
274 )
tierno701018c2019-06-25 11:13:14 +0000275
delacruzramo01b15d32019-07-02 14:37:47 +0200276 # TODO remove admin, this vill be used by roles RBAC
277 if project_name == "admin":
278 token_admin = True
279 else:
280 token_admin = proj.get("admin", False)
delacruzramoceb8baf2019-06-21 14:25:38 +0200281
delacruzramo01b15d32019-07-02 14:37:47 +0200282 # add token roles
283 roles = []
284 roles_list = []
285 for prm in prm_list:
286 if prm["project"] in [project_id, project_name]:
garciadeblas4568a372021-03-24 09:19:48 +0100287 role = self.db.get_one(
288 self.roles_collection,
289 {BaseTopic.id_field("roles", prm["role"]): prm["role"]},
290 )
delacruzramo01b15d32019-07-02 14:37:47 +0200291 rid = role["_id"]
292 if rid not in roles:
293 rnm = role["name"]
294 roles.append(rid)
295 roles_list.append({"name": rnm, "id": rid})
296 if not roles_list:
garciadeblas4568a372021-03-24 09:19:48 +0100297 rid = self.db.get_one(self.roles_collection, {"name": "project_admin"})[
298 "_id"
299 ]
delacruzramo01b15d32019-07-02 14:37:47 +0200300 roles_list = [{"name": "project_admin", "id": rid}]
delacruzramoceb8baf2019-06-21 14:25:38 +0200301
garciadeblas4568a372021-03-24 09:19:48 +0100302 new_token = {
303 "issued_at": now,
304 "expires": now + 3600,
305 "_id": token_id,
306 "id": token_id,
307 "project_id": proj["_id"],
308 "project_name": proj["name"],
309 "username": user_content["username"],
310 "user_id": user_content["_id"],
311 "admin": token_admin,
312 "roles": roles_list,
313 }
delacruzramoceb8baf2019-06-21 14:25:38 +0200314
K Sai Kiran7ddb0732020-10-30 11:14:44 +0530315 self.db.create(self.tokens_collection, new_token)
delacruzramo01b15d32019-07-02 14:37:47 +0200316 return deepcopy(new_token)
317
318 def get_role_list(self, filter_q={}):
delacruzramoceb8baf2019-06-21 14:25:38 +0200319 """
320 Get role list.
321
322 :return: returns the list of roles.
323 """
K Sai Kiran7ddb0732020-10-30 11:14:44 +0530324 return self.db.get_list(self.roles_collection, filter_q)
delacruzramoceb8baf2019-06-21 14:25:38 +0200325
delacruzramo01b15d32019-07-02 14:37:47 +0200326 def create_role(self, role_info):
delacruzramoceb8baf2019-06-21 14:25:38 +0200327 """
328 Create a role.
329
delacruzramo01b15d32019-07-02 14:37:47 +0200330 :param role_info: full role info.
331 :return: returns the role id.
delacruzramoceb8baf2019-06-21 14:25:38 +0200332 :raises AuthconnOperationException: if role creation failed.
333 """
delacruzramoceb8baf2019-06-21 14:25:38 +0200334 # TODO: Check that role name does not exist ?
delacruzramo01b15d32019-07-02 14:37:47 +0200335 rid = str(uuid4())
336 role_info["_id"] = rid
K Sai Kiran7ddb0732020-10-30 11:14:44 +0530337 rid = self.db.create(self.roles_collection, role_info)
delacruzramo01b15d32019-07-02 14:37:47 +0200338 return rid
delacruzramoceb8baf2019-06-21 14:25:38 +0200339
340 def delete_role(self, role_id):
341 """
342 Delete a role.
343
344 :param role_id: role identifier.
345 :raises AuthconnOperationException: if role deletion failed.
346 """
K Sai Kiran7ddb0732020-10-30 11:14:44 +0530347 rc = self.db.del_one(self.roles_collection, {"_id": role_id})
348 self.db.del_list(self.tokens_collection, {"roles.id": role_id})
delacruzramoad682a52019-12-10 16:26:34 +0100349 return rc
delacruzramo01b15d32019-07-02 14:37:47 +0200350
351 def update_role(self, role_info):
352 """
353 Update a role.
354
355 :param role_info: full role info.
356 :return: returns the role name and id.
357 :raises AuthconnOperationException: if user creation failed.
358 """
359 rid = role_info["_id"]
K Sai Kiran7ddb0732020-10-30 11:14:44 +0530360 self.db.set_one(self.roles_collection, {"_id": rid}, role_info)
delacruzramo01b15d32019-07-02 14:37:47 +0200361 return {"_id": rid, "name": role_info["name"]}
362
363 def create_user(self, user_info):
364 """
365 Create a user.
366
367 :param user_info: full user info.
368 :return: returns the username and id of the user.
369 """
370 BaseTopic.format_on_new(user_info, make_public=False)
371 salt = uuid4().hex
372 user_info["_admin"]["salt"] = salt
selvi.ja9a1fc82022-04-04 06:54:30 +0000373 present = time()
374 if not user_info["username"] == "admin":
375 if self.config.get("pwd_expiry_check"):
376 user_info["_admin"]["modified_time"] = present
377 user_info["_admin"]["expire_time"] = present
delacruzramo01b15d32019-07-02 14:37:47 +0200378 if "password" in user_info:
garciadeblas4568a372021-03-24 09:19:48 +0100379 user_info["password"] = sha256(
380 user_info["password"].encode("utf-8") + salt.encode("utf-8")
381 ).hexdigest()
delacruzramo01b15d32019-07-02 14:37:47 +0200382 # "projects" are not stored any more
383 if "projects" in user_info:
384 del user_info["projects"]
K Sai Kiran7ddb0732020-10-30 11:14:44 +0530385 self.db.create(self.users_collection, user_info)
delacruzramo01b15d32019-07-02 14:37:47 +0200386 return {"username": user_info["username"], "_id": user_info["_id"]}
387
388 def update_user(self, user_info):
389 """
390 Change the user name and/or password.
391
392 :param user_info: user info modifications
393 """
394 uid = user_info["_id"]
selvi.ja9a1fc82022-04-04 06:54:30 +0000395 old_pwd = user_info.get("old_password")
garciadeblas4568a372021-03-24 09:19:48 +0100396 user_data = self.db.get_one(
397 self.users_collection, {BaseTopic.id_field("users", uid): uid}
398 )
selvi.ja9a1fc82022-04-04 06:54:30 +0000399 if old_pwd:
400 salt = user_data["_admin"]["salt"]
garciadeblasf2af4a12023-01-24 16:56:54 +0100401 shadow_password = sha256(
402 old_pwd.encode("utf-8") + salt.encode("utf-8")
403 ).hexdigest()
selvi.ja9a1fc82022-04-04 06:54:30 +0000404 if shadow_password != user_data["password"]:
405 raise AuthconnConflictException(
garciadeblasf2af4a12023-01-24 16:56:54 +0100406 "Incorrect password", http_code=HTTPStatus.CONFLICT
selvi.ja9a1fc82022-04-04 06:54:30 +0000407 )
delacruzramo01b15d32019-07-02 14:37:47 +0200408 BaseTopic.format_on_edit(user_data, user_info)
409 # User Name
410 usnm = user_info.get("username")
411 if usnm:
412 user_data["username"] = usnm
413 # If password is given and is not already encripted
414 pswd = user_info.get("password")
garciadeblas4568a372021-03-24 09:19:48 +0100415 if pswd and (
416 len(pswd) != 64 or not re.match("[a-fA-F0-9]*", pswd)
417 ): # TODO: Improve check?
elumalai7802ff82023-04-24 20:38:32 +0530418 cef_event(
419 self.cef_logger,
420 {
421 "name": "Change Password",
422 "sourceUserName": user_data["username"],
423 "message": "Changing Password for user, Outcome=Success",
424 "severity": "2",
425 },
426 )
427 self.logger.info("{}".format(self.cef_logger))
delacruzramo01b15d32019-07-02 14:37:47 +0200428 salt = uuid4().hex
429 if "_admin" not in user_data:
430 user_data["_admin"] = {}
431 user_data["_admin"]["salt"] = salt
garciadeblas4568a372021-03-24 09:19:48 +0100432 user_data["password"] = sha256(
433 pswd.encode("utf-8") + salt.encode("utf-8")
434 ).hexdigest()
selvi.ja9a1fc82022-04-04 06:54:30 +0000435 if not user_data["username"] == "admin":
436 if self.config.get("pwd_expiry_check"):
437 present = time()
438 if self.config.get("days"):
439 expire = present + 86400 * self.config.get("days")
440 user_data["_admin"]["modified_time"] = present
441 user_data["_admin"]["expire_time"] = expire
delacruzramo01b15d32019-07-02 14:37:47 +0200442 # Project-Role Mappings
443 # TODO: Check that user_info NEVER includes "project_role_mappings"
444 if "project_role_mappings" not in user_data:
445 user_data["project_role_mappings"] = []
446 for prm in user_info.get("add_project_role_mappings", []):
447 user_data["project_role_mappings"].append(prm)
448 for prm in user_info.get("remove_project_role_mappings", []):
449 for pidf in ["project", "project_name"]:
450 for ridf in ["role", "role_name"]:
451 try:
garciadeblas4568a372021-03-24 09:19:48 +0100452 user_data["project_role_mappings"].remove(
453 {"role": prm[ridf], "project": prm[pidf]}
454 )
delacruzramo01b15d32019-07-02 14:37:47 +0200455 except KeyError:
456 pass
457 except ValueError:
458 pass
delacruzramo3d6881c2019-12-04 13:42:26 +0100459 idf = BaseTopic.id_field("users", uid)
K Sai Kiran7ddb0732020-10-30 11:14:44 +0530460 self.db.set_one(self.users_collection, {idf: uid}, user_data)
delacruzramo3d6881c2019-12-04 13:42:26 +0100461 if user_info.get("remove_project_role_mappings"):
delacruzramoad682a52019-12-10 16:26:34 +0100462 idf = "user_id" if idf == "_id" else idf
K Sai Kiran7ddb0732020-10-30 11:14:44 +0530463 self.db.del_list(self.tokens_collection, {idf: uid})
delacruzramo01b15d32019-07-02 14:37:47 +0200464
465 def delete_user(self, user_id):
466 """
467 Delete user.
468
469 :param user_id: user identifier.
470 :raises AuthconnOperationException: if user deletion failed.
471 """
K Sai Kiran7ddb0732020-10-30 11:14:44 +0530472 self.db.del_one(self.users_collection, {"_id": user_id})
473 self.db.del_list(self.tokens_collection, {"user_id": user_id})
delacruzramoceb8baf2019-06-21 14:25:38 +0200474 return True
delacruzramo01b15d32019-07-02 14:37:47 +0200475
476 def get_user_list(self, filter_q=None):
477 """
478 Get user list.
479
tierno5ec768a2020-03-31 09:46:44 +0000480 :param filter_q: dictionary to filter user list by:
481 name (username is also admitted). If a user id is equal to the filter name, it is also provided
482 other
delacruzramo01b15d32019-07-02 14:37:47 +0200483 :return: returns a list of users.
484 """
485 filt = filter_q or {}
tierno5ec768a2020-03-31 09:46:44 +0000486 if "name" in filt: # backward compatibility
487 filt["username"] = filt.pop("name")
488 if filt.get("username") and is_valid_uuid(filt["username"]):
489 # username cannot be a uuid. If this is the case, change from username to _id
490 filt["_id"] = filt.pop("username")
K Sai Kiran7ddb0732020-10-30 11:14:44 +0530491 users = self.db.get_list(self.users_collection, filt)
tierno1546f2a2019-08-20 15:38:11 +0000492 project_id_name = {}
493 role_id_name = {}
delacruzramo01b15d32019-07-02 14:37:47 +0200494 for user in users:
tierno1546f2a2019-08-20 15:38:11 +0000495 prms = user.get("project_role_mappings")
496 projects = user.get("projects")
497 if prms:
498 projects = []
499 # add project_name and role_name. Generate projects for backward compatibility
delacruzramo01b15d32019-07-02 14:37:47 +0200500 for prm in prms:
tierno1546f2a2019-08-20 15:38:11 +0000501 project_id = prm["project"]
502 if project_id not in project_id_name:
garciadeblas4568a372021-03-24 09:19:48 +0100503 pr = self.db.get_one(
504 self.projects_collection,
505 {BaseTopic.id_field("projects", project_id): project_id},
506 fail_on_empty=False,
507 )
tierno1546f2a2019-08-20 15:38:11 +0000508 project_id_name[project_id] = pr["name"] if pr else None
509 prm["project_name"] = project_id_name[project_id]
510 if prm["project_name"] not in projects:
511 projects.append(prm["project_name"])
512
513 role_id = prm["role"]
514 if role_id not in role_id_name:
garciadeblas4568a372021-03-24 09:19:48 +0100515 role = self.db.get_one(
516 self.roles_collection,
517 {BaseTopic.id_field("roles", role_id): role_id},
518 fail_on_empty=False,
519 )
tierno1546f2a2019-08-20 15:38:11 +0000520 role_id_name[role_id] = role["name"] if role else None
521 prm["role_name"] = role_id_name[role_id]
522 user["projects"] = projects # for backward compatibility
523 elif projects:
524 # user created with an old version. Create a project_role mapping with role project_admin
525 user["project_role_mappings"] = []
garciadeblas4568a372021-03-24 09:19:48 +0100526 role = self.db.get_one(
527 self.roles_collection,
528 {BaseTopic.id_field("roles", "project_admin"): "project_admin"},
529 )
tierno1546f2a2019-08-20 15:38:11 +0000530 for p_id_name in projects:
garciadeblas4568a372021-03-24 09:19:48 +0100531 pr = self.db.get_one(
532 self.projects_collection,
533 {BaseTopic.id_field("projects", p_id_name): p_id_name},
534 )
535 prm = {
536 "project": pr["_id"],
537 "project_name": pr["name"],
538 "role_name": "project_admin",
539 "role": role["_id"],
540 }
tierno1546f2a2019-08-20 15:38:11 +0000541 user["project_role_mappings"].append(prm)
542 else:
543 user["projects"] = []
544 user["project_role_mappings"] = []
545
delacruzramo01b15d32019-07-02 14:37:47 +0200546 return users
547
548 def get_project_list(self, filter_q={}):
549 """
550 Get role list.
551
552 :return: returns the list of projects.
553 """
K Sai Kiran7ddb0732020-10-30 11:14:44 +0530554 return self.db.get_list(self.projects_collection, filter_q)
delacruzramo01b15d32019-07-02 14:37:47 +0200555
556 def create_project(self, project_info):
557 """
558 Create a project.
559
560 :param project: full project info.
561 :return: the internal id of the created project
562 :raises AuthconnOperationException: if project creation failed.
563 """
K Sai Kiran7ddb0732020-10-30 11:14:44 +0530564 pid = self.db.create(self.projects_collection, project_info)
delacruzramo01b15d32019-07-02 14:37:47 +0200565 return pid
566
567 def delete_project(self, project_id):
568 """
569 Delete a project.
570
571 :param project_id: project identifier.
572 :raises AuthconnOperationException: if project deletion failed.
573 """
delacruzramoad682a52019-12-10 16:26:34 +0100574 idf = BaseTopic.id_field("projects", project_id)
K Sai Kiran7ddb0732020-10-30 11:14:44 +0530575 r = self.db.del_one(self.projects_collection, {idf: project_id})
delacruzramoad682a52019-12-10 16:26:34 +0100576 idf = "project_id" if idf == "_id" else "project_name"
K Sai Kiran7ddb0732020-10-30 11:14:44 +0530577 self.db.del_list(self.tokens_collection, {idf: project_id})
delacruzramo01b15d32019-07-02 14:37:47 +0200578 return r
579
580 def update_project(self, project_id, project_info):
581 """
582 Change the name of a project
583
584 :param project_id: project to be changed
585 :param project_info: full project info
586 :return: None
587 :raises AuthconnOperationException: if project update failed.
588 """
garciadeblas4568a372021-03-24 09:19:48 +0100589 self.db.set_one(
590 self.projects_collection,
591 {BaseTopic.id_field("projects", project_id): project_id},
592 project_info,
593 )