Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added ability to set a custom subject match method for the rule subject matcher #9

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion bouncer/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -71,4 +78,4 @@ def cannot(self, action, subject):
setattr(original_class, 'can', can)
setattr(original_class, 'cannot', cannot)

return original_class
return original_class
7 changes: 7 additions & 0 deletions bouncer/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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


Expand Down
30 changes: 27 additions & 3 deletions bouncer/test_bouncer/__init__.py
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand All @@ -214,4 +238,4 @@ def authorize(user, they):


if __name__ == "__main__":
nose.run()
nose.run()