Fix a Py3 compatability bug in pecan.jsonify.
This commit is contained in:
@@ -1,15 +1,15 @@
|
||||
import sys
|
||||
import inspect
|
||||
|
||||
# True if we are running on Python 3.
|
||||
PY3 = sys.version_info[0] == 3
|
||||
import six
|
||||
|
||||
if PY3: # pragma: no cover
|
||||
if six.PY3: # pragma: no cover
|
||||
text_type = str
|
||||
else:
|
||||
text_type = unicode
|
||||
|
||||
|
||||
if PY3: # pragma: no cover
|
||||
if six.PY3: # pragma: no cover
|
||||
def native_(s, encoding='latin-1', errors='strict'):
|
||||
""" If ``s`` is an instance of ``text_type``, return
|
||||
``s``, otherwise return ``str(s, encoding, errors)``"""
|
||||
@@ -31,3 +31,6 @@ return ``str(s, encoding, errors)``
|
||||
Python 2: If ``s`` is an instance of ``text_type``, return
|
||||
``s.encode(encoding, errors)``, otherwise return ``str(s)``
|
||||
"""
|
||||
|
||||
def is_bound_method(ob):
|
||||
return inspect.ismethod(ob) and six.get_method_self(ob) is not None
|
||||
|
||||
@@ -16,6 +16,7 @@ except ImportError: # pragma no cover
|
||||
from webob.multidict import MultiDict
|
||||
webob_dicts = (MultiDict,)
|
||||
|
||||
import six
|
||||
from simplegeneric import generic
|
||||
|
||||
try:
|
||||
@@ -77,7 +78,7 @@ class GenericJSON(JSONEncoder):
|
||||
returns webob_dicts.mixed() dictionary, which is guaranteed
|
||||
to be JSON-friendly.
|
||||
'''
|
||||
if hasattr(obj, '__json__') and callable(obj.__json__):
|
||||
if hasattr(obj, '__json__') and six.callable(obj.__json__):
|
||||
return obj.__json__()
|
||||
elif isinstance(obj, (date, datetime)):
|
||||
return str(obj)
|
||||
|
||||
@@ -1,9 +1,14 @@
|
||||
from functools import wraps
|
||||
from inspect import getmembers, ismethod, isfunction
|
||||
from inspect import getmembers, isfunction
|
||||
from webob import exc
|
||||
|
||||
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
|
||||
|
||||
@@ -21,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)
|
||||
|
||||
@@ -68,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)
|
||||
@@ -122,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
|
||||
@@ -130,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=[]
|
||||
)
|
||||
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)
|
||||
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)
|
||||
@@ -188,7 +205,10 @@ def handle_security(controller):
|
||||
check_permissions = controller._pecan['check_permissions']
|
||||
|
||||
if isinstance(check_permissions, six.string_types):
|
||||
check_permissions = getattr(controller.im_self, check_permissions)
|
||||
check_permissions = getattr(
|
||||
six.get_method_self(controller),
|
||||
check_permissions
|
||||
)
|
||||
|
||||
if not check_permissions():
|
||||
raise exc.HTTPUnauthorized
|
||||
|
||||
@@ -5,6 +5,7 @@ if sys.version_info < (2, 7):
|
||||
else:
|
||||
import unittest # noqa
|
||||
|
||||
from six import b as b_
|
||||
from webtest import TestApp
|
||||
|
||||
from pecan import expose, make_app
|
||||
@@ -59,11 +60,11 @@ class TestSecure(PecanTestCase):
|
||||
))
|
||||
response = app.get('/')
|
||||
assert response.status_int == 200
|
||||
assert response.body == 'Hello, World!'
|
||||
assert response.body == b_('Hello, World!')
|
||||
|
||||
response = app.get('/unlocked')
|
||||
assert response.status_int == 200
|
||||
assert response.body == 'Sure thing'
|
||||
assert response.body == b_('Sure thing')
|
||||
|
||||
response = app.get('/locked', expect_errors=True)
|
||||
assert response.status_int == 401
|
||||
@@ -73,7 +74,7 @@ class TestSecure(PecanTestCase):
|
||||
|
||||
response = app.get('/secret/allowed')
|
||||
assert response.status_int == 200
|
||||
assert response.body == 'Allowed!'
|
||||
assert response.body == b_('Allowed!')
|
||||
|
||||
def test_unlocked_attribute(self):
|
||||
class AuthorizedSubController(object):
|
||||
@@ -121,11 +122,11 @@ class TestSecure(PecanTestCase):
|
||||
))
|
||||
response = app.get('/')
|
||||
assert response.status_int == 200
|
||||
assert response.body == 'Hello, World!'
|
||||
assert response.body == b_('Hello, World!')
|
||||
|
||||
response = app.get('/unlocked')
|
||||
assert response.status_int == 200
|
||||
assert response.body == 'Sure thing'
|
||||
assert response.body == b_('Sure thing')
|
||||
|
||||
response = app.get('/locked', expect_errors=True)
|
||||
assert response.status_int == 401
|
||||
@@ -135,15 +136,15 @@ class TestSecure(PecanTestCase):
|
||||
|
||||
response = app.get('/secret/allowed')
|
||||
assert response.status_int == 200
|
||||
assert response.body == 'Allowed!'
|
||||
assert response.body == b_('Allowed!')
|
||||
|
||||
response = app.get('/secret/authorized/')
|
||||
assert response.status_int == 200
|
||||
assert response.body == 'Index'
|
||||
assert response.body == b_('Index')
|
||||
|
||||
response = app.get('/secret/authorized/allowed')
|
||||
assert response.status_int == 200
|
||||
assert response.body == 'Allowed!'
|
||||
assert response.body == b_('Allowed!')
|
||||
|
||||
def test_secure_attribute(self):
|
||||
authorized = False
|
||||
@@ -163,7 +164,7 @@ class TestSecure(PecanTestCase):
|
||||
app = TestApp(make_app(RootController()))
|
||||
response = app.get('/')
|
||||
assert response.status_int == 200
|
||||
assert response.body == 'Hello from root!'
|
||||
assert response.body == b_('Hello from root!')
|
||||
|
||||
response = app.get('/sub/', expect_errors=True)
|
||||
assert response.status_int == 401
|
||||
@@ -171,7 +172,7 @@ class TestSecure(PecanTestCase):
|
||||
authorized = True
|
||||
response = app.get('/sub/')
|
||||
assert response.status_int == 200
|
||||
assert response.body == 'Hello from sub!'
|
||||
assert response.body == b_('Hello from sub!')
|
||||
|
||||
def test_state_attribute(self):
|
||||
from pecan.secure import Any, Protected
|
||||
@@ -288,7 +289,7 @@ class TestObjectPathSecurity(PecanTestCase):
|
||||
def test_sub_of_both_not_secret(self):
|
||||
response = self.app.get('/notsecret/hi/')
|
||||
assert response.status_int == 200
|
||||
assert response.body == 'Index hi'
|
||||
assert response.body == b_('Index hi')
|
||||
|
||||
def test_protected_lookup(self):
|
||||
response = self.app.get('/secret/hi/', expect_errors=True)
|
||||
@@ -297,7 +298,7 @@ class TestObjectPathSecurity(PecanTestCase):
|
||||
self.secret_cls.authorized = True
|
||||
response = self.app.get('/secret/hi/')
|
||||
assert response.status_int == 200
|
||||
assert response.body == 'Index hi'
|
||||
assert response.body == b_('Index hi')
|
||||
assert 'secretcontroller' in self.permissions_checked
|
||||
|
||||
def test_secured_notfound_lookup(self):
|
||||
@@ -324,7 +325,7 @@ class TestObjectPathSecurity(PecanTestCase):
|
||||
self.deepsecret_cls.authorized = True
|
||||
response = self.app.get('/secret/hi/deepsecret/')
|
||||
assert response.status_int == 200
|
||||
assert response.body == 'Deep Secret'
|
||||
assert response.body == b_('Deep Secret')
|
||||
assert 'secretcontroller' in self.permissions_checked
|
||||
assert 'deepsecret' in self.permissions_checked
|
||||
|
||||
@@ -333,14 +334,14 @@ class TestObjectPathSecurity(PecanTestCase):
|
||||
self.deepsecret_cls.authorized = True
|
||||
response = self.app.get('/secret/1/deepsecret/2/deepsecret/')
|
||||
assert response.status_int == 200
|
||||
assert response.body == 'Deep Secret'
|
||||
assert response.body == b_('Deep Secret')
|
||||
assert 'secretcontroller' in self.permissions_checked
|
||||
assert 'deepsecret' in self.permissions_checked
|
||||
|
||||
def test_unlocked_lookup(self):
|
||||
response = self.app.get('/notsecret/1/deepsecret/2/')
|
||||
assert response.status_int == 200
|
||||
assert response.body == 'Index 2'
|
||||
assert response.body == b_('Index 2')
|
||||
assert 'deepsecret' not in self.permissions_checked
|
||||
|
||||
response = self.app.get(
|
||||
@@ -368,7 +369,7 @@ class TestObjectPathSecurity(PecanTestCase):
|
||||
self.secret_cls.independent_authorization = True
|
||||
response = self.app.get('/secret/independent')
|
||||
assert response.status_int == 200
|
||||
assert response.body == 'Independent Security'
|
||||
assert response.body == b_('Independent Security')
|
||||
assert len(self.permissions_checked) == 1
|
||||
assert 'independent' in self.permissions_checked
|
||||
|
||||
@@ -383,7 +384,7 @@ class TestObjectPathSecurity(PecanTestCase):
|
||||
self.secret_cls.independent_authorization = True
|
||||
response = self.app.get('/secret/wrapped/')
|
||||
assert response.status_int == 200
|
||||
assert response.body == 'Index wrapped'
|
||||
assert response.body == b_('Index wrapped')
|
||||
assert len(self.permissions_checked) == 1
|
||||
assert 'independent' in self.permissions_checked
|
||||
|
||||
@@ -392,7 +393,7 @@ class TestObjectPathSecurity(PecanTestCase):
|
||||
self.secret_cls.independent_authorization = True
|
||||
response = self.app.get('/secret/lookup_wrapped/')
|
||||
assert response.status_int == 200
|
||||
assert response.body == 'Index wrapped'
|
||||
assert response.body == b_('Index wrapped')
|
||||
assert len(self.permissions_checked) == 2
|
||||
assert 'independent' in self.permissions_checked
|
||||
assert 'secretcontroller' in self.permissions_checked
|
||||
@@ -400,7 +401,7 @@ class TestObjectPathSecurity(PecanTestCase):
|
||||
def test_unlocked_attribute_in_insecure(self):
|
||||
response = self.app.get('/notsecret/unlocked/')
|
||||
assert response.status_int == 200
|
||||
assert response.body == 'Index unlocked'
|
||||
assert response.body == b_('Index unlocked')
|
||||
|
||||
|
||||
class SecureControllerSharedPermissionsRegression(PecanTestCase):
|
||||
|
||||
Reference in New Issue
Block a user