From 16945f0e9c57aeabadb6f2e2f150a2687455be40 Mon Sep 17 00:00:00 2001 From: Raphael Michel Date: Sat, 7 Nov 2015 14:18:06 +0100 Subject: [PATCH] Fixed #25695 -- Added template_name parameter to csrf_failure() view. --- django/views/csrf.py | 15 +++++++++++--- docs/ref/settings.txt | 9 ++++++++ docs/releases/1.10.txt | 4 +++- tests/view_tests/tests/test_csrf.py | 32 ++++++++++++++++++++++++++++- 4 files changed, 55 insertions(+), 5 deletions(-) diff --git a/django/views/csrf.py b/django/views/csrf.py index 9ab9c44171..7d5f4e113e 100644 --- a/django/views/csrf.py +++ b/django/views/csrf.py @@ -1,6 +1,6 @@ from django.conf import settings from django.http import HttpResponseForbidden -from django.template import Context, Engine +from django.template import Context, Engine, TemplateDoesNotExist, loader from django.utils.translation import ugettext as _ from django.utils.version import get_docs_version @@ -95,14 +95,14 @@ CSRF_FAILURE_TEMPLATE = """ """ +CSRF_FAILURE_TEMPLATE_NAME = "403_csrf.html" -def csrf_failure(request, reason=""): +def csrf_failure(request, reason="", template_name=CSRF_FAILURE_TEMPLATE_NAME): """ Default view used when request fails CSRF protection """ from django.middleware.csrf import REASON_NO_REFERER, REASON_NO_CSRF_COOKIE - t = Engine().from_string(CSRF_FAILURE_TEMPLATE) c = Context({ 'title': _("Forbidden"), 'main': _("CSRF verification failed. Request aborted."), @@ -131,4 +131,13 @@ def csrf_failure(request, reason=""): 'docs_version': get_docs_version(), 'more': _("More information is available with DEBUG=True."), }) + try: + t = loader.get_template(template_name) + except TemplateDoesNotExist: + if template_name == CSRF_FAILURE_TEMPLATE_NAME: + # If the default template doesn't exist, use the string template. + t = Engine().from_string(CSRF_FAILURE_TEMPLATE) + else: + # Raise if a developer-specified template doesn't exist. + raise return HttpResponseForbidden(t.render(c), content_type='text/html') diff --git a/docs/ref/settings.txt b/docs/ref/settings.txt index ff3d420433..92a2ad4980 100644 --- a/docs/ref/settings.txt +++ b/docs/ref/settings.txt @@ -385,6 +385,15 @@ where ``reason`` is a short message (intended for developers or logging, not for end users) indicating the reason the request was rejected. See :doc:`/ref/csrf`. +``django.views.csrf.csrf_failure()`` accepts an additional ``template_name`` +parameter that defaults to ``'403_csrf.html'``. If a template with that name +exists, it will be used to render the page. + +.. versionchanged:: 1.10 + + The ``template_name`` parameter and the behavior of searching for a template + called ``403_csrf.html`` were added to ``csrf_failure()``. + .. setting:: CSRF_HEADER_NAME CSRF_HEADER_NAME diff --git a/docs/releases/1.10.txt b/docs/releases/1.10.txt index 6dbe0362b9..1e51c1dec9 100644 --- a/docs/releases/1.10.txt +++ b/docs/releases/1.10.txt @@ -114,7 +114,9 @@ Cache CSRF ^^^^ -* ... +* The default :setting:`CSRF_FAILURE_VIEW`, ``views.csrf.csrf_failure()`` now + accepts an optional ``template_name`` parameter, defaulting to + ``'403_csrf.html'``, to control the template used to render the page. Database backends ^^^^^^^^^^^^^^^^^ diff --git a/tests/view_tests/tests/test_csrf.py b/tests/view_tests/tests/test_csrf.py index f6531b882c..f595652978 100644 --- a/tests/view_tests/tests/test_csrf.py +++ b/tests/view_tests/tests/test_csrf.py @@ -1,5 +1,9 @@ -from django.test import Client, SimpleTestCase, override_settings +from django.template import TemplateDoesNotExist +from django.test import ( + Client, RequestFactory, SimpleTestCase, override_settings, +) from django.utils.translation import override +from django.views.csrf import CSRF_FAILURE_TEMPLATE_NAME, csrf_failure @override_settings(ROOT_URLCONF="view_tests.urls") @@ -70,3 +74,29 @@ class CsrfViewTests(SimpleTestCase): """ response = self.client.post('/') self.assertContains(response, "Forbidden", status_code=403) + + @override_settings(TEMPLATES=[{ + 'BACKEND': 'django.template.backends.django.DjangoTemplates', + 'OPTIONS': { + 'loaders': [ + ('django.template.loaders.locmem.Loader', { + CSRF_FAILURE_TEMPLATE_NAME: 'Test template for CSRF failure' + }), + ], + }, + }]) + def test_custom_template(self): + """ + A custom CSRF_FAILURE_TEMPLATE_NAME is used. + """ + response = self.client.post('/') + self.assertContains(response, "Test template for CSRF failure", status_code=403) + + def test_custom_template_does_not_exist(self): + """ + An exception is raised if a nonexistent template is supplied. + """ + factory = RequestFactory() + request = factory.post('/') + with self.assertRaises(TemplateDoesNotExist): + csrf_failure(request, template_name="nonexistent.html")