예제 #1
0
    def _normalize_url(self, url, method):
        # DEPRECATED !!!
        # Removing query strings
        normalized_url = url if '?' not in url else url[:url.find("?")]
        normalized_url_splitted = normalized_url.split("/")
        parameters = {}

        filtered_keys = [
            key for key in self.resources_to_operations_mapping.keys()
            if method in key.split()[0]
        ]

        for idx, path_part in enumerate(normalized_url_splitted):
            tmp_keys = []
            for tmp_key in filtered_keys:
                splitted = tmp_key.split()[1].split("/")
                if idx >= len(splitted):
                    continue
                elif "<" in splitted[idx] and ">" in splitted[idx]:
                    if splitted[idx] == "<artifactPath>":
                        tmp_keys.append(tmp_key)
                        continue
                    elif idx == len(normalized_url_splitted) - 1 and \
                            len(normalized_url_splitted) != len(splitted):
                        continue
                    else:
                        tmp_keys.append(tmp_key)
                elif splitted[idx] == path_part:
                    if idx == len(normalized_url_splitted) - 1 and \
                            len(normalized_url_splitted) != len(splitted):
                        continue
                    else:
                        tmp_keys.append(tmp_key)
            filtered_keys = tmp_keys
            if len(filtered_keys) == 1 and \
                    filtered_keys[0].split("/")[-1] == "<artifactPath>":
                break

        if len(filtered_keys) == 0:
            raise AuthException(
                "Cannot make an authorization decision. URL not found. URL: {0}"
                .format(url))
        elif len(filtered_keys) > 1:
            raise AuthException(
                "Cannot make an authorization decision. Multiple URLs found. URL: {0}"
                .format(url))

        filtered_key = filtered_keys[0]

        for idx, path_part in enumerate(filtered_key.split()[1].split("/")):
            if "<" in path_part and ">" in path_part:
                if path_part == "<artifactPath>":
                    parameters[path_part[1:-1]] = "/".join(
                        normalized_url_splitted[idx:])
                else:
                    parameters[path_part[1:-1]] = normalized_url_splitted[idx]

        return filtered_key, parameters
예제 #2
0
    def validate_token(self, token):
        """
        Check if the token is valid.

        :param token: token to validate
        :return: dictionary with information associated with the token:
            "_id": token id
            "project_id": project id
            "project_name": project name
            "user_id": user id
            "username": user name
            "roles": list with dict containing {name, id}
            "expires": expiration date
        If the token is not valid an exception is raised.
        """

        try:
            if not token:
                raise AuthException(
                    "Needed a token or Authorization HTTP header",
                    http_code=HTTPStatus.UNAUTHORIZED)

            # try to get from cache first
            now = time()
            token_info = self.token_cache.get(token)
            if token_info and token_info["expires"] < now:
                # delete token. MUST be done with care, as another thread maybe already delete it. Do not use del
                self.token_cache.pop(token, None)
                token_info = None

            # get from database if not in cache
            if not token_info:
                token_info = self.db.get_one("tokens", {"_id": token})
                if token_info["expires"] < now:
                    raise AuthException(
                        "Expired Token or Authorization HTTP header",
                        http_code=HTTPStatus.UNAUTHORIZED)

            return token_info

        except DbException as e:
            if e.http_code == HTTPStatus.NOT_FOUND:
                raise AuthException(
                    "Invalid Token or Authorization HTTP header",
                    http_code=HTTPStatus.UNAUTHORIZED)
            else:
                raise
        except AuthException:
            raise
        except Exception:
            self.logger.exception(
                "Error during token validation using internal backend")
            raise AuthException(
                "Error during token validation using internal backend",
                http_code=HTTPStatus.UNAUTHORIZED)
예제 #3
0
 def _internal_get_token(self, token_info, token_id):
     token_value = self.db.get_one("tokens", {"_id": token_id},
                                   fail_on_empty=False)
     if not token_value:
         raise AuthException("token not found",
                             http_code=HTTPStatus.NOT_FOUND)
     if token_value["username"] != token_info[
             "username"] and not token_info["admin"]:
         raise AuthException("needed admin privileges",
                             http_code=HTTPStatus.UNAUTHORIZED)
     return token_value
예제 #4
0
 def get_token(self, token_info, token):
     if self.config["authentication"]["backend"] == "internal":
         return self._internal_get_token(token_info, token)
     else:
         # TODO: check if this can be avoided. Backend may provide enough information
         token_value = self.tokens_cache.get(token)
         if not token_value:
             raise AuthException("token not found",
                                 http_code=HTTPStatus.NOT_FOUND)
         if token_value["username"] != token_info[
                 "username"] and not token_info["admin"]:
             raise AuthException("needed admin privileges",
                                 http_code=HTTPStatus.UNAUTHORIZED)
         return token_value
예제 #5
0
    def get_role_list(self, filter_q=None):
        """
        Get role list.

        :param filter_q: dictionary to filter role list by _id and/or name.
        :return: returns the list of roles.
        """
        try:
            filter_name = None
            if filter_q:
                filter_name = filter_q.get("name")
            roles_list = self.keystone.roles.list(name=filter_name)

            roles = [{
                "name": role.name,
                "_id": role.id,
                "_admin": role.to_dict().get("_admin", {}),
                "permissions": role.to_dict().get("permissions", {})
            } for role in roles_list if role.name != "service"]

            if filter_q and filter_q.get("_id"):
                roles = [
                    role for role in roles if filter_q["_id"] == role["_id"]
                ]

            return roles
        except ClientException as e:
            # self.logger.exception("Error during user role listing using keystone: {}".format(e))
            raise AuthException(
                "Error during user role listing using Keystone: {}".format(e),
                http_code=HTTPStatus.UNAUTHORIZED)
예제 #6
0
 def del_token(self, token):
     try:
         self.backend.revoke_token(token)
         self.tokens_cache.pop(token, None)
         return "token '{}' deleted".format(token)
     except KeyError:
         raise AuthException("Token '{}' not found".format(token),
                             http_code=HTTPStatus.NOT_FOUND)
예제 #7
0
    def revoke_token(self, token):
        """
        Invalidate a token.

        :param token: token to be revoked
        """
        try:
            self.token_cache.pop(token, None)
            self.db.del_one("tokens", {"_id": token})
            return True
        except DbException as e:
            if e.http_code == HTTPStatus.NOT_FOUND:
                raise AuthException("Token '{}' not found".format(token),
                                    http_code=HTTPStatus.NOT_FOUND)
            else:
                # raise
                msg = "Error during token revocation using internal backend"
                self.logger.exception(msg)
                raise AuthException(msg, http_code=HTTPStatus.UNAUTHORIZED)
예제 #8
0
    def revoke_token(self, token):
        """
        Invalidate a token.

        :param token: token to be revoked
        """
        try:
            self.logger.info("Revoking token: " + token)
            self.keystone.tokens.revoke_token(token=token)

            return True
        except ClientException as e:
            # self.logger.exception("Error during token revocation using keystone: {}".format(e))
            raise AuthException(
                "Error during token revocation using Keystone: {}".format(e),
                http_code=HTTPStatus.UNAUTHORIZED)
예제 #9
0
    def get_project_list(self, filter_q=None):
        """
        Get all the projects.

        :param filter_q: dictionary to filter project list.
        :return: list of projects
        """
        try:
            filter_name = None
            if filter_q:
                filter_name = filter_q.get("name")
            projects = self.keystone.projects.list(name=filter_name)

            projects = [
                {
                    "name": project.name,
                    "_id": project.id,
                    "_admin": project.to_dict().get("_admin",
                                                    {}),  # TODO: REVISE
                    "quotas": project.to_dict().get("quotas",
                                                    {}),  # TODO: REVISE
                } for project in projects
            ]

            if filter_q and filter_q.get("_id"):
                projects = [
                    project for project in projects
                    if filter_q["_id"] == project["_id"]
                ]

            return projects
        except ClientException as e:
            # self.logger.exception("Error during user project listing using keystone: {}".format(e))
            raise AuthException(
                "Error during user project listing using Keystone: {}".format(
                    e),
                http_code=HTTPStatus.UNAUTHORIZED)
예제 #10
0
    def __init__(self, config, db, token_cache):
        Authconn.__init__(self, config, db, token_cache)

        self.logger = logging.getLogger("nbi.authenticator.keystone")

        self.auth_url = "http://{0}:{1}/v3".format(
            config.get("auth_url", "keystone"),
            config.get("auth_port", "5000"))
        self.user_domain_name = config.get("user_domain_name", "default")
        self.admin_project = config.get("service_project", "service")
        self.admin_username = config.get("service_username", "nbi")
        self.admin_password = config.get("service_password", "nbi")
        self.project_domain_name = config.get("project_domain_name", "default")

        # Waiting for Keystone to be up
        available = None
        counter = 300
        while available is None:
            time.sleep(1)
            try:
                result = requests.get(self.auth_url)
                available = True if result.status_code == 200 else None
            except Exception:
                counter -= 1
                if counter == 0:
                    raise AuthException(
                        "Keystone not available after 300s timeout")

        self.auth = v3.Password(user_domain_name=self.user_domain_name,
                                username=self.admin_username,
                                password=self.admin_password,
                                project_domain_name=self.project_domain_name,
                                project_name=self.admin_project,
                                auth_url=self.auth_url)
        self.sess = session.Session(auth=self.auth)
        self.keystone = client.Client(session=self.sess)
예제 #11
0
    def validate_token(self, token):
        """
        Check if the token is valid.

        :param token: token id to be validated
        :return: dictionary with information associated with the token:
             "expires":
             "_id": token_id,
             "project_id": project_id,
             "username": ,
             "roles": list with dict containing {name, id}
         If the token is not valid an exception is raised.
        """
        if not token:
            return

        try:
            token_info = self.keystone.tokens.validate(token=token)
            ses = {
                "_id": token_info["auth_token"],
                "id": token_info["auth_token"],
                "project_id": token_info["project"]["id"],
                "project_name": token_info["project"]["name"],
                "user_id": token_info["user"]["id"],
                "username": token_info["user"]["name"],
                "roles": token_info["roles"],
                "expires": token_info.expires.timestamp(),
                "issued_at": token_info.issued.timestamp()
            }

            return ses
        except ClientException as e:
            # self.logger.exception("Error during token validation using keystone: {}".format(e))
            raise AuthException(
                "Error during token validation using Keystone: {}".format(e),
                http_code=HTTPStatus.UNAUTHORIZED)
예제 #12
0
    def authenticate(self, user, password, project=None, token_info=None):
        """
        Authenticate a user using username/password or token_info, plus project
        :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
        :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,

        """
        try:
            username = None
            user_id = None
            project_id = None
            project_name = None

            if user:
                if is_valid_uuid(user):
                    user_id = user
                else:
                    username = user

                # get an unscoped token firstly
                unscoped_token = self.keystone.get_raw_token_from_identity_service(
                    auth_url=self.auth_url,
                    user_id=user_id,
                    username=username,
                    password=password,
                    user_domain_name=self.user_domain_name,
                    project_domain_name=self.project_domain_name)
            elif token_info:
                unscoped_token = self.keystone.tokens.validate(
                    token=token_info.get("_id"))
            else:
                raise AuthException(
                    "Provide credentials: username/password or Authorization Bearer token",
                    http_code=HTTPStatus.UNAUTHORIZED)

            if not project:
                # get first project for the user
                project_list = self.keystone.projects.list(
                    user=unscoped_token["user"]["id"])
                if not project_list:
                    raise AuthException(
                        "The user {} has not any project and cannot be used for authentication"
                        .format(user),
                        http_code=HTTPStatus.UNAUTHORIZED)
                project_id = project_list[0].id
            else:
                if is_valid_uuid(project):
                    project_id = project
                else:
                    project_name = project

            scoped_token = self.keystone.get_raw_token_from_identity_service(
                auth_url=self.auth_url,
                project_name=project_name,
                project_id=project_id,
                user_domain_name=self.user_domain_name,
                project_domain_name=self.project_domain_name,
                token=unscoped_token["auth_token"])

            auth_token = {
                "_id": scoped_token.auth_token,
                "id": scoped_token.auth_token,
                "user_id": scoped_token.user_id,
                "username": scoped_token.username,
                "project_id": scoped_token.project_id,
                "project_name": scoped_token.project_name,
                "expires": scoped_token.expires.timestamp(),
                "issued_at": scoped_token.issued.timestamp()
            }

            return auth_token
        except ClientException as e:
            # self.logger.exception("Error during user authentication using keystone. Method: basic: {}".format(e))
            raise AuthException(
                "Error during user authentication using Keystone: {}".format(
                    e),
                http_code=HTTPStatus.UNAUTHORIZED)
예제 #13
0
 def stop(self):
     try:
         if self.db:
             self.db.db_disconnect()
     except DbException as e:
         raise AuthException(str(e), http_code=e.http_code)
예제 #14
0
    def start(self, config):
        """
        Method to configure the Authenticator object. This method should be called
        after object creation. It is responsible by initializing the selected backend,
        as well as the initialization of the database connection.

        :param config: dictionary containing the relevant parameters for this object.
        """
        self.config = config

        try:
            if not self.db:
                if config["database"]["driver"] == "mongo":
                    self.db = dbmongo.DbMongo()
                    self.db.db_connect(config["database"])
                elif config["database"]["driver"] == "memory":
                    self.db = dbmemory.DbMemory()
                    self.db.db_connect(config["database"])
                else:
                    raise AuthException(
                        "Invalid configuration param '{}' at '[database]':'driver'"
                        .format(config["database"]["driver"]))
            if not self.backend:
                if config["authentication"]["backend"] == "keystone":
                    self.backend = AuthconnKeystone(
                        self.config["authentication"], self.db,
                        self.tokens_cache)
                elif config["authentication"]["backend"] == "internal":
                    self.backend = AuthconnInternal(
                        self.config["authentication"], self.db,
                        self.tokens_cache)
                    self._internal_tokens_prune()
                else:
                    raise AuthException(
                        "Unknown authentication backend: {}".format(
                            config["authentication"]["backend"]))

            if not self.roles_to_operations_file:
                if "roles_to_operations" in config["rbac"]:
                    self.roles_to_operations_file = config["rbac"][
                        "roles_to_operations"]
                else:
                    possible_paths = (__file__[:__file__.rfind("auth.py")] +
                                      "roles_to_operations.yml",
                                      "./roles_to_operations.yml")
                    for config_file in possible_paths:
                        if path.isfile(config_file):
                            self.roles_to_operations_file = config_file
                            break
                if not self.roles_to_operations_file:
                    raise AuthException(
                        "Invalid permission configuration: roles_to_operations file missing"
                    )

            # load role_permissions
            def load_role_permissions(method_dict):
                for k in method_dict:
                    if k == "ROLE_PERMISSION":
                        for method in chain(method_dict.get("METHODS", ()),
                                            method_dict.get("TODO", ())):
                            permission = method_dict[
                                "ROLE_PERMISSION"] + method.lower()
                            if permission not in self.role_permissions:
                                self.role_permissions.append(permission)
                    elif k in ("TODO", "METHODS"):
                        continue
                    else:
                        load_role_permissions(method_dict[k])

            load_role_permissions(self.valid_methods)
            for query_string in self.valid_query_string:
                for method in ("get", "put", "patch", "post", "delete"):
                    permission = query_string.lower() + ":" + method
                    if permission not in self.role_permissions:
                        self.role_permissions.append(permission)

        except Exception as e:
            raise AuthException(str(e))
예제 #15
0
    def init_db(self, target_version='1.0'):
        """
        Check if the database has been initialized, with at least one user. If not, create the required tables
        and insert the predefined mappings between roles and permissions.

        :param target_version: schema version that should be present in the database.
        :return: None if OK, exception if error or version is different.
        """

        records = self.backend.get_role_list()

        # Loading permissions to MongoDB if there is not any permission.
        if not records or (len(records) == 1
                           and records[0]["name"] == "admin"):
            with open(self.roles_to_operations_file, "r") as stream:
                roles_to_operations_yaml = yaml.load(stream,
                                                     Loader=yaml.Loader)

            role_names = []
            for role_with_operations in roles_to_operations_yaml["roles"]:
                # Verifying if role already exists. If it does, raise exception
                if role_with_operations["name"] not in role_names:
                    role_names.append(role_with_operations["name"])
                else:
                    raise AuthException(
                        "Duplicated role name '{}' at file '{}''".format(
                            role_with_operations["name"],
                            self.roles_to_operations_file))

                if not role_with_operations["permissions"]:
                    continue

                for permission, is_allowed in role_with_operations[
                        "permissions"].items():
                    if not isinstance(is_allowed, bool):
                        raise AuthException(
                            "Invalid value for permission '{}' at role '{}'; at file '{}'"
                            .format(permission, role_with_operations["name"],
                                    self.roles_to_operations_file))

                    # TODO chek permission is ok
                    if permission[-1] == ":":
                        raise AuthException(
                            "Invalid permission '{}' terminated in ':' for role '{}'; at file {}"
                            .format(permission, role_with_operations["name"],
                                    self.roles_to_operations_file))

                if "default" not in role_with_operations["permissions"]:
                    role_with_operations["permissions"]["default"] = False
                if "admin" not in role_with_operations["permissions"]:
                    role_with_operations["permissions"]["admin"] = False

                now = time()
                role_with_operations["_admin"] = {
                    "created": now,
                    "modified": now,
                }

                # self.db.create(self.roles_to_operations_table, role_with_operations)
                self.backend.create_role(role_with_operations)
                self.logger.info("Role '{}' created at database".format(
                    role_with_operations["name"]))

        # Create admin project&user if required
        pid = self.create_admin_project()
        user_id = self.create_admin_user(pid)

        # try to assign system_admin role to user admin if not any user has this role
        if not user_id:
            try:
                users = self.backend.get_user_list()
                roles = self.backend.get_role_list({"name": "system_admin"})
                role_id = roles[0]["_id"]
                user_with_system_admin = False
                user_admin_id = None
                for user in users:
                    if not user_admin_id:
                        user_admin_id = user["_id"]
                    if user["username"] == "admin":
                        user_admin_id = user["_id"]
                    for prm in user.get("project_role_mappings", ()):
                        if prm["role"] == role_id:
                            user_with_system_admin = True
                            break
                    if user_with_system_admin:
                        break
                if not user_with_system_admin:
                    self.backend.update_user({
                        "_id":
                        user_admin_id,
                        "add_project_role_mappings": [{
                            "project": pid,
                            "role": role_id
                        }]
                    })
                    self.logger.info(
                        "Added role system admin to user='******' project=admin".
                        format(user_admin_id))
            except Exception as e:
                self.logger.error(
                    "Error in Authorization DataBase initialization: {}: {}".
                    format(type(e).__name__, e))

        self.load_operation_to_allowed_roles()
예제 #16
0
    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)
예제 #17
0
    def authorize(self,
                  role_permission=None,
                  query_string_operations=None,
                  item_id=None):
        token = None
        user_passwd64 = None
        try:
            # 1. Get token Authorization bearer
            auth = cherrypy.request.headers.get("Authorization")
            if auth:
                auth_list = auth.split(" ")
                if auth_list[0].lower() == "bearer":
                    token = auth_list[-1]
                elif auth_list[0].lower() == "basic":
                    user_passwd64 = auth_list[-1]
            if not token:
                if cherrypy.session.get("Authorization"):
                    # 2. Try using session before request a new token. If not, basic authentication will generate
                    token = cherrypy.session.get("Authorization")
                    if token == "logout":
                        token = None  # force Unauthorized response to insert user password again
                elif user_passwd64 and cherrypy.request.config.get(
                        "auth.allow_basic_authentication"):
                    # 3. Get new token from user password
                    user = None
                    passwd = None
                    try:
                        user_passwd = standard_b64decode(
                            user_passwd64).decode()
                        user, _, passwd = user_passwd.partition(":")
                    except Exception:
                        pass
                    outdata = self.new_token(None, {
                        "username": user,
                        "password": passwd
                    })
                    token = outdata["_id"]
                    cherrypy.session['Authorization'] = token

            if not token:
                raise AuthException(
                    "Needed a token or Authorization http header",
                    http_code=HTTPStatus.UNAUTHORIZED)
            token_info = self.backend.validate_token(token)
            # TODO add to token info remote host, port

            if role_permission:
                RBAC_auth = self.check_permissions(token_info,
                                                   cherrypy.request.method,
                                                   role_permission,
                                                   query_string_operations,
                                                   item_id)
                token_info["allow_show_user_project_role"] = RBAC_auth

            return token_info
        except AuthException as e:
            if not isinstance(e, AuthExceptionUnauthorized):
                if cherrypy.session.get('Authorization'):
                    del cherrypy.session['Authorization']
                cherrypy.response.headers[
                    "WWW-Authenticate"] = 'Bearer realm="{}"'.format(e)
            elif self.config.get("user_not_authorized"):
                # TODO provide user_id, roles id (not name), project_id
                return {
                    "id":
                    "fake-token-id-for-test",
                    "project_id":
                    self.config.get("project_not_authorized", "admin"),
                    "username":
                    self.config["user_not_authorized"],
                    "roles": ["system_admin"]
                }
            raise