Esempio n. 1
0
    async def update_groups(self, new_groups: list):
        from app.auth import userdb

        # new_groups = set(filter(valid_str_only, new_groups))
        valid_groups = set(await Group.filter(name__in=new_groups
                                              ).values_list('name', flat=True))
        if not valid_groups:
            return
        existing_groups = set(await self.get_groups())
        toadd: set = valid_groups - existing_groups
        toremove: set = existing_groups - valid_groups

        if toadd:
            toadd_obj = await Group.filter(name__in=toadd).only('id', 'name')
            if toadd_obj:
                await self.groups.add(*toadd_obj)

        if toremove:
            toremove_obj = await Group.filter(name__in=toremove
                                              ).only('id', 'name')
            if toremove_obj:
                await self.groups.remove(*toremove_obj)

        partialkey = s.CACHE_USERNAME.format(self.id)
        if user_dict := red.get(partialkey):
            user_dict = cache.restoreuser_dict(user_dict)
            user = userdb.usercomplete(**user_dict)
Esempio n. 2
0
    async def add_group(self, *groups) -> Optional[list]:
        """
        Add groups to a user and update redis
        :param groups:  Groups to add
        :return:        list The user's groups
        """
        from app.auth import userdb

        groups = list(filter(None, groups))
        # groups = list(filter(valid_str_only, groups))
        if not groups:
            return

        groups = await Group.filter(name__in=groups).only('id', 'name')
        if not groups:
            return

        await self.groups.add(*groups)
        names = await Group.filter(group_users__id=self.id) \
            .values_list('name', flat=True)

        partialkey = s.CACHE_USERNAME.format(self.id)
        if user_dict := red.get(partialkey):
            user_dict = cache.restoreuser_dict(user_dict)
            user = userdb.usercomplete(**user_dict)
Esempio n. 3
0
    async def create_group(cls, name: str, summary: Optional[str] = ''):
        """
        Create a group and update the cache
        """
        if await Group.get_or_none(name=name):
            return
        group = await Group.create(name=name, summary=summary)

        # Update cache
        if groups := red.get('groups'):
            groups.append(name)
Esempio n. 4
0
    async def ab():
        await tempdb()
        usermod_temp = await UserMod.get(email=VERIFIED_EMAIL_DEMO).only('id')
        await usermod_temp.get_and_cache(usermod_temp.id, model=True)

        partialkey = s.CACHE_USERNAME.format(usermod_temp.id)
        return red.get(partialkey)
Esempio n. 5
0
    async def get_permissions(self, perm_type: Optional[str] = None) -> list:
        """
        Collate all the permissions a user has from groups + user
        :param perm_type:   user or group
        :return:            List of permission codes to match data with
        """
        group_perms, user_perms = [], []
        groups = await self.get_groups()

        if perm_type is None or perm_type == 'group':
            for group in groups:
                partialkey = s.CACHE_GROUPNAME.format(group)
                if perms := red.get(partialkey):
                    group_perms += perms
                else:
                    perms = Group.get_and_cache(group)
                    group_perms += perms
                    red.set(partialkey, perms)
Esempio n. 6
0
 async def get_permissions(cls, *groups, debug=False) -> Union[list, tuple]:
     """
     Get a consolidated list of permissions for groups. Uses cache else query.
     :param groups:  Names of groups
     :param debug:   Return debug data for tests
     :return:        List of permissions for that group
     """
     debug = debug if s.DEBUG else False
     allperms, sources = set(), []
     for group in groups:
         partialkey = s.CACHE_GROUPNAME.format(group)
         if perms := red.get(partialkey):
             sources.append('CACHE')
         else:
             sources.append('QUERY')
             perms = await cls.get_and_cache(group)
         # ic(group, perms)
         if perms:
             allperms.update(perms)
Esempio n. 7
0
    async def delete_group(cls, name: str):
        """
        Delete a group
        :param name:    Name of group
        :return:
        """
        try:
            # Delete from db
            if name:
                if group := await Group.get_or_none(name=name).only('id'):
                    await group.delete()

                    # Update cache
                    partialkey = s.CACHE_GROUPNAME.format(name)
                    red.delete(partialkey)
                    if groups := red.get('groups'):
                        groups = list(filter(lambda y: y != name, groups))
                    else:
                        groups = await Group.all().values_list('name',
                                                               flat=True)
                    red.set('groups', groups, clear=True)
                    return True
Esempio n. 8
0
 async def get_perms(self) -> list:
     """
     Get group + user perms from cache else query.
     :return: List of perms
     """
     allperms = set()
 
     # Get group perms from cache
     for group in self.groups:                                                   # noqa
         group_partialkey = s.CACHE_GROUPNAME.format(group)
         if red.exists(group_partialkey):
             cached_perms = red.get(group_partialkey)
             allperms.update(cached_perms)
         else:
             queried_perms = await Group.get_and_cache(group)
             allperms.update(queried_perms)
     # Include any user perms if any
     allperms.update(self.permissions)                                           # noqa
     return list(allperms)
Esempio n. 9
0
    async def get_and_cache(cls, group: str) -> list:
        """
        Get a group's permissions and cache it for future use. Replaces data if exists.
        Only one group must be given so each can be cached separately.
        :param group:   Group name
        :return:        list
        """
        perms = await Permission.filter(groups__name=group
                                        ).values_list('code', flat=True)
        perms = perms or []

        if perms:
            # Save back to cache
            partialkey = s.CACHE_GROUPNAME.format(group)
            red.set(partialkey, perms, ttl=-1, clear=True)

            grouplist = red.exists('groups') and red.get('groups') or []
            if group not in grouplist:
                grouplist.append(group)
                red.set('groups', grouplist, clear=True)
        return perms
Esempio n. 10
0
    async def get_data(cls, id, force_query=False, debug=False):
        """
        Get the UserDBComplete data whether it be via cache or query. Checks cache first else query.
        :param force_query: Force use query instead of checking the cache
        :param id:          User id
        :param debug:       Debug data for tests
        :return:            UserDBComplete/tuple or None
        """
        from app.auth import userdb

        debug = debug if s.DEBUG else False
        partialkey = s.CACHE_USERNAME.format(id)
        if not force_query and red.exists(partialkey):
            source = 'CACHE'
            user_data = cache.restoreuser_dict(red.get(partialkey))
            user = userdb.usercomplete(**user_data)
        else:
            source = 'QUERY'
            user = await UserMod.get_and_cache(id)

        if debug:
            return user, source
        return user
Esempio n. 11
0
    async def get_groups(self,
                         force_query=False,
                         debug=False) -> Union[list, tuple]:
        """
        Return a user's groups as a list from the cache or not. Uses cache else query.
        :param force_query: Don't use cache
        :param debug:       Return debug data for tests
        :return:            List of groups if not debug
        """
        from app.auth import userdb

        debug = debug if s.DEBUG else False
        partialkey = s.CACHE_USERNAME.format(self.id)
        if not force_query and red.exists(partialkey):
            user_dict = red.get(partialkey)
            source = 'CACHE'
            user_dict = cache.restoreuser_dict(user_dict)
            user = userdb.usercomplete(**user_dict)
        else:
            source = 'QUERY'
            user = await UserMod.get_and_cache(self.id)
        if debug:
            return user.groups, source
        return user.groups
Esempio n. 12
0
 async def get(self, id: UUID4) -> Optional[UD]:  # noqa
     partialkey = s.CACHE_USERNAME.format(str(id))
     if user_dict := red.get(partialkey):
         # ic('CACHE')
         user_dict = cache.restoreuser_dict(user_dict)
Esempio n. 13
0
def test_get_permissions(tempdb, loop, groups, perms, remove, src):
    async def ab():
        await tempdb()
        return await Group.get_permissions(*listify(groups), debug=True)

    groups = listify(groups)
    for idx, group in enumerate(groups):
        partialkey = s.CACHE_GROUPNAME.format(group)
        remove = listify(remove)
        if remove[idx]:
            red.delete(partialkey)
            assert not red.get(partialkey)
            assert not red.exists(partialkey)

    loop.run_until_complete(ab())
    # ic(x)
    # allperms, sources = loop.run_until_complete(ab())
    # assert Counter(allperms) == Counter(perms)
    # assert Counter(sources) == Counter(listify(src))


















# param = [
#     ('user.create', 'AdminGroup', True),
#     ('user.create', 'NoaddGroup', True),
#     ('page.create', 'ContentGroup', True),
#     ('page.create', 'NoaddGroup', False),
#     ('page.create', 'abc', False),
#     ('', 'abc', False),
#     ('page.create', '', False),
# ]
# @pytest.mark.parametrize('perm, group, out', param)
# @pytest.mark.focus
# def test_is_group(loop, perm, group, out):
#     async def ab():
#         assert await Permission.is_group(perm, group) == out
#     loop.run_until_complete(ab())


# # @pytest.mark.focus
# def test_abc(loop, tempdb):
#     from app.authentication import Option
#
#     async def ab():
#         await tempdb()
#         await Option.create(name='foo', value='bar')
#         opt = await Option.all()
#         ic(opt)
#
#     loop.run_until_complete(ab())
Esempio n. 14
0
def apik_of_uuid(uuid_):
    """Given a uuid (key) retrieves the db id"""
    return red.get(_KEY_UUID_APIK.format(uuid_))
Esempio n. 15
0
class UserMod(DTMixin, TortoiseBaseUserModel):
    username = fields.CharField(max_length=50, null=True)
    first_name = fields.CharField(max_length=191, default='')
    middle_name = fields.CharField(max_length=191, default='')
    last_name = fields.CharField(max_length=191, default='')

    civil = fields.CharField(max_length=20, default='')
    bday = fields.DateField(null=True)
    mobile = fields.CharField(max_length=50, default='')
    telephone = fields.CharField(max_length=50, default='')
    avatar = fields.CharField(max_length=191, default='')
    status = fields.CharField(max_length=20, default='')
    bio = fields.CharField(max_length=191, default='')
    address1 = fields.CharField(max_length=191, default='')
    address2 = fields.CharField(max_length=191, default='')
    country = fields.CharField(max_length=2, default='')
    zipcode = fields.CharField(max_length=20, default='')
    timezone = fields.CharField(max_length=10, default='+00:00')
    website = fields.CharField(max_length=20, default='')
    last_login = fields.DatetimeField(null=True)

    groups = fields.ManyToManyField('models.Group',
                                    related_name='group_users',
                                    through='auth_user_groups',
                                    backward_key='user_id')
    permissions = fields.ManyToManyField('models.Permission',
                                         related_name='permission_users',
                                         through='auth_user_permissions',
                                         backward_key='user_id')

    full = Manager()

    class Meta:
        table = 'auth_user'
        manager = ActiveManager()

    def __str__(self):
        return modstr(self, 'id')

    @property
    def fullname(self):
        return f'{self.first_name} {self.last_name}'.strip()

    @property
    async def display_name(self):
        if self.username:
            return self.username
        elif self.fullname:
            return self.fullname.split()[0]
        else:
            emailname = self.email.split('@')[0]
            return ' '.join(emailname.split('.'))

    # @classmethod
    # def has_perm(cls, id: str, *perms):
    #     partialkey = s.CACHE_USERNAME.format('id')
    #     if red.exists(partialkey):
    #         groups = red.get(partialkey).get('groups')

    async def to_dict(self,
                      exclude: Optional[List[str]] = None,
                      prefetch=False) -> dict:
        """
        Converts a UserMod instance into UserModComplete. Included fields are based on UserDB +
        groups, options, and permissions.
        :param exclude:     Fields not to explicitly include
        :param prefetch:    Query used prefetch_related to save on db hits
        :return:            UserDBComplete
        """
        d = {}
        exclude = ['created_at', 'deleted_at', 'updated_at'
                   ] if exclude is None else exclude
        for field in self._meta.db_fields:
            if hasattr(self, field) and field not in exclude:
                d[field] = getattr(self, field)
                if field == 'id':
                    d[field] = str(d[field])

        if hasattr(self, 'groups'):
            if prefetch:
                d['groups'] = [i.name for i in self.groups]
            else:
                d['groups'] = await self.groups.all().values_list('name',
                                                                  flat=True)
        if hasattr(self, 'options'):
            if prefetch:
                d['options'] = {i.name: i.value for i in self.options}
            else:
                d['options'] = {
                    i.name: i.value
                    for i in await self.options.all().only(
                        'id', 'name', 'value', 'is_active') if i.is_active
                }
        if hasattr(self, 'permissions'):
            if prefetch:
                d['permissions'] = [i.code for i in self.permissions]
            else:
                d['permissions'] = await self.permissions.all().values_list(
                    'code', flat=True)
        # ic(d)
        return d

    @classmethod
    async def get_and_cache(cls, id, model=False):
        """
        Get a user's cachable data and cache it for future use. Replaces data if exists.
        Similar to the dependency current_user.
        :param id:      User id as str
        :param model:   Also return the UserMod instance
        :return:        DOESN'T NEED cache.restoreuser() since data is from the db not redis.
                        The id key in the hash is already formatted to a str from UUID.
                        Can be None if user doesn't exist.
        """
        from app.auth import userdb

        query = UserMod.get_or_none(pk=id) \
            .prefetch_related(
                Prefetch('groups', queryset=Group.all().only('id', 'name')),
                Prefetch('options', queryset=Option.all().only('user_id', 'name', 'value')),
                Prefetch('permissions', queryset=Permission.filter(deleted_at=None).only('id', 'code'))
            )
        if userdb.oauth_account_model is not None:
            query = query.prefetch_related("oauth_accounts")
        usermod = await query.only(*userdb.select_fields)

        if usermod:
            user_dict = await usermod.to_dict(prefetch=True)
            partialkey = s.CACHE_USERNAME.format(id)
            red.set(partialkey, cache.prepareuser_dict(user_dict), clear=True)

            if model:
                return userdb.usercomplete(**user_dict), usermod
            return userdb.usercomplete(**user_dict)

    @classmethod
    async def get_data(cls, id, force_query=False, debug=False):
        """
        Get the UserDBComplete data whether it be via cache or query. Checks cache first else query.
        :param force_query: Force use query instead of checking the cache
        :param id:          User id
        :param debug:       Debug data for tests
        :return:            UserDBComplete/tuple or None
        """
        from app.auth import userdb

        debug = debug if s.DEBUG else False
        partialkey = s.CACHE_USERNAME.format(id)
        if not force_query and red.exists(partialkey):
            source = 'CACHE'
            user_data = cache.restoreuser_dict(red.get(partialkey))
            user = userdb.usercomplete(**user_data)
        else:
            source = 'QUERY'
            user = await UserMod.get_and_cache(id)

        if debug:
            return user, source
        return user

    async def get_permissions(self, perm_type: Optional[str] = None) -> list:
        """
        Collate all the permissions a user has from groups + user
        :param perm_type:   user or group
        :return:            List of permission codes to match data with
        """
        group_perms, user_perms = [], []
        groups = await self.get_groups()

        if perm_type is None or perm_type == 'group':
            for group in groups:
                partialkey = s.CACHE_GROUPNAME.format(group)
                if perms := red.get(partialkey):
                    group_perms += perms
                else:
                    perms = Group.get_and_cache(group)
                    group_perms += perms
                    red.set(partialkey, perms)

        if perm_type is None or perm_type == 'user':
            partialkey = s.CACHE_USERNAME.format(self.id)
            if user_dict := red.get(partialkey):
                user_dict = cache.restoreuser_dict(user_dict)
                user_perms = user_dict.get('permissions')
            else:
                user = await UserMod.get_and_cache(self.id)
                user_perms = user.permissions