From 86ed4016ea6a681d4f579ace62cea032a679544d Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Sun, 24 May 2009 23:12:59 +0000 Subject: Features -------- - It is now possible to write a custom security policy that returns a customized ``Forbidden`` WSGI application when BFG cannot authorize an invocation of a view. To this end, ISecurityPolicy objects must now have a ``forbidden`` method. This method should return a WSGI application. The returned WSGI application should generate a response which is appropriate when access to a view resource was forbidden by the security policy (e.g. perhaps a login page). ``repoze.bfg`` is willing to operate with a custom security policy that does not have a ``forbidden`` method, but it will issue a warning; eventually security policies without a ``forbidden`` method will cease to work under ``repoze.bfg``. Note that the ``forbidden`` WSGI application returned by the security policy is not used if a developer has registered an IForbiddenAppFactory (see the "Hooks" narrative chapter); the explicitly registered IForbiddenAppFactory will be preferred over the (more general) security policy forbidden app factory. - All default security policies now have a ``forbidden`` callable attached to them. This particular callable returns a WSGI application which generates a ``401 Unauthorized`` response for backwards compatibility (had backwards compatibility not been an issue, this callable would have returned a WSGI app that generated a ``403 Forbidden`` response). Backwards Incompatibilities --------------------------- - Custom NotFound and Forbidden (nee' Unauthorized) WSGI applications (registered a a utility for INotFoundAppFactory and IUnauthorizedAppFactory) could rely on an environment key named ``message`` describing the circumstance of the response. This key has been renamed to ``repoze.bfg.message`` (as per the WSGI spec, which requires environment extensions to contain dots). Deprecations ------------ - The ``repoze.bfg.interfaces.IUnauthorizedAppFactory`` interface has been renamed to ``repoze.bfg.interfaces.IForbiddenAppFactory``. --- repoze/bfg/tests/test_router.py | 186 +++++++++++++++++++------------------- repoze/bfg/tests/test_security.py | 23 +++++ repoze/bfg/tests/test_wsgi.py | 4 +- 3 files changed, 117 insertions(+), 96 deletions(-) (limited to 'repoze/bfg/tests') diff --git a/repoze/bfg/tests/test_router.py b/repoze/bfg/tests/test_router.py index db47f832e..d7c55ae78 100644 --- a/repoze/bfg/tests/test_router.py +++ b/repoze/bfg/tests/test_router.py @@ -78,9 +78,11 @@ class RouterTests(unittest.TestCase): from repoze.bfg.interfaces import IViewPermission self.registry.registerAdapter(permission, for_, IViewPermission, name) - def _registerSecurityPolicy(self, secpol): + def _registerSecurityPolicy(self): + secpol = DummySecurityPolicy() from repoze.bfg.interfaces import ISecurityPolicy self.registry.registerUtility(secpol, ISecurityPolicy) + return secpol def _registerEventListener(self, iface): L = [] @@ -89,9 +91,11 @@ class RouterTests(unittest.TestCase): self.registry.registerHandler(listener, (iface,)) return L - def _registerRootFactory(self, root_factory): + def _registerRootFactory(self, val): + rootfactory = make_rootfactory(val) from repoze.bfg.interfaces import IRootFactory - self.registry.registerUtility(root_factory, IRootFactory) + self.registry.registerUtility(rootfactory, IRootFactory) + return rootfactory def _getTargetClass(self): from repoze.bfg.router import Router @@ -113,21 +117,67 @@ class RouterTests(unittest.TestCase): return environ def test_root_policy(self): - rootfactory = make_rootfactory(None) environ = self._makeEnviron() context = DummyContext() self._registerTraverserFactory(context) - self._registerRootFactory(rootfactory) + rootfactory = self._registerRootFactory(None) router = self._makeOne() self.assertEqual(router.root_policy, rootfactory) + def test_secpol_no_forbidden(self): + environ = self._makeEnviron() + context = DummyContext() + self._registerTraverserFactory(context) + rootfactory = self._registerRootFactory(None) + logger = self._registerLogger() + secpol = self._registerSecurityPolicy() + del secpol.forbidden + router = self._makeOne() + self.assertEqual(len(logger.messages), 1) + self.failUnless('which does not have a "forbidden" method' + in logger.messages[0]) + + def test_inotfound_appfactory_override(self): + from repoze.bfg.interfaces import INotFoundAppFactory + def app(): + """ """ + self.registry.registerUtility(app, INotFoundAppFactory) + self._registerRootFactory(None) + router = self._makeOne() + self.assertEqual(router.notfound_app_factory, app) + + def test_iforbidden_appfactory_override_withsecpol(self): + from repoze.bfg.interfaces import IForbiddenAppFactory + def app(): + """ """ + self.registry.registerUtility(app, IForbiddenAppFactory) + self._registerSecurityPolicy() + self._registerRootFactory(None) + router = self._makeOne() + self.assertEqual(router.forbidden_app_factory, app) + + def test_iforbidden_appfactory_override_nosecpol(self): + from repoze.bfg.interfaces import IForbiddenAppFactory + def app(): + """ """ + self.registry.registerUtility(app, IForbiddenAppFactory) + self._registerRootFactory(None) + router = self._makeOne() + self.assertEqual(router.forbidden_app_factory, app) + + def test_iforbidden_appfactory_nooverride(self): + secpol = self._registerSecurityPolicy() + context = DummyContext() + self._registerRootFactory(None) + router = self._makeOne() + self.assertEqual(router.forbidden_app_factory, secpol.forbidden) + def test_call_no_view_registered_no_isettings(self): - rootfactory = make_rootfactory(None) environ = self._makeEnviron() context = DummyContext() self._registerTraverserFactory(context) logger = self._registerLogger() - self._registerRootFactory(rootfactory) + self._registerRootFactory(None) router = self._makeOne() start_response = DummyStartResponse() result = router(environ, start_response) @@ -148,8 +198,7 @@ class RouterTests(unittest.TestCase): self._registerTraverserFactory(context) environ = self._makeEnviron() start_response = DummyStartResponse() - rootfactory = make_rootfactory(NotFound()) - self._registerRootFactory(rootfactory) + self._registerRootFactory(NotFound()) router = self._makeOne() result = router(environ, start_response) status = start_response.status @@ -157,13 +206,12 @@ class RouterTests(unittest.TestCase): self.failUnless('http://localhost:8080' in result[0], result) def test_call_no_view_registered_debug_notfound_false(self): - rootfactory = make_rootfactory(None) environ = self._makeEnviron() context = DummyContext() self._registerTraverserFactory(context) logger = self._registerLogger() self._registerSettings(debug_notfound=False) - self._registerRootFactory(rootfactory) + self._registerRootFactory(None) router = self._makeOne() start_response = DummyStartResponse() result = router(environ, start_response) @@ -176,13 +224,12 @@ class RouterTests(unittest.TestCase): self.assertEqual(len(logger.messages), 0) def test_call_no_view_registered_debug_notfound_true(self): - rootfactory = make_rootfactory(None) environ = self._makeEnviron() context = DummyContext() self._registerTraverserFactory(context) self._registerSettings(debug_notfound=True) logger = self._registerLogger() - self._registerRootFactory(rootfactory) + self._registerRootFactory(None) router = self._makeOne() start_response = DummyStartResponse() result = router(environ, start_response) @@ -205,19 +252,17 @@ class RouterTests(unittest.TestCase): self.failUnless("subpath: []" in message) def test_call_view_returns_nonresponse(self): - rootfactory = make_rootfactory(None) context = DummyContext() self._registerTraverserFactory(context) environ = self._makeEnviron() view = make_view('abc') self._registerView(view, '', None, None) - self._registerRootFactory(rootfactory) + self._registerRootFactory(None) router = self._makeOne() start_response = DummyStartResponse() self.assertRaises(ValueError, router, environ, start_response) def test_call_view_registered_nonspecific_default_path(self): - rootfactory = make_rootfactory(None) context = DummyContext() self._registerTraverserFactory(context) response = DummyResponse() @@ -225,7 +270,7 @@ class RouterTests(unittest.TestCase): view = make_view(response) environ = self._makeEnviron() self._registerView(view, '', None, None) - self._registerRootFactory(rootfactory) + self._registerRootFactory(None) router = self._makeOne() start_response = DummyStartResponse() result = router(environ, start_response) @@ -238,7 +283,6 @@ class RouterTests(unittest.TestCase): self.assertEqual(environ['webob.adhoc_attrs']['root'], None) def test_call_deprecation_warning(self): - rootfactory = make_rootfactory(None) context = DummyContext() self._registerTraverserFactory(context, _deprecation_warning='abc') response = DummyResponse() @@ -246,7 +290,7 @@ class RouterTests(unittest.TestCase): view = make_view(response) environ = self._makeEnviron() self._registerView(view, '', None, None) - self._registerRootFactory(rootfactory) + self._registerRootFactory(None) router = self._makeOne() logger = self._registerLogger() router.logger = logger @@ -256,7 +300,6 @@ class RouterTests(unittest.TestCase): self.assertEqual(logger.messages[0], 'abc') def test_call_view_registered_nonspecific_nondefault_path_and_subpath(self): - rootfactory = make_rootfactory(None) context = DummyContext() self._registerTraverserFactory(context, view_name='foo', subpath=['bar'], @@ -266,7 +309,7 @@ class RouterTests(unittest.TestCase): view = make_view(response) environ = self._makeEnviron() self._registerView(view, 'foo', None, None) - self._registerRootFactory(rootfactory) + self._registerRootFactory(None) router = self._makeOne() start_response = DummyStartResponse() result = router(environ, start_response) @@ -279,7 +322,6 @@ class RouterTests(unittest.TestCase): self.assertEqual(environ['webob.adhoc_attrs']['root'], None) def test_call_view_registered_specific_success(self): - rootfactory = make_rootfactory(None) from zope.interface import Interface from zope.interface import directlyProvides class IContext(Interface): @@ -293,7 +335,7 @@ class RouterTests(unittest.TestCase): view = make_view(response) environ = self._makeEnviron() self._registerView(view, '', IContext, IRequest) - self._registerRootFactory(rootfactory) + self._registerRootFactory(None) router = self._makeOne() start_response = DummyStartResponse() result = router(environ, start_response) @@ -306,7 +348,6 @@ class RouterTests(unittest.TestCase): self.assertEqual(environ['webob.adhoc_attrs']['root'], None) def test_call_view_registered_specific_fail(self): - rootfactory = make_rootfactory(None) from zope.interface import Interface from zope.interface import directlyProvides class IContext(Interface): @@ -321,7 +362,7 @@ class RouterTests(unittest.TestCase): view = make_view(response) environ = self._makeEnviron() self._registerView(view, '', IContext, IRequest) - self._registerRootFactory(rootfactory) + self._registerRootFactory(None) router = self._makeOne() start_response = DummyStartResponse() result = router(environ, start_response) @@ -329,7 +370,6 @@ class RouterTests(unittest.TestCase): self.failUnless('404' in result[0]) def test_call_view_registered_security_policy_permission_none(self): - rootfactory = make_rootfactory(None) from zope.interface import Interface from zope.interface import directlyProvides class IContext(Interface): @@ -342,16 +382,14 @@ class RouterTests(unittest.TestCase): view = make_view(response) environ = self._makeEnviron() self._registerView(view, '', IContext, IRequest) - secpol = DummySecurityPolicy() - self._registerSecurityPolicy(secpol) - self._registerRootFactory(rootfactory) + self._registerSecurityPolicy() + self._registerRootFactory(None) router = self._makeOne() start_response = DummyStartResponse() result = router(environ, start_response) self.assertEqual(start_response.status, '200 OK') def test_call_view_registered_security_policy_permission_succeeds(self): - rootfactory = make_rootfactory(None) from zope.interface import Interface from zope.interface import directlyProvides class IContext(Interface): @@ -362,13 +400,12 @@ class RouterTests(unittest.TestCase): self._registerTraverserFactory(context, subpath=['']) response = DummyResponse() view = make_view(response) - secpol = DummySecurityPolicy() permissionfactory = make_permission_factory(True) environ = self._makeEnviron() self._registerView(view, '', IContext, IRequest) - self._registerSecurityPolicy(secpol) + secpol = self._registerSecurityPolicy() self._registerPermission(permissionfactory, '', IContext, IRequest) - self._registerRootFactory(rootfactory) + self._registerRootFactory(None) router = self._makeOne() start_response = DummyStartResponse() result = router(environ, start_response) @@ -376,7 +413,6 @@ class RouterTests(unittest.TestCase): self.assertEqual(permissionfactory.checked_with, secpol) def test_call_view_permission_fails_nosettings(self): - rootfactory = make_rootfactory(None) from zope.interface import Interface from zope.interface import directlyProvides class IContext(Interface): @@ -387,26 +423,24 @@ class RouterTests(unittest.TestCase): self._registerTraverserFactory(context, subpath=['']) response = DummyResponse() view = make_view(response) - secpol = DummySecurityPolicy() from repoze.bfg.security import ACLDenied permissionfactory = make_permission_factory( ACLDenied('ace', 'acl', 'permission', ['principals'], context) ) environ = self._makeEnviron() self._registerView(view, '', IContext, IRequest) - self._registerSecurityPolicy(secpol) + secpol = self._registerSecurityPolicy() self._registerPermission(permissionfactory, '', IContext, IRequest) - self._registerRootFactory(rootfactory) + self._registerRootFactory(None) router = self._makeOne() start_response = DummyStartResponse() result = router(environ, start_response) self.assertEqual(start_response.status, '401 Unauthorized') - message = result[0] - self.failUnless('failed security policy check' in message) + message = environ['repoze.bfg.message'] + self.assertEqual(message, 'Unauthorized: failed security policy check') self.assertEqual(permissionfactory.checked_with, secpol) def test_call_view_permission_fails_no_debug_auth(self): - rootfactory = make_rootfactory(None) from zope.interface import Interface from zope.interface import directlyProvides class IContext(Interface): @@ -417,27 +451,25 @@ class RouterTests(unittest.TestCase): self._registerTraverserFactory(context, subpath=['']) response = DummyResponse() view = make_view(response) - secpol = DummySecurityPolicy() from repoze.bfg.security import ACLDenied permissionfactory = make_permission_factory( ACLDenied('ace', 'acl', 'permission', ['principals'], context) ) environ = self._makeEnviron() self._registerView(view, '', IContext, IRequest) - self._registerSecurityPolicy(secpol) + secpol = self._registerSecurityPolicy() self._registerPermission(permissionfactory, '', IContext, IRequest) self._registerSettings(debug_authorization=False) - self._registerRootFactory(rootfactory) + self._registerRootFactory(None) router = self._makeOne() start_response = DummyStartResponse() result = router(environ, start_response) self.assertEqual(start_response.status, '401 Unauthorized') - message = result[0] + message = environ['repoze.bfg.message'] self.failUnless('failed security policy check' in message) self.assertEqual(permissionfactory.checked_with, secpol) def test_call_view_permission_fails_with_debug_auth(self): - rootfactory = make_rootfactory(None) from zope.interface import Interface from zope.interface import directlyProvides class IContext(Interface): @@ -448,23 +480,22 @@ class RouterTests(unittest.TestCase): self._registerTraverserFactory(context, subpath=['']) response = DummyResponse() view = make_view(response) - secpol = DummySecurityPolicy() from repoze.bfg.security import ACLDenied permissionfactory = make_permission_factory( ACLDenied('ace', 'acl', 'permission', ['principals'], context) ) environ = self._makeEnviron() self._registerView(view, '', IContext, IRequest) - self._registerSecurityPolicy(secpol) + secpol = self._registerSecurityPolicy() self._registerPermission(permissionfactory, '', IContext, IRequest) self._registerSettings(debug_authorization=True) logger = self._registerLogger() - self._registerRootFactory(rootfactory) + self._registerRootFactory(None) router = self._makeOne() start_response = DummyStartResponse() result = router(environ, start_response) self.assertEqual(start_response.status, '401 Unauthorized') - message = result[0] + message = environ['repoze.bfg.message'] self.failUnless( "ACLDenied permission 'permission' via ACE 'ace' in ACL 'acl' " "on context" in message) @@ -482,7 +513,6 @@ class RouterTests(unittest.TestCase): "for principals ['principals']" in logged) def test_call_eventsends(self): - rootfactory = make_rootfactory(None) context = DummyContext() self._registerTraverserFactory(context) response = DummyResponse() @@ -494,7 +524,7 @@ class RouterTests(unittest.TestCase): from repoze.bfg.interfaces import INewResponse request_events = self._registerEventListener(INewRequest) response_events = self._registerEventListener(INewResponse) - self._registerRootFactory(rootfactory) + self._registerRootFactory(None) router = self._makeOne() start_response = DummyStartResponse() result = router(environ, start_response) @@ -508,7 +538,6 @@ class RouterTests(unittest.TestCase): from repoze.bfg.interfaces import IPOSTRequest from repoze.bfg.interfaces import IPUTRequest from repoze.bfg.interfaces import IRequest - rootfactory = make_rootfactory(None) context = DummyContext() self._registerTraverserFactory(context) response = DummyResponse() @@ -516,7 +545,7 @@ class RouterTests(unittest.TestCase): view = make_view(response) environ = self._makeEnviron(REQUEST_METHOD='POST') self._registerView(view, '', None, None) - self._registerRootFactory(rootfactory) + self._registerRootFactory(None) router = self._makeOne() start_response = DummyStartResponse() request_events = self._registerEventListener(INewRequest) @@ -531,7 +560,6 @@ class RouterTests(unittest.TestCase): from repoze.bfg.interfaces import IPUTRequest from repoze.bfg.interfaces import IPOSTRequest from repoze.bfg.interfaces import IRequest - rootfactory = make_rootfactory(None) context = DummyContext() self._registerTraverserFactory(context) response = DummyResponse() @@ -539,7 +567,7 @@ class RouterTests(unittest.TestCase): view = make_view(response) environ = self._makeEnviron(REQUEST_METHOD='PUT') self._registerView(view, '', None, None) - self._registerRootFactory(rootfactory) + self._registerRootFactory(None) router = self._makeOne() start_response = DummyStartResponse() request_events = self._registerEventListener(INewRequest) @@ -552,7 +580,6 @@ class RouterTests(unittest.TestCase): def test_call_unknown_method(self): from repoze.bfg.interfaces import INewRequest from repoze.bfg.interfaces import IRequest - rootfactory = make_rootfactory(None) context = DummyContext() self._registerTraverserFactory(context) response = DummyResponse() @@ -560,7 +587,7 @@ class RouterTests(unittest.TestCase): view = make_view(response) environ = self._makeEnviron(REQUEST_METHOD='UNKNOWN') self._registerView(view, '', None, None) - self._registerRootFactory(rootfactory) + self._registerRootFactory(None) router = self._makeOne() start_response = DummyStartResponse() request_events = self._registerEventListener(INewRequest) @@ -573,7 +600,6 @@ class RouterTests(unittest.TestCase): from repoze.bfg.interfaces import IRequestFactory from repoze.bfg.testing import DummyRequest self.registry.registerUtility(DummyRequest, IRequestFactory) - rootfactory = make_rootfactory(None) context = DummyContext() self._registerTraverserFactory(context) response = DummyResponse() @@ -581,7 +607,7 @@ class RouterTests(unittest.TestCase): view = make_view(response) environ = self._makeEnviron() self._registerView(view, '', None, None) - self._registerRootFactory(rootfactory) + self._registerRootFactory(None) router = self._makeOne() start_response = DummyStartResponse() request_events = self._registerEventListener(INewRequest) @@ -593,40 +619,6 @@ class RouterTests(unittest.TestCase): self.assertEqual(request.view_name, '') self.assertEqual(request.subpath, []) - def test_call_inotfound_appfactory_override(self): - from repoze.bfg.interfaces import INotFoundAppFactory - def app(): - """ """ - self.registry.registerUtility(app, INotFoundAppFactory) - rootfactory = make_rootfactory(None) - context = DummyContext() - self._registerTraverserFactory(context) - response = DummyResponse() - response.app_iter = ['Hello world'] - view = make_view(response) - environ = self._makeEnviron() - self._registerView(view, '', None, None) - self._registerRootFactory(rootfactory) - router = self._makeOne() - self.assertEqual(router.notfound_app_factory, app) - - def test_call_iunauth_appfactory_override(self): - from repoze.bfg.interfaces import IUnauthorizedAppFactory - def app(): - """ """ - self.registry.registerUtility(app, IUnauthorizedAppFactory) - rootfactory = make_rootfactory(None) - context = DummyContext() - self._registerTraverserFactory(context) - response = DummyResponse() - response.app_iter = ['Hello world'] - view = make_view(response) - environ = self._makeEnviron() - self._registerView(view, '', None, None) - self._registerRootFactory(rootfactory) - router = self._makeOne() - self.assertEqual(router.unauth_app_factory, app) - class MakeAppTests(unittest.TestCase): def setUp(self): cleanUp() @@ -774,5 +766,11 @@ class DummyResponse: app_iter = () class DummySecurityPolicy: - pass + def __init__(self): + def wsgiapp(environ, start_response): + self.environ = environ + self.start_response = start_response + start_response('401 Unauthorized', []) + return 'Unauthorized' + self.forbidden = lambda *x: wsgiapp diff --git a/repoze/bfg/tests/test_security.py b/repoze/bfg/tests/test_security.py index 03a466e7c..b596a1547 100644 --- a/repoze/bfg/tests/test_security.py +++ b/repoze/bfg/tests/test_security.py @@ -243,6 +243,18 @@ class TestACLSecurityPolicy(unittest.TestCase): result = policy.principals_allowed_by_permission(None, 'read') self.assertEqual(result, []) + def test_forbidden(self): + policy = self._makeOne(lambda *arg: None) + forbidden_app = policy.forbidden() + environ = {} + result = [] + def start_response(status, headers): + result.append((status, headers)) + response = forbidden_app(environ, start_response) + self.assertEqual(result[0][0], '401 Unauthorized') + self.failUnless(len(result[0][1]), 2) # headers + + class TestInheritingACLSecurityPolicy(unittest.TestCase): def setUp(self): cleanUp() @@ -430,6 +442,17 @@ class TestInheritingACLSecurityPolicy(unittest.TestCase): result = policy.authenticated_userid(request) self.assertEqual(result, None) + def test_forbidden(self): + policy = self._makeOne(lambda *arg: None) + forbidden_app = policy.forbidden() + environ = {} + result = [] + def start_response(status, headers): + result.append((status, headers)) + response = forbidden_app(environ, start_response) + self.assertEqual(result[0][0], '401 Unauthorized') + self.failUnless(len(result[0][1]), 2) # headers + class TestAllPermissionsList(unittest.TestCase): def setUp(self): cleanUp() diff --git a/repoze/bfg/tests/test_wsgi.py b/repoze/bfg/tests/test_wsgi.py index b9568eb82..893364635 100644 --- a/repoze/bfg/tests/test_wsgi.py +++ b/repoze/bfg/tests/test_wsgi.py @@ -131,7 +131,7 @@ class TestNotFound(unittest.TestCase): ('Content-Type', 'text/html')]) def test_with_message(self): - environ = {'message':''} + environ = {'repoze.bfg.message':''} L = [] def start_response(status, headers): L.append((status, headers)) @@ -166,7 +166,7 @@ class TestUnauthorized(unittest.TestCase): ('Content-Type', 'text/html')]) def test_with_message(self): - environ = {'message':''} + environ = {'repoze.bfg.message':''} L = [] def start_response(status, headers): L.append((status, headers)) -- cgit v1.2.3