wraps methods before applying security in SecurceController

Resolves issue #131

Patchset updates the metaclass to wrap unbound methods before adding
security.  Wrapping ensures that base controllers can safely be mixed in
both secure and unsecure contexts.  Wrapping resolves the issue since
method attributes are shared by all classes with the same bases.  The
original _pecan attributes of the method are shallow copied prior to adding the
security attributes.
This commit is contained in:
Mark McClain
2012-12-07 00:04:10 -05:00
parent f3bc87b6b5
commit e4d61c882a
2 changed files with 19 additions and 4 deletions

View File

@@ -1,3 +1,4 @@
from functools import wraps
from inspect import getmembers, ismethod, isfunction
from webob import exc
@@ -135,14 +136,20 @@ class SecureController(object):
unlocked=[]
)
for name, value in getmembers(cls):
for name, value in getmembers(cls)[:]:
if ismethod(value):
if iscontroller(value) and value._pecan.get(
'secured'
) is None:
value._pecan['secured'] = Protected
value._pecan['check_permissions'] = \
# Wrap the function so that the security context is
# local to this class definition. This works around
# the fact that unbound method attributes are shared
# across classes with the same bases.
wrapped = _make_wrapper(value)
wrapped._pecan['secured'] = Protected
wrapped._pecan['check_permissions'] = \
cls.check_permissions
setattr(cls, name, wrapped)
elif hasattr(value, '__class__'):
if name.startswith('__') and name.endswith('__'):
continue
@@ -163,6 +170,15 @@ class SecureController(object):
return False
def _make_wrapper(f):
"""return a wrapped function with a copy of the _pecan context"""
@wraps(f)
def wrapper(*args, **kwargs):
return f(*args, **kwargs)
wrapper._pecan = f._pecan.copy()
return wrapper
# methods to evaluate security during routing
def handle_security(controller):
""" Checks the security of a controller. """

View File

@@ -424,7 +424,6 @@ class SecureControllerSharedPermissionsRegression(unittest.TestCase):
self.app = TestApp(make_app(RootController()))
@unittest.expectedFailure
def test_inherited_security(self):
assert self.app.get('/secured/', status=401).status_int == 401
assert self.app.get('/unsecured/').status_int == 200