X-Git-Url: https://osm.etsi.org/gitweb/?p=osm%2FNBI.git;a=blobdiff_plain;f=osm_nbi%2Fauthconn_internal.py;h=b3de1cd77bdfb0625700048021fd7aaa704c5951;hp=b8cfe5b8a706843f9b7e5c5052ed88e23d413b9a;hb=341ac1bac7b115d64a50ec166aa5e6d186b39443;hpb=5ec768a6eba4a4d15d1db362e2463db1a32c57ed diff --git a/osm_nbi/authconn_internal.py b/osm_nbi/authconn_internal.py index b8cfe5b..b3de1cd 100644 --- a/osm_nbi/authconn_internal.py +++ b/osm_nbi/authconn_internal.py @@ -47,6 +47,11 @@ class AuthconnInternal(Authconn): token_time_window = 2 # seconds token_delay = 1 # seconds to wait upon second request within time window + users_collection = "users" + roles_collection = "roles" + projects_collection = "projects" + tokens_collection = "tokens" + def __init__(self, config, db, role_permissions): Authconn.__init__(self, config, db, role_permissions) self.logger = logging.getLogger("nbi.authenticator.internal") @@ -82,7 +87,7 @@ class AuthconnInternal(Authconn): # get from database if not in cache # if not token_info: - token_info = self.db.get_one("tokens", {"_id": token}) + token_info = self.db.get_one(self.tokens_collection, {"_id": token}) if token_info["expires"] < now: raise AuthException("Expired Token or Authorization HTTP header", http_code=HTTPStatus.UNAUTHORIZED) @@ -108,7 +113,7 @@ class AuthconnInternal(Authconn): """ try: # self.token_cache.pop(token, None) - self.db.del_one("tokens", {"_id": token}) + self.db.del_one(self.tokens_collection, {"_id": token}) return True except DbException as e: if e.http_code == HTTPStatus.NOT_FOUND: @@ -119,6 +124,22 @@ class AuthconnInternal(Authconn): self.logger.exception(exmsg) raise AuthException(exmsg, http_code=HTTPStatus.UNAUTHORIZED) + def validate_user(self, user, password): + """ + Validate username and password via appropriate backend. + :param user: username of the user. + :param password: password to be validated. + """ + user_rows = self.db.get_list(self.users_collection, {BaseTopic.id_field("users", user): user}) + user_content = None + if user_rows: + user_content = user_rows[0] + salt = user_content["_admin"]["salt"] + shadow_password = sha256(password.encode('utf-8') + salt.encode('utf-8')).hexdigest() + if shadow_password != user_content["password"]: + user_content = None + return user_content + def authenticate(self, credentials, token_info=None): """ Authenticate a user using username/password or previous token_info plus project; its creates a new token @@ -145,17 +166,13 @@ class AuthconnInternal(Authconn): # Try using username/password if user: - user_rows = self.db.get_list("users", {BaseTopic.id_field("users", user): user}) - if user_rows: - user_content = user_rows[0] - salt = user_content["_admin"]["salt"] - shadow_password = sha256(password.encode('utf-8') + salt.encode('utf-8')).hexdigest() - if shadow_password != user_content["password"]: - user_content = None + user_content = self.validate_user(user, password) if not user_content: raise AuthException("Invalid username/password", http_code=HTTPStatus.UNAUTHORIZED) + if not user_content.get("_admin", None): + raise AuthException("No default project for this user.", http_code=HTTPStatus.UNAUTHORIZED) elif token_info: - user_rows = self.db.get_list("users", {"username": token_info["username"]}) + user_rows = self.db.get_list(self.users_collection, {"username": token_info["username"]}) if user_rows: user_content = user_rows[0] else: @@ -163,13 +180,13 @@ class AuthconnInternal(Authconn): else: raise AuthException("Provide credentials: username/password or Authorization Bearer token", http_code=HTTPStatus.UNAUTHORIZED) - # Delay upon second request within time window if now - user_content["_admin"].get("last_token_time", 0) < self.token_time_window: sleep(self.token_delay) # user_content["_admin"]["last_token_time"] = now # self.db.replace("users", user_content["_id"], user_content) # might cause race conditions - self.db.set_one("users", {"_id": user_content["_id"]}, {"_admin.last_token_time": now}) + self.db.set_one(self.users_collection, + {"_id": user_content["_id"]}, {"_admin.last_token_time": now}) token_id = ''.join(random_choice('abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789') for _ in range(0, 32)) @@ -184,7 +201,8 @@ class AuthconnInternal(Authconn): projects = [prm["project"] for prm in prm_list] - proj = self.db.get_one("projects", {BaseTopic.id_field("projects", project): project}) + proj = self.db.get_one(self.projects_collection, + {BaseTopic.id_field("projects", project): project}) project_name = proj["name"] project_id = proj["_id"] if project_name not in projects and project_id not in projects: @@ -202,14 +220,15 @@ class AuthconnInternal(Authconn): roles_list = [] for prm in prm_list: if prm["project"] in [project_id, project_name]: - role = self.db.get_one("roles", {BaseTopic.id_field("roles", prm["role"]): prm["role"]}) + role = self.db.get_one(self.roles_collection, + {BaseTopic.id_field("roles", prm["role"]): prm["role"]}) rid = role["_id"] if rid not in roles: rnm = role["name"] roles.append(rid) roles_list.append({"name": rnm, "id": rid}) if not roles_list: - rid = self.db.get_one("roles", {"name": "project_admin"})["_id"] + rid = self.db.get_one(self.roles_collection, {"name": "project_admin"})["_id"] roles_list = [{"name": "project_admin", "id": rid}] new_token = {"issued_at": now, @@ -224,7 +243,7 @@ class AuthconnInternal(Authconn): "roles": roles_list, } - self.db.create("tokens", new_token) + self.db.create(self.tokens_collection, new_token) return deepcopy(new_token) def get_role_list(self, filter_q={}): @@ -233,7 +252,7 @@ class AuthconnInternal(Authconn): :return: returns the list of roles. """ - return self.db.get_list("roles", filter_q) + return self.db.get_list(self.roles_collection, filter_q) def create_role(self, role_info): """ @@ -246,7 +265,7 @@ class AuthconnInternal(Authconn): # TODO: Check that role name does not exist ? rid = str(uuid4()) role_info["_id"] = rid - rid = self.db.create("roles", role_info) + rid = self.db.create(self.roles_collection, role_info) return rid def delete_role(self, role_id): @@ -256,8 +275,8 @@ class AuthconnInternal(Authconn): :param role_id: role identifier. :raises AuthconnOperationException: if role deletion failed. """ - rc = self.db.del_one("roles", {"_id": role_id}) - self.db.del_list("tokens", {"roles.id": role_id}) + rc = self.db.del_one(self.roles_collection, {"_id": role_id}) + self.db.del_list(self.tokens_collection, {"roles.id": role_id}) return rc def update_role(self, role_info): @@ -269,7 +288,7 @@ class AuthconnInternal(Authconn): :raises AuthconnOperationException: if user creation failed. """ rid = role_info["_id"] - self.db.set_one("roles", {"_id": rid}, role_info) + self.db.set_one(self.roles_collection, {"_id": rid}, role_info) return {"_id": rid, "name": role_info["name"]} def create_user(self, user_info): @@ -287,7 +306,7 @@ class AuthconnInternal(Authconn): # "projects" are not stored any more if "projects" in user_info: del user_info["projects"] - self.db.create("users", user_info) + self.db.create(self.users_collection, user_info) return {"username": user_info["username"], "_id": user_info["_id"]} def update_user(self, user_info): @@ -297,7 +316,7 @@ class AuthconnInternal(Authconn): :param user_info: user info modifications """ uid = user_info["_id"] - user_data = self.db.get_one("users", {BaseTopic.id_field("users", uid): uid}) + user_data = self.db.get_one(self.users_collection, {BaseTopic.id_field("users", uid): uid}) BaseTopic.format_on_edit(user_data, user_info) # User Name usnm = user_info.get("username") @@ -327,10 +346,10 @@ class AuthconnInternal(Authconn): except ValueError: pass idf = BaseTopic.id_field("users", uid) - self.db.set_one("users", {idf: uid}, user_data) + self.db.set_one(self.users_collection, {idf: uid}, user_data) if user_info.get("remove_project_role_mappings"): idf = "user_id" if idf == "_id" else idf - self.db.del_list("tokens", {idf: uid}) + self.db.del_list(self.tokens_collection, {idf: uid}) def delete_user(self, user_id): """ @@ -339,8 +358,8 @@ class AuthconnInternal(Authconn): :param user_id: user identifier. :raises AuthconnOperationException: if user deletion failed. """ - self.db.del_one("users", {"_id": user_id}) - self.db.del_list("tokens", {"user_id": user_id}) + self.db.del_one(self.users_collection, {"_id": user_id}) + self.db.del_list(self.tokens_collection, {"user_id": user_id}) return True def get_user_list(self, filter_q=None): @@ -358,7 +377,7 @@ class AuthconnInternal(Authconn): if filt.get("username") and is_valid_uuid(filt["username"]): # username cannot be a uuid. If this is the case, change from username to _id filt["_id"] = filt.pop("username") - users = self.db.get_list("users", filt) + users = self.db.get_list(self.users_collection, filt) project_id_name = {} role_id_name = {} for user in users: @@ -370,7 +389,8 @@ class AuthconnInternal(Authconn): for prm in prms: project_id = prm["project"] if project_id not in project_id_name: - pr = self.db.get_one("projects", {BaseTopic.id_field("projects", project_id): project_id}, + pr = self.db.get_one(self.projects_collection, + {BaseTopic.id_field("projects", project_id): project_id}, fail_on_empty=False) project_id_name[project_id] = pr["name"] if pr else None prm["project_name"] = project_id_name[project_id] @@ -379,7 +399,8 @@ class AuthconnInternal(Authconn): role_id = prm["role"] if role_id not in role_id_name: - role = self.db.get_one("roles", {BaseTopic.id_field("roles", role_id): role_id}, + role = self.db.get_one(self.roles_collection, + {BaseTopic.id_field("roles", role_id): role_id}, fail_on_empty=False) role_id_name[role_id] = role["name"] if role else None prm["role_name"] = role_id_name[role_id] @@ -387,9 +408,11 @@ class AuthconnInternal(Authconn): elif projects: # user created with an old version. Create a project_role mapping with role project_admin user["project_role_mappings"] = [] - role = self.db.get_one("roles", {BaseTopic.id_field("roles", "project_admin"): "project_admin"}) + role = self.db.get_one(self.roles_collection, + {BaseTopic.id_field("roles", "project_admin"): "project_admin"}) for p_id_name in projects: - pr = self.db.get_one("projects", {BaseTopic.id_field("projects", p_id_name): p_id_name}) + pr = self.db.get_one(self.projects_collection, + {BaseTopic.id_field("projects", p_id_name): p_id_name}) prm = {"project": pr["_id"], "project_name": pr["name"], "role_name": "project_admin", @@ -408,7 +431,7 @@ class AuthconnInternal(Authconn): :return: returns the list of projects. """ - return self.db.get_list("projects", filter_q) + return self.db.get_list(self.projects_collection, filter_q) def create_project(self, project_info): """ @@ -418,7 +441,7 @@ class AuthconnInternal(Authconn): :return: the internal id of the created project :raises AuthconnOperationException: if project creation failed. """ - pid = self.db.create("projects", project_info) + pid = self.db.create(self.projects_collection, project_info) return pid def delete_project(self, project_id): @@ -429,9 +452,9 @@ class AuthconnInternal(Authconn): :raises AuthconnOperationException: if project deletion failed. """ idf = BaseTopic.id_field("projects", project_id) - r = self.db.del_one("projects", {idf: project_id}) + r = self.db.del_one(self.projects_collection, {idf: project_id}) idf = "project_id" if idf == "_id" else "project_name" - self.db.del_list("tokens", {idf: project_id}) + self.db.del_list(self.tokens_collection, {idf: project_id}) return r def update_project(self, project_id, project_info): @@ -443,4 +466,5 @@ class AuthconnInternal(Authconn): :return: None :raises AuthconnOperationException: if project update failed. """ - self.db.set_one("projects", {BaseTopic.id_field("projects", project_id): project_id}, project_info) + self.db.set_one(self.projects_collection, {BaseTopic.id_field("projects", project_id): project_id}, + project_info)