Example #1
0
File: utils.py Project: masaar/limp
def validate_attr(
    *,
    attr_name: str,
    attr_type: ATTR,
    attr_val: Any,
    allow_opers: bool = False,
    allow_none: bool = False,
    skip_events: List[str] = None,
    env: Dict[str, Any] = None,
    query: Union[LIMP_QUERY, Query] = None,
    doc: LIMP_DOC = None,
    scope: LIMP_DOC = None,
):
    from config import Config

    try:
        return validate_default(
            attr_type=attr_type,
            attr_val=attr_val,
            skip_events=skip_events,
            env=env,
            query=query,
            doc=doc,
            scope=scope if scope else doc,
            allow_none=allow_none,
        )
    except:
        pass

    attr_oper = False
    if allow_opers and type(attr_val) == dict:
        if '$add' in attr_val.keys():
            attr_oper = '$add'
            attr_val = attr_val['$add']
        elif '$multiply' in attr_val.keys():
            attr_oper = '$multiply'
            attr_val = attr_val['$multiply']
        elif '$append' in attr_val.keys():
            attr_oper = '$append'
            if '$unique' in attr_val.keys() and attr_val['$unique'] == True:
                attr_oper = '$append__unique'
            attr_val = [attr_val['$append']]
        elif '$remove' in attr_val.keys():
            attr_oper = '$remove'
            attr_val = attr_val['$remove']

    try:
        if attr_type._type == 'ANY':
            return return_valid_attr(attr_val=attr_val, attr_oper=attr_oper)

        elif attr_type._type == 'ACCESS':
            if (type(attr_val) == dict
                    and set(attr_val.keys()) == {'anon', 'users', 'groups'}
                    and type(attr_val['anon']) == bool
                    and type(attr_val['users']) == list
                    and type(attr_val['groups']) == list):
                return return_valid_attr(attr_val=attr_val,
                                         attr_oper=attr_oper)

        elif attr_type._type == 'BOOL':
            if type(attr_val) == bool:
                return return_valid_attr(attr_val=attr_val,
                                         attr_oper=attr_oper)

        elif attr_type._type == 'DATE':
            if re.match(r'^[0-9]{4}-[0-9]{2}-[0-9]{2}$', attr_val):
                return return_valid_attr(attr_val=attr_val,
                                         attr_oper=attr_oper)

        elif attr_type._type == 'DATETIME':
            if re.match(
                    r'^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}(:[0-9]{2}(\.[0-9]{6})?)?$',
                    attr_val,
            ):
                return return_valid_attr(attr_val=attr_val,
                                         attr_oper=attr_oper)

        elif attr_type._type == 'DICT':
            if type(attr_val) == dict:
                if '__key' in attr_type._args['dict'].keys():
                    if '__min' in attr_type._args['dict'].keys():
                        if len(attr_val.keys()
                               ) < attr_type._args['dict']['__min']:
                            raise InvalidAttrException(
                                attr_name=attr_name,
                                attr_type=attr_type,
                                val_type=type(attr_val),
                            )
                    if '__max' in attr_type._args['dict'].keys():
                        if len(attr_val.keys()
                               ) > attr_type._args['dict']['__max']:
                            raise InvalidAttrException(
                                attr_name=attr_name,
                                attr_type=attr_type,
                                val_type=type(attr_val),
                            )
                    shadow_attr_val = {}
                    for child_attr_val in attr_val.keys():
                        shadow_attr_val[validate_attr(
                            attr_name=f'{attr_name}.{child_attr_val}',
                            attr_type=attr_type._args['dict']['__key'],
                            attr_val=child_attr_val,
                            allow_opers=allow_opers,
                            allow_none=allow_none,
                            skip_events=skip_events,
                            env=env,
                            query=query,
                            doc=doc,
                            scope=attr_val,
                        )] = validate_attr(
                            attr_name=f'{attr_name}.{child_attr_val}',
                            attr_type=attr_type._args['dict']['__val'],
                            attr_val=attr_val[child_attr_val],
                            allow_opers=allow_opers,
                            allow_none=allow_none,
                            skip_events=skip_events,
                            env=env,
                            query=query,
                            doc=doc,
                            scope=attr_val,
                        )
                    if '__req' in attr_type._args['dict'].keys():
                        for req_key in attr_type._args['dict']['__req']:
                            if req_key not in shadow_attr_val.keys():
                                raise InvalidAttrException(
                                    attr_name=attr_name,
                                    attr_type=attr_type,
                                    val_type=type(attr_val),
                                )
                    return return_valid_attr(attr_val=shadow_attr_val,
                                             attr_oper=attr_oper)
                else:
                    for child_attr_type in attr_type._args['dict'].keys():
                        if child_attr_type not in attr_val.keys():
                            attr_val[child_attr_type] = None
                        attr_val[child_attr_type] = validate_attr(
                            attr_name=f'{attr_name}.{child_attr_type}',
                            attr_type=attr_type._args['dict'][child_attr_type],
                            attr_val=attr_val[child_attr_type],
                            allow_opers=allow_opers,
                            allow_none=allow_none,
                            skip_events=skip_events,
                            env=env,
                            query=query,
                            doc=doc,
                            scope=attr_val,
                        )
                    return return_valid_attr(attr_val=attr_val,
                                             attr_oper=attr_oper)

        elif attr_type._type == 'EMAIL':
            if re.match(r'^[^@]+@[^@]+\.[^@]+$', attr_val):
                return return_valid_attr(attr_val=attr_val,
                                         attr_oper=attr_oper)

        elif attr_type._type == 'FILE':
            if type(attr_val) == list and len(attr_val):
                try:
                    validate_attr(
                        attr_name=attr_name,
                        attr_type=attr_type,
                        attr_val=attr_val[0],
                        allow_opers=allow_opers,
                        allow_none=allow_none,
                        skip_events=skip_events,
                        env=env,
                        query=query,
                        doc=doc,
                        scope=attr_val,
                    )
                    attr_val = attr_val[0]
                except:
                    raise InvalidAttrException(
                        attr_name=attr_name,
                        attr_type=attr_type,
                        val_type=type(attr_val),
                    )
            file_type = (
                type(attr_val) == dict and set(attr_val.keys())
                == {'name', 'lastModified', 'type', 'size', 'content'}
                and type(attr_val['name']) == str
                and type(attr_val['lastModified']) == int
                and type(attr_val['size']) == int
                and type(attr_val['content']) in [binary.Binary, bytes])
            if not file_type:
                raise InvalidAttrException(attr_name=attr_name,
                                           attr_type=attr_type,
                                           val_type=type(attr_val))
            if attr_type._args['types']:
                for file_type in attr_type._args['types']:
                    if attr_val['type'].split('/')[0] == file_type.split(
                            '/')[0]:
                        if (file_type.split('/')[1] == '*'
                                or attr_val['type'].split('/')[1]
                                == file_type.split('/')[1]):
                            return return_valid_attr(attr_val=attr_val,
                                                     attr_oper=attr_oper)
            else:
                return return_valid_attr(attr_val=attr_val,
                                         attr_oper=attr_oper)

        elif attr_type._type == 'FLOAT':
            if type(attr_val) == str and re.match(r'^[0-9]+(\.[0-9]+)?$',
                                                  attr_val):
                attr_val = float(attr_val)
            elif type(attr_val) == int:
                attr_val = float(attr_val)

            if type(attr_val) == float:
                if attr_type._args['range']:
                    if int(attr_val) in attr_type._args['range']:
                        return return_valid_attr(attr_val=attr_val,
                                                 attr_oper=attr_oper)
                else:
                    return return_valid_attr(attr_val=attr_val,
                                             attr_oper=attr_oper)

        elif attr_type._type == 'GEO':
            if (type(attr_val) == dict
                    and list(attr_val.keys()) == ['type', 'coordinates']
                    and attr_val['type'] in ['Point']
                    and type(attr_val['coordinates']) == list
                    and len(attr_val['coordinates']) == 2
                    and type(attr_val['coordinates'][0]) in [int, float]
                    and type(attr_val['coordinates'][1]) in [int, float]):
                return return_valid_attr(attr_val=attr_val,
                                         attr_oper=attr_oper)

        elif attr_type._type == 'ID':
            if type(attr_val) == BaseModel or type(attr_val) == DictObj:
                return return_valid_attr(attr_val=attr_val._id,
                                         attr_oper=attr_oper)
            elif type(attr_val) == ObjectId:
                return return_valid_attr(attr_val=attr_val,
                                         attr_oper=attr_oper)
            elif type(attr_val) == str:
                try:
                    return return_valid_attr(attr_val=ObjectId(attr_val),
                                             attr_oper=attr_oper)
                except:
                    raise ConvertAttrException(
                        attr_name=attr_name,
                        attr_type=attr_type,
                        val_type=type(attr_val),
                    )

        elif attr_type._type == 'INT':
            if type(attr_val) == str and re.match(r'^[0-9]+$', attr_val):
                attr_val = int(attr_val)

            if type(attr_val) == int:
                if attr_type._args['range']:
                    if attr_val in attr_type._args['range']:
                        return return_valid_attr(attr_val=attr_val,
                                                 attr_oper=attr_oper)
                else:
                    return return_valid_attr(attr_val=attr_val,
                                             attr_oper=attr_oper)

        elif attr_type._type == 'IP':
            if re.match(
                    r'^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$',
                    attr_val,
            ):
                return return_valid_attr(attr_val=attr_val,
                                         attr_oper=attr_oper)

        elif attr_type._type == 'LIST':
            if type(attr_val) == list:
                for i in range(len(attr_val)):
                    child_attr_val = attr_val[i]
                    child_attr_check = False
                    for child_attr_type in attr_type._args['list']:
                        try:
                            attr_val[i] = validate_attr(
                                attr_name=attr_name,
                                attr_type=child_attr_type,
                                attr_val=child_attr_val,
                                allow_opers=allow_opers,
                                allow_none=allow_none,
                                skip_events=skip_events,
                                env=env,
                                query=query,
                                doc=doc,
                                scope=attr_val,
                            )
                            child_attr_check = True
                            break
                        except:
                            pass
                    if not child_attr_check:
                        raise InvalidAttrException(
                            attr_name=attr_name,
                            attr_type=attr_type,
                            val_type=type(attr_val),
                        )
                return return_valid_attr(attr_val=attr_val,
                                         attr_oper=attr_oper)

        elif attr_type._type == 'LOCALE':
            attr_val = validate_attr(
                attr_name=attr_name,
                attr_type=ATTR.DICT(
                    dict={
                        '__key':
                        ATTR.LITERAL(
                            literal=[locale for locale in Config.locales]),
                        '__val':
                        ATTR.STR(),
                        '__min':
                        1,
                        '__req': [Config.locale],
                    }),
                attr_val=attr_val,
                allow_opers=allow_opers,
                allow_none=allow_none,
                skip_events=skip_events,
                env=env,
                query=query,
                doc=doc,
                scope=attr_val,
            )
            attr_val = {
                locale: attr_val[locale]
                if locale in attr_val.keys() else attr_val[Config.locale]
                for locale in Config.locales
            }
            return return_valid_attr(attr_val=attr_val, attr_oper=attr_oper)

        elif attr_type._type == 'LOCALES':
            if attr_val in Config.locales:
                return return_valid_attr(attr_val=attr_val,
                                         attr_oper=attr_oper)

        elif attr_type._type == 'PHONE':
            if attr_type._args['codes']:
                for phone_code in attr_type._args['codes']:
                    if re.match(fr'^\+{phone_code}[0-9]+$', attr_val):
                        return return_valid_attr(attr_val=attr_val,
                                                 attr_oper=attr_oper)
            else:
                if re.match(r'^\+[0-9]+$', attr_val):
                    return return_valid_attr(attr_val=attr_val,
                                             attr_oper=attr_oper)

        elif attr_type._type == 'STR':
            if type(attr_val) == str:
                if attr_type._args['pattern']:
                    if re.match(f'^{attr_type._args["pattern"]}$', attr_val):
                        return return_valid_attr(attr_val=attr_val,
                                                 attr_oper=attr_oper)
                else:
                    return return_valid_attr(attr_val=attr_val,
                                             attr_oper=attr_oper)

        elif attr_type._type == 'TIME':
            if re.match(r'^[0-9]{2}:[0-9]{2}(:[0-9]{2}(\.[0-9]{6})?)?$',
                        attr_val):
                return return_valid_attr(attr_val=attr_val,
                                         attr_oper=attr_oper)

        elif attr_type._type == 'URI_WEB':
            if re.match(
                    r'^https?:\/\/(?:[\w\-\_]+\.)(?:\.?[\w]{2,})+([\?\/].*)?$',
                    attr_val):
                return return_valid_attr(attr_val=attr_val,
                                         attr_oper=attr_oper)

        elif attr_type._type == 'LITERAL':
            if attr_val in attr_type._args['literal']:
                return return_valid_attr(attr_val=attr_val,
                                         attr_oper=attr_oper)

        elif attr_type._type == 'UNION':
            for child_attr in attr_type._args['union']:
                try:
                    validate_attr(
                        attr_name=attr_name,
                        attr_type=child_attr,
                        attr_val=attr_val,
                        allow_opers=allow_opers,
                        allow_none=allow_none,
                        skip_events=skip_events,
                        env=env,
                        query=query,
                        doc=doc,
                        scope=attr_val,
                    )
                except:
                    continue
                return return_valid_attr(attr_val=attr_val,
                                         attr_oper=attr_oper)

        elif attr_type._type == 'TYPE':
            return return_valid_attr(
                attr_val=Config.types[attr_type._args['type']](
                    attr_name=attr_name,
                    attr_type=attr_type,
                    attr_val=attr_val),
                attr_oper=attr_oper,
            )

    except Exception as e:
        if type(e) in [InvalidAttrException, ConvertAttrException]:
            if allow_none:
                return None
            elif attr_type._default != LIMP_VALUES.NONE_VALUE:
                return attr_type._default
            else:
                raise e

    raise InvalidAttrException(attr_name=attr_name,
                               attr_type=attr_type,
                               val_type=type(attr_val))
Example #2
0
File: user.py Project: masaar/limp
class User(BaseModule):
    '''`User` module provides data type and controller for users in LIMP eco-system. This module is supposed to be used for internal calls only, however it has wide-access permissions in order to allow admins, proxy modules to easily expose the methods.'''
    collection = 'users'
    attrs = {
        'name':
        ATTR.LOCALE(desc='Name of the user as `LOCALE`.'),
        'locale':
        ATTR.LOCALES(desc='Default locale of the user.'),
        'create_time':
        ATTR.DATETIME(
            desc='Python `datetime` ISO format of the doc creation.'),
        'login_time':
        ATTR.DATETIME(desc='Python `datetime` ISO format of the last login.'),
        'groups':
        ATTR.LIST(
            desc='List of `_id` for every group the user is member of.',
            list=[ATTR.ID(desc='`_id` of Group doc the user is member of.')]),
        'privileges':
        ATTR.DICT(
            desc=
            'Privileges of the user. These privileges are always available to the user regardless of whether groups user is part of have them or not.',
            dict={
                '__key': ATTR.STR(),
                '__val': ATTR.LIST(list=[ATTR.STR()])
            }),
        'status':
        ATTR.LITERAL(
            desc=
            'Status of the user to determine whether user has access to the app or not.',
            literal=['active', 'banned', 'deleted', 'disabled_password']),
    }
    defaults = {
        'login_time': None,
        'status': 'active',
        'groups': [],
        'privileges': {}
    }
    unique_attrs = []
    methods = {
        'read': {
            'permissions': [
                PERM(privilege='admin'),
                PERM(privilege='read', query_mod={'_id': '$__user'}),
            ]
        },
        'create': {
            'permissions': [PERM(privilege='admin')]
        },
        'update': {
            'permissions': [
                PERM(privilege='admin', doc_mod={'groups': None}),
                PERM(
                    privilege='update',
                    query_mod={'_id': '$__user'},
                    doc_mod={
                        'groups': None,
                        'privileges': None
                    },
                ),
            ],
            'query_args': {
                '_id': ATTR.ID()
            },
        },
        'delete': {
            'permissions': [
                PERM(privilege='admin'),
                PERM(privilege='delete', query_mod={'_id': '$__user'}),
            ],
            'query_args': {
                '_id': ATTR.ID()
            },
        },
        'read_privileges': {
            'permissions': [
                PERM(privilege='admin'),
                PERM(privilege='read', query_mod={'_id': '$__user'}),
            ],
            'query_args': {
                '_id': ATTR.ID()
            },
        },
        'add_group': {
            'permissions': [PERM(privilege='admin')],
            'query_args': {
                '_id': ATTR.ID()
            },
            'doc_args': [{
                'group': ATTR.ID()
            }, {
                'group': ATTR.LIST(list=[ATTR.ID()])
            }],
        },
        'delete_group': {
            'permissions': [PERM(privilege='admin')],
            'query_args': {
                '_id': ATTR.ID(),
                'group': ATTR.ID()
            },
        },
        'retrieve_file': {
            'permissions': [PERM(privilege='__sys')],
            'get_method': True
        },
        'create_file': {
            'permissions': [PERM(privilege='__sys')]
        },
        'delete_file': {
            'permissions': [PERM(privilege='__sys')]
        },
    }

    async def on_read(self, results, skip_events, env, query, doc, payload):
        for i in range(len(results['docs'])):
            user = results['docs'][i]
            user['settings'] = {}
            for auth_attr in Config.user_auth_attrs:
                del user[f'{auth_attr}_hash']
            if len(Config.user_doc_settings):
                setting_results = await Config.modules['setting'].read(
                    skip_events=[Event.PERM, Event.ARGS],
                    env=env,
                    query=[{
                        'user': user._id,
                        'var': {
                            '$in': Config.user_doc_settings
                        }
                    }],
                )
                if setting_results.args.count:
                    user['settings'] = {
                        setting_doc['var']: setting_doc['val']
                        for setting_doc in setting_results.args.docs
                    }
        return (results, skip_events, env, query, doc, payload)

    async def pre_create(self, skip_events, env, query, doc, payload):
        if Event.ARGS not in skip_events:
            if Config.realm:
                realm_results = await Config.modules['realm'].read(
                    skip_events=[Event.PERM], env=env)
                realm = realm_results.args.docs[0]
                doc['groups'] = [realm.default]
            else:
                doc['groups'] = [ObjectId('f00000000000000000000013')]
        if 'settings' in doc.keys():
            payload['settings'] = doc['settings']
        return (skip_events, env, query, doc, payload)

    async def on_create(self, results, skip_events, env, query, doc, payload):
        if 'settings' in payload.keys():
            for setting in payload['settings'].keys():
                if callable(payload['settings'][setting]['val']):
                    setting_val = payload['settings'][setting]['val'](
                        skip_events=skip_events, env=env, query=query, doc=doc)
                else:
                    setting_val = payload['settings'][setting]['val']
                setting_results = await Config.modules['setting'].create(
                    skip_events=[Event.PERM, Event.ARGS],
                    env=env,
                    doc={
                        'user': results['docs'][0]._id,
                        'var': setting,
                        'val': setting_val,
                        'type': payload['settings'][setting]['type'],
                    },
                )
                if setting_results.status != 200:
                    return setting_results
        return (results, skip_events, env, query, doc, payload)

    async def read_privileges(self, skip_events=[], env={}, query=[], doc={}):
        # [DOC] Confirm _id is valid
        results = await self.read(skip_events=[Event.PERM],
                                  env=env,
                                  query=[{
                                      '_id': query['_id'][0]
                                  }])
        if not results.args.count:
            return self.status(status=400,
                               msg='User is invalid.',
                               args={'code': 'INVALID_USER'})
        user = results.args.docs[0]
        for group in user.groups:
            group_results = await Config.modules['group'].read(
                skip_events=[Event.PERM], env=env, query=[{
                    '_id': group
                }])
            group = group_results.args.docs[0]
            for privilege in group.privileges.keys():
                if privilege not in user.privileges.keys():
                    user.privileges[privilege] = []
                for i in range(len(group.privileges[privilege])):
                    if group.privileges[privilege][i] not in user.privileges[
                            privilege]:
                        user.privileges[privilege].append(
                            group.privileges[privilege][i])
        return results

    async def add_group(self, skip_events=[], env={}, query=[], doc={}):
        # [DOC] Check for list group attr
        if type(doc['group']) == list:
            for i in range(0, len(doc['group']) - 1):
                await self.add_group(
                    skip_events=skip_events,
                    env=env,
                    query=query,
                    doc={'group': doc['group'][i]},
                )
            doc['group'] = doc['group'][-1]
        # [DOC] Confirm all basic args are provided
        doc['group'] = ObjectId(doc['group'])
        # [DOC] Confirm group is valid
        results = await Config.modules['group'].read(skip_events=[Event.PERM],
                                                     env=env,
                                                     query=[{
                                                         '_id': doc['group']
                                                     }])
        if not results.args.count:
            return self.status(status=400,
                               msg='Group is invalid.',
                               args={'code': 'INVALID_GROUP'})
        # [DOC] Get user details
        results = await self.read(skip_events=[Event.PERM],
                                  env=env,
                                  query=query)
        if not results.args.count:
            return self.status(status=400,
                               msg='User is invalid.',
                               args={'code': 'INVALID_USER'})
        user = results.args.docs[0]
        # [DOC] Confirm group was not added before
        if doc['group'] in user.groups:
            return self.status(
                status=400,
                msg='User is already a member of the group.',
                args={'code': 'GROUP_ADDED'},
            )
        user.groups.append(doc['group'])
        # [DOC] Update the user
        results = await self.update(skip_events=[Event.PERM],
                                    env=env,
                                    query=query,
                                    doc={'groups': user.groups})
        return results

    async def delete_group(self, skip_events=[], env={}, query=[], doc={}):
        # [DOC] Confirm group is valid
        results = await Config.modules['group'].read(skip_events=[Event.PERM],
                                                     env=env,
                                                     query=[{
                                                         '_id':
                                                         query['group'][0]
                                                     }])
        if not results.args.count:
            return self.status(status=400,
                               msg='Group is invalid.',
                               args={'code': 'INVALID_GROUP'})
        # [DOC] Get user details
        results = await self.read(skip_events=[Event.PERM],
                                  env=env,
                                  query=[{
                                      '_id': query['_id'][0]
                                  }])
        if not results.args.count:
            return self.status(status=400,
                               msg='User is invalid.',
                               args={'code': 'INVALID_USER'})
        user = results.args.docs[0]
        # [DOC] Confirm group was not added before
        if query['group'][0] not in user.groups:
            return self.status(
                status=400,
                msg='User is not a member of the group.',
                args={'code': 'GROUP_NOT_ADDED'},
            )
        # [DOC] Update the user
        results = await self.update(
            skip_events=[Event.PERM],
            env=env,
            query=[{
                '_id': query['_id'][0]
            }],
            doc={'groups': {
                '$remove': [query['group'][0]]
            }},
        )
        return results
Example #3
0
class Setting(BaseModule):
    '''`Setting` module module provides data type and controller for settings in LIMP eco-system. This is used by `User` module tp provide additional user-wise settings. It also allows for global-typed settings.'''
    collection = 'settings'
    attrs = {
        'user':
        ATTR.ID(desc='`_id` of `User` doc the doc belongs to.'),
        'var':
        ATTR.STR(
            desc=
            'Name of the setting. This is unique for every `user` in the module.'
        ),
        'val':
        ATTR.ANY(desc='Value of the setting.'),
        'type':
        ATTR.LITERAL(
            desc=
            'Type of the setting. This sets whether setting is global, or belong to user, and whether use can update it or not.',
            literal=['global', 'user', 'user_sys']),
    }
    diff = True
    unique_attrs = [('user', 'var', 'type')]
    extns = {
        'val':
        ATTR_MOD(
            condition=lambda skip_events, env, query, doc, scope: type(scope)
            == dict and '__extn' in scope.keys(),
            default=lambda skip_events, env, query, doc, scope: {
                '__extn':
                EXTN(
                    module=scope['__extn']['__module'],
                    attrs=scope['__extn']['__attrs'],
                    force=scope['__extn']['__force'],
                ),
                '__val':
                scope['__extn']['__val'],
            },
        )
    }
    methods = {
        'read': {
            'permissions': [
                PERM(privilege='admin', query_mod={'$limit': 1}),
                PERM(
                    privilege='read',
                    query_mod={
                        'user':
                        '******',
                        'type':
                        ATTR_MOD(
                            condition=lambda skip_events, env, query, doc:
                            'type' in doc.keys() and doc['type'] == 'user_sys',
                            default=lambda skip_events, env, query, doc:
                            InvalidAttrException(
                                attr_name='type',
                                attr_type=ATTR.LITERAL(literal=
                                                       ['global', 'user']),
                                val_type=str,
                            ),
                        ),
                        '$limit':
                        1,
                    },
                ),
            ],
            'query_args': [
                {
                    '_id': ATTR.ID(),
                    'type':
                    ATTR.LITERAL(literal=['global', 'user', 'user_sys']),
                },
                {
                    'var': ATTR.STR(),
                    'type': ATTR.LITERAL(literal=['global']),
                },
                {
                    'var': ATTR.STR(),
                    'user': ATTR.ID(),
                    'type': ATTR.LITERAL(literal=['user', 'user_sys']),
                },
            ],
        },
        'create': {
            'permissions': [
                PERM(privilege='admin'),
                PERM(privilege='create', doc_mod={'type': 'user'}),
            ]
        },
        'update': {
            'permissions': [
                PERM(privilege='admin', query_mod={'$limit': 1}),
                PERM(
                    privilege='update',
                    query_mod={
                        'type': 'user',
                        'user': '******',
                        '$limit': 1
                    },
                    doc_mod={'type': None},
                ),
            ],
            'query_args': [
                {
                    '_id': ATTR.ID(),
                    'type':
                    ATTR.LITERAL(literal=['global', 'user', 'user_sys']),
                },
                {
                    'var': ATTR.STR(),
                    'type': ATTR.LITERAL(literal=['global']),
                },
                {
                    'var': ATTR.STR(),
                    'user': ATTR.ID(),
                    'type': ATTR.LITERAL(literal=['user', 'user_sys']),
                },
            ],
            'doc_args': {
                'val': ATTR.ANY()
            },
        },
        'delete': {
            'permissions': [PERM(privilege='admin', query_mod={'$limit': 1})],
            'query_args': [{
                '_id': ATTR.ID()
            }, {
                'var': ATTR.STR()
            }],
        },
        'retrieve_file': {
            'permissions': [PERM(privilege='*', query_mod={'type': 'global'})],
            'get_method': True,
        },
    }

    async def pre_create(self, skip_events, env, query, doc, payload):
        if (type(doc['val']) == list and len(doc['val']) == 1
                and type(doc['val'][0]) == dict
                and 'content' in doc['val'][0].keys()):
            doc['val'] = doc['val'][0]
        return (skip_events, env, query, doc, payload)

    async def on_create(self, results, skip_events, env, query, doc, payload):
        if doc['type'] in ['user', 'user_sys']:
            if doc['user'] == env['session'].user._id:
                env['session'].user.settings[doc['var']] = doc['val']
        return (results, skip_events, env, query, doc, payload)

    async def pre_update(self, skip_events, env, query, doc, payload):
        if (type(doc['val']) == list and len(doc['val']) == 1
                and type(doc['val'][0]) == dict
                and 'content' in doc['val'][0].keys()):
            doc['val'] = doc['val'][0]
        return (skip_events, env, query, doc, payload)

    async def on_update(self, results, skip_events, env, query, doc, payload):
        if query['type'][0] in ['user', 'user_sys']:
            if query['user'][0] == env['session'].user._id:
                if type(doc['val']) == dict and '$add' in doc['val'].keys():
                    env['session'].user.settings[query['var']
                                                 [0]] += doc['val']['$add']
                elif type(doc['val']
                          ) == dict and '$multiply' in doc['val'].keys():
                    env['session'].user.settings[
                        query['var'][0]] *= doc['val']['$multiply']
                elif type(
                        doc['val']) == dict and '$append' in doc['val'].keys():
                    env['session'].user.settings[query['var'][0]].append(
                        doc['val']['$append'])
                elif type(
                        doc['val']) == dict and '$remove' in doc['val'].keys():
                    env['session'].user.settings[query['var'][0]].remove(
                        doc['val']['$remove'])
                else:
                    env['session'].user.settings[query['var'][0]] = doc['val']
        return (results, skip_events, env, query, doc, payload)