|
16 | 16 | import re |
17 | 17 | import urllib |
18 | 18 | import zlib |
| 19 | +from functools import lru_cache, wraps |
19 | 20 | from typing import Optional |
20 | 21 |
|
21 | 22 | from django.conf import settings |
|
24 | 25 | from django.shortcuts import resolve_url |
25 | 26 | from django.urls import NoReverseMatch |
26 | 27 | from django.utils.http import url_has_allowed_host_and_scheme |
| 28 | +from django.utils.module_loading import import_string |
27 | 29 |
|
28 | 30 | from saml2.config import SPConfig |
29 | 31 | from saml2.mdstore import MetaDataMDX |
@@ -206,3 +208,55 @@ def add_idp_hinting(request, http_response) -> bool: |
206 | 208 | f"Idp hinting: cannot detect request type [{http_response.status_code}]" |
207 | 209 | ) |
208 | 210 | return False |
| 211 | + |
| 212 | + |
| 213 | +@lru_cache() |
| 214 | +def get_csp_handler(): |
| 215 | + """Returns a view decorator for CSP.""" |
| 216 | + |
| 217 | + def empty_view_decorator(view): |
| 218 | + return view |
| 219 | + |
| 220 | + csp_handler_string = get_custom_setting("SAML_CSP_HANDLER", None) |
| 221 | + |
| 222 | + if csp_handler_string is None: |
| 223 | + # No CSP handler configured, attempt to use django-csp |
| 224 | + return _django_csp_update_decorator() or empty_view_decorator |
| 225 | + |
| 226 | + if csp_handler_string.strip() != "": |
| 227 | + # Non empty string is configured, attempt to import it |
| 228 | + csp_handler = import_string(csp_handler_string) |
| 229 | + |
| 230 | + def custom_csp_updater(f): |
| 231 | + @wraps(f) |
| 232 | + def wrapper(*args, **kwargs): |
| 233 | + return csp_handler(f(*args, **kwargs)) |
| 234 | + |
| 235 | + return wrapper |
| 236 | + |
| 237 | + return custom_csp_updater |
| 238 | + |
| 239 | + # Fall back to empty decorator when csp_handler_string is empty |
| 240 | + return empty_view_decorator |
| 241 | + |
| 242 | + |
| 243 | +def _django_csp_update_decorator(): |
| 244 | + """Returns a view CSP decorator if django-csp is available, otherwise None.""" |
| 245 | + try: |
| 246 | + from csp.decorators import csp_update |
| 247 | + except ModuleNotFoundError: |
| 248 | + # If csp is not installed, do not update fields as Content-Security-Policy |
| 249 | + # is not used |
| 250 | + logger.warning( |
| 251 | + "django-csp could not be found, not updating Content-Security-Policy. Please " |
| 252 | + "make sure CSP is configured. This can be done by your reverse proxy, " |
| 253 | + "django-csp or a custom CSP handler via SAML_CSP_HANDLER. See " |
| 254 | + "https://djangosaml2.readthedocs.io/contents/security.html#content-security-policy" |
| 255 | + " for more information. " |
| 256 | + "This warning can be disabled by setting `SAML_CSP_HANDLER=''` in your settings." |
| 257 | + ) |
| 258 | + return |
| 259 | + else: |
| 260 | + # script-src 'unsafe-inline' to autosubmit forms, |
| 261 | + # form-action https: to send data to IdPs |
| 262 | + return csp_update(SCRIPT_SRC=["'unsafe-inline'"], FORM_ACTION=["https:"]) |
0 commit comments