33import base64
44import http .client as http_client
55import os
6+ import warnings
7+ from dataclasses import asdict , dataclass
68from functools import partial
79from typing import TYPE_CHECKING
810
1113from django .utils .functional import SimpleLazyObject
1214
1315from csp .constants import HEADER , HEADER_REPORT_ONLY
14- from csp .utils import build_policy
16+ from csp .utils import _DIRECTIVES , build_policy
1517
1618if TYPE_CHECKING :
1719 from django .http import HttpRequest , HttpResponseBase
1820
1921
22+ @dataclass
23+ class PolicyParts :
24+ # A dataclass is used rather than a namedtuple so that the attributes are mutable
25+ config : _DIRECTIVES = None
26+ update : _DIRECTIVES = None
27+ replace : _DIRECTIVES = None
28+ nonce : str | None = None
29+
30+
2031class CSPMiddleware (MiddlewareMixin ):
2132 """
2233 Implements the Content-Security-Policy response header, which
@@ -25,6 +36,7 @@ class CSPMiddleware(MiddlewareMixin):
2536
2637 See http://www.w3.org/TR/CSP/
2738
39+ Can be customised by subclassing and extending the get_policy_parts method.
2840 """
2941
3042 def _make_nonce (self , request : HttpRequest ) -> str :
@@ -49,7 +61,8 @@ def process_response(self, request: HttpRequest, response: HttpResponseBase) ->
4961 if response .status_code in exempted_debug_codes and settings .DEBUG :
5062 return response
5163
52- csp = self .build_policy (request , response )
64+ policy_parts = self .get_policy_parts (request = request , response = response )
65+ csp = build_policy (** asdict (policy_parts ))
5366 if csp :
5467 # Only set header if not already set and not an excluded prefix and not exempted.
5568 is_not_exempt = getattr (response , "_csp_exempt" , False ) is False
@@ -60,7 +73,8 @@ def process_response(self, request: HttpRequest, response: HttpResponseBase) ->
6073 if no_header and is_not_exempt and is_not_excluded :
6174 response [HEADER ] = csp
6275
63- csp_ro = self .build_policy_ro (request , response )
76+ policy_parts_ro = self .get_policy_parts (request = request , response = response , report_only = True )
77+ csp_ro = build_policy (** asdict (policy_parts_ro ), report_only = True )
6478 if csp_ro :
6579 # Only set header if not already set and not an excluded prefix and not exempted.
6680 is_not_exempt = getattr (response , "_csp_exempt_ro" , False ) is False
@@ -74,15 +88,25 @@ def process_response(self, request: HttpRequest, response: HttpResponseBase) ->
7488 return response
7589
7690 def build_policy (self , request : HttpRequest , response : HttpResponseBase ) -> str :
77- config = getattr (response , "_csp_config" , None )
78- update = getattr (response , "_csp_update" , None )
79- replace = getattr (response , "_csp_replace" , None )
80- nonce = getattr (request , "_csp_nonce" , None )
81- return build_policy (config = config , update = update , replace = replace , nonce = nonce )
91+ warnings .warn ("deprecated in favor of get_policy_parts" , DeprecationWarning )
92+ policy_parts = self .get_policy_parts (request = request , response = response , report_only = False )
93+ return build_policy (** asdict (policy_parts ))
8294
8395 def build_policy_ro (self , request : HttpRequest , response : HttpResponseBase ) -> str :
84- config = getattr (response , "_csp_config_ro" , None )
85- update = getattr (response , "_csp_update_ro" , None )
86- replace = getattr (response , "_csp_replace_ro" , None )
96+ warnings .warn ("deprecated in favor of get_policy_parts" , DeprecationWarning )
97+ policy_parts_ro = self .get_policy_parts (request = request , response = response , report_only = True )
98+ return build_policy (** asdict (policy_parts_ro ), report_only = True )
99+
100+ def get_policy_parts (self , request : HttpRequest , response : HttpResponseBase , report_only : bool = False ) -> PolicyParts :
101+ if report_only :
102+ config = getattr (response , "_csp_config_ro" , None )
103+ update = getattr (response , "_csp_update_ro" , None )
104+ replace = getattr (response , "_csp_replace_ro" , None )
105+ else :
106+ config = getattr (response , "_csp_config" , None )
107+ update = getattr (response , "_csp_update" , None )
108+ replace = getattr (response , "_csp_replace" , None )
109+
87110 nonce = getattr (request , "_csp_nonce" , None )
88- return build_policy (config = config , update = update , replace = replace , nonce = nonce , report_only = True )
111+
112+ return PolicyParts (config , update , replace , nonce )
0 commit comments