1+ from __future__ import annotations
2+
13import inspect
24
35from copy import deepcopy
6+ from typing import TYPE_CHECKING , Any , Generic , TypeGuard , TypeVar
7+
8+ if TYPE_CHECKING :
9+ from collections .abc import Callable , Generator
10+ from typing_extensions import Self
11+
12+ from webob .response import Response as BaseResponse
13+
14+ _T = TypeVar ("_T" )
415
516SELF = "'self'"
617UNSAFE_INLINE = "'unsafe-inline'"
920STRICT_DYNAMIC = "'strict-dynamic'"
1021
1122
12- class Directive :
23+ class Directive ( Generic [ _T ]) :
1324 """Descriptor for the management and rendering of CSP directives.
1425
1526 Uses types to do some basic sanity checking. This does not ensure
@@ -20,13 +31,19 @@ class Directive:
2031
2132 """
2233
23- def __init__ (self , name , type , default , render ):
34+ def __init__ (
35+ self ,
36+ name : str ,
37+ type : type [_T ],
38+ default : Callable [[], _T ],
39+ render : Callable [[_T ], str | None ],
40+ ) -> None :
2441 self .name = name
2542 self .type = type
2643 self .default = default
2744 self .renderer = render
2845
29- def render (self , instance ) :
46+ def render (self , instance : ContentSecurityPolicy ) -> str | None :
3047 if self .name not in instance .__dict__ :
3148 return None
3249
@@ -35,7 +52,9 @@ def render(self, instance):
3552
3653 return self .renderer (instance .__dict__ [self .name ])
3754
38- def __get__ (self , instance , cls ):
55+ def __get__ (
56+ self , instance : ContentSecurityPolicy , cls : type [ContentSecurityPolicy ]
57+ ) -> _T :
3958 if instance is None :
4059 return self
4160
@@ -44,40 +63,37 @@ def __get__(self, instance, cls):
4463
4564 return instance .__dict__ [self .name ]
4665
47- def __set__ (self , instance , value ) :
66+ def __set__ (self , instance : ContentSecurityPolicy , value : _T ) -> None :
4867 if not isinstance (value , self .type ):
4968 raise TypeError (f"Expected type { self .type } " )
5069
5170 instance .__dict__ [self .name ] = value
5271
5372
54- class SetDirective (Directive ):
55- def __init__ (self , name ):
56- parent = super ()
57- parent .__init__ (name , type = set , default = set , render = render_set )
73+ class SetDirective (Directive [set [str ]]):
74+ def __init__ (self , name : str ) -> None :
75+ super ().__init__ (name , type = set , default = set , render = render_set )
5876
5977
60- class SingleValueDirective (Directive ):
61- def __init__ (self , name ):
62- parent = super ()
63- parent .__init__ (name , type = str , default = str , render = str )
78+ class SingleValueDirective (Directive [str ]):
79+ def __init__ (self , name : str ) -> None :
80+ super ().__init__ (name , type = str , default = str , render = str )
6481
6582
66- class BooleanDirective (Directive ):
67- def __init__ (self , name ):
68- parent = super ()
69- parent .__init__ (name , type = bool , default = bool , render = render_bool )
83+ class BooleanDirective (Directive [bool ]):
84+ def __init__ (self , name : str ) -> None :
85+ super ().__init__ (name , type = bool , default = bool , render = render_bool )
7086
7187
72- def is_directive (obj ) :
88+ def is_directive (obj : object ) -> TypeGuard [ Directive [ Any ]] :
7389 return isinstance (obj , Directive )
7490
7591
76- def render_set (value ) :
92+ def render_set (value : set [ str ]) -> str :
7793 return " " .join (sorted (value ))
7894
7995
80- def render_bool (value ) :
96+ def render_bool (value : bool ) -> str | None :
8197 return "" if value else None
8298
8399
@@ -103,39 +119,52 @@ class ContentSecurityPolicy:
103119 """
104120
105121 # Fetch directives
106- child_src = SetDirective ("child-src" )
107- connect_src = SetDirective ("connect-src" )
108- default_src = SetDirective ("default-src" )
109- font_src = SetDirective ("font-src" )
110- frame_src = SetDirective ("frame-src" )
111- img_src = SetDirective ("img-src" )
112- manifest_src = SetDirective ("manifest-src" )
113- media_src = SetDirective ("media-src" )
114- object_src = SetDirective ("object-src" )
115- script_src = SetDirective ("script-src" )
116- style_src = SetDirective ("style-src" )
117- worker_src = SetDirective ("worker-src" )
122+ child_src : SetDirective = SetDirective ("child-src" )
123+ connect_src : SetDirective = SetDirective ("connect-src" )
124+ default_src : SetDirective = SetDirective ("default-src" )
125+ font_src : SetDirective = SetDirective ("font-src" )
126+ frame_src : SetDirective = SetDirective ("frame-src" )
127+ img_src : SetDirective = SetDirective ("img-src" )
128+ manifest_src : SetDirective = SetDirective ("manifest-src" )
129+ media_src : SetDirective = SetDirective ("media-src" )
130+ object_src : SetDirective = SetDirective ("object-src" )
131+ script_src : SetDirective = SetDirective ("script-src" )
132+ style_src : SetDirective = SetDirective ("style-src" )
133+ worker_src : SetDirective = SetDirective ("worker-src" )
118134
119135 # Document directives
120- base_uri = SetDirective ("base-uri" )
121- plugin_types = SetDirective ("plugin-types" )
122- sandbox = SingleValueDirective ("sandbox" )
123- disown_opener = BooleanDirective ("disown-opener" )
136+ base_uri : SetDirective = SetDirective ("base-uri" )
137+ plugin_types : SetDirective = SetDirective ("plugin-types" )
138+ sandbox : SingleValueDirective = SingleValueDirective ("sandbox" )
139+ disown_opener : BooleanDirective = BooleanDirective ("disown-opener" )
124140
125141 # Navigation directives
126- form_action = SetDirective ("form-action" )
127- frame_ancestors = SetDirective ("frame-ancestors" )
142+ form_action : SetDirective = SetDirective ("form-action" )
143+ frame_ancestors : SetDirective = SetDirective ("frame-ancestors" )
128144
129145 # Reporting directives
130- report_uri = SingleValueDirective ("report-uri" )
131- report_to = SingleValueDirective ("report-to" )
146+ report_uri : SingleValueDirective = SingleValueDirective ("report-uri" )
147+ report_to : SingleValueDirective = SingleValueDirective ("report-to" )
132148
133149 # Other directives
134- block_all_mixed_content = BooleanDirective ("block-all-mixed-content" )
135- require_sri_for = SingleValueDirective ("require-sri-for" )
136- upgrade_insecure_requeists = BooleanDirective ("upgrade-insecure-requests" )
137-
138- def __init__ (self , report_only = False , ** directives ):
150+ block_all_mixed_content : BooleanDirective = BooleanDirective (
151+ "block-all-mixed-content"
152+ )
153+ require_sri_for : SingleValueDirective = SingleValueDirective ("require-sri-for" )
154+ upgrade_insecure_requeists : BooleanDirective = BooleanDirective (
155+ "upgrade-insecure-requests"
156+ )
157+
158+ def __init__ (
159+ self ,
160+ report_only : bool = False ,
161+ # NOTE: This is both a little too lax and a little too strict, but
162+ # it doesn't seem worth defining a TypedDict, to get better
163+ # type checking on this, this will work for most cases and
164+ # is not the recommended style of defining the directives
165+ # anyways.
166+ ** directives : set [str ] | str | bool ,
167+ ) -> None :
139168 self .report_only = report_only
140169
141170 for directive in directives :
@@ -144,32 +173,35 @@ def __init__(self, report_only=False, **directives):
144173 assert hasattr (self , name )
145174 setattr (self , name , directives [directive ])
146175
147- def copy (self ):
176+ def copy (self ) -> Self :
148177 policy = self .__class__ ()
149178 policy .__dict__ = deepcopy (self .__dict__ )
150179
151180 return policy
152181
153182 @property
154- def directives (self ):
183+ def directives (self ) -> Generator [ Directive [ Any ]] :
155184 for name , value in inspect .getmembers (self .__class__ , is_directive ):
156185 yield value
157186
158187 @property
159- def text (self ):
160- values = ((d .name , d .render (self )) for d in self .directives )
161- values = ((name , text ) for name , text in values if text is not None )
188+ def text (self ) -> str :
189+ values = (
190+ (d .name , text )
191+ for d in self .directives
192+ if (text := d .render (self )) is not None
193+ )
162194
163195 return ";" .join (" " .join (v ).strip () for v in values )
164196
165197 @property
166- def header_name (self ):
198+ def header_name (self ) -> str :
167199 if self .report_only :
168200 return "Content-Security-Policy-Report-Only"
169201 else :
170202 return "Content-Security-Policy"
171203
172- def apply (self , response ) :
204+ def apply (self , response : BaseResponse ) -> None :
173205 text = self .text
174206
175207 if text :
0 commit comments