summaryrefslogtreecommitdiff
path: root/django
diff options
context:
space:
mode:
authorRob Hudson <rob@cogit8.org>2025-08-23 12:23:53 -0700
committernessita <124304+nessita@users.noreply.github.com>2025-08-28 17:23:48 -0300
commit550822bceea227b07445d1852c4376b663c09ea4 (patch)
treec7b9a032939c7611156b54767c10059d7d6e373c /django
parent292b9e6fe8f23491680d9cc60f328562e2b1c823 (diff)
Fixed #36532 -- Added Content Security Policy view decorators to override or disable policies.
Co-authored-by: Natalia <124304+nessita@users.noreply.github.com>
Diffstat (limited to 'django')
-rw-r--r--django/middleware/csp.py23
-rw-r--r--django/views/debug.py5
-rw-r--r--django/views/decorators/csp.py39
3 files changed, 54 insertions, 13 deletions
diff --git a/django/middleware/csp.py b/django/middleware/csp.py
index e1c66ada5a..ba08cfff0c 100644
--- a/django/middleware/csp.py
+++ b/django/middleware/csp.py
@@ -1,5 +1,3 @@
-from http import HTTPStatus
-
from django.conf import settings
from django.utils.csp import CSP, LazyNonce, build_policy
from django.utils.deprecation import MiddlewareMixin
@@ -14,22 +12,21 @@ class ContentSecurityPolicyMiddleware(MiddlewareMixin):
request._csp_nonce = LazyNonce()
def process_response(self, request, response):
- # In DEBUG mode, exclude CSP headers for specific status codes that
- # trigger the debug view.
- exempted_status_codes = {
- HTTPStatus.NOT_FOUND,
- HTTPStatus.INTERNAL_SERVER_ERROR,
- }
- if settings.DEBUG and response.status_code in exempted_status_codes:
- return response
-
nonce = get_nonce(request)
+
+ sentinel = object()
+ if (csp_config := getattr(response, "_csp_config", sentinel)) is sentinel:
+ csp_config = settings.SECURE_CSP
+ if (csp_ro_config := getattr(response, "_csp_ro_config", sentinel)) is sentinel:
+ csp_ro_config = settings.SECURE_CSP_REPORT_ONLY
+
for header, config in [
- (CSP.HEADER_ENFORCE, settings.SECURE_CSP),
- (CSP.HEADER_REPORT_ONLY, settings.SECURE_CSP_REPORT_ONLY),
+ (CSP.HEADER_ENFORCE, csp_config),
+ (CSP.HEADER_REPORT_ONLY, csp_ro_config),
]:
# If headers are already set on the response, don't overwrite them.
# This allows for views to set their own CSP headers as needed.
+ # An empty config means CSP headers are not added to the response.
if config and header not in response:
response.headers[str(header)] = build_policy(config, nonce)
diff --git a/django/views/debug.py b/django/views/debug.py
index 75f30ca601..5a1b4aee91 100644
--- a/django/views/debug.py
+++ b/django/views/debug.py
@@ -18,6 +18,7 @@ from django.utils.encoding import force_str
from django.utils.module_loading import import_string
from django.utils.regex_helper import _lazy_re_compile
from django.utils.version import get_docs_version
+from django.views.decorators.csp import csp_override, csp_report_only_override
from django.views.decorators.debug import coroutine_functions_to_sensitive_variables
# Minimal Django templates engine to render the error templates
@@ -59,6 +60,8 @@ class CallableSettingWrapper:
return repr(self._wrapped)
+@csp_override({})
+@csp_report_only_override({})
def technical_500_response(request, exc_type, exc_value, tb, status_code=500):
"""
Create a technical server error response. The last three arguments are
@@ -606,6 +609,8 @@ class ExceptionReporter:
tb = tb.tb_next
+@csp_override({})
+@csp_report_only_override({})
def technical_404_response(request, exception):
"""Create a technical 404 error response. `exception` is the Http404."""
try:
diff --git a/django/views/decorators/csp.py b/django/views/decorators/csp.py
new file mode 100644
index 0000000000..9033d9cdf0
--- /dev/null
+++ b/django/views/decorators/csp.py
@@ -0,0 +1,39 @@
+from functools import wraps
+
+from asgiref.sync import iscoroutinefunction
+
+
+def _make_csp_decorator(config_attr_name, config_attr_value):
+ """General CSP override decorator factory."""
+
+ if not isinstance(config_attr_value, dict):
+ raise TypeError("CSP config should be a mapping.")
+
+ def decorator(view_func):
+ @wraps(view_func)
+ async def _wrapped_async_view(request, *args, **kwargs):
+ response = await view_func(request, *args, **kwargs)
+ setattr(response, config_attr_name, config_attr_value)
+ return response
+
+ @wraps(view_func)
+ def _wrapped_sync_view(request, *args, **kwargs):
+ response = view_func(request, *args, **kwargs)
+ setattr(response, config_attr_name, config_attr_value)
+ return response
+
+ if iscoroutinefunction(view_func):
+ return _wrapped_async_view
+ return _wrapped_sync_view
+
+ return decorator
+
+
+def csp_override(config):
+ """Override the Content-Security-Policy header for a view."""
+ return _make_csp_decorator("_csp_config", config)
+
+
+def csp_report_only_override(config):
+ """Override the Content-Security-Policy-Report-Only header for a view."""
+ return _make_csp_decorator("_csp_ro_config", config)