]> git.apps.os.sepia.ceph.com Git - ceph-ci.git/commitdiff
mgr/dashboard: local role-based authorization system implementation
authorRicardo Dias <rdias@suse.com>
Fri, 20 Apr 2018 15:36:28 +0000 (16:36 +0100)
committerRicardo Dias <rdias@suse.com>
Tue, 26 Jun 2018 11:28:53 +0000 (12:28 +0100)
Signed-off-by: Ricardo Dias <rdias@suse.com>
src/pybind/mgr/dashboard/exceptions.py
src/pybind/mgr/dashboard/services/access_control.py [new file with mode: 0644]

index c24eab497e896828d65d3c941787dc0a50070a1a..48452e3098f5d116f7e726e32a427339f5d08b16 100644 (file)
@@ -43,3 +43,61 @@ class DashboardException(Exception):
         if self._code:
             return str(self._code)
         return str(abs(self.errno))
+
+
+# access control module exceptions
+class RoleAlreadyExists(Exception):
+    def __init__(self, name):
+        super(RoleAlreadyExists, self).__init__(
+            "Role '{}' already exists".format(name))
+
+
+class RoleDoesNotExist(Exception):
+    def __init__(self, name):
+        super(RoleDoesNotExist, self).__init__(
+            "Role '{}' does not exist".format(name))
+
+
+class ScopeNotValid(Exception):
+    def __init__(self, name):
+        super(ScopeNotValid, self).__init__(
+            "Scope '{}' is not valid".format(name))
+
+
+class PermissionNotValid(Exception):
+    def __init__(self, name):
+        super(PermissionNotValid, self).__init__(
+            "Permission '{}' is not valid".format(name))
+
+
+class RoleIsAssociatedWithUser(Exception):
+    def __init__(self, rolename, username):
+        super(RoleIsAssociatedWithUser, self).__init__(
+            "Role '{}' is still associated with user '{}'"
+            .format(rolename, username))
+
+
+class UserAlreadyExists(Exception):
+    def __init__(self, name):
+        super(UserAlreadyExists, self).__init__(
+            "User '{}' already exists".format(name))
+
+
+class UserDoesNotExist(Exception):
+    def __init__(self, name):
+        super(UserDoesNotExist, self).__init__(
+            "User '{}' does not exist".format(name))
+
+
+class ScopeNotInRole(Exception):
+    def __init__(self, scopename, rolename):
+        super(ScopeNotInRole, self).__init__(
+            "There are no permissions for scope '{}' in role '{}'"
+            .format(scopename, rolename))
+
+
+class RoleNotInUser(Exception):
+    def __init__(self, rolename, username):
+        super(RoleNotInUser, self).__init__(
+            "Role '{}' is not associated with user '{}'"
+            .format(rolename, username))
diff --git a/src/pybind/mgr/dashboard/services/access_control.py b/src/pybind/mgr/dashboard/services/access_control.py
new file mode 100644 (file)
index 0000000..a8784b0
--- /dev/null
@@ -0,0 +1,664 @@
+# -*- coding: utf-8 -*-
+# pylint: disable=too-many-arguments,too-many-return-statements
+# pylint: disable=too-many-branches, too-many-locals, too-many-statements
+from __future__ import absolute_import
+
+import errno
+import json
+import threading
+
+import bcrypt
+
+from .. import mgr, logger
+from ..security import Scope, Permission
+from ..exceptions import RoleAlreadyExists, RoleDoesNotExist, ScopeNotValid, \
+                         PermissionNotValid, RoleIsAssociatedWithUser, \
+                         UserAlreadyExists, UserDoesNotExist, ScopeNotInRole, \
+                         RoleNotInUser
+
+
+# password hashing algorithm
+def password_hash(password, salt_password=None):
+    if not salt_password:
+        salt_password = bcrypt.gensalt()
+    else:
+        salt_password = salt_password.encode('utf8')
+    return bcrypt.hashpw(password.encode('utf8'), salt_password).decode('utf8')
+
+
+_P = Permission  # short alias
+
+
+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 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', 'Administrator', dict([
+    (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', 'Read-Only', dict([
+    (scope_name, [_P.READ]) for scope_name in Scope.all_scopes()
+]))
+
+
+# block manager role provides all permission for block related scopes
+BLOCK_MGR_ROLE = Role('block-manager', 'Block Manager', {
+    Scope.RBD_IMAGE: [_P.READ, _P.CREATE, _P.UPDATE, _P.DELETE],
+    Scope.ISCSI: [_P.READ, _P.CREATE, _P.UPDATE, _P.DELETE],
+    Scope.RBD_MIRRORING: [_P.READ, _P.CREATE, _P.UPDATE, _P.DELETE],
+})
+
+
+# RadosGW manager role provides all permissions for block related scopes
+RGW_MGR_ROLE = Role('rgw-manager', 'RGW Manager', {
+    Scope.RGW: [_P.READ, _P.CREATE, _P.UPDATE, _P.DELETE],
+})
+
+
+# Cluster manager role provides all permission for OSDs, Monitors, and
+# Config options
+CLUSTER_MGR_ROLE = Role('cluster-manager', 'Cluster Manager', {
+    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],
+})
+
+
+# Pool manager role provides all permissions for pool related scopes
+POOL_MGR_ROLE = Role('pool-manager', 'Pool Manager', {
+    Scope.POOL: [_P.READ, _P.CREATE, _P.UPDATE, _P.DELETE],
+})
+
+# Pool manager role provides all permissions for CephFS related scopes
+CEPHFS_MGR_ROLE = Role('cephfs-manager', 'CephFS Manager', {
+    Scope.CEPHFS: [_P.READ, _P.CREATE, _P.UPDATE, _P.DELETE],
+})
+
+
+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,
+}
+
+
+class User(object):
+    def __init__(self, username, password, name=None, email=None, roles=None):
+        self.username = username
+        self.password = password
+        self.name = name
+        self.email = email
+        if roles is None:
+            self.roles = set()
+        else:
+            self.roles = roles
+
+    def set_password(self, password):
+        self.password = password_hash(password)
+
+    def set_roles(self, roles):
+        self.roles = set(roles)
+
+    def add_roles(self, roles):
+        self.roles = self.roles.union(set(roles))
+
+    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))
+
+    def authorize(self, scope, permissions):
+        for role in self.roles:
+            if role.authorize(scope, permissions):
+                return True
+        return False
+
+    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
+        }
+
+    @classmethod
+    def from_dict(cls, u_dict, roles):
+        return User(u_dict['username'], u_dict['password'], u_dict['name'],
+                    u_dict['email'], set([roles[r] for r in u_dict['roles']]))
+
+
+class AccessControlDB(object):
+    VERSION = 1
+    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 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):
+        logger.debug("AC: creating user: username=%s", username)
+        with self.lock:
+            if username in self.users:
+                raise UserAlreadyExists(username)
+            user = User(username, password_hash(password), name, email)
+            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 save(self):
+        with self.lock:
+            db = {
+                'users': dict([(un, u.to_dict()) for un, u in self.users.items()]),
+                'roles': dict([(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("AC: Checking for previews DB versions")
+        if self.VERSION == 1:  # current version
+            # check if there is username/password from previous version
+            username = mgr.get_config('username', None)
+            password = mgr.get_config('password', None)
+            if username and password:
+                logger.debug("AC: Found single user credentials: user=%s",
+                             username)
+                # found user credentials
+                user = self.create_user(username, "", None, None)
+                # password is already hashed, so setting manually
+                user.password = password
+                user.add_roles([ADMIN_ROLE])
+                self.save()
+        else:
+            raise NotImplementedError()
+
+    @classmethod
+    def load(cls):
+        logger.info("AC: Loading user roles DB version=%s", cls.VERSION)
+
+        json_db = mgr.get_store(cls.accessdb_config_key(), None)
+        if json_db is None:
+            logger.debug("AC: 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
+
+        db = json.loads(json_db)
+        roles = dict([(rn, Role.from_dict(r))
+                      for rn, r in db.get('roles', {}).items()])
+        users = dict([(un, User.from_dict(u, dict(roles, **SYSTEM_ROLES)))
+                      for un, u in db.get('users', {}).items()])
+        return cls(db['version'], users, roles)
+
+
+ACCESS_CTRL_DB = None
+
+
+def load_access_control_db():
+    # pylint: disable=W0603
+    global ACCESS_CTRL_DB
+    ACCESS_CTRL_DB = AccessControlDB.load()
+
+
+# CLI dashboard access controll scope commands
+ACCESS_CONTROL_COMMANDS = [
+    # for backwards compatibility
+    {
+        'cmd': 'dashboard set-login-credentials '
+               'name=username,type=CephString '
+               'name=password,type=CephString',
+        'desc': 'Set the login credentials',
+        'perm': 'w'
+    },
+    {
+        'cmd': 'dashboard ac-role-show '
+               'name=rolename,type=CephString,req=false',
+        'desc': 'Show role info',
+        'perm': 'r'
+    },
+    {
+        'cmd': 'dashboard ac-role-create '
+               'name=rolename,type=CephString '
+               'name=description,type=CephString,req=false',
+        'desc': 'Create a new access control role',
+        'perm': 'w'
+    },
+    {
+        'cmd': 'dashboard ac-role-delete '
+               'name=rolename,type=CephString',
+        'desc': 'Delete an access control role',
+        'perm': 'w'
+    },
+    {
+        'cmd': 'dashboard ac-role-add-scope-perms '
+               'name=rolename,type=CephString '
+               'name=scopename,type=CephString '
+               'name=permissions,type=CephString,n=N',
+        'desc': 'Add the scope permissions for a role',
+        'perm': 'w'
+    },
+    {
+        'cmd': 'dashboard ac-role-del-scope-perms '
+               'name=rolename,type=CephString '
+               'name=scopename,type=CephString',
+        'desc': 'Delete the scope permissions for a role',
+        'perm': 'w'
+    },
+    {
+        'cmd': 'dashboard ac-user-show '
+               'name=username,type=CephString,req=false',
+        'desc': 'Show user info',
+        'perm': 'r'
+    },
+    {
+        'cmd': 'dashboard ac-user-create '
+               'name=username,type=CephString '
+               'name=password,type=CephString '
+               'name=rolename,type=CephString,req=false '
+               'name=name,type=CephString,req=false '
+               'name=email,type=CephString,req=false',
+        'desc': 'Create a user',
+        'perm': 'w'
+    },
+    {
+        'cmd': 'dashboard ac-user-delete '
+               'name=username,type=CephString',
+        'desc': 'Delete user',
+        'perm': 'w'
+    },
+    {
+        'cmd': 'dashboard ac-user-set-roles '
+               'name=username,type=CephString '
+               'name=roles,type=CephString,n=N',
+        'desc': 'Set user roles',
+        'perm': 'w'
+    },
+    {
+        'cmd': 'dashboard ac-user-add-roles '
+               'name=username,type=CephString '
+               'name=roles,type=CephString,n=N',
+        'desc': 'Add roles to user',
+        'perm': 'w'
+    },
+    {
+        'cmd': 'dashboard ac-user-del-roles '
+               'name=username,type=CephString '
+               'name=roles,type=CephString,n=N',
+        'desc': 'Delete roles from user',
+        'perm': 'w'
+    },
+    {
+        'cmd': 'dashboard ac-user-set-password '
+               'name=username,type=CephString '
+               'name=password,type=CephString',
+        'desc': 'Set user password',
+        'perm': 'w'
+    },
+    {
+        'cmd': 'dashboard ac-user-set-info '
+               'name=username,type=CephString '
+               'name=name,type=CephString '
+               'name=email,type=CephString',
+        'desc': 'Set user info',
+        'perm': 'w'
+    }
+]
+
+
+def handle_access_control_command(cmd):
+    if cmd['prefix'] == 'dashboard set-login-credentials':
+        username = cmd['username']
+        password = cmd['password']
+        try:
+            user = ACCESS_CTRL_DB.get_user(username)
+            user.set_password(password)
+        except UserDoesNotExist:
+            user = ACCESS_CTRL_DB.create_user(username, password, None, None)
+            user.set_roles([ADMIN_ROLE])
+
+        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''', ''
+
+    if cmd['prefix'] == 'dashboard ac-role-show':
+        rolename = cmd['rolename'] if 'rolename' in cmd else None
+        if not rolename:
+            roles = dict(ACCESS_CTRL_DB.roles)
+            roles.update(SYSTEM_ROLES)
+            roles_list = [name for name, _ in roles.items()]
+            return 0, json.dumps(roles_list), ''
+        try:
+            role = 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()), ''
+
+    elif cmd['prefix'] == 'dashboard ac-role-create':
+        rolename = cmd['rolename']
+        description = cmd['description'] if 'description' in cmd else None
+        try:
+            role = ACCESS_CTRL_DB.create_role(rolename, description)
+            ACCESS_CTRL_DB.save()
+            return 0, json.dumps(role.to_dict()), ''
+        except RoleAlreadyExists as ex:
+            return -errno.EEXIST, '', str(ex)
+
+    elif cmd['prefix'] == 'dashboard ac-role-delete':
+        rolename = cmd['rolename']
+        try:
+            ACCESS_CTRL_DB.delete_role(rolename)
+            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)
+
+    elif cmd['prefix'] == 'dashboard ac-role-add-scope-perms':
+        rolename = cmd['rolename']
+        scopename = cmd['scopename']
+        permissions = cmd['permissions']
+        try:
+            role = ACCESS_CTRL_DB.get_role(rolename)
+            perms_array = [perm.strip() for perm in permissions]
+            role.set_scope_permissions(scopename, perms_array)
+            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())
+
+    elif cmd['prefix'] == 'dashboard ac-role-del-scope-perms':
+        rolename = cmd['rolename']
+        scopename = cmd['scopename']
+        try:
+            role = ACCESS_CTRL_DB.get_role(rolename)
+            role.del_scope_permissions(scopename)
+            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)
+
+    elif cmd['prefix'] == 'dashboard ac-user-show':
+        username = cmd['username'] if 'username' in cmd else None
+        if not username:
+            users = ACCESS_CTRL_DB.users
+            users_list = [name for name, _ in users.items()]
+            return 0, json.dumps(users_list), ''
+        try:
+            user = ACCESS_CTRL_DB.get_user(username)
+            return 0, json.dumps(user.to_dict()), ''
+        except UserDoesNotExist as ex:
+            return -errno.ENOENT, '', str(ex)
+
+    elif cmd['prefix'] == 'dashboard ac-user-create':
+        username = cmd['username']
+        password = cmd['password']
+        rolename = cmd['rolename'] if 'rolename' in cmd else None
+        name = cmd['name'] if 'name' in cmd else None
+        email = cmd['email'] if 'email' in cmd else None
+        try:
+            user = ACCESS_CTRL_DB.create_user(username, password, name, email)
+            role = ACCESS_CTRL_DB.get_role(rolename) if rolename else None
+        except UserAlreadyExists as ex:
+            return -errno.EEXIST, '', str(ex)
+        except RoleDoesNotExist as ex:
+            if rolename not in SYSTEM_ROLES:
+                return -errno.ENOENT, '', str(ex)
+            role = SYSTEM_ROLES[rolename]
+
+        if role:
+            user.set_roles([role])
+        ACCESS_CTRL_DB.save()
+        return 0, json.dumps(user.to_dict()), ''
+
+    elif cmd['prefix'] == 'dashboard ac-user-delete':
+        username = cmd['username']
+        try:
+            ACCESS_CTRL_DB.delete_user(username)
+            ACCESS_CTRL_DB.save()
+            return 0, "User '{}' deleted".format(username), ""
+        except UserDoesNotExist as ex:
+            return -errno.ENOENT, '', str(ex)
+
+    elif cmd['prefix'] == 'dashboard ac-user-set-roles':
+        username = cmd['username']
+        rolesname = cmd['roles']
+        roles = []
+        for rolename in rolesname:
+            try:
+                roles.append(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 = ACCESS_CTRL_DB.get_user(username)
+            user.set_roles(roles)
+            ACCESS_CTRL_DB.save()
+            return 0, json.dumps(user.to_dict()), ''
+        except UserDoesNotExist as ex:
+            return -errno.ENOENT, '', str(ex)
+
+    elif cmd['prefix'] == 'dashboard ac-user-add-roles':
+        username = cmd['username']
+        rolesname = cmd['roles']
+        roles = []
+        for rolename in rolesname:
+            try:
+                roles.append(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 = ACCESS_CTRL_DB.get_user(username)
+            user.add_roles(roles)
+            ACCESS_CTRL_DB.save()
+            return 0, json.dumps(user.to_dict()), ''
+        except UserDoesNotExist as ex:
+            return -errno.ENOENT, '', str(ex)
+
+    elif cmd['prefix'] == 'dashboard ac-user-del-roles':
+        username = cmd['username']
+        rolesname = cmd['roles']
+        roles = []
+        for rolename in rolesname:
+            try:
+                roles.append(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 = ACCESS_CTRL_DB.get_user(username)
+            user.del_roles(roles)
+            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)
+
+    elif cmd['prefix'] == 'dashboard ac-user-set-password':
+        username = cmd['username']
+        password = cmd['password']
+        try:
+            user = ACCESS_CTRL_DB.get_user(username)
+            user.set_password(password)
+
+            ACCESS_CTRL_DB.save()
+            return 0, json.dumps(user.to_dict()), ''
+        except UserDoesNotExist as ex:
+            return -errno.ENOENT, '', str(ex)
+
+    elif cmd['prefix'] == 'dashboard ac-user-set-info':
+        username = cmd['username']
+        name = cmd['name']
+        email = cmd['email']
+        try:
+            user = ACCESS_CTRL_DB.get_user(username)
+            if name:
+                user.name = name
+            if email:
+                user.email = email
+            ACCESS_CTRL_DB.save()
+            return 0, json.dumps(user.to_dict()), ''
+        except UserDoesNotExist as ex:
+            return -errno.ENOENT, '', str(ex)
+
+    return -errno.ENOSYS, '', ''
+
+
+class LocalAuthenticator(object):
+    def __init__(self):
+        load_access_control_db()
+
+    def authenticate(self, username, password):
+        try:
+            user = ACCESS_CTRL_DB.get_user(username)
+            pass_hash = password_hash(password, user.password)
+            return pass_hash == user.password
+        except UserDoesNotExist:
+            logger.debug("User '%s' does not exist", username)
+            return False
+
+    def authorize(self, username, scope, permissions):
+        user = ACCESS_CTRL_DB.get_user(username)
+        return user.authorize(scope, permissions)