1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114
|
import secrets
from enum import StrEnum
from django.utils.functional import SimpleLazyObject, empty
class CSP(StrEnum):
"""
Content Security Policy constants for directive values and special tokens.
These constants represent:
1. Standard quoted string values from the CSP spec (e.g., 'self',
'unsafe-inline')
2. Special placeholder tokens (NONCE) that get replaced by the middleware
Using this enum instead of raw strings provides better type checking,
autocompletion, and protection against common mistakes like:
- Typos (e.g., 'noone' instead of 'none')
- Missing quotes (e.g., ["self"] instead of ["'self'"])
- Inconsistent quote styles (e.g., ["'self'", "\"unsafe-inline\""])
Example usage in Django settings:
SECURE_CSP = {
"default-src": [CSP.NONE],
"script-src": [CSP.SELF, CSP.NONCE],
}
"""
# HTTP Headers.
HEADER_ENFORCE = "Content-Security-Policy"
HEADER_REPORT_ONLY = "Content-Security-Policy-Report-Only"
# Standard CSP directive values.
NONE = "'none'"
REPORT_SAMPLE = "'report-sample'"
SELF = "'self'"
STRICT_DYNAMIC = "'strict-dynamic'"
UNSAFE_EVAL = "'unsafe-eval'"
UNSAFE_HASHES = "'unsafe-hashes'"
UNSAFE_INLINE = "'unsafe-inline'"
WASM_UNSAFE_EVAL = "'wasm-unsafe-eval'"
# Special placeholder that gets replaced by the middleware.
# The value itself is arbitrary and should not be mistaken for a real
# nonce.
NONCE = "<CSP_NONCE_SENTINEL>"
class LazyNonce(SimpleLazyObject):
"""
Lazily generates a cryptographically secure nonce string, for use in CSP
headers.
The nonce is only generated when first accessed (e.g., via string
interpolation or inside a template).
The nonce will evaluate as `True` if it has been generated, and `False` if
it has not. This is useful for third-party Django libraries that want to
support CSP without requiring it.
Example Django template usage with context processors enabled:
<script{% if csp_nonce %} nonce="{{ csp_nonce }}"...{% endif %}>
The `{% if %}` block will only render if the nonce has been evaluated
elsewhere.
"""
def __init__(self):
super().__init__(self._generate)
def _generate(self):
return secrets.token_urlsafe(16)
def __bool__(self):
return self._wrapped is not empty
def build_policy(config, nonce=None):
policy = []
for directive, values in config.items():
if values in (None, False):
continue
if values is True:
rendered_value = ""
else:
if isinstance(values, set):
# Sort values for consistency, preventing cache invalidation
# between requests and ensuring reliable browser caching.
values = sorted(values)
elif not isinstance(values, list | tuple):
values = [values]
# Replace the nonce sentinel with the actual nonce values, if the
# sentinel is found and a nonce is provided. Otherwise, remove it.
if (has_sentinel := CSP.NONCE in values) and nonce:
values = [f"'nonce-{nonce}'" if v == CSP.NONCE else v for v in values]
elif has_sentinel:
values = [v for v in values if v != CSP.NONCE]
if not values:
continue
rendered_value = " ".join(values)
policy.append(f"{directive} {rendered_value}".rstrip())
return "; ".join(policy)
|