From e6918187568dbd01842d8d1d2c808ce16a894239 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 21 Apr 2024 13:54:28 +0200 Subject: Adding upstream version 18.2.2. Signed-off-by: Daniel Baumann --- src/pybind/mgr/dashboard/controllers/user.py | 214 +++++++++++++++++++++++++++ 1 file changed, 214 insertions(+) create mode 100644 src/pybind/mgr/dashboard/controllers/user.py (limited to 'src/pybind/mgr/dashboard/controllers/user.py') diff --git a/src/pybind/mgr/dashboard/controllers/user.py b/src/pybind/mgr/dashboard/controllers/user.py new file mode 100644 index 000000000..9141cfe68 --- /dev/null +++ b/src/pybind/mgr/dashboard/controllers/user.py @@ -0,0 +1,214 @@ +# -*- coding: utf-8 -*- + +import time +from datetime import datetime + +import cherrypy +from ceph_argparse import CephString + +from .. import mgr +from ..exceptions import DashboardException, PasswordPolicyException, \ + PwdExpirationDateNotValid, UserAlreadyExists, UserDoesNotExist +from ..security import Scope +from ..services.access_control import SYSTEM_ROLES, PasswordPolicy +from ..services.auth import JwtManager +from . import APIDoc, APIRouter, BaseController, Endpoint, EndpointDoc, \ + RESTController, allow_empty_body, validate_ceph_type + +USER_SCHEMA = ([{ + "username": (str, 'Username of the user'), + "roles": ([str], 'User Roles'), + "name": (str, 'User Name'), + "email": (str, 'User email address'), + "lastUpdate": (int, 'Details last updated'), + "enabled": (bool, 'Is the user enabled?'), + "pwdExpirationDate": (str, 'Password Expiration date'), + "pwdUpdateRequired": (bool, 'Is Password Update Required?') +}], '') + + +def validate_password_policy(password, username=None, old_password=None): + """ + :param password: The password to validate. + :param username: The name of the user (optional). + :param old_password: The old password (optional). + :return: Returns the password complexity credits. + :rtype: int + :raises DashboardException: If a password policy fails. + """ + pw_policy = PasswordPolicy(password, username, old_password) + try: + pw_policy.check_all() + return pw_policy.complexity_credits + except PasswordPolicyException as ex: + raise DashboardException(msg=str(ex), + code='password_policy_validation_failed', + component='user') + + +@APIRouter('/user', Scope.USER) +@APIDoc("Display User Details", "User") +class User(RESTController): + + @staticmethod + def _user_to_dict(user): + result = user.to_dict() + del result['password'] + return result + + @staticmethod + def _get_user_roles(roles): + all_roles = dict(mgr.ACCESS_CTRL_DB.roles) + all_roles.update(SYSTEM_ROLES) + try: + return [all_roles[rolename] for rolename in roles] + except KeyError: + raise DashboardException(msg='Role does not exist', + code='role_does_not_exist', + component='user') + + @EndpointDoc("Get List Of Users", + responses={200: USER_SCHEMA}) + def list(self): + users = mgr.ACCESS_CTRL_DB.users + result = [User._user_to_dict(u) for _, u in users.items()] + return result + + def get(self, username): + try: + user = mgr.ACCESS_CTRL_DB.get_user(username) + except UserDoesNotExist: + raise cherrypy.HTTPError(404) + return User._user_to_dict(user) + + @validate_ceph_type([('username', CephString())], 'user') + def create(self, username=None, password=None, name=None, email=None, + roles=None, enabled=True, pwdExpirationDate=None, pwdUpdateRequired=True): + if not username: + raise DashboardException(msg='Username is required', + code='username_required', + component='user') + user_roles = None + if roles: + user_roles = User._get_user_roles(roles) + if password: + validate_password_policy(password, username) + try: + user = mgr.ACCESS_CTRL_DB.create_user(username, password, name, + email, enabled, pwdExpirationDate, + pwdUpdateRequired) + except UserAlreadyExists: + raise DashboardException(msg='Username already exists', + code='username_already_exists', + component='user') + except PwdExpirationDateNotValid: + raise DashboardException(msg='Password expiration date must not be in ' + 'the past', + code='pwd_past_expiration_date', + component='user') + + if user_roles: + user.set_roles(user_roles) + mgr.ACCESS_CTRL_DB.save() + return User._user_to_dict(user) + + def delete(self, username): + session_username = JwtManager.get_username() + if session_username == username: + raise DashboardException(msg='Cannot delete current user', + code='cannot_delete_current_user', + component='user') + try: + mgr.ACCESS_CTRL_DB.delete_user(username) + except UserDoesNotExist: + raise cherrypy.HTTPError(404) + mgr.ACCESS_CTRL_DB.save() + + def set(self, username, password=None, name=None, email=None, roles=None, + enabled=None, pwdExpirationDate=None, pwdUpdateRequired=False): + if JwtManager.get_username() == username and enabled is False: + raise DashboardException(msg='You are not allowed to disable your user', + code='cannot_disable_current_user', + component='user') + + try: + user = mgr.ACCESS_CTRL_DB.get_user(username) + except UserDoesNotExist: + raise cherrypy.HTTPError(404) + user_roles = [] + if roles: + user_roles = User._get_user_roles(roles) + if password: + validate_password_policy(password, username) + user.set_password(password) + if pwdExpirationDate and \ + (pwdExpirationDate < int(time.mktime(datetime.utcnow().timetuple()))): + raise DashboardException( + msg='Password expiration date must not be in the past', + code='pwd_past_expiration_date', component='user') + user.name = name + user.email = email + if enabled is not None: + user.enabled = enabled + user.pwd_expiration_date = pwdExpirationDate + user.set_roles(user_roles) + user.pwd_update_required = pwdUpdateRequired + mgr.ACCESS_CTRL_DB.save() + return User._user_to_dict(user) + + +@APIRouter('/user') +@APIDoc("Get User Password Policy Details", "UserPasswordPolicy") +class UserPasswordPolicy(RESTController): + + @Endpoint('POST') + @allow_empty_body + def validate_password(self, password, username=None, old_password=None): + """ + Check if the password meets the password policy. + :param password: The password to validate. + :param username: The name of the user (optional). + :param old_password: The old password (optional). + :return: An object with properties valid, credits and valuation. + 'credits' contains the password complexity credits and + 'valuation' the textual summary of the validation. + """ + result = {'valid': False, 'credits': 0, 'valuation': None} + try: + result['credits'] = validate_password_policy(password, username, old_password) + if result['credits'] < 15: + result['valuation'] = 'Weak' + elif result['credits'] < 20: + result['valuation'] = 'OK' + elif result['credits'] < 25: + result['valuation'] = 'Strong' + else: + result['valuation'] = 'Very strong' + result['valid'] = True + except DashboardException as ex: + result['valuation'] = str(ex) + return result + + +@APIRouter('/user/{username}') +@APIDoc("Change User Password", "UserChangePassword") +class UserChangePassword(BaseController): + + @Endpoint('POST') + def change_password(self, username, old_password, new_password): + session_username = JwtManager.get_username() + if username != session_username: + raise DashboardException(msg='Invalid user context', + code='invalid_user_context', + component='user') + try: + user = mgr.ACCESS_CTRL_DB.get_user(session_username) + except UserDoesNotExist: + raise cherrypy.HTTPError(404) + if not user.compare_password(old_password): + raise DashboardException(msg='Invalid old password', + code='invalid_old_password', + component='user') + validate_password_policy(new_password, username, old_password) + user.set_password(new_password) + mgr.ACCESS_CTRL_DB.save() -- cgit v1.2.3