示例#1
0
文件: analytic.py 项目: masaar/limp
class Analytic(BaseModule):
    '''`Analytic` module provides data type and controller from `Analytics Workflow` and accompanying analytics docs. It uses `pre_create` handler to assure no events duplications occur and all occurrences of the same event are recorded in one doc.'''
    collection = 'analytics'
    attrs = {
        'user':
        ATTR.ID(desc='`_id` of `User` doc the doc belongs to.'),
        'event':
        ATTR.STR(desc='Analytics event name.'),
        'subevent':
        ATTR.ANY(
            desc=
            'Analytics subevent distinguishing attribute. This is usually `STR`, or `ID` but it is introduced in the module as `ANY` to allow wider use-cases by developers.'
        ),
        'occurrences':
        ATTR.LIST(
            desc='All occurrences of the event as list.',
            list=[
                ATTR.DICT(
                    desc='Single occurrence of the event details.',
                    dict={
                        'args':
                        ATTR.DICT(
                            desc=
                            'Key-value `dict` containing event args, if any.',
                            dict={
                                '__key': ATTR.STR(),
                                '__val': ATTR.ANY()
                            }),
                        'score':
                        ATTR.INT(
                            desc='Numerical score for occurrence of the event.'
                        ),
                        'create_time':
                        ATTR.DATETIME(
                            desc=
                            'Python `datetime` ISO format of the occurrence of the event.'
                        ),
                    })
            ]),
        'score':
        ATTR.INT(
            desc=
            'Total score of all scores of all occurrences of the event. This can be used for data analysis.'
        ),
    }
    unique_attrs = [('user', 'event', 'subevent')]
    methods = {
        'read': {
            'permissions': [PERM(privilege='read')]
        },
        'create': {
            'permissions': [PERM(privilege='__sys')],
            'doc_args': {
                'event': ATTR.STR(),
                'subevent': ATTR.ANY(),
                'args': ATTR.DICT(dict={
                    '__key': ATTR.STR(),
                    '__val': ATTR.ANY()
                }),
            },
        },
        'update': {
            'permissions': [PERM(privilege='__sys')]
        },
        'delete': {
            'permissions': [PERM(privilege='delete')]
        },
    }

    async def pre_create(self, skip_events, env, query, doc, payload):
        analytic_results = await self.read(
            skip_events=[Event.PERM],
            env=env,
            query=[
                {
                    'user': env['session'].user._id,
                    'event': doc['event'],
                    'subevent': doc['subevent'],
                },
                {
                    '$limit': 1
                },
            ],
        )
        if analytic_results.args.count:
            analytic_results = await self.update(
                skip_events=[Event.PERM],
                env=env,
                query=[{
                    '_id': analytic_results.args.docs[0]._id
                }],
                doc={
                    'occurrences': {
                        '$append': {
                            'args': doc['args'],
                            'score':
                            doc['score'] if 'score' in doc.keys() else 0,
                            'create_time':
                            datetime.datetime.utcnow().isoformat(),
                        }
                    },
                    'score': {
                        '$add': doc['score'] if 'score' in doc.keys() else 0
                    },
                },
            )
            return analytic_results
        else:
            doc = {
                'event':
                doc['event'],
                'subevent':
                doc['subevent'],
                'occurrences': [{
                    'args':
                    doc['args'],
                    'score':
                    doc['score'] if 'score' in doc.keys() else 0,
                    'create_time':
                    datetime.datetime.utcnow().isoformat(),
                }],
                'score':
                doc['score'] if 'score' in doc.keys() else 0,
            }
            return (skip_events, env, query, doc, payload)
示例#2
0
文件: group.py 项目: masaar/limp
class Group(BaseModule):
    '''`Group` module provides data type and controller for groups in LIMP eco-system.'''
    collection = 'groups'
    attrs = {
        'user':
        ATTR.ID(desc='`_id` of `User` doc the doc belongs to.'),
        'name':
        ATTR.LOCALE(desc='Name of the groups as `LOCALE`.'),
        'desc':
        ATTR.LOCALE(
            desc=
            'Description of the group as `LOCALE`. This can be used for dynamic generated groups that are meant to be exposed to end-users.'
        ),
        'privileges':
        ATTR.DICT(
            desc='Privileges that any user is a member of the group has.',
            dict={
                '__key': ATTR.STR(),
                '__val': ATTR.LIST(list=[ATTR.STR()])
            }),
        'settings':
        ATTR.DICT(
            desc=
            '`Setting` docs to be created, or required for members users when added to the group.',
            dict={
                '__key': ATTR.STR(),
                '__val': ATTR.ANY()
            }),
        'create_time':
        ATTR.DATETIME(
            desc='Python `datetime` ISO format of the doc creation.'),
    }
    defaults = {
        'desc': {locale: ''
                 for locale in Config.locales},
        'privileges': {},
        'settings': {},
    }
    methods = {
        'read': {
            'permissions': [PERM(privilege='admin')]
        },
        'create': {
            'permissions': [PERM(privilege='admin')]
        },
        'update': {
            'permissions': [
                PERM(privilege='admin'),
                PERM(
                    privilege='update',
                    query_mod={'user': '******'},
                    doc_mod={'privileges': None},
                ),
            ],
            'query_args': {
                '_id': ATTR.ID()
            },
        },
        'delete': {
            'permissions': [
                PERM(privilege='admin'),
                PERM(privilege='delete', query_mod={'user': '******'}),
            ],
            'query_args': {
                '_id': ATTR.ID()
            },
        },
    }

    async def pre_create(self, skip_events, env, query, doc, payload):
        return (skip_events, env, query, doc, payload)

    async def pre_update(self, skip_events, env, query, doc, payload):
        # [DOC] Make sure no attrs overwriting would happen
        if 'attrs' in doc.keys():
            results = await self.read(skip_events=[Event.PERM],
                                      env=env,
                                      query=query)
            if not results.args.count:
                return self.status(status=400,
                                   msg='Group is invalid.',
                                   args={'code': 'INVALID_GROUP'})
            if results.args.count > 1:
                return self.status(
                    status=400,
                    msg=
                    'Updating group attrs can be done only to individual groups.',
                    args={'code': 'MULTI_ATTRS_UPDATE'},
                )
            results.args.docs[0]['attrs'].update({
                attr: doc['attrs'][attr]
                for attr in doc['attrs'].keys()
                if doc['attrs'][attr] != None and doc['attrs'][attr] != ''
            })
            doc['attrs'] = results.args.docs[0]['attrs']
        return (skip_events, env, query, doc, payload)
示例#3
0
class Diff(BaseModule):
    '''`Diff` module provides data type and controller for `Diff Workflow`. It is meant for use by internal calls only. Best practice to accessing diff docs is by creating proxy modules or writing LIMP methods that expose the diff docs.'''
    collection = 'diff'
    attrs = {
        'user':
        ATTR.ID(desc='`_id` of `User` doc the doc belongs to.'),
        'module':
        ATTR.STR(desc='Name of the module the original doc is part of.'),
        'doc':
        ATTR.ID(desc='`_id` of the original doc.'),
        'vars':
        ATTR.DICT(
            desc=
            'Key-value `dict` containing all attrs that have been updated from the original doc.',
            dict={
                '__key': ATTR.STR(),
                '__val': ATTR.ANY()
            }),
        'remarks':
        ATTR.STR(
            desc=
            'Human-readable remarks of the doc. This is introduced to allow developers to add log messages to diff docs.'
        ),
        'create_time':
        ATTR.DATETIME(
            desc='Python `datetime` ISO format of the doc creation.'),
    }
    defaults = {'doc': None, 'remarks': ''}
    methods = {
        'read': {
            'permissions': [PERM(privilege='read')]
        },
        'create': {
            'permissions': [PERM(privilege='__sys')]
        },
        'delete': {
            'permissions': [PERM(privilege='delete')]
        },
    }

    async def pre_create(self, skip_events, env, query, doc, payload):
        # [DOC] Detect non-_id update query:
        if '_id' not in query:
            results = await Config.modules[doc['module']
                                           ].read(skip_events=[Event.PERM],
                                                  env=env,
                                                  query=query)
            if results.args.count > 1:
                query.append(
                    {'_id': {
                        '$in': [doc._id for doc in results.args.docs]
                    }})
            elif results.args.count == 1:
                query.append({'_id': results.args.docs[0]._id})
            else:
                return self.status(status=400,
                                   msg='No update docs matched.',
                                   args={'code': 'NO_MATCH'})
        if '_id' in query and type(query['_id'][0]) == list:
            for i in range(len(query['_id'][0]) - 1):
                self.create(
                    skip_events=[Event.PERM],
                    env=env,
                    query=[{
                        '_id': query['_id'][0][i]
                    }],
                    doc=doc,
                )
            query['_id'][0] = query['_id'][0][-1]
        doc['doc'] = ObjectId(query['_id'][0])
        return (skip_events, env, query, doc, payload)
示例#4
0
文件: user.py 项目: 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
示例#5
0
文件: session.py 项目: masaar/limp
class Session(BaseModule):
    '''`Session` module provides data type and controller for sessions in LIMP eco-system. CRUD methods of the module are supposed to used for internal calls only, while methods `auth`, `reauth`, and `signout` are available for use by API as well as internal calls when needed.'''
    collection = 'sessions'
    attrs = {
        'user':
        ATTR.ID(desc='`_id` of `User` doc the doc belongs to.'),
        'groups':
        ATTR.LIST(
            desc=
            'List of `_id` for every group the session is authenticated against. This attr is set by `auth` method when called with `groups` Doc Arg for Controller Auth Sequence.',
            list=[
                ATTR.ID(
                    desc=
                    '`_id` of Group doc the session is authenticated against.')
            ]),
        'host_add':
        ATTR.IP(desc='IP of the host the user used to authenticate.'),
        'user_agent':
        ATTR.STR(desc='User-agent of the app the user used to authenticate.'),
        'expiry':
        ATTR.DATETIME(desc='Python `datetime` ISO format of session expiry.'),
        'token':
        ATTR.STR(desc='System-generated session token.'),
        'create_time':
        ATTR.DATETIME(
            desc='Python `datetime` ISO format of the doc creation.'),
    }
    defaults = {'groups': []}
    extns = {'user': EXTN(module='user', force=True)}
    methods = {
        'read': {
            'permissions':
            [PERM(privilege='read', query_mod={'user': '******'})]
        },
        'create': {
            'permissions': [PERM(privilege='create')]
        },
        'update': {
            'permissions': [
                PERM(
                    privilege='update',
                    query_mod={'user': '******'},
                    doc_mod={'user': None},
                )
            ],
            'query_args': {
                '_id': ATTR.ID()
            },
        },
        'delete': {
            'permissions':
            [PERM(privilege='delete', query_mod={'user': '******'})],
            'query_args': {
                '_id': ATTR.ID()
            },
        },
        'auth': {
            'permissions': [PERM(privilege='*')],
            'doc_args': []
        },
        'reauth': {
            'permissions': [PERM(privilege='*')],
            'query_args': [
                {
                    '_id': ATTR.ID(),
                    'hash': ATTR.STR(),
                    'groups': ATTR.LIST(list=[ATTR.ID()]),
                },
                {
                    '_id': ATTR.ID(),
                    'hash': ATTR.STR()
                },
            ],
        },
        'signout': {
            'permissions': [PERM(privilege='*')],
            'query_args': {
                '_id': ATTR.ID()
            },
        },
    }

    async def auth(self, skip_events=[], env={}, query=[], doc={}):
        for attr in Config.modules['user'].unique_attrs:
            if attr in doc.keys():
                key = attr
                break
        user_query = [{key: doc[key], f'{key}_hash': doc['hash'], '$limit': 1}]
        if 'groups' in doc.keys():
            user_query.append([{
                'groups': {
                    '$in': doc['groups']
                }
            }, {
                'privileges': {
                    '*': ['*']
                }
            }])
        user_results = await Config.modules['user'].read(
            skip_events=[Event.PERM, Event.ON], env=env, query=user_query)
        if not user_results.args.count:
            return self.status(
                status=403,
                msg='Wrong auth credentials.',
                args={'code': 'INVALID_CREDS'},
            )
        user = user_results.args.docs[0]

        if Event.ON not in skip_events:
            if user.status in ['banned', 'deleted']:
                return self.status(
                    status=403,
                    msg=f'User is {user.status}.',
                    args={'code': 'INVALID_USER'},
                )
            elif user.status == 'disabled_password':
                return self.status(
                    status=403,
                    msg='User password is disabled.',
                    args={'code': 'INVALID_USER'},
                )

        token = secrets.token_urlsafe(32)
        session = {
            'user':
            user._id,
            'groups':
            doc['groups'] if 'groups' in doc.keys() else [],
            'host_add':
            env['REMOTE_ADDR'],
            'user_agent':
            env['HTTP_USER_AGENT'],
            'expiry': (datetime.datetime.utcnow() +
                       datetime.timedelta(days=30)).isoformat(),
            'token':
            token,
        }

        results = await self.create(skip_events=[Event.PERM],
                                    env=env,
                                    doc=session)
        if results.status != 200:
            return results

        session['_id'] = results.args.docs[0]._id
        session['user'] = user
        results.args.docs[0] = BaseModel(session)

        # [DOC] read user privileges and return them
        user_results = await Config.modules['user'].read_privileges(
            skip_events=[Event.PERM], env=env, query=[{
                '_id': user._id
            }])
        if user_results.status != 200:
            return user_results
        results.args.docs[0]['user'] = user_results.args.docs[0]

        # [DOC] Create CONN_AUTH Analytic doc
        if Config.analytics_events['session_conn_auth']:
            analytic_doc = {
                'event': 'CONN_AUTH',
                'subevent': env['client_app'],
                'args': {
                    'user': user_results.args.docs[0]._id,
                    'session': results.args.docs[0]._id,
                    'REMOTE_ADDR': env['REMOTE_ADDR'],
                    'HTTP_USER_AGENT': env['HTTP_USER_AGENT'],
                },
            }
            analytic_results = await Config.modules['analytic'].create(
                skip_events=[Event.PERM], env=env, doc=analytic_doc)
            if analytic_results.status != 200:
                logger.error(
                    f'Failed to create \'Analytic\' doc: {analytic_doc}. Results: {analytic_results}'
                )
        # [DOC] Create USER_AUTH Analytic doc
        if Config.analytics_events['session_user_auth']:
            analytic_doc = {
                'event': 'USER_AUTH',
                'subevent': user_results.args.docs[0]._id,
                'args': {
                    'session': results.args.docs[0]._id,
                    'REMOTE_ADDR': env['REMOTE_ADDR'],
                    'HTTP_USER_AGENT': env['HTTP_USER_AGENT'],
                    'client_app': env['client_app'],
                },
            }
            analytic_results = await Config.modules['analytic'].create(
                skip_events=[Event.PERM], env=env, doc=analytic_doc)
            if analytic_results.status != 200:
                logger.error(
                    f'Failed to create \'Analytic\' doc: {analytic_doc}. Results: {analytic_results}'
                )

        return self.status(
            status=200,
            msg='You were successfully authed.',
            args={'session': results.args.docs[0]},
        )

    async def reauth(self, skip_events=[], env={}, query=[], doc={}):
        if str(query['_id'][0]) == 'f00000000000000000000012':
            return self.status(
                status=400,
                msg='Reauth is not required for \'__ANON\' user.',
                args={'code': 'ANON_REAUTH'},
            )
        session_query = [{'_id': query['_id'][0]}]
        if 'groups' in query:
            session_query.append({'groups': {'$in': query['groups'][0]}})
        results = await self.read(skip_events=[Event.PERM],
                                  env=env,
                                  query=session_query)
        if not results.args.count:
            return self.status(status=403,
                               msg='Session is invalid.',
                               args={'code': 'INVALID_SESSION'})

        if (jwt.encode({
                'token': results.args.docs[0].token
        }, results.args.docs[0].token).decode('utf-8').split('.')[1] !=
                query['hash'][0]):
            return self.status(
                status=403,
                msg='Reauth token hash invalid.',
                args={'code': 'INVALID_REAUTH_HASH'},
            )
        if results.args.docs[0].expiry < datetime.datetime.utcnow().isoformat(
        ):
            results = await self.delete(
                skip_events=[Event.PERM, Event.SOFT],
                env=env,
                query=[{
                    '_id': env['session']._id
                }],
            )
            return self.status(status=403,
                               msg='Session had expired.',
                               args={'code': 'SESSION_EXPIRED'})
        # [DOC] update user's last_login timestamp
        await Config.modules['user'].update(
            skip_events=[Event.PERM],
            env=env,
            query=[{
                '_id': results.args.docs[0].user
            }],
            doc={'login_time': datetime.datetime.utcnow().isoformat()},
        )
        await self.update(
            skip_events=[Event.PERM],
            env=env,
            query=[{
                '_id': results.args.docs[0]._id
            }],
            doc={
                'expiry': (datetime.datetime.utcnow() +
                           datetime.timedelta(days=30)).isoformat()
            },
        )
        # [DOC] read user privileges and return them
        user_results = await Config.modules['user'].read_privileges(
            skip_events=[Event.PERM],
            env=env,
            query=[{
                '_id': results.args.docs[0].user._id
            }],
        )
        results.args.docs[0]['user'] = user_results.args.docs[0]

        # [DOC] Create CONN_AUTH Analytic doc
        if Config.analytics_events['session_conn_reauth']:
            analytic_doc = {
                'event': 'CONN_REAUTH',
                'subevent': env['client_app'],
                'args': {
                    'user': user_results.args.docs[0]._id,
                    'session': results.args.docs[0]._id,
                    'REMOTE_ADDR': env['REMOTE_ADDR'],
                    'HTTP_USER_AGENT': env['HTTP_USER_AGENT'],
                },
            }
            analytic_results = await Config.modules['analytic'].create(
                skip_events=[Event.PERM], env=env, doc=analytic_doc)
            if analytic_results.status != 200:
                logger.error(
                    f'Failed to create \'Analytic\' doc: {analytic_doc}. Results: {analytic_results}'
                )
        # [DOC] Create USER_AUTH Analytic doc
        if Config.analytics_events['session_user_reauth']:
            analytic_doc = {
                'event': 'USER_REAUTH',
                'subevent': user_results.args.docs[0]._id,
                'args': {
                    'session': results.args.docs[0]._id,
                    'REMOTE_ADDR': env['REMOTE_ADDR'],
                    'HTTP_USER_AGENT': env['HTTP_USER_AGENT'],
                    'client_app': env['client_app'],
                },
            }
            analytic_results = await Config.modules['analytic'].create(
                skip_events=[Event.PERM], env=env, doc=analytic_doc)
            if analytic_results.status != 200:
                logger.error(
                    f'Failed to create \'Analytic\' doc: {analytic_doc}. Results: {analytic_results}'
                )

        return self.status(
            status=200,
            msg='You were succefully reauthed.',
            args={'session': results.args.docs[0]},
        )

    async def signout(self, skip_events=[], env={}, query=[], doc={}):
        if str(query['_id'][0]) == 'f00000000000000000000012':
            return self.status(
                status=400,
                msg='Singout is not allowed for \'__ANON\' user.',
                args={'code': 'ANON_SIGNOUT'},
            )
        results = await self.read(skip_events=[Event.PERM],
                                  env=env,
                                  query=[{
                                      '_id': query['_id'][0]
                                  }])

        if not results.args.count:
            return self.status(status=403,
                               msg='Session is invalid.',
                               args={'code': 'INVALID_SESSION'})
        results = await self.delete(skip_events=[Event.PERM],
                                    env=env,
                                    query=[{
                                        '_id': env['session']._id
                                    }])

        # [DOC] Create CONN_AUTH Analytic doc
        if Config.analytics_events['session_conn_deauth']:
            analytic_doc = {
                'event': 'CONN_DEAUTH',
                'subevent': env['client_app'],
                'args': {
                    'user': env['session'].user._id,
                    'session': env['session']._id,
                    'REMOTE_ADDR': env['REMOTE_ADDR'],
                    'HTTP_USER_AGENT': env['HTTP_USER_AGENT'],
                },
            }
            analytic_results = await Config.modules['analytic'].create(
                skip_events=[Event.PERM], env=env, doc=analytic_doc)
            if analytic_results.status != 200:
                logger.error(
                    f'Failed to create \'Analytic\' doc: {analytic_doc}. Results: {analytic_results}'
                )
        # [DOC] Create USER_AUTH Analytic doc
        if Config.analytics_events['session_user_deauth']:
            analytic_doc = {
                'event': 'USER_DEAUTH',
                'subevent': env['session'].user._id,
                'args': {
                    'session': env['session']._id,
                    'REMOTE_ADDR': env['REMOTE_ADDR'],
                    'HTTP_USER_AGENT': env['HTTP_USER_AGENT'],
                    'client_app': env['client_app'],
                },
            }
            analytic_results = await Config.modules['analytic'].create(
                skip_events=[Event.PERM], env=env, doc=analytic_doc)
            if analytic_results.status != 200:
                logger.error(
                    f'Failed to create \'Analytic\' doc: {analytic_doc}. Results: {analytic_results}'
                )

        return self.status(
            status=200,
            msg='You are successfully signed-out.',
            args={'session': DictObj({'_id': 'f00000000000000000000012'})},
        )

    def check_permissions(
        self,
        skip_events: List[str],
        env: Dict[str, Any],
        query: Union[LIMP_QUERY, Query],
        doc: LIMP_DOC,
        module: BaseModule,
        permissions: List[PERM],
    ):
        user = env['session'].user

        permissions = copy.deepcopy(permissions)

        for permission in permissions:
            logger.debug(
                f'checking permission: {permission} against: {user.privileges}'
            )
            permission_pass = False
            if permission.privilege == '*':
                permission_pass = True

            if not permission_pass:
                if permission.privilege.find('.') == -1:
                    permission_module = module.module_name
                    permission_attr = permission.privilege
                elif permission.privilege.find('.') != -1:
                    permission_module = permission.privilege.split('.')[0]
                    permission_attr = permission.privilege.split('.')[1]

                if ('*' in user.privileges.keys()
                        and permission_module not in user.privileges.keys()):
                    user.privileges[permission_module] = copy.deepcopy(
                        user.privileges['*'])
                if permission_module in user.privileges.keys():
                    if (type(user.privileges[permission_module]) == list
                            and '*' in user.privileges[permission_module]):
                        user.privileges[permission_module] += copy.deepcopy(
                            module.privileges)
                if permission_module not in user.privileges.keys():
                    user.privileges[permission_module] = []

                if permission_attr in user.privileges[permission_module]:
                    permission_pass = True

            if permission_pass:
                query = self._parse_permission_args(
                    skip_events=skip_events,
                    env=env,
                    query=query,
                    doc=doc,
                    permission_args=permission.query_mod,
                )
                doc = self._parse_permission_args(
                    skip_events=skip_events,
                    env=env,
                    query=query,
                    doc=doc,
                    permission_args=permission.doc_mod,
                )
                return {'query': query, 'doc': doc}
        # [DOC] If all permission checks fail
        return False

    def _parse_permission_args(
        self,
        skip_events: List[str],
        env: Dict[str, Any],
        query: Union[LIMP_QUERY, Query],
        doc: LIMP_DOC,
        permission_args: Any,
    ):
        user = env['session'].user

        if type(permission_args) == list:
            args_iter = range(len(permission_args))
        elif type(permission_args) == dict:
            args_iter = list(permission_args.keys())

        for j in args_iter:
            if type(permission_args[j]) == ATTR_MOD:
                # [DOC] If attr is of type ATTR_MOD, call condition callable
                if permission_args[j].condition(skip_events=skip_events,
                                                env=env,
                                                query=query,
                                                doc=doc):
                    # [DOC] If condition return is True, update attr value
                    if callable(permission_args[j].default):
                        permission_args[j] = permission_args[j].default(
                            skip_events=skip_events,
                            env=env,
                            query=query,
                            doc=doc)
                        if type(permission_args[j]) == Exception:
                            raise permission_args[j]
                    else:
                        permission_args[j] = permission_args[j].default
            elif type(permission_args[j]) == dict:
                # [DOC] Check opers
                for oper in [
                        '$gt',
                        '$lt',
                        '$gte',
                        '$lte',
                        '$bet',
                        '$ne',
                        '$regex',
                        '$all',
                        '$in',
                ]:
                    if oper in permission_args[j].keys():
                        if oper == '$bet':
                            permission_args[j][
                                '$bet'] = self._parse_permission_args(
                                    skip_events=skip_events,
                                    env=env,
                                    query=query,
                                    doc=doc,
                                    permission_args=permission_args[j]['$bet'],
                                )
                        else:
                            permission_args[j][
                                oper] = self._parse_permission_args(
                                    skip_events=skip_events,
                                    env=env,
                                    query=query,
                                    doc=doc,
                                    permission_args=[permission_args[j][oper]],
                                )[0]
                        # [DOC] Continue the iteration
                        continue
                # [DOC] Child args, parse
                permission_args[j] = self._parse_permission_args(
                    skip_events=skip_events,
                    env=env,
                    query=query,
                    doc=doc,
                    permission_args=permission_args[j],
                )
            elif type(permission_args[j]) == list:
                permission_args[j] = self._parse_permission_args(
                    skip_events=skip_events,
                    env=env,
                    query=query,
                    doc=doc,
                    permission_args=permission_args[j],
                )
            elif type(permission_args[j]) == str:
                # [DOC] Check for variables
                if permission_args[j] == '$__user':
                    permission_args[j] = user._id
                elif permission_args[j].startswith('$__user.'):
                    permission_args[j] = extract_attr(
                        scope=user,
                        attr_path=permission_args[j].replace(
                            '$__user.', '$__'),
                    )
                elif permission_args[j] == '$__access':
                    permission_args[j] = {
                        '$__user': user._id,
                        '$__groups': user.groups
                    }
                elif permission_args[j] == '$__datetime':
                    permission_args[j] = datetime.datetime.utcnow().isoformat()
                elif permission_args[j] == '$__date':
                    permission_args[j] = datetime.date.today().isoformat()
                elif permission_args[j] == '$__time':
                    permission_args[j] = datetime.datetime.now().time(
                    ).isoformat()

        return permission_args
示例#6
0
class Realm(BaseModule):
	'''`Realm` module module provides data type and controller for Realm Mode in LIMP eco-system.'''
	collection = 'realms'
	attrs = {
		'user': ATTR.ID(desc='`_id` of `User` doc the doc belongs to. This is also the ADMIN of the realm.'),
		'name': ATTR.STR(desc='Name of the realm. This is both readable as well as unique name.'),
		'default': ATTR.ID(desc='`_id` of `Group` doc that serves as `DEFAULT` group of the realm.'),
		'create_time': ATTR.DATETIME(desc='Python `datetime` ISO format of the doc creation.'),
	}
	methods = {
		'read': {'permissions': [PERM(privilege='read')]},
		'create': {'permissions': [PERM(privilege='create')]},
		'update': {
			'permissions': [
				PERM(privilege='update', doc_mod={'user': None, 'create_time': None})
			],
			'query_args': {'_id': ATTR.ID()},
		},
		'delete': {
			'permissions': [PERM(privilege='delete')],
			'query_args': {'_id': ATTR.ID()},
		},
	}

	async def pre_create(self, skip_events, env, query, doc, payload):
		user_doc = {attr: doc['user'][attr] for attr in Config.user_attrs}
		user_doc.update(
			{
				'locale': Config.locale,
				'groups': [],
				'privileges': {'*': '*'},
				'status': 'active',
				'attrs': {},
				'realm': doc['name'],
			}
		)
		user_results = await Config.modules['user'].create(
			skip_events=[Event.PERM, Event.ARGS, Event.PRE], env=env, doc=user_doc
		)
		if user_results.status != 200:
			return user_results
		user = user_results.args.docs[0]

		group_results = await Config.modules['group'].create(
			skip_events=[Event.PERM, Event.ARGS],
			env=env,
			doc={
				'user': user._id,
				'name': {locale: '__DEFAULT' for locale in Config.locales},
				'bio': {locale: '__DEFAULT' for locale in Config.locales},
				'privileges': Config.default_privileges,
				'attrs': {},
				'realm': doc['name'],
			},
		)
		if group_results.status != 200:
			return group_results
		group = group_results.args.docs[0]

		skip_events.append(Event.ARGS)
		doc['user'] = user._id
		doc['default'] = group._id
		return (skip_events, env, query, doc, payload)

	async def on_create(self, results, skip_events, env, query, doc, payload):
		for doc in results['docs']:
			realm_results = await self.read(
				skip_events=[Event.PERM, Event.ARGS], env=env, query=[{'_id': doc._id}]
			)
			realm = realm_results.args.docs[0]
			Config._realms[realm.name] = realm
			Config._sys_docs[realm._id] = {'module': 'realm'}
		return (results, skip_events, env, query, doc, payload)
示例#7
0
文件: utils.py 项目: masaar/limp
def import_modules(*, packages=None):
    import modules as package
    from base_module import BaseModule
    from config import Config
    from test import TEST

    # [DOC] Assign required variables
    modules: Dict[str, BaseModule] = {}
    modules_packages: Dict[str, List[str]] = {}
    user_config = {
        'user_attrs': {},
        'user_auth_attrs': [],
        'user_attrs_defaults': {}
    }

    # [DOC] Iterate over packages in modules folder
    package_prefix = package.__name__ + '.'
    for _, pkgname, _ in pkgutil.iter_modules(package.__path__,
                                              package_prefix):  # pylint: disable=unused-variable
        # [DOC] Check if package should be skipped
        if packages and pkgname.replace('modules.', '') not in packages:
            logger.debug(f'Skipping package: {pkgname}')
            continue
        logger.debug(f'Importing package: {pkgname}')

        # [DOC] Load package and attempt to load config
        child_package = __import__(pkgname, fromlist='*')
        for k, v in child_package.config().items():
            if k == 'packages_versions':
                Config.packages_versions[pkgname.replace('modules.', '')] = v
            elif k in ['tests', 'l10n']:
                logger.warning(
                    f'Defining \'{k}\' in package config is not recommended. define your values in separate Python module with the name \'__{k}__\'. Refer to LIMP Docs for more.'
                )
            elif k == 'envs':
                if Config.env:
                    if Config.env in v.keys():
                        for kk, vv in v[Config.env].items():
                            setattr(Config, kk, vv)
                    else:
                        logger.warning(
                            f'Package \'{pkgname.replace("modules.", "")}\' has \'envs\' Config Attr defined, but \'env\' defintion \'{Config.env}\' not found.'
                        )
            elif k in ['user_attrs', 'user_auth_attrs', 'user_attrs_defaults']:
                user_config[k] = v
                setattr(Config, k, v)
            elif type(v) == dict:
                if not getattr(Config, k):
                    setattr(Config, k, {})
                getattr(Config, k).update(v)
            else:
                setattr(Config, k, v)

        # [DOC] Iterate over python modules in package
        child_prefix = child_package.__name__ + '.'
        for importer, modname, ispkg in pkgutil.iter_modules(
                child_package.__path__, child_prefix):
            # [DOC] Iterate over python classes in module
            module = __import__(modname, fromlist='*')
            if modname.endswith('__tests__'):
                for test_name in dir(module):
                    if type(getattr(module, test_name)) == TEST:
                        Config.tests[test_name] = getattr(module, test_name)
                continue
            elif modname.endswith('__l10n__'):
                for l10n_name in dir(module):
                    if type(getattr(module, l10n_name)) == L10N:
                        Config.l10n[l10n_name] = getattr(module, l10n_name)
                continue
            for clsname in dir(module):
                # [DOC] Confirm class is subclass of BaseModule
                if (clsname != 'BaseModule'
                        and inspect.isclass(getattr(module, clsname))
                        and issubclass(getattr(module, clsname), BaseModule)):
                    # [DOC] Deny loading LIMPd-reserved named LIMP modules
                    if clsname.lower() in ['conn', 'heart', 'file', 'watch']:
                        logger.error(
                            f'Module with LIMPd-reserved name \'{clsname.lower()}\' was found. Exiting.'
                        )
                        exit()
                    # [DOC] Load LIMP module and assign module_name attr
                    cls = getattr(module, clsname)
                    module_name = re.sub(r'([A-Z])', r'_\1',
                                         clsname[0].lower() +
                                         clsname[1:]).lower()
                    # [DOC] Deny duplicat LIMP modules names
                    if module_name in modules.keys():
                        logger.error(
                            f'Duplicate module name \'{module_name}\'. Exiting.'
                        )
                        exit()
                    # [DOC] Add module to loaded modules dict
                    modules[module_name] = cls()
                    if pkgname not in modules_packages.keys():
                        modules_packages[pkgname] = []
                    modules_packages[pkgname].append(module_name)
    # [DOC] Update User, Session modules with populated attrs
    modules['user'].attrs.update(user_config['user_attrs'])
    modules['user'].defaults['locale'] = Config.locale
    for attr in user_config['user_auth_attrs']:
        modules['user'].unique_attrs.append(attr)
        modules['user'].attrs[f'{attr}_hash'] = ATTR.STR()
        modules['session'].methods['auth']['doc_args'].append({
            'hash':
            ATTR.STR(),
            attr:
            user_config['user_attrs'][attr],
            'groups':
            ATTR.LIST(list=[ATTR.ID()]),
        })
        modules['session'].methods['auth']['doc_args'].append({
            'hash':
            ATTR.STR(),
            attr:
            user_config['user_attrs'][attr]
        })
    modules['user'].defaults.update(user_config['user_attrs_defaults'])
    # [DOC] Call update_modules, effectively finalise initlising modules
    Config.modules = modules
    for module in modules.values():
        module._initialise()
    # [DOC] Write api_ref if generate_ref mode
    if Config.generate_ref:
        generate_ref(modules_packages=modules_packages, modules=modules)
示例#8
0
文件: setting.py 项目: masaar/limp
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)