A bunch of changes from last night, late:

* Secured controller support.
 * Static file support.
This commit is contained in:
Jonathan LaCour
2010-09-29 11:18:06 -04:00
parent 426ac3328a
commit 31c2d31565
7 changed files with 172 additions and 2 deletions

View File

@@ -1,2 +1,25 @@
from paste.urlparser import StaticURLParser
from paste.cascade import Cascade
from pecan import Pecan, request, override_template
from decorators import expose
from decorators import expose
def make_app(root, renderers = None,
default_renderer = None,
template_path = None,
hooks = None,
static_root = None):
kw = {}
if renderers is not None: kw['renderers'] = renderers
if default_renderer is not None: kw['default_renderer'] = default_renderer
if template_path is not None: kw['template_path'] = template_path
if hooks is not None: kw['hooks'] = hooks
app = Pecan(root, **kw)
if static_root:
app = Cascade([StaticURLParser(static_root), app])
return app

View File

@@ -71,6 +71,11 @@ class Pecan(object):
response.status = 404
return response(environ, start_response)
# handle security
if controller.pecan.get('secured', False):
if not controller.pecan['check_permissions']():
raise exc.HTTPUnauthorized
# determine content type
if content_type is None:
content_type = controller.pecan.get('content_type', 'text/html')

50
pecan/secure.py Normal file
View File

@@ -0,0 +1,50 @@
from webob.exc import HTTPUnauthorized
from inspect import getmembers, ismethod
from routing import iscontroller
__all__ = ['secure', 'unlocked', 'SecureController']
def unlocked(func):
if not hasattr(func, 'pecan'): func.pecan = {}
func.pecan['unlocked'] = True
return func
def secure(check_permissions):
def wrap(func):
if not hasattr(func, 'pecan'): func.pecan = {}
func.pecan['secured'] = True
func.pecan['check_permissions'] = check_permissions
return func
return wrap
def walk_controller(root_class, controller):
if hasattr(controller, '_lookup'):
# TODO: what about this?
controller._check_security = root_class._perform_validation
if not isinstance(controller, (int, dict)):
for name, value in getmembers(controller):
if name == 'controller': continue
if ismethod(value):
if iscontroller(value) and not value.pecan.get('unlocked', False):
value.pecan['secured'] = True
value.pecan['check_permissions'] = root_class.check_permissions
elif hasattr(value, '__class__'):
if name.startswith('__') and name.endswith('__'): continue
walk_controller(root_class, value)
class SecureController(object):
class __metaclass__(type):
def __init__(cls, name, bases, dict_):
walk_controller(cls, cls)
@classmethod
def check_permissions(cls):
return True

View File

@@ -24,7 +24,8 @@ setup(
"Kajiki >= 0.2.2",
"Mako >= 0.3",
"py >= 1.3.4",
"WebTest >= 1.2.2"
"WebTest >= 1.2.2",
"Paste >= 1.7.5.1"
],
entry_points = """
# -*- Entry points: -*-

9
tests/static/test.txt Normal file
View File

@@ -0,0 +1,9 @@
This is a test text file.
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod
tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim
veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea
commodo consequat. Duis aute irure dolor in reprehenderit in voluptate
velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint
occaecat cupidatat non proident, sunt in culpa qui officia deserunt
mollit anim id est laborum.

61
tests/test_secure.py Normal file
View File

@@ -0,0 +1,61 @@
from pecan import expose, make_app
from pecan.secure import secure, unlocked, SecureController
from webob import exc
from webtest import TestApp
from py.test import raises
class TestSecure(object):
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(self):
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(), 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'
with raises(exc.HTTPUnauthorized):
response = app.get('/locked')
with raises(exc.HTTPUnauthorized):
response = app.get('/secret')
response = app.get('/secret/allowed')
assert response.status_int == 200
assert response.body == 'Allowed!'

21
tests/test_static.py Normal file
View File

@@ -0,0 +1,21 @@
from pecan import expose, make_app
from webtest import TestApp
class TestStatic(object):
def test_simple_static(self):
class RootController(object):
@expose()
def index(self):
return 'Hello, World!'
# make sure Cascade is working properly
app = TestApp(make_app(RootController(), static_root='tests/static'))
response = app.get('/index.html')
assert response.status_int == 200
assert response.body == 'Hello, World!'
# get a static resource
response = app.get('/test.txt')
assert response.status_int == 200
assert response.body == open('tests/static/test.txt', 'rb').read()