diff --git a/bouncer/__init__.py b/bouncer/__init__.py index 29577f9..906505d 100644 --- a/bouncer/__init__.py +++ b/bouncer/__init__.py @@ -54,6 +54,13 @@ def get_authorization_method(): _authorization_target = None +def custom_subject_matcher(original_method): + """The method which extends the usual subject matching process of a subject""" + Rule._custom_subject_matcher = original_method + return original_method + +def get_custom_subject_matcher(): + return Rule._custom_subject_matcher def authorization_target(original_class): """ Add bouncer goodness to the model. This is a class decorator, when added to your User model if will add @@ -71,4 +78,4 @@ def cannot(self, action, subject): setattr(original_class, 'can', can) setattr(original_class, 'cannot', cannot) - return original_class \ No newline at end of file + return original_class diff --git a/bouncer/models.py b/bouncer/models.py index 9eeb64f..0901b47 100644 --- a/bouncer/models.py +++ b/bouncer/models.py @@ -18,6 +18,9 @@ def listify(list_or_single): class Rule(object): + + _custom_subject_matcher = None + def __init__(self, base_behavior, action, subject, conditions=None, **conditions_hash): self.base_behavior = base_behavior self.actions = listify(action) @@ -92,6 +95,10 @@ def matches_subject_class(self, subject): return subject.__name__ == sub else: return subject.__class__.__name__ == sub + + if callable(Rule._custom_subject_matcher): + return Rule._custom_subject_matcher(self, subject) + return False diff --git a/bouncer/test_bouncer/__init__.py b/bouncer/test_bouncer/__init__.py index f3578d9..757b485 100644 --- a/bouncer/test_bouncer/__init__.py +++ b/bouncer/test_bouncer/__init__.py @@ -1,6 +1,6 @@ import nose from nose.tools import assert_raises, raises -from bouncer import authorization_target, authorization_method, Ability, can, cannot, ensure +from bouncer import authorization_target, authorization_method, custom_subject_matcher, Ability, can, cannot, ensure from bouncer.exceptions import AccessDenied from bouncer.constants import * from models import User, Article, BlogPost @@ -168,7 +168,6 @@ def if_author(article): assert relevant_rules[0].actions == [EDIT] assert relevant_rules[0].subjects == [Article] - def test_using_class_strings(): @authorization_method @@ -188,6 +187,31 @@ def authorize(user, they): assert sally.can(EDIT, article) +def test_using_custom_subject_function(): + + class TestAuth: + name = 'Test' + + try: + @custom_subject_matcher + def subject_match(rule, subject): + for subject in rule.subjects: + if subject == TestAuth.name: + return True + return False + + @authorization_method + def authorize(user, they): + they.can(READ, 'Test') + + article = Article() + sally = User(name='sally', admin=False) + + assert sally.can(READ, article) + + finally: + custom_subject_matcher(None) + def test_cannot_override(): @authorization_method @@ -214,4 +238,4 @@ def authorize(user, they): if __name__ == "__main__": - nose.run() \ No newline at end of file + nose.run()