From d63241ebc7067fdebbaf704989b34fcd8f26bbe9 Mon Sep 17 00:00:00 2001 From: Rob Hudson Date: Sat, 3 May 2025 10:01:58 -0700 Subject: Fixed #15727 -- Added Content Security Policy (CSP) support. This initial work adds a pair of settings to configure specific CSP directives for enforcing or reporting policy violations, a new `django.middleware.csp.ContentSecurityPolicyMiddleware` to apply the appropriate headers to responses, and a context processor to support CSP nonces in templates for safely inlining assets. Relevant documentation has been added for the 6.0 release notes, security overview, a new how-to page, and a dedicated reference section. Thanks to the multiple reviewers for their precise and valuable feedback. Co-authored-by: Natalia <124304+nessita@users.noreply.github.com> --- .../templates/context_processors/csp_nonce.html | 17 ++++++ tests/context_processors/tests.py | 65 +++++++++++++++++++++- tests/context_processors/urls.py | 1 + tests/context_processors/views.py | 4 ++ 4 files changed, 86 insertions(+), 1 deletion(-) create mode 100644 tests/context_processors/templates/context_processors/csp_nonce.html (limited to 'tests/context_processors') diff --git a/tests/context_processors/templates/context_processors/csp_nonce.html b/tests/context_processors/templates/context_processors/csp_nonce.html new file mode 100644 index 0000000000..13612e3840 --- /dev/null +++ b/tests/context_processors/templates/context_processors/csp_nonce.html @@ -0,0 +1,17 @@ + + + + + CSP Nonce Test + + +

CSP Nonce Test

+

CSP Nonce is present: {{ csp_nonce }}

+ + + + diff --git a/tests/context_processors/tests.py b/tests/context_processors/tests.py index ba92ff8b05..737ff3e1cf 100644 --- a/tests/context_processors/tests.py +++ b/tests/context_processors/tests.py @@ -2,7 +2,8 @@ Tests for Django's bundled context processors. """ -from django.test import SimpleTestCase, TestCase, override_settings +from django.test import SimpleTestCase, TestCase, modify_settings, override_settings +from django.utils.csp import CSP @override_settings( @@ -96,3 +97,65 @@ class DebugContextProcessorTests(TestCase): self.assertContains(response, "Third query list: 2") # Check queries for DB connection 'other' self.assertContains(response, "Fourth query list: 3") + + +@override_settings( + ROOT_URLCONF="context_processors.urls", + TEMPLATES=[ + { + "BACKEND": "django.template.backends.django.DjangoTemplates", + "APP_DIRS": True, + "OPTIONS": { + "context_processors": [ + "django.template.context_processors.csp", + ], + }, + } + ], + MIDDLEWARE=[ + "django.middleware.csp.ContentSecurityPolicyMiddleware", + ], + SECURE_CSP={ + "script-src": [CSP.SELF, CSP.NONCE], + }, +) +class CSPContextProcessorTests(TestCase): + """ + Tests for the django.template.context_processors.csp_nonce processor. + """ + + def test_csp_nonce_in_context(self): + response = self.client.get("/csp_nonce/") + self.assertIn("csp_nonce", response.context) + + @modify_settings( + MIDDLEWARE={"remove": "django.middleware.csp.ContentSecurityPolicyMiddleware"} + ) + def test_csp_nonce_in_context_no_middleware(self): + response = self.client.get("/csp_nonce/") + self.assertIn("csp_nonce", response.context) + + def test_csp_nonce_in_header(self): + response = self.client.get("/csp_nonce/") + self.assertIn(CSP.HEADER_ENFORCE, response.headers) + csp_header = response.headers[CSP.HEADER_ENFORCE] + nonce = response.context["csp_nonce"] + self.assertIn(f"'nonce-{nonce}'", csp_header) + + def test_different_nonce_per_request(self): + response1 = self.client.get("/csp_nonce/") + response2 = self.client.get("/csp_nonce/") + self.assertNotEqual( + response1.context["csp_nonce"], + response2.context["csp_nonce"], + ) + + def test_csp_nonce_in_template(self): + response = self.client.get("/csp_nonce/") + nonce = response.context["csp_nonce"] + self.assertIn(f'