Fixed #9847 -- Added 403 response handler. Many thanks to kgrandis, adamnelson, vkryachko, fvox13 and Chris Beaven.

git-svn-id: http://code.djangoproject.com/svn/django/trunk@16606 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
Jannis Leidel 2011-08-12 14:15:31 +00:00
parent 958e049d4d
commit 1ca6e9b9e2
10 changed files with 103 additions and 12 deletions

View File

@ -457,6 +457,7 @@ answer newbie questions, and generally made Django that much better:
Ben Slavin <benjamin.slavin@gmail.com> Ben Slavin <benjamin.slavin@gmail.com>
sloonz <simon.lipp@insa-lyon.fr> sloonz <simon.lipp@insa-lyon.fr>
Paul Smith <blinkylights23@gmail.com> Paul Smith <blinkylights23@gmail.com>
Steven L. Smith (fvox13) <steven@stevenlsmith.com>
Warren Smith <warren@wandrsmith.net> Warren Smith <warren@wandrsmith.net>
smurf@smurf.noris.de smurf@smurf.noris.de
Vsevolod Solovyov Vsevolod Solovyov

View File

@ -6,6 +6,7 @@ from django.utils.importlib import import_module
__all__ = ['handler404', 'handler500', 'include', 'patterns', 'url'] __all__ = ['handler404', 'handler500', 'include', 'patterns', 'url']
handler403 = 'django.views.defaults.permission_denied'
handler404 = 'django.views.defaults.page_not_found' handler404 = 'django.views.defaults.page_not_found'
handler500 = 'django.views.defaults.server_error' handler500 = 'django.views.defaults.server_error'

View File

@ -154,12 +154,22 @@ class BaseHandler(object):
finally: finally:
receivers = signals.got_request_exception.send(sender=self.__class__, request=request) receivers = signals.got_request_exception.send(sender=self.__class__, request=request)
except exceptions.PermissionDenied: except exceptions.PermissionDenied:
logger.warning('Forbidden (Permission denied): %s' % request.path, logger.warning(
extra={ 'Forbidden (Permission denied): %s' % request.path,
'status_code': 403, extra={
'request': request 'status_code': 403,
}) 'request': request
response = http.HttpResponseForbidden('<h1>Permission denied</h1>') })
try:
callback, param_dict = resolver.resolve403()
response = callback(request, **param_dict)
except:
try:
response = self.handle_uncaught_exception(request,
resolver, sys.exc_info())
finally:
receivers = signals.got_request_exception.send(
sender=self.__class__, request=request)
except SystemExit: except SystemExit:
# Allow sys.exit() to actually exit. See tickets #1023 and #4701 # Allow sys.exit() to actually exit. See tickets #1023 and #4701
raise raise

View File

@ -331,6 +331,9 @@ class RegexURLResolver(LocaleRegexProvider):
callback = getattr(defaults, 'handler%s' % view_type) callback = getattr(defaults, 'handler%s' % view_type)
return get_callable(callback), {} return get_callable(callback), {}
def resolve403(self):
return self._resolve_special('403')
def resolve404(self): def resolve404(self):
return self._resolve_special('404') return self._resolve_special('404')

View File

@ -1,10 +1,12 @@
from django import http from django import http
from django.template import (Context, RequestContext,
loader, TemplateDoesNotExist)
from django.views.decorators.csrf import requires_csrf_token from django.views.decorators.csrf import requires_csrf_token
from django.template import Context, RequestContext, loader
# This can be called when CsrfViewMiddleware.process_view has not run, therefore # This can be called when CsrfViewMiddleware.process_view has not run,
# need @requires_csrf_token in case the template needs {% csrf_token %}. # therefore need @requires_csrf_token in case the template needs
# {% csrf_token %}.
@requires_csrf_token @requires_csrf_token
def page_not_found(request, template_name='404.html'): def page_not_found(request, template_name='404.html'):
""" """
@ -31,6 +33,27 @@ def server_error(request, template_name='500.html'):
return http.HttpResponseServerError(t.render(Context({}))) return http.HttpResponseServerError(t.render(Context({})))
# This can be called when CsrfViewMiddleware.process_view has not run,
# therefore need @requires_csrf_token in case the template needs
# {% csrf_token %}.
@requires_csrf_token
def permission_denied(request, template_name='403.html'):
"""
Permission denied (403) handler.
Templates: `403.html`
Context: None
If the template does not exist, an Http403 response containing the text
"403 Forbidden" (as per RFC 2616) will be returned.
"""
try:
template = loader.get_template(template_name)
except TemplateDoesNotExist:
return http.HttpResponseForbidden('<h1>403 Forbidden</h1>')
return http.HttpResponseForbidden(template.render(RequestContext(request)))
def shortcut(request, content_type_id, object_id): def shortcut(request, content_type_id, object_id):
# TODO: Remove this in Django 2.0. # TODO: Remove this in Django 2.0.
# This is a legacy view that depends on the contenttypes framework. # This is a legacy view that depends on the contenttypes framework.

View File

@ -261,6 +261,11 @@ Django 1.4 also includes several smaller improvements worth noting:
* It is now possible to load fixtures containing forward references when using * It is now possible to load fixtures containing forward references when using
MySQL with the InnoDB database engine. MySQL with the InnoDB database engine.
* A new 403 response handler has been added as
``'django.views.defaults.permission_denied'``. See the documentation
about :ref:`the 403 (HTTP Forbidden) view<http_forbidden_view>` for more
information.
.. _backwards-incompatible-changes-1.4: .. _backwards-incompatible-changes-1.4:
Backwards incompatible changes in 1.4 Backwards incompatible changes in 1.4

View File

@ -197,3 +197,24 @@ Two things to note about 500 views:
* If :setting:`DEBUG` is set to ``True`` (in your settings module), then * If :setting:`DEBUG` is set to ``True`` (in your settings module), then
your 500 view will never be used, and the traceback will be displayed your 500 view will never be used, and the traceback will be displayed
instead, with some debug information. instead, with some debug information.
.. _http_forbidden_view:
The 403 (HTTP Forbidden) view
----------------------------
.. versionadded:: 1.4
In the same vein as the 404 and 500 views, Django has a view to handle 403
Forbidden errors. If a view results in a 403 exception then Django will, by
default, call the view ``django.views.defaults.permission_denied``.
This view loads and renders the template ``403.html`` in your root template
directory, or if this file does not exist, instead serves the text
"403 Forbidden", as per RFC 2616 (the HTTP 1.1 Specification).
It is possible to override ``django.views.defaults.permission_denied`` in the
same way you can for the 404 and 500 views by specifying a ``handler403`` in
your URLconf::
handler403 = 'mysite.views.my_custom_permission_denied_view'

View File

@ -6,6 +6,8 @@ import sys
from django.conf import settings from django.conf import settings
from django.core.files.uploadedfile import SimpleUploadedFile from django.core.files.uploadedfile import SimpleUploadedFile
from django.test import TestCase, RequestFactory from django.test import TestCase, RequestFactory
from django.test.utils import (setup_test_template_loader,
restore_template_loaders)
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from django.template import TemplateSyntaxError from django.template import TemplateSyntaxError
from django.views.debug import ExceptionReporter from django.views.debug import ExceptionReporter
@ -40,6 +42,26 @@ class DebugViewTests(TestCase):
self.assertTrue('file_data.txt' in response.content) self.assertTrue('file_data.txt' in response.content)
self.assertFalse('haha' in response.content) self.assertFalse('haha' in response.content)
def test_403(self):
# Ensure no 403.html template exists to test the default case.
setup_test_template_loader({})
try:
response = self.client.get('/views/raises403/')
self.assertContains(response, '<h1>403 Forbidden</h1>', status_code=403)
finally:
restore_template_loaders()
def test_403_template(self):
# Set up a test 403.html template.
setup_test_template_loader(
{'403.html': 'This is a test template for a 403 Forbidden error.'}
)
try:
response = self.client.get('/views/raises403/')
self.assertContains(response, 'test template', status_code=403)
finally:
restore_template_loaders()
def test_404(self): def test_404(self):
response = self.client.get('/views/raises404/') response = self.client.get('/views/raises404/')
self.assertEqual(response.status_code, 404) self.assertEqual(response.status_code, 404)

View File

@ -39,8 +39,9 @@ urlpatterns = patterns('',
(r'^server_error/', 'django.views.defaults.server_error'), (r'^server_error/', 'django.views.defaults.server_error'),
# a view that raises an exception for the debug view # a view that raises an exception for the debug view
(r'^raises/$', views.raises), (r'raises/$', views.raises),
(r'^raises404/$', views.raises404), (r'raises404/$', views.raises404),
(r'raises403/$', views.raises403),
# i18n views # i18n views
(r'^i18n/', include('django.conf.urls.i18n')), (r'^i18n/', include('django.conf.urls.i18n')),

View File

@ -1,8 +1,9 @@
import sys import sys
from django import forms from django import forms
from django.http import HttpResponse, HttpResponseRedirect from django.core.exceptions import PermissionDenied
from django.core.urlresolvers import get_resolver from django.core.urlresolvers import get_resolver
from django.http import HttpResponse, HttpResponseRedirect
from django.shortcuts import render_to_response, render from django.shortcuts import render_to_response, render
from django.template import Context, RequestContext, TemplateDoesNotExist from django.template import Context, RequestContext, TemplateDoesNotExist
from django.views.debug import technical_500_response, SafeExceptionReporterFilter from django.views.debug import technical_500_response, SafeExceptionReporterFilter
@ -53,6 +54,9 @@ def raises404(request):
resolver = get_resolver(None) resolver = get_resolver(None)
resolver.resolve('') resolver.resolve('')
def raises403(request):
raise PermissionDenied
def redirect(request): def redirect(request):
""" """
Forces an HTTP redirect. Forces an HTTP redirect.