Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 8e34187

Browse files
authoredFeb 20, 2025··
CSP: add helper middleware (#12004)
Sometimes we need to add additional CSP headers to views from third party apps, this middleware helps with that, instead of subclassing and overriding URLs. This will mainly be used on .com.
1 parent ade2e8f commit 8e34187

File tree

2 files changed

+46
-0
lines changed

2 files changed

+46
-0
lines changed
 

‎readthedocs/core/middleware.py

+44
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import structlog
2+
from django.conf import settings
23
from django.http import HttpResponse
34

45
log = structlog.get_logger(__name__)
@@ -33,3 +34,46 @@ def __call__(self, request):
3334
status=400,
3435
)
3536
return self.get_response(request)
37+
38+
39+
class UpdateCSPMiddleware:
40+
"""
41+
Middleware to update the CSP headers for specific views given its URL name.
42+
43+
This is useful for views that we don't have much control over,
44+
like views from third-party packages. For views that we have control over,
45+
we should update the CSP headers directly in the view.
46+
47+
Use the `RTD_CSP_UPDATE_HEADERS` setting to define the views that need to
48+
update the CSP headers. The setting should be a dictionary where the key is
49+
the URL name of the view and the value is a dictionary with the CSP headers,
50+
for example:
51+
52+
.. code-block:: python
53+
54+
RTD_CSP_UPDATE_HEADERS = {
55+
"login": {"form-action": ["https:"]},
56+
}
57+
"""
58+
59+
def __init__(self, get_response):
60+
self.get_response = get_response
61+
62+
def __call__(self, request):
63+
response = self.get_response(request)
64+
65+
# Views that raised an exception don't have a resolver_match object.
66+
resolver_match = request.resolver_match
67+
if not resolver_match:
68+
return response
69+
70+
url_name = resolver_match.url_name
71+
update_csp_headers = settings.RTD_CSP_UPDATE_HEADERS
72+
if settings.RTD_EXT_THEME_ENABLED and url_name in update_csp_headers:
73+
if hasattr(response, "_csp_update"):
74+
raise ValueError(
75+
"Can't update CSP headers at the view and middleware at the same time, use one or the other."
76+
)
77+
response._csp_update = update_csp_headers[url_name]
78+
79+
return response

‎readthedocs/settings/base.py

+2
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,7 @@ def SWITCH_PRODUCTION_DOMAIN(self):
143143
CSP_REPORT_URI = None
144144
CSP_REPORT_ONLY = False
145145
CSP_EXCLUDE_URL_PREFIXES = ("/admin/",)
146+
RTD_CSP_UPDATE_HEADERS = {}
146147

147148
# Read the Docs
148149
READ_THE_DOCS_EXTENSIONS = ext
@@ -349,6 +350,7 @@ def MIDDLEWARE(self):
349350
"allauth.account.middleware.AccountMiddleware",
350351
"dj_pagination.middleware.PaginationMiddleware",
351352
"csp.middleware.CSPMiddleware",
353+
"readthedocs.core.middleware.UpdateCSPMiddleware",
352354
"simple_history.middleware.HistoryRequestMiddleware",
353355
"readthedocs.core.logs.ReadTheDocsRequestMiddleware",
354356
"django_structlog.middlewares.CeleryMiddleware",

0 commit comments

Comments
 (0)
Please sign in to comment.