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``. --- CHANGES.txt | 48 ++++++++++ docs/narr/hooks.rst | 54 +++++------ repoze/bfg/interfaces.py | 19 +++- repoze/bfg/router.py | 46 +++++++--- repoze/bfg/security.py | 9 +- repoze/bfg/tests/test_router.py | 186 +++++++++++++++++++------------------- repoze/bfg/tests/test_security.py | 23 +++++ repoze/bfg/tests/test_wsgi.py | 4 +- repoze/bfg/wsgi.py | 2 +- 9 files changed, 250 insertions(+), 141 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index 40a810305..3650b674f 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,3 +1,51 @@ +Next release +============ + +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``. + + 0.8.1 (2009-05-21) ================== diff --git a/docs/narr/hooks.rst b/docs/narr/hooks.rst index 21906e466..5eab5e41e 100644 --- a/docs/narr/hooks.rst +++ b/docs/narr/hooks.rst @@ -88,7 +88,7 @@ an object that implements any particular interface; it simply needs have a ``status`` attribute, a ``headerlist`` attribute, and and ``app_iter`` attribute. -Changing the NotFound application +Changing the NotFound Application --------------------------------- When :mod:`repoze.bfg` can't map a URL to code, it creates and invokes @@ -119,54 +119,54 @@ sample code that implements a minimal NotFound application factory: .. note:: When a NotFound application factory is invoked, it is passed the WSGI environ and the WSGI ``start_response`` handler by :mod:`repoze.bfg`. Within the WSGI environ will be a key named - ``message`` that has a value explaining why the not found error was - raised. This error will be different when the ``debug_notfound`` - environment setting is true than it is when it is false. + ``repoze.bfg.message`` that has a value explaining why the not + found error was raised. This error will be different when the + ``debug_notfound`` environment setting is true than it is when it + is false. -Changing the Unauthorized application -------------------------------------- +Changing the Forbidden Application +---------------------------------- When :mod:`repoze.bfg` can't authorize execution of a view based on -the security policy in use, it creates and invokes an Unauthorized -WSGI application. The application it invokes can be customized by -placing something like the following ZCML in your ``configure.zcml`` -file. +the security policy in use, it creates and invokes a Forbidden WSGI +application. The application it invokes can be customized by placing +something like the following ZCML in your ``configure.zcml`` file. .. code-block:: xml :linenos: - + -Replace ``helloworld.factories.unauthorized_app_factory`` with the -Python dotted name to the request factory you want to use. Here's +Replace ``helloworld.factories.forbidden_app_factory`` with the Python +dotted name to the WSGI application factory you want to use. Here's some sample code that implements a minimal Unauthorized application factory: .. code-block:: python - from webob.exc import HTTPUnauthorized + from webob.exc import HTTPForbidden - class MyUnauthorized(HTTPUnauthorized): + class MyForbidden(HTTPForbidden): pass - def notfound_app_factory(): - return MyUnauthorized + def forbidden_app_factory(): + return MyForbidden -.. note:: When an Unauthorized application factory is invoked, it is +.. note:: When an Forbidden application factory is invoked, it is passed the WSGI environ and the WSGI ``start_response`` handler by :mod:`repoze.bfg`. Within the WSGI environ will be a key named - ``message`` that has a value explaining why the action was not - authorized. This error will be different when the + ``repoze.bfg.message`` that has a value explaining why the action + was forbidden. This error will be different when the ``debug_authorization`` environment setting is true than it is when it is false. -.. note:: You can influence the status code of Unauthorized responses - by using an alterate unauthorized application factory. For - example, you may return an unauthorized application with a ``403 - Forbidden`` status code, rather than use the default unauthorized - application factory, which sends a response with a ``401 - Unauthorized`` status code. +.. warning:: the default forbidden application factory sends a + response with a ``401 Unauthorized`` status code for backwards + compatibility reasons. You can influence the status code of + Forbidden responses by using an alterate forbidden application + factory. For example, it would make sense to return an forbidden + application with a ``403 Forbidden`` status code. Changing the Default Routes Context Factory ------------------------------------------- diff --git a/repoze/bfg/interfaces.py b/repoze/bfg/interfaces.py index cecc3a397..034e0ac1c 100644 --- a/repoze/bfg/interfaces.py +++ b/repoze/bfg/interfaces.py @@ -131,6 +131,17 @@ class ISecurityPolicy(Interface): implementation, in which case, it should raise a ``NotImplementedError`` exception.""" + def forbidden(): + """ This method should return a WSGI application (a callable + accepting ``environ`` and ``start_response``). This WSGI + application will be called by ``repoze.bfg`` when view + invocation is denied due to a security policy deny. The WSGI + application should return a response appropriate when access + to a view resource was forbidden by the security policy. Note + that the ``repoze.bfg.message`` key in the environ passed to + the WSGI app will contain the 'raw' reason that view + invocation was denied by repoze.bfg.""" + class IViewPermission(Interface): def __call__(security_policy): """ Return True if the permission allows, return False if it denies. """ @@ -197,7 +208,7 @@ class INotFoundAppFactory(Interface): a``message`` key in the WSGI environ provides information pertaining to the reason for the notfound.""" -class IUnauthorizedAppFactory(Interface): +class IForbiddenAppFactory(Interface): """ A utility which returns an Unauthorized WSGI application factory""" def __call__(): @@ -206,6 +217,12 @@ class IUnauthorizedAppFactory(Interface): ``message`` key in the WSGI environ provides information pertaining to the reason for the unauthorized.""" +IUnauthorizedAppFactory = IForbiddenAppFactory +deprecated('IUnauthorizedAppFactory', + '(repoze.bfg.interfaces.IUnauthorizedAppFactory should now be ' + 'imported as repoze.bfg.interfaces.IForbiddenAppFactory)', + ) + class IContextURL(Interface): """ An adapter which deals with URLs related to a context. """ diff --git a/repoze/bfg/router.py b/repoze/bfg/router.py index 81bc6e4ef..885a08184 100644 --- a/repoze/bfg/router.py +++ b/repoze/bfg/router.py @@ -17,7 +17,7 @@ from repoze.bfg.interfaces import IRouter from repoze.bfg.interfaces import IRoutesMapper from repoze.bfg.interfaces import ISecurityPolicy from repoze.bfg.interfaces import ISettings -from repoze.bfg.interfaces import IUnauthorizedAppFactory +from repoze.bfg.interfaces import IForbiddenAppFactory from repoze.bfg.interfaces import IView from repoze.bfg.interfaces import IViewPermission @@ -50,22 +50,40 @@ class Router(object): def __init__(self, registry): self.registry = registry + self.logger = registry.queryUtility(ILogger, 'repoze.bfg.debug') self.request_factory = registry.queryUtility(IRequestFactory) - self.security_policy = registry.queryUtility(ISecurityPolicy) - self.notfound_app_factory = registry.queryUtility( - INotFoundAppFactory, - default=NotFound) - self.unauth_app_factory = registry.queryUtility( - IUnauthorizedAppFactory, - default=Unauthorized) + security_policy = registry.queryUtility(ISecurityPolicy) + + self.forbidden_app_factory = registry.queryUtility(IForbiddenAppFactory) + if security_policy is not None: + if hasattr(security_policy, 'forbidden'): + security_policy_forbidden = security_policy.forbidden + else: + security_policy_forbidden = Unauthorized + warning = ('You are running with a security policy (%s) which ' + 'does not have a "forbidden" method; in BFG 0.8.2+ ' + 'the ISecurityPolicy interface in the ' + 'repoze.bfg.interfaces module defines this method ' + 'as required; your application will not work under ' + 'a future release of BFG if you continue using a ' + 'security policy without a "forbidden" method.' % + security_policy) + self.logger and self.logger.warn(warning) + # allow a specifically-registered IForbiddenAppFactory to + # override the security policy's forbidden + self.forbidden_app_factory = (self.forbidden_app_factory or + security_policy_forbidden) + + self.security_policy = security_policy + self.notfound_app_factory = registry.queryUtility(INotFoundAppFactory, + default=NotFound) settings = registry.queryUtility(ISettings) if settings is not None: self.debug_authorization = settings.debug_authorization self.debug_notfound = settings.debug_notfound - self.logger = registry.queryUtility(ILogger, 'repoze.bfg.debug') self.root_factory = registry.getUtility(IRootFactory) self.root_policy = self.root_factory # b/w compat self.traverser_warned = {} @@ -144,14 +162,16 @@ class Router(object): 'context %r): %s' % ( request.url, view_name, context, permitted) ) + if not permitted: if debug_authorization: msg = str(permitted) else: msg = 'Unauthorized: failed security policy check' - environ['message'] = msg - unauth_app = self.unauth_app_factory() - return unauth_app(environ, start_response) + + environ['repoze.bfg.message'] = msg + + return self.forbidden_app_factory()(environ, start_response) response = registry.queryMultiAdapter( (context, request), IView, name=view_name) @@ -168,7 +188,7 @@ class Router(object): logger and logger.debug(msg) else: msg = request.url - environ['message'] = msg + environ['repoze.bfg.message'] = msg notfound_app = self.notfound_app_factory() return notfound_app(environ, start_response) diff --git a/repoze/bfg/security.py b/repoze/bfg/security.py index 90916bac2..bd1edaf6d 100644 --- a/repoze/bfg/security.py +++ b/repoze/bfg/security.py @@ -8,6 +8,8 @@ from repoze.bfg.interfaces import ISecurityPolicy from repoze.bfg.interfaces import IViewPermission from repoze.bfg.interfaces import IViewPermissionFactory +from repoze.bfg.wsgi import Unauthorized as UnauthorizedApp + Everyone = 'system.Everyone' Authenticated = 'system.Authenticated' Allow = 'Allow' @@ -145,6 +147,8 @@ class ACLSecurityPolicy(object): return [] + forbidden = UnauthorizedApp + class InheritingACLSecurityPolicy(object): """ A security policy which uses ACLs in the following ways: @@ -268,6 +272,8 @@ class InheritingACLSecurityPolicy(object): return allowed + forbidden = UnauthorizedApp + def get_remoteuser(request): user_id = request.environ.get('REMOTE_USER') if user_id: @@ -595,6 +601,3 @@ class ViewPermissionFactory(object): class Unauthorized(Exception): pass - - - 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)) diff --git a/repoze/bfg/wsgi.py b/repoze/bfg/wsgi.py index abe7ebead..027345673 100644 --- a/repoze/bfg/wsgi.py +++ b/repoze/bfg/wsgi.py @@ -105,7 +105,7 @@ def wsgiapp2(wrapped): class HTTPException(object): def __call__(self, environ, start_response, exc_info=False): try: - msg = escape(environ['message']) + msg = escape(environ['repoze.bfg.message']) except KeyError: msg = '' html = """ -- cgit v1.2.3