summaryrefslogtreecommitdiffstats
path: root/src/pybind/mgr/dashboard/services/access_control.py
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-21 11:54:28 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-21 11:54:28 +0000
commite6918187568dbd01842d8d1d2c808ce16a894239 (patch)
tree64f88b554b444a49f656b6c656111a145cbbaa28 /src/pybind/mgr/dashboard/services/access_control.py
parentInitial commit. (diff)
downloadceph-e6918187568dbd01842d8d1d2c808ce16a894239.tar.xz
ceph-e6918187568dbd01842d8d1d2c808ce16a894239.zip
Adding upstream version 18.2.2.upstream/18.2.2
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/pybind/mgr/dashboard/services/access_control.py')
-rw-r--r--src/pybind/mgr/dashboard/services/access_control.py942
1 files changed, 942 insertions, 0 deletions
diff --git a/src/pybind/mgr/dashboard/services/access_control.py b/src/pybind/mgr/dashboard/services/access_control.py
new file mode 100644
index 000000000..0cbe49bb1
--- /dev/null
+++ b/src/pybind/mgr/dashboard/services/access_control.py
@@ -0,0 +1,942 @@
+# -*- coding: utf-8 -*-
+# pylint: disable=too-many-arguments,too-many-return-statements
+# pylint: disable=too-many-branches, too-many-locals, too-many-statements
+
+import errno
+import json
+import logging
+import re
+import threading
+import time
+from datetime import datetime, timedelta
+from string import ascii_lowercase, ascii_uppercase, digits, punctuation
+from typing import List, Optional, Sequence
+
+import bcrypt
+from mgr_module import CLICheckNonemptyFileInput, CLIReadCommand, CLIWriteCommand
+from mgr_util import password_hash
+
+from .. import mgr
+from ..exceptions import PasswordPolicyException, PermissionNotValid, \
+ PwdExpirationDateNotValid, RoleAlreadyExists, RoleDoesNotExist, \
+ RoleIsAssociatedWithUser, RoleNotInUser, ScopeNotInRole, ScopeNotValid, \
+ UserAlreadyExists, UserDoesNotExist
+from ..security import Permission, Scope
+from ..settings import Settings
+
+logger = logging.getLogger('access_control')
+DEFAULT_FILE_DESC = 'password/secret'
+
+
+_P = Permission # short alias
+
+
+class PasswordPolicy(object):
+ def __init__(self, password, username=None, old_password=None):
+ """
+ :param password: The new plain password.
+ :type password: str
+ :param username: The name of the user.
+ :type username: str | None
+ :param old_password: The old plain password.
+ :type old_password: str | None
+ """
+ self.password = password
+ self.username = username
+ self.old_password = old_password
+ self.forbidden_words = Settings.PWD_POLICY_EXCLUSION_LIST.split(',')
+ self.complexity_credits = 0
+
+ @staticmethod
+ def _check_if_contains_word(password, word):
+ return re.compile('(?:{0})'.format(word),
+ flags=re.IGNORECASE).search(password)
+
+ def check_password_complexity(self):
+ if not Settings.PWD_POLICY_CHECK_COMPLEXITY_ENABLED:
+ return Settings.PWD_POLICY_MIN_COMPLEXITY
+ digit_credit = 1
+ small_letter_credit = 1
+ big_letter_credit = 2
+ special_character_credit = 3
+ other_character_credit = 5
+ self.complexity_credits = 0
+ for ch in self.password:
+ if ch in ascii_uppercase:
+ self.complexity_credits += big_letter_credit
+ elif ch in ascii_lowercase:
+ self.complexity_credits += small_letter_credit
+ elif ch in digits:
+ self.complexity_credits += digit_credit
+ elif ch in punctuation:
+ self.complexity_credits += special_character_credit
+ else:
+ self.complexity_credits += other_character_credit
+ return self.complexity_credits
+
+ def check_is_old_password(self):
+ if not Settings.PWD_POLICY_CHECK_OLDPWD_ENABLED:
+ return False
+ return self.old_password and self.password == self.old_password
+
+ def check_if_contains_username(self):
+ if not Settings.PWD_POLICY_CHECK_USERNAME_ENABLED:
+ return False
+ if not self.username:
+ return False
+ return self._check_if_contains_word(self.password, self.username)
+
+ def check_if_contains_forbidden_words(self):
+ if not Settings.PWD_POLICY_CHECK_EXCLUSION_LIST_ENABLED:
+ return False
+ return self._check_if_contains_word(self.password,
+ '|'.join(self.forbidden_words))
+
+ def check_if_sequential_characters(self):
+ if not Settings.PWD_POLICY_CHECK_SEQUENTIAL_CHARS_ENABLED:
+ return False
+ for i in range(1, len(self.password) - 1):
+ if ord(self.password[i - 1]) + 1 == ord(self.password[i])\
+ == ord(self.password[i + 1]) - 1:
+ return True
+ return False
+
+ def check_if_repetitive_characters(self):
+ if not Settings.PWD_POLICY_CHECK_REPETITIVE_CHARS_ENABLED:
+ return False
+ for i in range(1, len(self.password) - 1):
+ if self.password[i - 1] == self.password[i] == self.password[i + 1]:
+ return True
+ return False
+
+ def check_password_length(self):
+ if not Settings.PWD_POLICY_CHECK_LENGTH_ENABLED:
+ return True
+ return len(self.password) >= Settings.PWD_POLICY_MIN_LENGTH
+
+ def check_all(self):
+ """
+ Perform all password policy checks.
+ :raise PasswordPolicyException: If a password policy check fails.
+ """
+ if not Settings.PWD_POLICY_ENABLED:
+ return
+ if self.check_password_complexity() < Settings.PWD_POLICY_MIN_COMPLEXITY:
+ raise PasswordPolicyException('Password is too weak.')
+ if not self.check_password_length():
+ raise PasswordPolicyException('Password is too weak.')
+ if self.check_is_old_password():
+ raise PasswordPolicyException('Password must not be the same as the previous one.')
+ if self.check_if_contains_username():
+ raise PasswordPolicyException('Password must not contain username.')
+ result = self.check_if_contains_forbidden_words()
+ if result:
+ raise PasswordPolicyException('Password must not contain the keyword "{}".'.format(
+ result.group(0)))
+ if self.check_if_repetitive_characters():
+ raise PasswordPolicyException('Password must not contain repetitive characters.')
+ if self.check_if_sequential_characters():
+ raise PasswordPolicyException('Password must not contain sequential characters.')
+
+
+class Role(object):
+ def __init__(self, name, description=None, scope_permissions=None):
+ self.name = name
+ self.description = description
+ if scope_permissions is None:
+ self.scopes_permissions = {}
+ else:
+ self.scopes_permissions = scope_permissions
+
+ def __hash__(self):
+ return hash(self.name)
+
+ def __eq__(self, other):
+ return self.name == other.name
+
+ def set_scope_permissions(self, scope, permissions):
+ if not Scope.valid_scope(scope):
+ raise ScopeNotValid(scope)
+ for perm in permissions:
+ if not Permission.valid_permission(perm):
+ raise PermissionNotValid(perm)
+
+ permissions.sort()
+ self.scopes_permissions[scope] = permissions
+
+ def del_scope_permissions(self, scope):
+ if scope not in self.scopes_permissions:
+ raise ScopeNotInRole(scope, self.name)
+ del self.scopes_permissions[scope]
+
+ def reset_scope_permissions(self):
+ self.scopes_permissions = {}
+
+ def authorize(self, scope, permissions):
+ if scope in self.scopes_permissions:
+ role_perms = self.scopes_permissions[scope]
+ for perm in permissions:
+ if perm not in role_perms:
+ return False
+ return True
+ return False
+
+ def to_dict(self):
+ return {
+ 'name': self.name,
+ 'description': self.description,
+ 'scopes_permissions': self.scopes_permissions
+ }
+
+ @classmethod
+ def from_dict(cls, r_dict):
+ return Role(r_dict['name'], r_dict['description'],
+ r_dict['scopes_permissions'])
+
+
+# static pre-defined system roles
+# this roles cannot be deleted nor updated
+
+# admin role provides all permissions for all scopes
+ADMIN_ROLE = Role(
+ 'administrator', 'allows full permissions for all security scopes', {
+ scope_name: Permission.all_permissions()
+ for scope_name in Scope.all_scopes()
+ })
+
+
+# read-only role provides read-only permission for all scopes
+READ_ONLY_ROLE = Role(
+ 'read-only',
+ 'allows read permission for all security scope except dashboard settings and config-opt', {
+ scope_name: [_P.READ] for scope_name in Scope.all_scopes()
+ if scope_name not in (Scope.DASHBOARD_SETTINGS, Scope.CONFIG_OPT)
+ })
+
+
+# block manager role provides all permission for block related scopes
+BLOCK_MGR_ROLE = Role(
+ 'block-manager', 'allows full permissions for rbd-image, rbd-mirroring, and iscsi scopes', {
+ Scope.RBD_IMAGE: [_P.READ, _P.CREATE, _P.UPDATE, _P.DELETE],
+ Scope.POOL: [_P.READ],
+ Scope.ISCSI: [_P.READ, _P.CREATE, _P.UPDATE, _P.DELETE],
+ Scope.RBD_MIRRORING: [_P.READ, _P.CREATE, _P.UPDATE, _P.DELETE],
+ Scope.GRAFANA: [_P.READ],
+ })
+
+
+# RadosGW manager role provides all permissions for block related scopes
+RGW_MGR_ROLE = Role(
+ 'rgw-manager', 'allows full permissions for the rgw scope', {
+ Scope.RGW: [_P.READ, _P.CREATE, _P.UPDATE, _P.DELETE],
+ Scope.GRAFANA: [_P.READ],
+ })
+
+
+# Cluster manager role provides all permission for OSDs, Monitors, and
+# Config options
+CLUSTER_MGR_ROLE = Role(
+ 'cluster-manager', """allows full permissions for the hosts, osd, mon, mgr,
+ and config-opt scopes""", {
+ Scope.HOSTS: [_P.READ, _P.CREATE, _P.UPDATE, _P.DELETE],
+ Scope.OSD: [_P.READ, _P.CREATE, _P.UPDATE, _P.DELETE],
+ Scope.MONITOR: [_P.READ, _P.CREATE, _P.UPDATE, _P.DELETE],
+ Scope.MANAGER: [_P.READ, _P.CREATE, _P.UPDATE, _P.DELETE],
+ Scope.CONFIG_OPT: [_P.READ, _P.CREATE, _P.UPDATE, _P.DELETE],
+ Scope.LOG: [_P.READ, _P.CREATE, _P.UPDATE, _P.DELETE],
+ Scope.GRAFANA: [_P.READ],
+ })
+
+
+# Pool manager role provides all permissions for pool related scopes
+POOL_MGR_ROLE = Role(
+ 'pool-manager', 'allows full permissions for the pool scope', {
+ Scope.POOL: [_P.READ, _P.CREATE, _P.UPDATE, _P.DELETE],
+ Scope.GRAFANA: [_P.READ],
+ })
+
+# CephFS manager role provides all permissions for CephFS related scopes
+CEPHFS_MGR_ROLE = Role(
+ 'cephfs-manager', 'allows full permissions for the cephfs scope', {
+ Scope.CEPHFS: [_P.READ, _P.CREATE, _P.UPDATE, _P.DELETE],
+ Scope.GRAFANA: [_P.READ],
+ })
+
+GANESHA_MGR_ROLE = Role(
+ 'ganesha-manager', 'allows full permissions for the nfs-ganesha scope', {
+ Scope.NFS_GANESHA: [_P.READ, _P.CREATE, _P.UPDATE, _P.DELETE],
+ Scope.CEPHFS: [_P.READ, _P.CREATE, _P.UPDATE, _P.DELETE],
+ Scope.RGW: [_P.READ, _P.CREATE, _P.UPDATE, _P.DELETE],
+ Scope.GRAFANA: [_P.READ],
+ })
+
+
+SYSTEM_ROLES = {
+ ADMIN_ROLE.name: ADMIN_ROLE,
+ READ_ONLY_ROLE.name: READ_ONLY_ROLE,
+ BLOCK_MGR_ROLE.name: BLOCK_MGR_ROLE,
+ RGW_MGR_ROLE.name: RGW_MGR_ROLE,
+ CLUSTER_MGR_ROLE.name: CLUSTER_MGR_ROLE,
+ POOL_MGR_ROLE.name: POOL_MGR_ROLE,
+ CEPHFS_MGR_ROLE.name: CEPHFS_MGR_ROLE,
+ GANESHA_MGR_ROLE.name: GANESHA_MGR_ROLE,
+}
+
+
+class User(object):
+ def __init__(self, username, password, name=None, email=None, roles=None,
+ last_update=None, enabled=True, pwd_expiration_date=None,
+ pwd_update_required=False):
+ self.username = username
+ self.password = password
+ self.name = name
+ self.email = email
+ self.invalid_auth_attempt = 0
+ if roles is None:
+ self.roles = set()
+ else:
+ self.roles = roles
+ if last_update is None:
+ self.refresh_last_update()
+ else:
+ self.last_update = last_update
+ self._enabled = enabled
+ self.pwd_expiration_date = pwd_expiration_date
+ if self.pwd_expiration_date is None:
+ self.refresh_pwd_expiration_date()
+ self.pwd_update_required = pwd_update_required
+
+ def refresh_last_update(self):
+ self.last_update = int(time.time())
+
+ def refresh_pwd_expiration_date(self):
+ if Settings.USER_PWD_EXPIRATION_SPAN > 0:
+ expiration_date = datetime.utcnow() + timedelta(
+ days=Settings.USER_PWD_EXPIRATION_SPAN)
+ self.pwd_expiration_date = int(time.mktime(expiration_date.timetuple()))
+ else:
+ self.pwd_expiration_date = None
+
+ @property
+ def enabled(self):
+ return self._enabled
+
+ @enabled.setter
+ def enabled(self, value):
+ self._enabled = value
+ self.refresh_last_update()
+
+ def set_password(self, password):
+ self.set_password_hash(password_hash(password))
+
+ def set_password_hash(self, hashed_password):
+ self.invalid_auth_attempt = 0
+ self.password = hashed_password
+ self.refresh_last_update()
+ self.refresh_pwd_expiration_date()
+ self.pwd_update_required = False
+
+ def compare_password(self, password):
+ """
+ Compare the specified password with the user password.
+ :param password: The plain password to check.
+ :type password: str
+ :return: `True` if the passwords are equal, otherwise `False`.
+ :rtype: bool
+ """
+ pass_hash = password_hash(password, salt_password=self.password)
+ return pass_hash == self.password
+
+ def is_pwd_expired(self):
+ if self.pwd_expiration_date:
+ current_time = int(time.mktime(datetime.utcnow().timetuple()))
+ return self.pwd_expiration_date < current_time
+ return False
+
+ def set_roles(self, roles):
+ self.roles = set(roles)
+ self.refresh_last_update()
+
+ def add_roles(self, roles):
+ self.roles = self.roles.union(set(roles))
+ self.refresh_last_update()
+
+ def del_roles(self, roles):
+ for role in roles:
+ if role not in self.roles:
+ raise RoleNotInUser(role.name, self.username)
+ self.roles.difference_update(set(roles))
+ self.refresh_last_update()
+
+ def authorize(self, scope, permissions):
+ if self.pwd_update_required:
+ return False
+
+ for role in self.roles:
+ if role.authorize(scope, permissions):
+ return True
+ return False
+
+ def permissions_dict(self):
+ # type: () -> dict
+ perms = {} # type: dict
+ for role in self.roles:
+ for scope, perms_list in role.scopes_permissions.items():
+ if scope in perms:
+ perms_tmp = set(perms[scope]).union(set(perms_list))
+ perms[scope] = list(perms_tmp)
+ else:
+ perms[scope] = perms_list
+
+ return perms
+
+ def to_dict(self):
+ return {
+ 'username': self.username,
+ 'password': self.password,
+ 'roles': sorted([r.name for r in self.roles]),
+ 'name': self.name,
+ 'email': self.email,
+ 'lastUpdate': self.last_update,
+ 'enabled': self.enabled,
+ 'pwdExpirationDate': self.pwd_expiration_date,
+ 'pwdUpdateRequired': self.pwd_update_required
+ }
+
+ @classmethod
+ def from_dict(cls, u_dict, roles):
+ return User(u_dict['username'], u_dict['password'], u_dict['name'],
+ u_dict['email'], {roles[r] for r in u_dict['roles']},
+ u_dict['lastUpdate'], u_dict['enabled'],
+ u_dict['pwdExpirationDate'], u_dict['pwdUpdateRequired'])
+
+
+class AccessControlDB(object):
+ VERSION = 2
+ ACDB_CONFIG_KEY = "accessdb_v"
+
+ def __init__(self, version, users, roles):
+ self.users = users
+ self.version = version
+ self.roles = roles
+ self.lock = threading.RLock()
+
+ def create_role(self, name, description=None):
+ with self.lock:
+ if name in SYSTEM_ROLES or name in self.roles:
+ raise RoleAlreadyExists(name)
+ role = Role(name, description)
+ self.roles[name] = role
+ return role
+
+ def get_role(self, name):
+ with self.lock:
+ if name not in self.roles:
+ raise RoleDoesNotExist(name)
+ return self.roles[name]
+
+ def increment_attempt(self, username):
+ with self.lock:
+ if username in self.users:
+ self.users[username].invalid_auth_attempt += 1
+
+ def reset_attempt(self, username):
+ with self.lock:
+ if username in self.users:
+ self.users[username].invalid_auth_attempt = 0
+
+ def get_attempt(self, username):
+ with self.lock:
+ try:
+ return self.users[username].invalid_auth_attempt
+ except KeyError:
+ return 0
+
+ def delete_role(self, name):
+ with self.lock:
+ if name not in self.roles:
+ raise RoleDoesNotExist(name)
+ role = self.roles[name]
+
+ # check if role is not associated with a user
+ for username, user in self.users.items():
+ if role in user.roles:
+ raise RoleIsAssociatedWithUser(name, username)
+
+ del self.roles[name]
+
+ def create_user(self, username, password, name, email, enabled=True,
+ pwd_expiration_date=None, pwd_update_required=False):
+ logger.debug("creating user: username=%s", username)
+ with self.lock:
+ if username in self.users:
+ raise UserAlreadyExists(username)
+ if pwd_expiration_date and \
+ (pwd_expiration_date < int(time.mktime(datetime.utcnow().timetuple()))):
+ raise PwdExpirationDateNotValid()
+ user = User(username, password_hash(password), name, email, enabled=enabled,
+ pwd_expiration_date=pwd_expiration_date,
+ pwd_update_required=pwd_update_required)
+ self.users[username] = user
+ return user
+
+ def get_user(self, username):
+ with self.lock:
+ if username not in self.users:
+ raise UserDoesNotExist(username)
+ return self.users[username]
+
+ def delete_user(self, username):
+ with self.lock:
+ if username not in self.users:
+ raise UserDoesNotExist(username)
+ del self.users[username]
+
+ def update_users_with_roles(self, role):
+ with self.lock:
+ if not role:
+ return
+ for _, user in self.users.items():
+ if role in user.roles:
+ user.refresh_last_update()
+
+ def save(self):
+ with self.lock:
+ db = {
+ 'users': {un: u.to_dict() for un, u in self.users.items()},
+ 'roles': {rn: r.to_dict() for rn, r in self.roles.items()},
+ 'version': self.version
+ }
+ mgr.set_store(self.accessdb_config_key(), json.dumps(db))
+
+ @classmethod
+ def accessdb_config_key(cls, version=None):
+ if version is None:
+ version = cls.VERSION
+ return "{}{}".format(cls.ACDB_CONFIG_KEY, version)
+
+ def check_and_update_db(self):
+ logger.debug("Checking for previous DB versions")
+
+ def check_migrate_v1_to_current():
+ # Check if version 1 exists in the DB and migrate it to current version
+ v1_db = mgr.get_store(self.accessdb_config_key(1))
+ if v1_db:
+ logger.debug("Found database v1 credentials")
+ v1_db = json.loads(v1_db)
+
+ for user, _ in v1_db['users'].items():
+ v1_db['users'][user]['enabled'] = True
+ v1_db['users'][user]['pwdExpirationDate'] = None
+ v1_db['users'][user]['pwdUpdateRequired'] = False
+
+ self.roles = {rn: Role.from_dict(r) for rn, r in v1_db.get('roles', {}).items()}
+ self.users = {un: User.from_dict(u, dict(self.roles, **SYSTEM_ROLES))
+ for un, u in v1_db.get('users', {}).items()}
+
+ self.save()
+
+ check_migrate_v1_to_current()
+
+ @classmethod
+ def load(cls):
+ logger.info("Loading user roles DB version=%s", cls.VERSION)
+
+ json_db = mgr.get_store(cls.accessdb_config_key())
+ if json_db is None:
+ logger.debug("No DB v%s found, creating new...", cls.VERSION)
+ db = cls(cls.VERSION, {}, {})
+ # check if we can update from a previous version database
+ db.check_and_update_db()
+ return db
+
+ dict_db = json.loads(json_db)
+ roles = {rn: Role.from_dict(r)
+ for rn, r in dict_db.get('roles', {}).items()}
+ users = {un: User.from_dict(u, dict(roles, **SYSTEM_ROLES))
+ for un, u in dict_db.get('users', {}).items()}
+ return cls(dict_db['version'], users, roles)
+
+
+def load_access_control_db():
+ mgr.ACCESS_CTRL_DB = AccessControlDB.load() # type: ignore
+
+
+# CLI dashboard access control scope commands
+
+@CLIWriteCommand('dashboard set-login-credentials')
+@CLICheckNonemptyFileInput(desc=DEFAULT_FILE_DESC)
+def set_login_credentials_cmd(_, username: str, inbuf: str):
+ '''
+ Set the login credentials. Password read from -i <file>
+ '''
+ password = inbuf
+ try:
+ user = mgr.ACCESS_CTRL_DB.get_user(username)
+ user.set_password(password)
+ except UserDoesNotExist:
+ user = mgr.ACCESS_CTRL_DB.create_user(username, password, None, None)
+ user.set_roles([ADMIN_ROLE])
+
+ mgr.ACCESS_CTRL_DB.save()
+
+ return 0, '''\
+******************************************************************
+*** WARNING: this command is deprecated. ***
+*** Please use the ac-user-* related commands to manage users. ***
+******************************************************************
+Username and password updated''', ''
+
+
+@CLIReadCommand('dashboard ac-role-show')
+def ac_role_show_cmd(_, rolename: Optional[str] = None):
+ '''
+ Show role info
+ '''
+ if not rolename:
+ roles = dict(mgr.ACCESS_CTRL_DB.roles)
+ roles.update(SYSTEM_ROLES)
+ roles_list = [name for name, _ in roles.items()]
+ return 0, json.dumps(roles_list), ''
+ try:
+ role = mgr.ACCESS_CTRL_DB.get_role(rolename)
+ except RoleDoesNotExist as ex:
+ if rolename not in SYSTEM_ROLES:
+ return -errno.ENOENT, '', str(ex)
+ role = SYSTEM_ROLES[rolename]
+ return 0, json.dumps(role.to_dict()), ''
+
+
+@CLIWriteCommand('dashboard ac-role-create')
+def ac_role_create_cmd(_, rolename: str, description: Optional[str] = None):
+ '''
+ Create a new access control role
+ '''
+ try:
+ role = mgr.ACCESS_CTRL_DB.create_role(rolename, description)
+ mgr.ACCESS_CTRL_DB.save()
+ return 0, json.dumps(role.to_dict()), ''
+ except RoleAlreadyExists as ex:
+ return -errno.EEXIST, '', str(ex)
+
+
+@CLIWriteCommand('dashboard ac-role-delete')
+def ac_role_delete_cmd(_, rolename: str):
+ '''
+ Delete an access control role
+ '''
+ try:
+ mgr.ACCESS_CTRL_DB.delete_role(rolename)
+ mgr.ACCESS_CTRL_DB.save()
+ return 0, "Role '{}' deleted".format(rolename), ""
+ except RoleDoesNotExist as ex:
+ if rolename in SYSTEM_ROLES:
+ return -errno.EPERM, '', "Cannot delete system role '{}'" \
+ .format(rolename)
+ return -errno.ENOENT, '', str(ex)
+ except RoleIsAssociatedWithUser as ex:
+ return -errno.EPERM, '', str(ex)
+
+
+@CLIWriteCommand('dashboard ac-role-add-scope-perms')
+def ac_role_add_scope_perms_cmd(_,
+ rolename: str,
+ scopename: str,
+ permissions: Sequence[str]):
+ '''
+ Add the scope permissions for a role
+ '''
+ try:
+ role = mgr.ACCESS_CTRL_DB.get_role(rolename)
+ perms_array = [perm.strip() for perm in permissions]
+ role.set_scope_permissions(scopename, perms_array)
+ mgr.ACCESS_CTRL_DB.update_users_with_roles(role)
+ mgr.ACCESS_CTRL_DB.save()
+ return 0, json.dumps(role.to_dict()), ''
+ except RoleDoesNotExist as ex:
+ if rolename in SYSTEM_ROLES:
+ return -errno.EPERM, '', "Cannot update system role '{}'" \
+ .format(rolename)
+ return -errno.ENOENT, '', str(ex)
+ except ScopeNotValid as ex:
+ return -errno.EINVAL, '', str(ex) + "\n Possible values: {}" \
+ .format(Scope.all_scopes())
+ except PermissionNotValid as ex:
+ return -errno.EINVAL, '', str(ex) + \
+ "\n Possible values: {}" \
+ .format(Permission.all_permissions())
+
+
+@CLIWriteCommand('dashboard ac-role-del-scope-perms')
+def ac_role_del_scope_perms_cmd(_, rolename: str, scopename: str):
+ '''
+ Delete the scope permissions for a role
+ '''
+ try:
+ role = mgr.ACCESS_CTRL_DB.get_role(rolename)
+ role.del_scope_permissions(scopename)
+ mgr.ACCESS_CTRL_DB.update_users_with_roles(role)
+ mgr.ACCESS_CTRL_DB.save()
+ return 0, json.dumps(role.to_dict()), ''
+ except RoleDoesNotExist as ex:
+ if rolename in SYSTEM_ROLES:
+ return -errno.EPERM, '', "Cannot update system role '{}'" \
+ .format(rolename)
+ return -errno.ENOENT, '', str(ex)
+ except ScopeNotInRole as ex:
+ return -errno.ENOENT, '', str(ex)
+
+
+@CLIReadCommand('dashboard ac-user-show')
+def ac_user_show_cmd(_, username: Optional[str] = None):
+ '''
+ Show user info
+ '''
+ if not username:
+ users = mgr.ACCESS_CTRL_DB.users
+ users_list = [name for name, _ in users.items()]
+ return 0, json.dumps(users_list), ''
+ try:
+ user = mgr.ACCESS_CTRL_DB.get_user(username)
+ return 0, json.dumps(user.to_dict()), ''
+ except UserDoesNotExist as ex:
+ return -errno.ENOENT, '', str(ex)
+
+
+@CLIWriteCommand('dashboard ac-user-create')
+@CLICheckNonemptyFileInput(desc=DEFAULT_FILE_DESC)
+def ac_user_create_cmd(_, username: str, inbuf: str,
+ rolename: Optional[str] = None,
+ name: Optional[str] = None,
+ email: Optional[str] = None,
+ enabled: bool = True,
+ force_password: bool = False,
+ pwd_expiration_date: Optional[int] = None,
+ pwd_update_required: bool = False):
+ '''
+ Create a user. Password read from -i <file>
+ '''
+ password = inbuf
+ try:
+ role = mgr.ACCESS_CTRL_DB.get_role(rolename) if rolename else None
+ except RoleDoesNotExist as ex:
+ if rolename not in SYSTEM_ROLES:
+ return -errno.ENOENT, '', str(ex)
+ role = SYSTEM_ROLES[rolename]
+
+ try:
+ if not force_password:
+ pw_check = PasswordPolicy(password, username)
+ pw_check.check_all()
+ user = mgr.ACCESS_CTRL_DB.create_user(username, password, name, email,
+ enabled, pwd_expiration_date,
+ pwd_update_required)
+ except PasswordPolicyException as ex:
+ return -errno.EINVAL, '', str(ex)
+ except UserAlreadyExists as ex:
+ return 0, str(ex), ''
+
+ if role:
+ user.set_roles([role])
+ mgr.ACCESS_CTRL_DB.save()
+ return 0, json.dumps(user.to_dict()), ''
+
+
+@CLIWriteCommand('dashboard ac-user-enable')
+def ac_user_enable(_, username: str):
+ '''
+ Enable a user
+ '''
+ try:
+ user = mgr.ACCESS_CTRL_DB.get_user(username)
+ user.enabled = True
+ mgr.ACCESS_CTRL_DB.reset_attempt(username)
+
+ mgr.ACCESS_CTRL_DB.save()
+ return 0, json.dumps(user.to_dict()), ''
+ except UserDoesNotExist as ex:
+ return -errno.ENOENT, '', str(ex)
+
+
+@CLIWriteCommand('dashboard ac-user-disable')
+def ac_user_disable(_, username: str):
+ '''
+ Disable a user
+ '''
+ try:
+ user = mgr.ACCESS_CTRL_DB.get_user(username)
+ user.enabled = False
+
+ mgr.ACCESS_CTRL_DB.save()
+ return 0, json.dumps(user.to_dict()), ''
+ except UserDoesNotExist as ex:
+ return -errno.ENOENT, '', str(ex)
+
+
+@CLIWriteCommand('dashboard ac-user-delete')
+def ac_user_delete_cmd(_, username: str):
+ '''
+ Delete user
+ '''
+ try:
+ mgr.ACCESS_CTRL_DB.delete_user(username)
+ mgr.ACCESS_CTRL_DB.save()
+ return 0, "User '{}' deleted".format(username), ""
+ except UserDoesNotExist as ex:
+ return -errno.ENOENT, '', str(ex)
+
+
+@CLIWriteCommand('dashboard ac-user-set-roles')
+def ac_user_set_roles_cmd(_, username: str, roles: Sequence[str]):
+ '''
+ Set user roles
+ '''
+ rolesname = roles
+ roles: List[Role] = []
+ for rolename in rolesname:
+ try:
+ roles.append(mgr.ACCESS_CTRL_DB.get_role(rolename))
+ except RoleDoesNotExist as ex:
+ if rolename not in SYSTEM_ROLES:
+ return -errno.ENOENT, '', str(ex)
+ roles.append(SYSTEM_ROLES[rolename])
+ try:
+ user = mgr.ACCESS_CTRL_DB.get_user(username)
+ user.set_roles(roles)
+ mgr.ACCESS_CTRL_DB.save()
+ return 0, json.dumps(user.to_dict()), ''
+ except UserDoesNotExist as ex:
+ return -errno.ENOENT, '', str(ex)
+
+
+@CLIWriteCommand('dashboard ac-user-add-roles')
+def ac_user_add_roles_cmd(_, username: str, roles: Sequence[str]):
+ '''
+ Add roles to user
+ '''
+ rolesname = roles
+ roles: List[Role] = []
+ for rolename in rolesname:
+ try:
+ roles.append(mgr.ACCESS_CTRL_DB.get_role(rolename))
+ except RoleDoesNotExist as ex:
+ if rolename not in SYSTEM_ROLES:
+ return -errno.ENOENT, '', str(ex)
+ roles.append(SYSTEM_ROLES[rolename])
+ try:
+ user = mgr.ACCESS_CTRL_DB.get_user(username)
+ user.add_roles(roles)
+ mgr.ACCESS_CTRL_DB.save()
+ return 0, json.dumps(user.to_dict()), ''
+ except UserDoesNotExist as ex:
+ return -errno.ENOENT, '', str(ex)
+
+
+@CLIWriteCommand('dashboard ac-user-del-roles')
+def ac_user_del_roles_cmd(_, username: str, roles: Sequence[str]):
+ '''
+ Delete roles from user
+ '''
+ rolesname = roles
+ roles: List[Role] = []
+ for rolename in rolesname:
+ try:
+ roles.append(mgr.ACCESS_CTRL_DB.get_role(rolename))
+ except RoleDoesNotExist as ex:
+ if rolename not in SYSTEM_ROLES:
+ return -errno.ENOENT, '', str(ex)
+ roles.append(SYSTEM_ROLES[rolename])
+ try:
+ user = mgr.ACCESS_CTRL_DB.get_user(username)
+ user.del_roles(roles)
+ mgr.ACCESS_CTRL_DB.save()
+ return 0, json.dumps(user.to_dict()), ''
+ except UserDoesNotExist as ex:
+ return -errno.ENOENT, '', str(ex)
+ except RoleNotInUser as ex:
+ return -errno.ENOENT, '', str(ex)
+
+
+@CLIWriteCommand('dashboard ac-user-set-password')
+@CLICheckNonemptyFileInput(desc=DEFAULT_FILE_DESC)
+def ac_user_set_password(_, username: str, inbuf: str,
+ force_password: bool = False):
+ '''
+ Set user password from -i <file>
+ '''
+ password = inbuf
+ try:
+ user = mgr.ACCESS_CTRL_DB.get_user(username)
+ if not force_password:
+ pw_check = PasswordPolicy(password, user.name)
+ pw_check.check_all()
+ user.set_password(password)
+ mgr.ACCESS_CTRL_DB.save()
+ return 0, json.dumps(user.to_dict()), ''
+ except PasswordPolicyException as ex:
+ return -errno.EINVAL, '', str(ex)
+ except UserDoesNotExist as ex:
+ return -errno.ENOENT, '', str(ex)
+
+
+@CLIWriteCommand('dashboard ac-user-set-password-hash')
+@CLICheckNonemptyFileInput(desc=DEFAULT_FILE_DESC)
+def ac_user_set_password_hash(_, username: str, inbuf: str):
+ '''
+ Set user password bcrypt hash from -i <file>
+ '''
+ hashed_password = inbuf
+ try:
+ # make sure the hashed_password is actually a bcrypt hash
+ bcrypt.checkpw(b'', hashed_password.encode('utf-8'))
+ user = mgr.ACCESS_CTRL_DB.get_user(username)
+ user.set_password_hash(hashed_password)
+
+ mgr.ACCESS_CTRL_DB.save()
+ return 0, json.dumps(user.to_dict()), ''
+ except ValueError:
+ return -errno.EINVAL, '', 'Invalid password hash'
+ except UserDoesNotExist as ex:
+ return -errno.ENOENT, '', str(ex)
+
+
+@CLIWriteCommand('dashboard ac-user-set-info')
+def ac_user_set_info(_, username: str, name: str, email: str):
+ '''
+ Set user info
+ '''
+ try:
+ user = mgr.ACCESS_CTRL_DB.get_user(username)
+ if name:
+ user.name = name
+ if email:
+ user.email = email
+ mgr.ACCESS_CTRL_DB.save()
+ return 0, json.dumps(user.to_dict()), ''
+ except UserDoesNotExist as ex:
+ return -errno.ENOENT, '', str(ex)
+
+
+class LocalAuthenticator(object):
+ def __init__(self):
+ load_access_control_db()
+
+ def get_user(self, username):
+ return mgr.ACCESS_CTRL_DB.get_user(username)
+
+ def authenticate(self, username, password):
+ try:
+ user = mgr.ACCESS_CTRL_DB.get_user(username)
+ if user.password:
+ if user.enabled and user.compare_password(password) \
+ and not user.is_pwd_expired():
+ return {'permissions': user.permissions_dict(),
+ 'pwdExpirationDate': user.pwd_expiration_date,
+ 'pwdUpdateRequired': user.pwd_update_required}
+ except UserDoesNotExist:
+ logger.debug("User '%s' does not exist", username)
+ return None
+
+ def authorize(self, username, scope, permissions):
+ user = mgr.ACCESS_CTRL_DB.get_user(username)
+ return user.authorize(scope, permissions)