diff options
| author | Johannes Maron <johannes@maron.family> | 2026-05-11 17:35:36 +0200 |
|---|---|---|
| committer | Jacob Walls <jacobtylerwalls@gmail.com> | 2026-05-18 21:38:07 -0700 |
| commit | 3e4e0db66961a48a080ff3ff91f6c0d954261366 (patch) | |
| tree | af88763cafb19fab54308406a3312b03f8557db9 | |
| parent | fbe902d4a4b8b9dcb371509b25ba8feff3852e64 (diff) | |
Fixed #36825 -- Extended admin templates so CSP nonce is included if available.
Error pages, admin, and registration templates were updated to use
`{% csp_nonce %}` on their explicit `<script>`, `<link>`, and `<style>`
HTML elements.
Co-authored-by: Antoliny0919 <antoliny0919@gmail.com>
Co-authored-by: Natalia <124304+nessita@users.noreply.github.com>
22 files changed, 208 insertions, 38 deletions
diff --git a/django/contrib/admin/static/admin/css/forms.css b/django/contrib/admin/static/admin/css/forms.css index e28006cfa4..f24feba737 100644 --- a/django/contrib/admin/static/admin/css/forms.css +++ b/django/contrib/admin/static/admin/css/forms.css @@ -1,5 +1,3 @@ -@import url("widgets.css"); - /* FORM ROWS */ .form-row { diff --git a/django/contrib/admin/templates/admin/auth/user/add_form.html b/django/contrib/admin/templates/admin/auth/user/add_form.html index f5a17dde7d..96eecd3444 100644 --- a/django/contrib/admin/templates/admin/auth/user/add_form.html +++ b/django/contrib/admin/templates/admin/auth/user/add_form.html @@ -8,5 +8,5 @@ {% endblock %} {% block extrahead %} {{ block.super }} - <link rel="stylesheet" href="{% static 'admin/css/unusable_password_field.css' %}"> + <link rel="stylesheet" href="{% static 'admin/css/unusable_password_field.css' %}" {% csp_nonce_attr %}> {% endblock %} diff --git a/django/contrib/admin/templates/admin/auth/user/change_password.html b/django/contrib/admin/templates/admin/auth/user/change_password.html index 70b68f6de5..e7f97ae97f 100644 --- a/django/contrib/admin/templates/admin/auth/user/change_password.html +++ b/django/contrib/admin/templates/admin/auth/user/change_password.html @@ -5,8 +5,9 @@ {% block title %}{% if form.errors %}{% translate "Error:" %} {% endif %}{{ block.super }}{% endblock %} {% block extrastyle %} {{ block.super }} - <link rel="stylesheet" href="{% static "admin/css/forms.css" %}"> - <link rel="stylesheet" href="{% static 'admin/css/unusable_password_field.css' %}"> + <link rel="stylesheet" href="{% static "admin/css/widgets.css" %}" {% csp_nonce_attr %}> + <link rel="stylesheet" href="{% static "admin/css/forms.css" %}" {% csp_nonce_attr %}> + <link rel="stylesheet" href="{% static 'admin/css/unusable_password_field.css' %}" {% csp_nonce_attr %}> {% endblock %} {% block bodyclass %}{{ block.super }} {{ opts.app_label }}-{{ opts.model_name }} change-form{% endblock %} {% if not is_popup %} diff --git a/django/contrib/admin/templates/admin/base.html b/django/contrib/admin/templates/admin/base.html index 8ef92802a9..b8330c02f5 100644 --- a/django/contrib/admin/templates/admin/base.html +++ b/django/contrib/admin/templates/admin/base.html @@ -4,22 +4,22 @@ <head> <title>{% block title %}{% endblock %}</title> <meta name="color-scheme" content="light dark" /> -<link rel="stylesheet" href="{% block stylesheet %}{% static "admin/css/base.css" %}{% endblock %}"> +<link rel="stylesheet" href="{% block stylesheet %}{% static "admin/css/base.css" %}{% endblock %}" {% csp_nonce_attr %}> {% block dark-mode-vars %} - <link rel="stylesheet" href="{% static "admin/css/dark_mode.css" %}"> - <script src="{% static "admin/js/theme.js" %}"></script> + <link rel="stylesheet" href="{% static "admin/css/dark_mode.css" %}" {% csp_nonce_attr %}> + <script src="{% static "admin/js/theme.js" %}" {% csp_nonce_attr %}></script> {% endblock %} {% if not is_popup and is_nav_sidebar_enabled %} - <link rel="stylesheet" href="{% static "admin/css/nav_sidebar.css" %}"> - <script src="{% static 'admin/js/nav_sidebar.js' %}" defer></script> + <link rel="stylesheet" href="{% static "admin/css/nav_sidebar.css" %}" {% csp_nonce_attr %}> + <script src="{% static 'admin/js/nav_sidebar.js' %}" defer {% csp_nonce_attr %}></script> {% endif %} {% block extrastyle %}{% endblock %} -{% if LANGUAGE_BIDI %}<link rel="stylesheet" href="{% block stylesheet_rtl %}{% static "admin/css/rtl.css" %}{% endblock %}">{% endif %} +{% if LANGUAGE_BIDI %}<link rel="stylesheet" href="{% block stylesheet_rtl %}{% static "admin/css/rtl.css" %}{% endblock %}" {% csp_nonce_attr %}>{% endif %} {% block extrahead %}{% endblock %} {% block responsive %} <meta name="viewport" content="width=device-width, initial-scale=1.0"> - <link rel="stylesheet" href="{% static "admin/css/responsive.css" %}"> - {% if LANGUAGE_BIDI %}<link rel="stylesheet" href="{% static "admin/css/responsive_rtl.css" %}">{% endif %} + <link rel="stylesheet" href="{% static "admin/css/responsive.css" %}" {% csp_nonce_attr %}> + {% if LANGUAGE_BIDI %}<link rel="stylesheet" href="{% static "admin/css/responsive_rtl.css" %}" {% csp_nonce_attr %}>{% endif %} {% endblock %} {% block blockbots %}<meta name="robots" content="NONE,NOARCHIVE">{% endblock %} </head> diff --git a/django/contrib/admin/templates/admin/change_form.html b/django/contrib/admin/templates/admin/change_form.html index 39c883a727..f7dac55e0d 100644 --- a/django/contrib/admin/templates/admin/change_form.html +++ b/django/contrib/admin/templates/admin/change_form.html @@ -3,11 +3,15 @@ {% block title %}{% if errors %}{% translate "Error:" %} {% endif %}{{ block.super }}{% endblock %} {% block extrahead %}{{ block.super }} -<script src="{% url 'admin:jsi18n' %}"></script> -{{ media }} +<script src="{% url 'admin:jsi18n' %}" {% csp_nonce_attr %}></script> +{% csp_nonce_attr media %} {% endblock %} -{% block extrastyle %}{{ block.super }}<link rel="stylesheet" href="{% static "admin/css/forms.css" %}">{% endblock %} +{% block extrastyle %} + {{ block.super }} + <link rel="stylesheet" href="{% static "admin/css/widgets.css" %}" {% csp_nonce_attr %}> + <link rel="stylesheet" href="{% static "admin/css/forms.css" %}" {% csp_nonce_attr %}> +{% endblock %} {% block coltype %}colM{% endblock %} @@ -76,6 +80,7 @@ {% if adminform and add %} data-model-name="{{ opts.model_name }}" {% endif %} + {% csp_nonce_attr %} async> </script> {% endblock %} diff --git a/django/contrib/admin/templates/admin/change_list.html b/django/contrib/admin/templates/admin/change_list.html index 4bb1b2f03b..872a962740 100644 --- a/django/contrib/admin/templates/admin/change_list.html +++ b/django/contrib/admin/templates/admin/change_list.html @@ -4,16 +4,17 @@ {% block title %}{% if cl.formset and cl.formset.errors %}{% translate "Error:" %} {% endif %}{{ block.super }}{% endblock %} {% block extrastyle %} {{ block.super }} - <link rel="stylesheet" href="{% static "admin/css/changelists.css" %}"> + <link rel="stylesheet" href="{% static "admin/css/changelists.css" %}" {% csp_nonce_attr %}> {% if cl.formset %} - <link rel="stylesheet" href="{% static "admin/css/forms.css" %}"> + <link rel="stylesheet" href="{% static "admin/css/widgets.css" %}" {% csp_nonce_attr %}> + <link rel="stylesheet" href="{% static "admin/css/forms.css" %}" {% csp_nonce_attr %}> {% endif %} {% if cl.formset or action_form %} - <script src="{% url 'admin:jsi18n' %}"></script> + <script src="{% url 'admin:jsi18n' %}" {% csp_nonce_attr %}></script> {% endif %} - {{ media.css }} + {% csp_nonce_attr media.css %} {% if not actions_on_top and not actions_on_bottom %} - <style> + <style {% csp_nonce_attr %}> #changelist table thead th:first-child {width: inherit} </style> {% endif %} @@ -21,8 +22,8 @@ {% block extrahead %} {{ block.super }} -{{ media.js }} -<script src="{% static 'admin/js/filters.js' %}" defer></script> +{% csp_nonce_attr media.js %} +<script src="{% static 'admin/js/filters.js' %}" defer {% csp_nonce_attr %}></script> {% endblock %} {% block bodyclass %}{{ block.super }} app-{{ opts.app_label }} model-{{ opts.model_name }} change-list{% endblock %} diff --git a/django/contrib/admin/templates/admin/delete_confirmation.html b/django/contrib/admin/templates/admin/delete_confirmation.html index 1c34a84d95..7797f44eb3 100644 --- a/django/contrib/admin/templates/admin/delete_confirmation.html +++ b/django/contrib/admin/templates/admin/delete_confirmation.html @@ -3,8 +3,8 @@ {% block extrahead %} {{ block.super }} - {{ media }} - <script src="{% static 'admin/js/cancel.js' %}" async></script> + {% csp_nonce_attr media %} + <script src="{% static 'admin/js/cancel.js' %}" async {% csp_nonce_attr %}></script> {% endblock %} {% block bodyclass %}{{ block.super }} app-{{ opts.app_label }} model-{{ opts.model_name }} delete-confirmation{% endblock %} diff --git a/django/contrib/admin/templates/admin/delete_selected_confirmation.html b/django/contrib/admin/templates/admin/delete_selected_confirmation.html index 40cfdbcc4c..cf503ec123 100644 --- a/django/contrib/admin/templates/admin/delete_selected_confirmation.html +++ b/django/contrib/admin/templates/admin/delete_selected_confirmation.html @@ -3,8 +3,8 @@ {% block extrahead %} {{ block.super }} - {{ media }} - <script src="{% static 'admin/js/cancel.js' %}" async></script> + {% csp_nonce_attr media %} + <script src="{% static 'admin/js/cancel.js' %}" async {% csp_nonce_attr %}></script> {% endblock %} {% block bodyclass %}{{ block.super }} app-{{ opts.app_label }} model-{{ opts.model_name }} delete-confirmation delete-selected-confirmation{% endblock %} diff --git a/django/contrib/admin/templates/admin/index.html b/django/contrib/admin/templates/admin/index.html index 6f39d375eb..f728fa88ff 100644 --- a/django/contrib/admin/templates/admin/index.html +++ b/django/contrib/admin/templates/admin/index.html @@ -1,7 +1,7 @@ {% extends "admin/base_site.html" %} {% load i18n static admin_filters %} -{% block extrastyle %}{{ block.super }}<link rel="stylesheet" href="{% static "admin/css/dashboard.css" %}">{% endblock %} +{% block extrastyle %}{{ block.super }}<link rel="stylesheet" href="{% static "admin/css/dashboard.css" %}" {% csp_nonce_attr %}>{% endblock %} {% block coltype %}colMS{% endblock %} diff --git a/django/contrib/admin/templates/admin/login.html b/django/contrib/admin/templates/admin/login.html index fa0dcbc01d..26af943968 100644 --- a/django/contrib/admin/templates/admin/login.html +++ b/django/contrib/admin/templates/admin/login.html @@ -2,8 +2,8 @@ {% load i18n static %} {% block title %}{% if form.errors %}{% translate "Error:" %} {% endif %}{{ block.super }}{% endblock %} -{% block extrastyle %}{{ block.super }}<link rel="stylesheet" href="{% static "admin/css/login.css" %}"> -{{ form.media }} +{% block extrastyle %}{{ block.super }}<link rel="stylesheet" href="{% static "admin/css/login.css" %}" {% csp_nonce_attr %}> +{% csp_nonce_attr form.media %} {% endblock %} {% block bodyclass %}{{ block.super }} login{% endblock %} diff --git a/django/contrib/admin/templates/admin/popup_response.html b/django/contrib/admin/templates/admin/popup_response.html index 57a1ae3661..eae9dcb07a 100644 --- a/django/contrib/admin/templates/admin/popup_response.html +++ b/django/contrib/admin/templates/admin/popup_response.html @@ -4,7 +4,8 @@ <body> <script id="django-admin-popup-response-constants" src="{% static "admin/js/popup_response.js" %}" - data-popup-response="{{ popup_response_data }}"> + data-popup-response="{{ popup_response_data }}" + {% csp_nonce_attr %}> </script> </body> </html> diff --git a/django/contrib/admin/templates/admin/prepopulated_fields_js.html b/django/contrib/admin/templates/admin/prepopulated_fields_js.html index dd6e56100d..7080e23785 100644 --- a/django/contrib/admin/templates/admin/prepopulated_fields_js.html +++ b/django/contrib/admin/templates/admin/prepopulated_fields_js.html @@ -1,5 +1,6 @@ {% load static %} <script id="django-admin-prepopulated-fields-constants" src="{% static "admin/js/prepopulate_init.js" %}" - data-prepopulated-fields="{{ prepopulated_fields_json }}"> + data-prepopulated-fields="{{ prepopulated_fields_json }}" + {% csp_nonce_attr %}> </script> diff --git a/django/contrib/admin/templates/registration/password_change_form.html b/django/contrib/admin/templates/registration/password_change_form.html index 91c99c7fd1..aa1a21f205 100644 --- a/django/contrib/admin/templates/registration/password_change_form.html +++ b/django/contrib/admin/templates/registration/password_change_form.html @@ -2,7 +2,11 @@ {% load i18n static %} {% block title %}{% if form.errors %}{% translate "Error:" %} {% endif %}{{ block.super }}{% endblock %} -{% block extrastyle %}{{ block.super }}<link rel="stylesheet" href="{% static "admin/css/forms.css" %}">{% endblock %} +{% block extrastyle %} + {{ block.super }} + <link rel="stylesheet" href="{% static "admin/css/widgets.css" %}" {% csp_nonce_attr %}> + <link rel="stylesheet" href="{% static "admin/css/forms.css" %}" {% csp_nonce_attr %}> +{% endblock %} {% block userlinks %} {% url 'django-admindocs-docroot' as docsroot %}{% if docsroot %}<a href="{{ docsroot }}">{% translate 'Documentation' %}</a> / {% endif %} {% translate 'Change password' %} / <form id="logout-form" method="post" action="{% url 'admin:logout' %}"> diff --git a/django/contrib/admin/templates/registration/password_reset_confirm.html b/django/contrib/admin/templates/registration/password_reset_confirm.html index ffe51b59f8..ff7fbb3ecb 100644 --- a/django/contrib/admin/templates/registration/password_reset_confirm.html +++ b/django/contrib/admin/templates/registration/password_reset_confirm.html @@ -2,7 +2,11 @@ {% load i18n static %} {% block title %}{% if form.new_password1.errors or form.new_password2.errors %}{% translate "Error:" %} {% endif %}{{ block.super }}{% endblock %} -{% block extrastyle %}{{ block.super }}<link rel="stylesheet" href="{% static "admin/css/forms.css" %}">{% endblock %} +{% block extrastyle %} + {{ block.super }} + <link rel="stylesheet" href="{% static "admin/css/widgets.css" %}" {% csp_nonce_attr %}> + <link rel="stylesheet" href="{% static "admin/css/forms.css" %}" {% csp_nonce_attr %}> +{% endblock %} {% block breadcrumbs %} <ol class="breadcrumbs"> <li><a href="{% url 'admin:index' %}">{% translate 'Home' %}</a></li> diff --git a/django/contrib/admin/templates/registration/password_reset_form.html b/django/contrib/admin/templates/registration/password_reset_form.html index 31c84fdcc7..0ec1c039a9 100644 --- a/django/contrib/admin/templates/registration/password_reset_form.html +++ b/django/contrib/admin/templates/registration/password_reset_form.html @@ -2,7 +2,11 @@ {% load i18n static %} {% block title %}{% if form.email.errors %}{% translate "Error:" %} {% endif %}{{ block.super }}{% endblock %} -{% block extrastyle %}{{ block.super }}<link rel="stylesheet" href="{% static "admin/css/forms.css" %}">{% endblock %} +{% block extrastyle %} + {{ block.super }} + <link rel="stylesheet" href="{% static "admin/css/widgets.css" %}" {% csp_nonce_attr %}> + <link rel="stylesheet" href="{% static "admin/css/forms.css" %}" {% csp_nonce_attr %}> +{% endblock %} {% block breadcrumbs %} <ol class="breadcrumbs"> <li><a href="{% url 'admin:index' %}">{% translate 'Home' %}</a></li> diff --git a/django/contrib/admindocs/templates/admin_doc/model_detail.html b/django/contrib/admindocs/templates/admin_doc/model_detail.html index 6cf05d8f1b..0ae776a341 100644 --- a/django/contrib/admindocs/templates/admin_doc/model_detail.html +++ b/django/contrib/admindocs/templates/admin_doc/model_detail.html @@ -3,7 +3,7 @@ {% block extrahead %} {{ block.super }} -<style> +<style {% csp_nonce_attr %}> .module table { width:100%; } .module table p { padding: 0; margin: 0; } </style> diff --git a/django/views/csrf.py b/django/views/csrf.py index adc629e843..55fd68c703 100644 --- a/django/views/csrf.py +++ b/django/views/csrf.py @@ -24,6 +24,7 @@ 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_CSRF_COOKIE, REASON_NO_REFERER + from django.template.context_processors import csp c = { "title": _("Forbidden"), @@ -64,7 +65,7 @@ def csrf_failure(request, reason="", template_name=CSRF_FAILURE_TEMPLATE_NAME): "DEBUG": settings.DEBUG, "docs_version": get_docs_version(), "more": _("More information is available with DEBUG=True."), - } + } | csp(request) try: t = loader.get_template(template_name) body = t.render(request=request) diff --git a/django/views/debug.py b/django/views/debug.py index f7e141d1c6..caa3d05cd8 100644 --- a/django/views/debug.py +++ b/django/views/debug.py @@ -673,6 +673,8 @@ def technical_404_response(request, exception): return HttpResponseNotFound(t.render(c)) +@csp_override({}) +@csp_report_only_override({}) def default_urlconf(request): """Create an empty URLconf 404 error response.""" with builtin_template_path("default_urlconf.html").open(encoding="utf-8") as fh: diff --git a/django/views/templates/csrf_403.html b/django/views/templates/csrf_403.html index 6a0daad5ee..fc367cad04 100644 --- a/django/views/templates/csrf_403.html +++ b/django/views/templates/csrf_403.html @@ -5,7 +5,7 @@ <meta name="color-scheme" content="light dark" /> <meta name="robots" content="NONE,NOARCHIVE"> <title>403 Forbidden</title> - <style> + <style {% csp_nonce_attr %}> html * { padding:0; margin:0; } body * { padding:10px 20px; } body * * { padding:0; } diff --git a/docs/releases/6.1.txt b/docs/releases/6.1.txt index 8f7172256e..0415856c95 100644 --- a/docs/releases/6.1.txt +++ b/docs/releases/6.1.txt @@ -282,6 +282,11 @@ CSP with ``CSP.NONCE`` in a CSP policy but ``django.template.context_processors.csp`` is not configured. +* CSP nonce attributes are now added on ``<script>``, ``<style>``, and + ``<link>`` elements in admin templates and all built-in templates when the + :func:`~django.template.context_processors.csp` context processor is + configured. See :ref:`csp-nonce-config` for setup instructions. + CSRF ~~~~ diff --git a/tests/admin_views/test_csp.py b/tests/admin_views/test_csp.py new file mode 100644 index 0000000000..1b99e2c363 --- /dev/null +++ b/tests/admin_views/test_csp.py @@ -0,0 +1,111 @@ +from django.contrib.auth.models import User +from django.test import TestCase, modify_settings, override_settings +from django.urls import reverse + + +@override_settings( + ROOT_URLCONF="admin_views.urls", + TEMPLATES=[ + { + "BACKEND": "django.template.backends.django.DjangoTemplates", + "APP_DIRS": True, + "OPTIONS": { + "context_processors": [ + "django.template.context_processors.request", + "django.contrib.auth.context_processors.auth", + "django.template.context_processors.csp", + ], + }, + } + ], +) +@modify_settings( + MIDDLEWARE={"append": "django.middleware.csp.ContentSecurityPolicyMiddleware"} +) +class AdminCspNonceTests(TestCase): + @classmethod + def setUpTestData(cls): + cls.superuser = User.objects.create_superuser( + username="super", password="secret", email="super@example.com" + ) + + def setUp(self): + self.client.force_login(self.superuser) + + @override_settings( + TEMPLATES=[ + { + "BACKEND": "django.template.backends.django.DjangoTemplates", + "APP_DIRS": True, + "OPTIONS": { + "context_processors": [ + "django.template.context_processors.request", + "django.contrib.auth.context_processors.auth", + ], + }, + } + ], + ) + def test_no_nonce_without_csp_context_processor(self): + response = self.client.get(reverse("admin:index")) + self.assertNotContains(response, 'nonce="') + + def test_index_base_scripts_have_nonce(self): + response = self.client.get(reverse("admin:index")) + content = response.content.decode() + self.assertRegex(content, r'<script src="[^"]*theme\.js"[^>]*nonce="[^"]+"') + self.assertRegex( + content, r'<script src="[^"]*nav_sidebar\.js"[^>]*nonce="[^"]+"' + ) + + def test_index_base_links_have_nonce(self): + response = self.client.get(reverse("admin:index")) + content = response.content.decode() + self.assertRegex(content, r'<link[^>]+base\.css"[^>]*nonce="[^"]+"') + self.assertRegex(content, r'<link[^>]+dashboard\.css"[^>]*nonce="[^"]+"') + + def test_change_form_scripts_have_nonce(self): + response = self.client.get( + reverse("admin:auth_user_change", args=[self.superuser.pk]) + ) + content = response.content.decode() + self.assertRegex( + content, r'<script[^>]*src="[^"]*change_form\.js"[^>]*nonce="[^"]+"' + ) + + def test_change_form_links_have_nonce(self): + response = self.client.get( + reverse("admin:auth_user_change", args=[self.superuser.pk]) + ) + self.assertRegex( + response.content.decode(), r'<link[^>]+forms\.css"[^>]*nonce="[^"]+"' + ) + + def test_change_list_scripts_have_nonce(self): + response = self.client.get(reverse("admin:auth_user_changelist")) + self.assertRegex( + response.content.decode(), + r'<script src="[^"]*filters\.js"[^>]*nonce="[^"]+"', + ) + + def test_change_list_links_have_nonce(self): + response = self.client.get(reverse("admin:auth_user_changelist")) + self.assertRegex( + response.content.decode(), r'<link[^>]+changelists\.css"[^>]*nonce="[^"]+"' + ) + + def test_delete_confirmation_script_has_nonce(self): + response = self.client.get( + reverse("admin:auth_user_delete", args=[self.superuser.pk]) + ) + self.assertRegex( + response.content.decode(), + r'<script src="[^"]*cancel\.js"[^>]*nonce="[^"]+"', + ) + + def test_login_link_has_nonce(self): + self.client.logout() + response = self.client.get(reverse("admin:login")) + self.assertRegex( + response.content.decode(), r'<link[^>]+login\.css"[^>]*nonce="[^"]+"' + ) diff --git a/tests/view_tests/tests/test_csrf.py b/tests/view_tests/tests/test_csrf.py index 2d530cc586..ddf59559aa 100644 --- a/tests/view_tests/tests/test_csrf.py +++ b/tests/view_tests/tests/test_csrf.py @@ -2,6 +2,7 @@ from unittest import mock from django.template import TemplateDoesNotExist from django.test import Client, RequestFactory, SimpleTestCase, override_settings +from django.utils.csp import CSP from django.utils.translation import override from django.views.csrf import CSRF_FAILURE_TEMPLATE_NAME, csrf_failure @@ -114,6 +115,37 @@ class CsrfViewTests(SimpleTestCase): self.assertContains(response, "Test template for CSRF failure", status_code=403) self.assertIs(response.wsgi_request, response.context.request) + @override_settings( + TEMPLATES=[ + { + "BACKEND": "django.template.backends.django.DjangoTemplates", + "OPTIONS": { + "loaders": [ + ( + "django.template.loaders.locmem.Loader", + {CSRF_FAILURE_TEMPLATE_NAME: ("{% csp_nonce_attr %}")}, + ), + ], + "context_processors": [ + "django.template.context_processors.csp", + ], + }, + } + ], + MIDDLEWARE=[ + "django.middleware.csrf.CsrfViewMiddleware", + "django.middleware.csp.ContentSecurityPolicyMiddleware", + ], + SECURE_CSP={ + "default-src": [CSP.NONCE], + }, + ) + def test_custom_template_with_csp_nonce(self): + """A custom CSRF_FAILURE_TEMPLATE_NAME with a CSP nonce is used.""" + response = self.client.post("/") + self.assertContains(response, "nonce=", status_code=403) + self.assertIs(response.wsgi_request, response.context.request) + def test_custom_template_does_not_exist(self): """An exception is raised if a nonexistent template is supplied.""" factory = RequestFactory() |
