Skip to content

Commit

Permalink
fix navigation use of callable permissions on classes/blueprints
Browse files Browse the repository at this point in the history
  • Loading branch information
guruofgentoo committed Nov 29, 2021
1 parent 07fe642 commit f19f513
Show file tree
Hide file tree
Showing 3 changed files with 55 additions and 2 deletions.
8 changes: 7 additions & 1 deletion keg_auth/libs/decorators.py
Expand Up @@ -166,7 +166,13 @@ def __init__(self, condition, on_authentication_failure=None, on_authorization_f

def store_auth_info(self, obj):
super(RequiresPermissions, self).store_auth_info(obj)
obj.__keg_auth_requires_permissions__ = self.condition
condition = self.condition
if callable(condition) and isinstance(obj, type):
# When applying to a class (usually a class view or a blueprint), we have to explicitly
# wrap callables as static. Otherwise, when the obj class is instantiated, the function
# will become bound, and we'll get parameter count exceptions.
condition = staticmethod(condition)
obj.__keg_auth_requires_permissions__ = condition

def on_authorization_failure(self):
if self._on_authorization_failure:
Expand Down
33 changes: 33 additions & 0 deletions keg_auth/tests/test_navigation.py
Expand Up @@ -205,6 +205,22 @@ def test_leaf_method_requires_permissions(self):
node.clear_authorization(user.get_id())
assert node.is_permitted

def test_leaf_method_requires_callable_permissions(self):
node = NavItem('Foo', NavURL('private.secret_callable'))

with flask.current_app.test_request_context('/'):
flask_login.logout_user()
assert not node.is_permitted

user = flask.current_app.auth_manager.entity_registry.user_cls.testing_create()
flask_login.login_user(user)
node.clear_authorization(user.get_id())
assert not node.is_permitted

flask_login.current_user.email = 'foo@bar.baz'
node.clear_authorization(user.get_id())
assert node.is_permitted

@pytest.mark.parametrize('endpoint', ['private.secret3', 'private.secret-flask'])
def test_leaf_class_requires_permissions(self, endpoint):
node = NavItem('Foo', NavURL(endpoint))
Expand All @@ -226,6 +242,23 @@ def test_leaf_class_requires_permissions(self, endpoint):
node.clear_authorization(user.get_id())
assert node.is_permitted

def test_leaf_class_requires_callable_permissions(self):
node = NavItem('Foo', NavURL('callable_protected.callable-protected-class'))

with flask.current_app.test_request_context('/'):
flask_login.logout_user()
assert not node.is_permitted

user = flask.current_app.auth_manager.entity_registry.user_cls.testing_create()
flask_login.login_user(user)
node.clear_authorization(user.get_id())
assert not node.is_permitted

user.class_pass = True
user.blueprint_pass = True
node.clear_authorization(user.get_id())
assert node.is_permitted

@pytest.mark.parametrize('endpoint', ['private.secret4', 'private.secret-flask4'])
def test_leaf_method_and_class_both_require(self, endpoint):
node = NavItem('Foo', NavURL(endpoint))
Expand Down
16 changes: 15 additions & 1 deletion keg_auth_ta/views.py
Expand Up @@ -18,6 +18,11 @@ def on_authentication_failure(self):
flask.abort(405)


@requires_permissions(lambda user: hasattr(user, 'blueprint_pass'))
class CallableProtectedBlueprint(flask.Blueprint):
pass


@requires_user(http_methods_excluded=['OPTIONS'])
class ProtectedBlueprint2(flask.Blueprint):
pass
Expand All @@ -41,9 +46,10 @@ def create_form(self, obj):
private_bp = flask.Blueprint('private', __name__)
protected_bp = ProtectedBlueprint('protected', __name__)
protected_bp2 = ProtectedBlueprint2('protected2', __name__)
callable_protected_bp = CallableProtectedBlueprint('callable_protected', __name__)
auth_bp = make_blueprint(__name__, auth_manager, user_crud_cls=User)

blueprints = public_bp, private_bp, protected_bp, protected_bp2, auth_bp
blueprints = public_bp, private_bp, protected_bp, protected_bp2, callable_protected_bp, auth_bp

# Exempt from CSRF or we have problems with Secret2.post.
csrf.exempt(private_bp)
Expand Down Expand Up @@ -199,6 +205,14 @@ def secret_nested_callable():
return 'secret_nested_callable'


@requires_permissions(lambda user: hasattr(user, 'class_pass'))
class CallableProtectedClass(keg.web.BaseView):
blueprint = callable_protected_bp

def get(self):
return 'callable-protected-class'


class SecretNavURLOnClass(keg.web.BaseView):
blueprint = private_bp

Expand Down

0 comments on commit f19f513

Please sign in to comment.