Why Gemfury? Push, build, and install  RubyGems npm packages Python packages Maven artifacts PHP packages Go Modules Debian packages RPM packages NuGet packages

Repository URL to install this package:

Details    
django-iam / mixins.py
Size: Mime:
from typing import Any

import rules
from django.db import models

from .registry import get_registered_roles


class RolePredicateMixin(models.Model):
    """
    Mixin used on profile classes. Provides class method `get_predicate` to create rules predicates to check if a user
    has a profile or not.
    Provides `parent` attribute. If a role has a parent, and a user has the parent profile, the user will have all
    permissions associated with the child role too.
    """
    parent: 'RolePredicateMixin' = None

    class Meta:
        abstract = True

    @classmethod
    def get_predicate(cls, extra_check=None):
        """
        Return a rules predicate that checks if a user has a corresponding profile thus having that role.

        :param extra_check: Optionally check extra parameters on the user's profile instance.
        :return: Predicate
        """

        def has_role(user):
            if user.is_superuser:
                return True

            profile_instance = user.get_or_set_role(cls._meta.model)
            return bool(profile_instance) and (not extra_check or extra_check(profile_instance))

        role_name = cls._meta.verbose_name.rstrip(' profile').replace(' ', '_')
        predicate = rules.predicate(has_role, name=f'is_{role_name}')
        if cls.parent:
            return predicate | cls.parent.get_predicate(extra_check)
        return predicate


class IAMUserMixin:
    """
    Mixin used on custom user models that use IAM for their permission management. Provides interfaces to the user model
    to interact with the IAM, and cache permissions.
    """

    def __init__(self, *args, **kwargs):
        self._roles = {}  # { ProfileModel: instance | False }
        super(IAMUserMixin, self).__init__(*args, **kwargs)

    @property
    def roles(self):
        return self._roles

    def _set_role(self, model_cls) -> Any | bool:
        """
        Check if this user has a particular profile denoted by `model_cls`. If they do cache the profile instance. If
        not cache a False value, so we don't need to hit the database again for this check.

        :param model_cls: The model class of the profile to be checked.
        :return: The profile instance belonging to the user or False
        """
        try:
            profile_instance = model_cls.objects.available().get(user=self)
        except model_cls.DoesNotExist:
            profile_instance = False
        self._roles[model_cls] = profile_instance
        return profile_instance

    def get_or_set_role(self, model_cls):
        """
        Check if this user has a particular profile denoted by `model_cls`. First hit the cache. If there is no value
        in the cache, hit the database. If the user has the profile cache the profile instance. If
        not cache a False value, so we don't need to hit the database again for this check.

        :param model_cls: The model class of the profile to be checked.
        :return: The profile instance belonging to the user or False
        """
        profile_instance = self.roles.get(model_cls)  # None | False | instance
        if profile_instance is None:
            profile_instance = self._set_role(model_cls)
        return profile_instance

    def load_roles(self):
        """
        Load all the roles (and profiles) this user has in their cache.
        """
        self._roles = {}
        for model_cls in get_registered_roles():
            self._set_role(model_cls)