77from django .utils .deprecation import MiddlewareMixin
88from django .utils .functional import SimpleLazyObject
99
10+ from csp .constants import HEADER , HEADER_REPORT_ONLY
1011from csp .utils import build_policy
1112
1213
@@ -21,8 +22,7 @@ class CSPMiddleware(MiddlewareMixin):
2122 """
2223
2324 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
2626 if not getattr (request , "_csp_nonce" , None ):
2727 request ._csp_nonce = base64 .b64encode (os .urandom (16 )).decode ("ascii" )
2828 return request ._csp_nonce
@@ -32,32 +32,33 @@ def process_request(self, request):
3232 request .csp_nonce = SimpleLazyObject (nonce )
3333
3434 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-
4335 # Check for debug view
44- status_code = response .status_code
4536 exempted_debug_codes = (
4637 http_client .INTERNAL_SERVER_ERROR ,
4738 http_client .NOT_FOUND ,
4839 )
49- if status_code in exempted_debug_codes and settings .DEBUG :
40+ if response . status_code in exempted_debug_codes and settings .DEBUG :
5041 return response
5142
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
6162
6263 return response
6364
@@ -67,3 +68,10 @@ def build_policy(self, request, response):
6768 replace = getattr (response , "_csp_replace" , None )
6869 nonce = getattr (request , "_csp_nonce" , None )
6970 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