Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

do not enforce default permissions on exception views #2534

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 10 additions & 8 deletions pyramid/config/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@
from zope.interface.interfaces import IInterface

from pyramid.interfaces import (
IDefaultPermission,
IException,
IExceptionViewClassifier,
IMultiView,
Expand Down Expand Up @@ -878,11 +877,6 @@ def register(permission=permission, renderer=renderer):
registry=self.registry
)

if permission is None:
# intent: will be None if no default permission is registered
# (reg'd in phase 1)
permission = self.registry.queryUtility(IDefaultPermission)

# added by discrim_func above during conflict resolving
preds = view_intr['predicates']
order = view_intr['order']
Expand Down Expand Up @@ -1436,7 +1430,10 @@ def forbidden(request):

.. versionadded:: 1.3
"""
for arg in ('name', 'permission', 'context', 'for_', 'http_cache'):
for arg in (
'name', 'permission', 'context', 'for_', 'http_cache',
'require_csrf',
):
if arg in view_options:
raise ConfigurationError(
'%s may not be used as an argument to add_forbidden_view'
Expand Down Expand Up @@ -1464,6 +1461,7 @@ def forbidden(request):
match_param=match_param,
route_name=route_name,
permission=NO_PERMISSION_REQUIRED,
require_csrf=False,
attr=attr,
renderer=renderer,
)
Expand Down Expand Up @@ -1548,7 +1546,10 @@ def notfound(request):
.. versionchanged:: 1.6
.. versionadded:: 1.3
"""
for arg in ('name', 'permission', 'context', 'for_', 'http_cache'):
for arg in (
'name', 'permission', 'context', 'for_', 'http_cache',
'require_csrf',
):
if arg in view_options:
raise ConfigurationError(
'%s may not be used as an argument to add_notfound_view'
Expand Down Expand Up @@ -1576,6 +1577,7 @@ def notfound(request):
match_param=match_param,
route_name=route_name,
permission=NO_PERMISSION_REQUIRED,
require_csrf=False,
)
settings.update(view_options)
if append_slash:
Expand Down
4 changes: 2 additions & 2 deletions pyramid/interfaces.py
Original file line number Diff line number Diff line change
Expand Up @@ -1224,9 +1224,9 @@ def __call__(view, info):
class IViewDeriverInfo(Interface):
""" An object implementing this interface is passed to every
:term:`view deriver` during configuration."""
registry = Attribute('The "current" application registry when the '
registry = Attribute('The "current" application registry where the '
'view was created')
package = Attribute('The "current package" when the view '
package = Attribute('The "current package" where the view '
'configuration statement was found')
settings = Attribute('The deployment settings dictionary related '
'to the current application')
Expand Down
57 changes: 57 additions & 0 deletions pyramid/tests/test_viewderivers.py
Original file line number Diff line number Diff line change
Expand Up @@ -628,6 +628,63 @@ def myview(request): pass
else: # pragma: no cover
raise AssertionError

def test_secured_view_skipped_by_default_on_exception_view(self):
from pyramid.request import Request
from pyramid.security import NO_PERMISSION_REQUIRED
def view(request):
raise ValueError
def excview(request):
return 'hello'
self._registerSecurityPolicy(False)
self.config.add_settings({'debug_authorization': True})
self.config.set_default_permission('view')
self.config.add_view(view, name='foo', permission=NO_PERMISSION_REQUIRED)
self.config.add_view(excview, context=ValueError, renderer='string')
app = self.config.make_wsgi_app()
request = Request.blank('/foo', base_url='http://example.com')
request.method = 'POST'
response = request.get_response(app)
self.assertTrue(b'hello' in response.body)

def test_secured_view_failed_on_explicit_exception_view(self):
from pyramid.httpexceptions import HTTPForbidden
from pyramid.request import Request
from pyramid.security import NO_PERMISSION_REQUIRED
def view(request):
raise ValueError
def excview(request): pass
self._registerSecurityPolicy(False)
self.config.add_view(view, name='foo', permission=NO_PERMISSION_REQUIRED)
self.config.add_view(excview, context=ValueError, renderer='string',
permission='view')
app = self.config.make_wsgi_app()
request = Request.blank('/foo', base_url='http://example.com')
request.method = 'POST'
try:
request.get_response(app)
except HTTPForbidden:
pass
else: # pragma: no cover
raise AssertionError

def test_secured_view_passed_on_explicit_exception_view(self):
from pyramid.request import Request
from pyramid.security import NO_PERMISSION_REQUIRED
def view(request):
raise ValueError
def excview(request):
return 'hello'
self._registerSecurityPolicy(True)
self.config.add_view(view, name='foo', permission=NO_PERMISSION_REQUIRED)
self.config.add_view(excview, context=ValueError, renderer='string',
permission='view')
app = self.config.make_wsgi_app()
request = Request.blank('/foo', base_url='http://example.com')
request.method = 'POST'
request.headers['X-CSRF-Token'] = 'foo'
response = request.get_response(app)
self.assertTrue(b'hello' in response.body)

def test_predicate_mismatch_view_has_no_name(self):
from pyramid.exceptions import PredicateMismatch
response = DummyResponse()
Expand Down
21 changes: 19 additions & 2 deletions pyramid/viewderivers.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
IAuthenticationPolicy,
IAuthorizationPolicy,
IDefaultCSRFOptions,
IDefaultPermission,
IDebugLogger,
IResponse,
IViewMapper,
Expand Down Expand Up @@ -272,7 +273,9 @@ def secured_view(view, info):
secured_view.options = ('permission',)

def _secured_view(view, info):
permission = info.options.get('permission')
permission = explicit_val = info.options.get('permission')
if permission is None:
permission = info.registry.queryUtility(IDefaultPermission)
if permission == NO_PERMISSION_REQUIRED:
# allow views registered within configurations that have a
# default permission to explicitly override the default
Expand All @@ -288,6 +291,12 @@ def _permitted(context, request):
principals = authn_policy.effective_principals(request)
return authz_policy.permits(context, principals, permission)
def _secured_view(context, request):
if (
getattr(request, 'exception', None) is not None and
explicit_val is None
):
return view(context, request)

result = _permitted(context, request)
if result:
return view(context, request)
Expand All @@ -306,12 +315,20 @@ def _secured_view(context, request):
def _authdebug_view(view, info):
wrapped_view = view
settings = info.settings
permission = info.options.get('permission')
permission = explicit_val = info.options.get('permission')
if permission is None:
permission = info.registry.queryUtility(IDefaultPermission)
authn_policy = info.registry.queryUtility(IAuthenticationPolicy)
authz_policy = info.registry.queryUtility(IAuthorizationPolicy)
logger = info.registry.queryUtility(IDebugLogger)
if settings and settings.get('debug_authorization', False):
def _authdebug_view(context, request):
if (
getattr(request, 'exception', None) is not None and
explicit_val is None
):
return view(context, request)

view_name = getattr(request, 'view_name', None)

if authn_policy and authz_policy:
Expand Down