7
7
from django .utils .deprecation import MiddlewareMixin
8
8
from django .utils .functional import SimpleLazyObject
9
9
10
+ from csp .constants import HEADER , HEADER_REPORT_ONLY
10
11
from csp .utils import build_policy
11
12
12
13
@@ -21,8 +22,7 @@ class CSPMiddleware(MiddlewareMixin):
21
22
"""
22
23
23
24
def _make_nonce (self , request ):
24
- # Ensure that any subsequent calls to request.csp_nonce return the
25
- # same value
25
+ # Ensure that any subsequent calls to request.csp_nonce return the same value
26
26
if not getattr (request , "_csp_nonce" , None ):
27
27
request ._csp_nonce = base64 .b64encode (os .urandom (16 )).decode ("ascii" )
28
28
return request ._csp_nonce
@@ -32,32 +32,33 @@ def process_request(self, request):
32
32
request .csp_nonce = SimpleLazyObject (nonce )
33
33
34
34
def process_response (self , request , response ):
35
- if getattr (response , "_csp_exempt" , False ):
36
- return response
37
-
38
- # Check for ignored path prefix.
39
- prefixes = getattr (settings , "CSP_EXCLUDE_URL_PREFIXES" , ())
40
- if request .path_info .startswith (prefixes ):
41
- return response
42
-
43
35
# Check for debug view
44
- status_code = response .status_code
45
36
exempted_debug_codes = (
46
37
http_client .INTERNAL_SERVER_ERROR ,
47
38
http_client .NOT_FOUND ,
48
39
)
49
- if status_code in exempted_debug_codes and settings .DEBUG :
40
+ if response . status_code in exempted_debug_codes and settings .DEBUG :
50
41
return response
51
42
52
- header = "Content-Security-Policy"
53
- if getattr (settings , "CSP_REPORT_ONLY" , False ):
54
- header += "-Report-Only"
55
-
56
- if header in response :
57
- # Don't overwrite existing headers.
58
- return response
59
-
60
- response [header ] = self .build_policy (request , response )
43
+ csp = self .build_policy (request , response )
44
+ if csp :
45
+ # Only set header if not already set and not an excluded prefix and not exempted.
46
+ is_not_exempt = getattr (response , "_csp_exempt" , False ) is False
47
+ no_header = HEADER not in response
48
+ prefixes = getattr (settings , "CONTENT_SECURITY_POLICY" , {}).get ("EXCLUDE_URL_PREFIXES" , ())
49
+ is_not_excluded = not request .path_info .startswith (prefixes )
50
+ if all ((no_header , is_not_exempt , is_not_excluded )):
51
+ response [HEADER ] = csp
52
+
53
+ csp_ro = self .build_policy_ro (request , response )
54
+ if csp_ro :
55
+ # Only set header if not already set and not an excluded prefix and not exempted.
56
+ is_not_exempt = getattr (response , "_csp_exempt_ro" , False ) is False
57
+ no_header = HEADER_REPORT_ONLY not in response
58
+ prefixes = getattr (settings , "CONTENT_SECURITY_POLICY_REPORT_ONLY" , {}).get ("EXCLUDE_URL_PREFIXES" , ())
59
+ is_not_excluded = not request .path_info .startswith (prefixes )
60
+ if all ((no_header , is_not_exempt , is_not_excluded )):
61
+ response [HEADER_REPORT_ONLY ] = csp_ro
61
62
62
63
return response
63
64
@@ -67,3 +68,10 @@ def build_policy(self, request, response):
67
68
replace = getattr (response , "_csp_replace" , None )
68
69
nonce = getattr (request , "_csp_nonce" , None )
69
70
return build_policy (config = config , update = update , replace = replace , nonce = nonce )
71
+
72
+ def build_policy_ro (self , request , response ):
73
+ config = getattr (response , "_csp_config_ro" , None )
74
+ update = getattr (response , "_csp_update_ro" , None )
75
+ replace = getattr (response , "_csp_replace_ro" , None )
76
+ nonce = getattr (request , "_csp_nonce" , None )
77
+ return build_policy (config = config , update = update , replace = replace , nonce = nonce , report_only = True )
0 commit comments