Added UnlockedControllerMeta, a controller metaclass which can be used to forcibly apply @unlocked() to every exposed method in a routing branch.

This commit is contained in:
Ryan Petrello
2011-01-03 10:58:47 -05:00
parent ed3345ee96
commit e75770caea
2 changed files with 109 additions and 5 deletions

View File

@@ -40,10 +40,41 @@ def walk_controller(root_class, controller):
class SecureController(object):
"""
Used to apply security to a controller and its children.
Implementations of SecureController should extend the
`check_permissions` method to return a True or False
value (depending on whether or not the user has access
to the controller).
"""
class __metaclass__(type):
def __init__(cls, name, bases, dict_):
walk_controller(cls, cls)
@classmethod
def check_permissions(cls):
return True
return True
class UnlockedControllerMeta(type):
"""
Can be used to force (override) a controller and all of its
subcontrollers to be unlocked/unsecured.
This has the same effect as applying @pecan.secure.unlocked
to every method in the class and its subclasses.
"""
def __init__(cls, name, bases, ns):
cls.walk_and_apply_unlocked(cls, cls)
def walk_and_apply_unlocked(cls, root_class, controller):
if not isinstance(controller, (int, dict)):
for name, value in getmembers(controller):
if name == 'controller': continue
if ismethod(value):
if iscontroller(value):
value = unlocked(value)
elif hasattr(value, '__class__'):
if name.startswith('__') and name.endswith('__'): continue
cls.walk_and_apply_unlocked(root_class, value)

View File

@@ -1,8 +1,6 @@
from pecan import expose, make_app
from pecan.secure import secure, unlocked, SecureController
from webob import exc
from webtest import TestApp, AppError
from py.test import raises
from pecan.secure import secure, unlocked, SecureController, UnlockedControllerMeta
from webtest import TestApp
class TestSecure(object):
@@ -58,4 +56,79 @@ class TestSecure(object):
response = app.get('/secret/allowed')
assert response.status_int == 200
assert response.body == 'Allowed!'
def test_unlocked_meta(self):
class AuthorizedSubController(object):
__metaclass__ = UnlockedControllerMeta
@expose()
def index(self):
return 'Index'
@expose()
def allowed(self):
return 'Allowed!'
class SecretController(SecureController):
@expose()
def index(self):
return 'Index'
@expose()
@unlocked
def allowed(self):
return 'Allowed!'
@classmethod
def check_permissions(self):
return False
authorized = AuthorizedSubController()
class RootController(object):
@expose()
def index(self):
return 'Hello, World!'
@expose()
@secure(lambda: False)
def locked(self):
return 'No dice!'
@expose()
@secure(lambda: True)
def unlocked(self):
return 'Sure thing'
secret = SecretController()
app = TestApp(make_app(RootController(), static_root='tests/static'))
response = app.get('/')
assert response.status_int == 200
assert response.body == 'Hello, World!'
response = app.get('/unlocked')
assert response.status_int == 200
assert response.body == 'Sure thing'
response = app.get('/locked', expect_errors=True)
assert response.status_int == 401
response = app.get('/secret', expect_errors=True)
assert response.status_int == 401
response = app.get('/secret/allowed')
assert response.status_int == 200
assert response.body == 'Allowed!'
response = app.get('/secret/authorized')
assert response.status_int == 200
assert response.body == 'Index'
response = app.get('/secret/authorized/allowed')
assert response.status_int == 200
assert response.body == 'Allowed!'