Files
deb-python-pecan/pecan/tests/test_secure.py
Mark McClain e4d61c882a 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.
2012-12-07 00:04:10 -05:00

430 lines
13 KiB
Python

import sys
if sys.version_info < (2, 7):
import unittest2 as unittest
else:
import unittest # noqa
from webtest import TestApp
from pecan import expose, make_app
from pecan.secure import secure, unlocked, SecureController
try:
set()
except:
from sets import Set as set
class TestSecure(unittest.TestCase):
def test_simple_secure(self):
authorized = False
class SecretController(SecureController):
@expose()
def index(self):
return 'Index'
@expose()
@unlocked
def allowed(self):
return 'Allowed!'
@classmethod
def check_permissions(cls):
return authorized
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(),
debug=True,
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!'
def test_unlocked_attribute(self):
class AuthorizedSubController(object):
@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!'
authorized = unlocked(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(),
debug=True,
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!'
def test_secure_attribute(self):
authorized = False
class SubController(object):
@expose()
def index(self):
return 'Hello from sub!'
class RootController(object):
@expose()
def index(self):
return 'Hello from root!'
sub = secure(SubController(), lambda: authorized)
app = TestApp(make_app(RootController()))
response = app.get('/')
assert response.status_int == 200
assert response.body == 'Hello from root!'
response = app.get('/sub/', expect_errors=True)
assert response.status_int == 401
authorized = True
response = app.get('/sub/')
assert response.status_int == 200
assert response.body == 'Hello from sub!'
def test_state_attribute(self):
from pecan.secure import Any, Protected
assert repr(Any) == '<SecureState Any>'
assert bool(Any) is False
assert repr(Protected) == '<SecureState Protected>'
assert bool(Protected) is True
def test_secure_obj_only_failure(self):
class Foo(object):
pass
try:
secure(Foo())
except Exception, e:
assert isinstance(e, TypeError)
class TestObjectPathSecurity(unittest.TestCase):
def setUp(self):
permissions_checked = set()
class DeepSecretController(SecureController):
authorized = False
@expose()
@unlocked
def _lookup(self, someID, *remainder):
if someID == 'notfound':
return None
return SubController(someID), remainder
@expose()
def index(self):
return 'Deep Secret'
@classmethod
def check_permissions(cls):
permissions_checked.add('deepsecret')
return cls.authorized
class SubController(object):
def __init__(self, myID):
self.myID = myID
@expose()
def index(self):
return 'Index %s' % self.myID
deepsecret = DeepSecretController()
class SecretController(SecureController):
authorized = False
independent_authorization = False
@expose()
def _lookup(self, someID, *remainder):
if someID == 'notfound':
return None
elif someID == 'lookup_wrapped':
return self.wrapped, remainder
return SubController(someID), remainder
@secure('independent_check_permissions')
@expose()
def independent(self):
return 'Independent Security'
wrapped = secure(
SubController('wrapped'), 'independent_check_permissions'
)
@classmethod
def check_permissions(cls):
permissions_checked.add('secretcontroller')
return cls.authorized
@classmethod
def independent_check_permissions(cls):
permissions_checked.add('independent')
return cls.independent_authorization
class NotSecretController(object):
@expose()
def _lookup(self, someID, *remainder):
if someID == 'notfound':
return None
return SubController(someID), remainder
unlocked = unlocked(SubController('unlocked'))
class RootController(object):
secret = SecretController()
notsecret = NotSecretController()
self.deepsecret_cls = DeepSecretController
self.secret_cls = SecretController
self.permissions_checked = permissions_checked
self.app = TestApp(make_app(
RootController(),
debug=True,
static_root='tests/static'
))
def tearDown(self):
self.permissions_checked.clear()
self.secret_cls.authorized = False
self.deepsecret_cls.authorized = False
def test_sub_of_both_not_secret(self):
response = self.app.get('/notsecret/hi/')
assert response.status_int == 200
assert response.body == 'Index hi'
def test_protected_lookup(self):
response = self.app.get('/secret/hi/', expect_errors=True)
assert response.status_int == 401
self.secret_cls.authorized = True
response = self.app.get('/secret/hi/')
assert response.status_int == 200
assert response.body == 'Index hi'
assert 'secretcontroller' in self.permissions_checked
def test_secured_notfound_lookup(self):
response = self.app.get('/secret/notfound/', expect_errors=True)
assert response.status_int == 404
def test_secret_through_lookup(self):
response = self.app.get(
'/notsecret/hi/deepsecret/', expect_errors=True
)
assert response.status_int == 401
def test_layered_protection(self):
response = self.app.get('/secret/hi/deepsecret/', expect_errors=True)
assert response.status_int == 401
assert 'secretcontroller' in self.permissions_checked
self.secret_cls.authorized = True
response = self.app.get('/secret/hi/deepsecret/', expect_errors=True)
assert response.status_int == 401
assert 'secretcontroller' in self.permissions_checked
assert 'deepsecret' in self.permissions_checked
self.deepsecret_cls.authorized = True
response = self.app.get('/secret/hi/deepsecret/')
assert response.status_int == 200
assert response.body == 'Deep Secret'
assert 'secretcontroller' in self.permissions_checked
assert 'deepsecret' in self.permissions_checked
def test_cyclical_protection(self):
self.secret_cls.authorized = True
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 '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 'deepsecret' not in self.permissions_checked
response = self.app.get(
'/notsecret/1/deepsecret/notfound/', expect_errors=True
)
assert response.status_int == 404
assert 'deepsecret' not in self.permissions_checked
def test_mixed_protection(self):
self.secret_cls.authorized = True
response = self.app.get(
'/secret/1/deepsecret/notfound/', expect_errors=True
)
assert response.status_int == 404
assert 'secretcontroller' in self.permissions_checked
assert 'deepsecret' not in self.permissions_checked
def test_independent_check_failure(self):
response = self.app.get('/secret/independent/', expect_errors=True)
assert response.status_int == 401
assert len(self.permissions_checked) == 1
assert 'independent' in self.permissions_checked
def test_independent_check_success(self):
self.secret_cls.independent_authorization = True
response = self.app.get('/secret/independent')
assert response.status_int == 200
assert response.body == 'Independent Security'
assert len(self.permissions_checked) == 1
assert 'independent' in self.permissions_checked
def test_wrapped_attribute_failure(self):
self.secret_cls.independent_authorization = False
response = self.app.get('/secret/wrapped/', expect_errors=True)
assert response.status_int == 401
assert len(self.permissions_checked) == 1
assert 'independent' in self.permissions_checked
def test_wrapped_attribute_success(self):
self.secret_cls.independent_authorization = True
response = self.app.get('/secret/wrapped/')
assert response.status_int == 200
assert response.body == 'Index wrapped'
assert len(self.permissions_checked) == 1
assert 'independent' in self.permissions_checked
def test_lookup_to_wrapped_attribute_on_self(self):
self.secret_cls.authorized = True
self.secret_cls.independent_authorization = True
response = self.app.get('/secret/lookup_wrapped/')
assert response.status_int == 200
assert response.body == 'Index wrapped'
assert len(self.permissions_checked) == 2
assert 'independent' in self.permissions_checked
assert 'secretcontroller' in self.permissions_checked
def test_unlocked_attribute_in_insecure(self):
response = self.app.get('/notsecret/unlocked/')
assert response.status_int == 200
assert response.body == 'Index unlocked'
class SecureControllerSharedPermissionsRegression(unittest.TestCase):
"""Regression tests for https://github.com/dreamhost/pecan/issues/131"""
def setUp(self):
class Parent(object):
@expose()
def index(self):
return 'hello'
class UnsecuredChild(Parent):
pass
class SecureChild(Parent, SecureController):
@classmethod
def check_permissions(cls):
return False
class RootController(object):
secured = SecureChild()
unsecured = UnsecuredChild()
self.app = TestApp(make_app(RootController()))
def test_inherited_security(self):
assert self.app.get('/secured/', status=401).status_int == 401
assert self.app.get('/unsecured/').status_int == 200