3
3
import base64
4
4
import http .client as http_client
5
5
import os
6
+ import warnings
7
+ from dataclasses import asdict , dataclass
6
8
from functools import partial
7
9
from typing import TYPE_CHECKING
8
10
11
13
from django .utils .functional import SimpleLazyObject
12
14
13
15
from csp .constants import HEADER , HEADER_REPORT_ONLY
14
- from csp .utils import build_policy
16
+ from csp .utils import _DIRECTIVES , build_policy
15
17
16
18
if TYPE_CHECKING :
17
19
from django .http import HttpRequest , HttpResponseBase
18
20
19
21
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
+
20
31
class CSPMiddleware (MiddlewareMixin ):
21
32
"""
22
33
Implements the Content-Security-Policy response header, which
@@ -25,6 +36,7 @@ class CSPMiddleware(MiddlewareMixin):
25
36
26
37
See http://www.w3.org/TR/CSP/
27
38
39
+ Can be customised by subclassing and extending the get_policy_parts method.
28
40
"""
29
41
30
42
def _make_nonce (self , request : HttpRequest ) -> str :
@@ -49,7 +61,8 @@ def process_response(self, request: HttpRequest, response: HttpResponseBase) ->
49
61
if response .status_code in exempted_debug_codes and settings .DEBUG :
50
62
return response
51
63
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 ))
53
66
if csp :
54
67
# Only set header if not already set and not an excluded prefix and not exempted.
55
68
is_not_exempt = getattr (response , "_csp_exempt" , False ) is False
@@ -60,7 +73,8 @@ def process_response(self, request: HttpRequest, response: HttpResponseBase) ->
60
73
if no_header and is_not_exempt and is_not_excluded :
61
74
response [HEADER ] = csp
62
75
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 )
64
78
if csp_ro :
65
79
# Only set header if not already set and not an excluded prefix and not exempted.
66
80
is_not_exempt = getattr (response , "_csp_exempt_ro" , False ) is False
@@ -74,15 +88,25 @@ def process_response(self, request: HttpRequest, response: HttpResponseBase) ->
74
88
return response
75
89
76
90
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 ))
82
94
83
95
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
+
87
110
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