HEX
Server: Apache/2.4.52 (Ubuntu)
System: Linux spn-python 5.15.0-89-generic #99-Ubuntu SMP Mon Oct 30 20:42:41 UTC 2023 x86_64
User: arjun (1000)
PHP: 8.1.2-1ubuntu2.20
Disabled: NONE
Upload Files
File: //home/arjun/projects/env/lib/python3.10/site-packages/bouncer/models.py
from bouncer.constants import *
import inspect
import sys

PY3 = sys.version_info[0] == 3

if PY3:
    string_types = str,
else:
    string_types = basestring,


def listify(list_or_single):
    if isinstance(list_or_single, (list, tuple)):
        return list_or_single
    else:
        return [list_or_single]


class Rule(object):
    def __init__(self, base_behavior, action, subject, conditions=None, **conditions_hash):
        self.base_behavior = base_behavior
        self.actions = listify(action)
        self.subjects = listify(subject)
        self.conditions = None

        if conditions is not None and len(conditions_hash) > 0:
            raise TypeError('cannot provide both a condition method and hash -- pick one')
        elif conditions is not None:
            self.conditions = conditions
        elif len(conditions_hash) > 0:
            self.conditions = conditions_hash

    def __repr__(self):
        return "%s(%r)" % (self.__class__, self.__dict__)

    # Matches both the subject and action, not necessarily the conditions
    def is_relavant(self, action, subject):
        # print "Maches Action: [{}]".format(self.matches_action(action))
        # print "Maches Subject: [{}]".format(self.matches_subject(subject))
        return self.matches_action(action) and self.matches_subject(subject)

    @property
    def expanded_actions(self):
        return self._expanded_actions

    @expanded_actions.setter
    def expanded_actions(self, value):
        self._expanded_actions = value

    def matches_action(self, action):
        return MANAGE in self.expanded_actions or action in self.expanded_actions

    def matches_subject(self, subject):
        return ALL in self.subjects or subject in self.subjects or self.matches_subject_class(subject)

    # Matches the conditions
    def matches_conditions(self, action, subject):
        if self.conditions is None:
            return True
        elif inspect.isclass(subject):
            # IMPORTANT we only check conditions if we are testing specific instances of a class NOT of the classes
            # themselves
            return True
        elif isinstance(self.conditions, dict):
            return self.matches_dict_conditions(action, subject)
        else:
            return self.matches_function_conditions(action, subject)

    def matches_dict_conditions(self, action, subject):
        return all(self.matches_hash_condition(subject, key, self.conditions[key]) for key in self.conditions)

    def matches_hash_condition(self, subject, key, value):
        return getattr(subject, key) == value

    def matches_function_conditions(self, action, subject):
        return self.conditions(subject)

    def matches_subject_class(self, subject):
        """
        subject can be either Classes or instances of classes
        self.subjects can either be string or Classes
        """
        for sub in self.subjects:
            if inspect.isclass(sub):
                if inspect.isclass(subject):
                    return issubclass(subject, sub)
                else:
                    return isinstance(subject, sub)
            elif isinstance(sub, string_types):
                if inspect.isclass(subject):
                    return subject.__name__ == sub
                else:
                    return subject.__class__.__name__ == sub
        return False



class RuleList(list):
    def append(self, *item_description_or_rule, **kwargs):
        # Will check it a Rule or a description of a rule
        # construct a rule if necessary then append
        if len(item_description_or_rule) == 1 and isinstance(item_description_or_rule[0], Rule):
            item = item_description_or_rule[0]
            super(RuleList, self).append(item)
        else:
            # try to construct a rule
            item = Rule(True, *item_description_or_rule, **kwargs)
            super(RuleList, self).append(item)

    # alias append
    # so you can do things like this:
    #     @authorization_method
    # def authorize(user, they):
    #
    #     if user.is_admin:
    #         # self.can_manage(ALL)
    #         they.can(MANAGE, ALL)
    #     else:
    #         they.can(READ, ALL)
    #
    #         def if_author(article):
    #             return article.author == user
    #
    #         they.can(EDIT, Article, if_author)
    can = append

    def cannot(self, *item_description_or_rule, **kwargs):
        # Will check it a Rule or a description of a rule
        # construct a rule if necessary then append
        if len(item_description_or_rule) == 1 and isinstance(item_description_or_rule[0], Rule):
            item = item_description_or_rule[0]
            super(RuleList, self).append(item)
        else:
            # try to construct a rule
            item = Rule(False, *item_description_or_rule, **kwargs)
            super(RuleList, self).append(item)


class Ability(object):

    def __init__(self, user, authorization_method=None):
        from . import get_authorization_method
        self.rules = RuleList()
        self.user = user
        self._aliased_actions = self.default_alias_actions

        if authorization_method is not None:
            self.authorization_method = authorization_method
        else:
            # see if one has been set globaly
            self.authorization_method = get_authorization_method()

    def __repr__(self):
        return "%s(%r)" % (self.__class__, self.__dict__)

    @property
    def authorization_method(self):
        return self._authorization_method

    @authorization_method.setter
    def authorization_method(self, value):
        self._authorization_method = value
        # Now that we have it run use it to set the rules
        if self._authorization_method is not None:
            self._authorization_method(self.user, self.rules)

    def can(self, action, subject):
        matches = [rule for rule in self.relevant_rules_for_match(action, subject) if rule.matches_conditions(action, subject)]
        if matches:
            match = matches[0]
            return match.base_behavior
        else:
            return False

    def cannot(self, action, subject):
        return not self.can(action, subject)

    def relevant_rules_for_match(self, action, subject):
        matching_rules = []
        for rule in self.rules:
            rule.expanded_actions = self.expand_actions(rule.actions)
            if rule.is_relavant(action, subject):
                matching_rules.append(rule)

        # reverse it (better than .reverse() for it does not return None if list is empty
        # later rules take precidence to earlier defined rules
        return matching_rules[::-1]

    def expand_actions(self, actions):
        """Accepts an array of actions and returns an array of actions which match.
        This should be called before "matches?" and other checking methods since they
        rely on the actions to be expanded."""
        results = list()

        for action in actions:
            if action in self.aliased_actions:
                results.append(action)
                for item in self.expand_actions(self.aliased_actions[action]):
                    results.append(item)
            else:
                results.append(action)

        return results

    @property
    def aliased_actions(self):
        return self._aliased_actions

    @aliased_actions.setter
    def aliased_actions(self, value):
        self._aliased_actions = value

    @property
    def default_alias_actions(self):
        return {
            READ: [INDEX, SHOW],
            CREATE: [NEW],
            UPDATE: [EDIT]
        }