Example #1
0
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)
Example #2
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)
Example #3
0
File: group.py Project: 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)
Example #4
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 #5
0
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
Example #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)