From e4d61c882aa664d9fb7d632f11bbf0200f096ef4 Mon Sep 17 00:00:00 2001 From: Mark McClain Date: Fri, 7 Dec 2012 00:04:10 -0500 Subject: [PATCH] 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. --- pecan/secure.py | 22 +++++++++++++++++++--- pecan/tests/test_secure.py | 1 - 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/pecan/secure.py b/pecan/secure.py index 3e8c2af..c6f29cf 100644 --- a/pecan/secure.py +++ b/pecan/secure.py @@ -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. """ diff --git a/pecan/tests/test_secure.py b/pecan/tests/test_secure.py index 749d045..f71e740 100644 --- a/pecan/tests/test_secure.py +++ b/pecan/tests/test_secure.py @@ -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