def update_user(self, user_info): """ Change the user name and/or password. :param user_info: user info modifications """ uid = user_info["_id"] user_data = self.db.get_one("users", {BaseTopic.id_field("users", uid): uid}) BaseTopic.format_on_edit(user_data, user_info) # User Name usnm = user_info.get("username") if usnm: user_data["username"] = usnm # If password is given and is not already encripted pswd = user_info.get("password") if pswd and (len(pswd) != 64 or not re.match( '[a-fA-F0-9]*', pswd)): # TODO: Improve check? salt = uuid4().hex if "_admin" not in user_data: user_data["_admin"] = {} user_data["_admin"]["salt"] = salt user_data["password"] = sha256( pswd.encode('utf-8') + salt.encode('utf-8')).hexdigest() # Project-Role Mappings # TODO: Check that user_info NEVER includes "project_role_mappings" if "project_role_mappings" not in user_data: user_data["project_role_mappings"] = [] for prm in user_info.get("add_project_role_mappings", []): user_data["project_role_mappings"].append(prm) for prm in user_info.get("remove_project_role_mappings", []): for pidf in ["project", "project_name"]: for ridf in ["role", "role_name"]: try: user_data["project_role_mappings"].remove({ "role": prm[ridf], "project": prm[pidf] }) except KeyError: pass except ValueError: pass idf = BaseTopic.id_field("users", uid) self.db.set_one("users", {idf: uid}, user_data) if user_info.get("remove_project_role_mappings"): self.db.del_list("tokens", {"user_id" if idf == "_id" else idf: uid}) self.token_cache.clear()
def delete_project(self, project_id): """ Delete a project. :param project_id: project identifier. :raises AuthconnOperationException: if project deletion failed. """ filter_q = {BaseTopic.id_field("projects", project_id): project_id} r = self.db.del_one("projects", filter_q) return r
def update_project(self, project_id, project_info): """ Change the name of a project :param project_id: project to be changed :param project_info: full project info :return: None :raises AuthconnOperationException: if project update failed. """ self.db.set_one( "projects", {BaseTopic.id_field("projects", project_id): project_id}, project_info)
def get_project(self, _id, fail=True): """ Get one project :param _id: id or name :param fail: True to raise exception on not found. False to return None on not found :return: dictionary with the project information """ filt = {BaseTopic.id_field("projects", _id): _id} projs = self.get_project_list(filt) if not projs: if fail: raise AuthconnNotFoundException("project with {} not found".format(filt)) else: return None return projs[0]
def get_role(self, _id, fail=True): """ Get one role :param _id: id or name :param fail: True to raise exception on not found. False to return None on not found :return: dictionary with the role information """ filt = {BaseTopic.id_field("roles", _id): _id} roles = self.get_role_list(filt) if not roles: if fail: raise AuthconnNotFoundException("Role with {} not found".format(filt)) else: return None return roles[0]
def get_user(self, _id, fail=True): """ Get one user :param _id: id or name :param fail: True to raise exception on not found. False to return None on not found :return: dictionary with the user information """ filt = {BaseTopic.id_field("users", _id): _id} users = self.get_user_list(filt) if not users: if fail: raise AuthconnNotFoundException("User with {} not found".format(filt), http_code=HTTPStatus.NOT_FOUND) else: return None return users[0]
def get_user_list(self, filter_q=None): """ Get user list. :param filter_q: dictionary to filter user list by name (username is also admited) and/or _id :return: returns a list of users. """ filt = filter_q or {} if "name" in filt: filt["username"] = filt["name"] del filt["name"] users = self.db.get_list("users", filt) project_id_name = {} role_id_name = {} for user in users: prms = user.get("project_role_mappings") projects = user.get("projects") if prms: projects = [] # add project_name and role_name. Generate projects for backward compatibility 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 }, fail_on_empty=False) project_id_name[ project_id] = pr["name"] if pr else None prm["project_name"] = project_id_name[project_id] if prm["project_name"] not in projects: projects.append(prm["project_name"]) 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}, fail_on_empty=False) role_id_name[role_id] = role["name"] if role else None prm["role_name"] = role_id_name[role_id] user["projects"] = projects # for backward compatibility 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" }) for p_id_name in projects: pr = self.db.get_one( "projects", {BaseTopic.id_field("projects", p_id_name): p_id_name}) prm = { "project": pr["_id"], "project_name": pr["name"], "role_name": "project_admin", "role": role["_id"] } user["project_role_mappings"].append(prm) else: user["projects"] = [] user["project_role_mappings"] = [] return users
def authenticate(self, user, password, project=None, token_info=None): """ Authenticate a user using username/password or previous token_info plus project; its creates a new token :param user: user: name, id or None :param password: password or None :param project: name, id, or None. If None first found project will be used to get an scope token :param token_info: previous token_info to obtain authorization :param remote: remote host information :return: the scoped token info or raises an exception. The token is a dictionary with: _id: token string id, username: username, project_id: scoped_token project_id, project_name: scoped_token project_name, expires: epoch time when it expires, """ now = time() user_content = None # 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 if not user_content: raise AuthException("Invalid username/password", http_code=HTTPStatus.UNAUTHORIZED) elif token_info: user_rows = self.db.get_list("users", {"username": token_info["username"]}) if user_rows: user_content = user_rows[0] else: raise AuthException("Invalid token", http_code=HTTPStatus.UNAUTHORIZED) else: raise AuthException( "Provide credentials: username/password or Authorization Bearer token", http_code=HTTPStatus.UNAUTHORIZED) token_id = ''.join( random_choice( 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789' ) for _ in range(0, 32)) # projects = user_content.get("projects", []) prm_list = user_content.get("project_role_mappings", []) if not project: project = prm_list[0]["project"] if prm_list else None if not project: raise AuthException("can't find a default project for this user", http_code=HTTPStatus.UNAUTHORIZED) projects = [prm["project"] for prm in prm_list] proj = self.db.get_one( "projects", {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: raise AuthException( "project {} not allowed for this user".format(project), http_code=HTTPStatus.UNAUTHORIZED) # TODO remove admin, this vill be used by roles RBAC if project_name == "admin": token_admin = True else: token_admin = proj.get("admin", False) # add token roles roles = [] 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"]}) 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"] roles_list = [{"name": "project_admin", "id": rid}] new_token = { "issued_at": now, "expires": now + 3600, "_id": token_id, "id": token_id, "project_id": proj["_id"], "project_name": proj["name"], "username": user_content["username"], "user_id": user_content["_id"], "admin": token_admin, "roles": roles_list, } self.token_cache[token_id] = new_token self.db.create("tokens", new_token) return deepcopy(new_token)