From 1ad1dbb5987c74d8e6802221c78af96e24ca1960 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Tue, 26 Oct 2010 19:17:22 -0400 Subject: forward port of bugfix from bfg trunk --- docs/narr/security.rst | 15 +++++++++------ pyramid/configuration.py | 24 +++++++++++++++--------- pyramid/interfaces.py | 1 + pyramid/tests/defpermbugapp/__init__.py | 14 ++++++++++++++ pyramid/tests/defpermbugapp/configure.zcml | 14 ++++++++++++++ pyramid/tests/test_configuration.py | 22 ++++++++++++++++++++++ pyramid/tests/test_integration.py | 16 ++++++++++++++++ 7 files changed, 91 insertions(+), 15 deletions(-) create mode 100644 pyramid/tests/defpermbugapp/__init__.py create mode 100644 pyramid/tests/defpermbugapp/configure.zcml diff --git a/docs/narr/security.rst b/docs/narr/security.rst index 25d1c2ecf..109009842 100644 --- a/docs/narr/security.rst +++ b/docs/narr/security.rst @@ -239,13 +239,16 @@ application: - The :ref:`default_permission_directive` ZCML directive. -When a default permission is registered, if a view configuration -*does* name its own permission, the default permission is ignored for -that view registration, and the view-configuration-named permission is -used. +When a default permission is registered: -.. note:: All APIs and ZCML directives related to default permissions - are new in :mod:`pyramid` 1.3. +- if a view configuration names an explicit ``permission`, the default + permission is ignored for that view registration, and the + view-configuration-named permission is used. + +- if a view configuration names an explicit permission as the string + ``__no_permission_required__``, the default permission is ignored, + and the view is registered *without* a permission (making it + available to all callers regardless of their credentials). .. index:: single: ACL diff --git a/pyramid/configuration.py b/pyramid/configuration.py index bf06143bd..21dce8af1 100644 --- a/pyramid/configuration.py +++ b/pyramid/configuration.py @@ -89,8 +89,6 @@ DEFAULT_RENDERERS = ( ('string', renderers.string_renderer_factory), ) -_marker = object() - class Configurator(object): """ A Configurator is used to configure a :mod:`pyramid` @@ -746,7 +744,7 @@ class Configurator(object): return route - def add_view(self, view=None, name="", for_=None, permission=_marker, + def add_view(self, view=None, name="", for_=None, permission=None, request_type=None, route_name=None, request_method=None, request_param=None, containment=None, attr=None, renderer=None, wrapper=None, xhr=False, accept=None, @@ -783,11 +781,12 @@ class Configurator(object): :class:`pyramid.configuration.Configurator` constructor's ``default_permission`` argument, or if :meth:`pyramid.configuration.Configurator.set_default_permission` - was used prior to this view registration. Pass ``None`` as - the permission to explicitly indicate that the view should - always be executable by entirely anonymous users, regardless - of the default permission, bypassing any - :term:`authorization policy` that may be in effect. + was used prior to this view registration. Pass the string + ``__no_permission_required__`` as the permission argument to + explicitly indicate that the view should always be + executable by entirely anonymous users, regardless of the + default permission, bypassing any :term:`authorization + policy` that may be in effect. attr @@ -1051,10 +1050,11 @@ class Configurator(object): containment=containment, request_type=request_type, custom=custom_predicates) - if permission is _marker: + if permission is None: # intent: will be None if no default permission is registered permission = self.registry.queryUtility(IDefaultPermission) + # NO_PERMISSION_REQUIRED handled by _secure_view derived_view = self._derive_view(view, permission, predicates, attr, renderer, wrapper, name, accept, order, phash) @@ -2514,6 +2514,12 @@ def _predicate_wrap(view, predicates): return predicate_wrapper def _secure_view(view, permission, authn_policy, authz_policy): + if permission == '__no_permission_required__': + # allow views registered within configurations that have a + # default permission to explicitly override the default + # permission, replacing it with no permission at all + permission = None + wrapped_view = view if authn_policy and authz_policy and (permission is not None): def _secured_view(context, request): diff --git a/pyramid/interfaces.py b/pyramid/interfaces.py index afb0376cd..9b7488f1e 100644 --- a/pyramid/interfaces.py +++ b/pyramid/interfaces.py @@ -376,3 +376,4 @@ class IDefaultPermission(Interface): for all view configurations which do not explicitly declare their own.""" +NO_PERMISSION_REQUIRED = '__no_permission_required__' diff --git a/pyramid/tests/defpermbugapp/__init__.py b/pyramid/tests/defpermbugapp/__init__.py new file mode 100644 index 000000000..3b1a00083 --- /dev/null +++ b/pyramid/tests/defpermbugapp/__init__.py @@ -0,0 +1,14 @@ +from webob import Response +from pyramid.view import bfg_view + +@bfg_view(name='x') +def x_view(request): + return Response('this is private!') + +@bfg_view(name='y', permission='private2') +def y_view(request): + return Response('this is private too!') + +@bfg_view(name='z', permission='__no_permission_required__') +def z_view(request): + return Response('this is public') diff --git a/pyramid/tests/defpermbugapp/configure.zcml b/pyramid/tests/defpermbugapp/configure.zcml new file mode 100644 index 000000000..f9680bb04 --- /dev/null +++ b/pyramid/tests/defpermbugapp/configure.zcml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + diff --git a/pyramid/tests/test_configuration.py b/pyramid/tests/test_configuration.py index de2de621f..fa2318fcc 100644 --- a/pyramid/tests/test_configuration.py +++ b/pyramid/tests/test_configuration.py @@ -2775,6 +2775,28 @@ class ConfiguratorTests(unittest.TestCase): permitted = result.__permitted__(None, None) self.assertEqual(permitted, False) + def test__derive_view_debug_auth_permission_authpol_overridden(self): + view = lambda *arg: 'OK' + config = self._makeOne() + self._registerSettings(config, + debug_authorization=True, reload_templates=True) + logger = self._registerLogger(config) + self._registerSecurityPolicy(config, False) + result = config._derive_view(view, + permission='__no_permission_required__') + self.assertEqual(view.__module__, result.__module__) + self.assertEqual(view.__doc__, result.__doc__) + self.assertEqual(view.__name__, result.__name__) + self.failIf(hasattr(result, '__call_permissive__')) + request = self._makeRequest(config) + request.view_name = 'view_name' + request.url = 'url' + self.assertEqual(result(None, request), 'OK') + self.assertEqual(len(logger.messages), 1) + self.assertEqual(logger.messages[0], + "debug_authorization of url url (view name " + "'view_name' against context None): False") + def test__derive_view_with_predicates_all(self): view = lambda *arg: 'OK' predicates = [] diff --git a/pyramid/tests/test_integration.py b/pyramid/tests/test_integration.py index 1c6752808..fffd8c03e 100644 --- a/pyramid/tests/test_integration.py +++ b/pyramid/tests/test_integration.py @@ -198,6 +198,22 @@ class TestViewPermissionBug(TwillBase): browser.go('http://localhost:6543/x') self.assertEqual(browser.get_code(), 401) +class TestDefaultViewPermissionBug(TwillBase): + # default_view_permission bug as reported by Wiggy at http://lists.repoze.org/pipermail/repoze-dev/2010-October/003602.html + config = 'pyramid.tests.defpermbugapp:configure.zcml' + def test_it(self): + import twill.commands + browser = twill.commands.get_browser() + browser.go('http://localhost:6543/x') + self.assertEqual(browser.get_code(), 401) + self.failUnless('failed permission check' in browser.get_html()) + browser.go('http://localhost:6543/y') + self.assertEqual(browser.get_code(), 401) + self.failUnless('failed permission check' in browser.get_html()) + browser.go('http://localhost:6543/z') + self.assertEqual(browser.get_code(), 200) + self.failUnless('public' in browser.get_html()) + from pyramid.tests.exceptionviewapp.models import AnException, NotAnException excroot = {'anexception':AnException(), 'notanexception':NotAnException()} -- cgit v1.2.3