diff options
Diffstat (limited to 'pecan/secure.py')
-rw-r--r-- | pecan/secure.py | 112 |
1 files changed, 67 insertions, 45 deletions
diff --git a/pecan/secure.py b/pecan/secure.py index c6f29cf..e44aacb 100644 --- a/pecan/secure.py +++ b/pecan/secure.py @@ -1,9 +1,16 @@ from functools import wraps -from inspect import getmembers, ismethod, isfunction +from inspect import getmembers, isfunction from webob import exc -from decorators import expose -from util import _cfg, iscontroller +import six + +if six.PY3: + from .compat import is_bound_method as ismethod +else: + from inspect import ismethod + +from .decorators import expose +from .util import _cfg, iscontroller __all__ = ['unlocked', 'secure', 'SecureController'] @@ -19,6 +26,9 @@ class _SecureState(object): def __nonzero__(self): return self.boolean_value + def __bool__(self): + return self.__nonzero__() + Any = _SecureState('Any', False) Protected = _SecureState('Protected', True) @@ -56,7 +66,7 @@ class _SecuredAttribute(object): self._parent = None def _check_permissions(self): - if isinstance(self.check_permissions, basestring): + if isinstance(self.check_permissions, six.string_types): return getattr(self.parent, self.check_permissions)() else: return self.check_permissions() @@ -66,7 +76,7 @@ class _SecuredAttribute(object): def __set_parent(self, parent): if ismethod(parent): - self._parent = parent.im_self + self._parent = six.get_method_self(parent) else: self._parent = parent parent = property(__get_parent, __set_parent) @@ -82,7 +92,7 @@ def _allowed_check_permissions_types(x): return ( ismethod(x) or isfunction(x) or - isinstance(x, basestring) + isinstance(x, six.string_types) ) @@ -120,7 +130,7 @@ def secure(func_or_obj, check_permissions_for_obj=None): return _SecuredAttribute(func_or_obj, check_permissions_for_obj) -class SecureController(object): +class SecureControllerMeta(type): """ Used to apply security to a controller. Implementations of SecureController should extend the @@ -128,48 +138,57 @@ class SecureController(object): value (depending on whether or not the user has permissions to the controller). """ - class __metaclass__(type): - def __init__(cls, name, bases, dict_): - cls._pecan = dict( - secured=Protected, - check_permissions=cls.check_permissions, - unlocked=[] - ) - - for name, value in getmembers(cls)[:]: - if ismethod(value): - if iscontroller(value) and value._pecan.get( - 'secured' - ) is None: - # 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 - if isinstance(value, _UnlockedAttribute): - # mark it as unlocked and remove wrapper - cls._pecan['unlocked'].append(value.obj) - setattr(cls, name, value.obj) - elif isinstance(value, _SecuredAttribute): - # The user has specified a different check_permissions - # than the class level version. As far as the class - # is concerned, this method is unlocked because - # it is using a check_permissions function embedded in - # the _SecuredAttribute wrapper - cls._pecan['unlocked'].append(value) + def __init__(cls, name, bases, dict_): + cls._pecan = dict( + secured=Protected, + check_permissions=cls.check_permissions, + unlocked=[] + ) + + for name, value in getmembers(cls)[:]: + if (isfunction if six.PY3 else ismethod)(value): + if iscontroller(value) and value._pecan.get( + 'secured' + ) is None: + # 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 + if isinstance(value, _UnlockedAttribute): + # mark it as unlocked and remove wrapper + cls._pecan['unlocked'].append(value.obj) + setattr(cls, name, value.obj) + elif isinstance(value, _SecuredAttribute): + # The user has specified a different check_permissions + # than the class level version. As far as the class + # is concerned, this method is unlocked because + # it is using a check_permissions function embedded in + # the _SecuredAttribute wrapper + cls._pecan['unlocked'].append(value) + + +class SecureControllerBase(object): @classmethod def check_permissions(cls): return False +SecureController = SecureControllerMeta( + 'SecureController', + (SecureControllerBase,), + {} +) + + def _make_wrapper(f): """return a wrapped function with a copy of the _pecan context""" @wraps(f) @@ -185,8 +204,11 @@ def handle_security(controller): if controller._pecan.get('secured', False): check_permissions = controller._pecan['check_permissions'] - if isinstance(check_permissions, basestring): - check_permissions = getattr(controller.im_self, check_permissions) + if isinstance(check_permissions, six.string_types): + check_permissions = getattr( + six.get_method_self(controller), + check_permissions + ) if not check_permissions(): raise exc.HTTPUnauthorized |