44module SecureHeaders
55 class Configuration
66 DEFAULT_CONFIG = :default
7- NOOP_CONFIGURATION = "secure_headers_noop_config "
7+ NOOP_OVERRIDE = "secure_headers_noop_override "
88 class NotYetConfiguredError < StandardError ; end
99 class IllegalPolicyModificationError < StandardError ; end
1010 class << self
@@ -15,8 +15,13 @@ class << self
1515 # Returns the newly created config.
1616 def default ( &block )
1717 config = new ( &block )
18- add_noop_configuration
1918 add_configuration ( DEFAULT_CONFIG , config )
19+ override ( NOOP_OVERRIDE ) do |config |
20+ CONFIG_ATTRIBUTES . each do |attr |
21+ config . instance_variable_set ( "@#{ attr } " , OPT_OUT )
22+ end
23+ end
24+ config
2025 end
2126 alias_method :configure , :default
2227
@@ -27,13 +32,14 @@ def default(&block)
2732 # if no value is supplied.
2833 #
2934 # Returns: the newly created config
30- def override ( name , base = DEFAULT_CONFIG , &block )
31- unless get ( base )
32- raise NotYetConfiguredError , "#{ base } policy not yet supplied"
33- end
34- override = @configurations [ base ] . dup
35- override . instance_eval ( &block ) if block_given?
36- add_configuration ( name , override )
35+ def override ( name , &block )
36+ @overrides ||= { }
37+ @overrides [ name ] = block
38+ end
39+
40+ def overrides ( name )
41+ @overrides ||= { }
42+ @overrides [ name ]
3743 end
3844
3945 # Public: retrieve a global configuration object
@@ -72,25 +78,10 @@ def named_append(name, target = nil, &block)
7278 def add_configuration ( name , config )
7379 config . validate_config!
7480 @configurations ||= { }
75- config . send ( :cache_headers! )
76- config . send ( :cache_hpkp_report_host )
7781 config . freeze
7882 @configurations [ name ] = config
7983 end
8084
81- # Private: Automatically add an "opt-out of everything" override.
82- #
83- # Returns the noop config
84- def add_noop_configuration
85- noop_config = new do |config |
86- ALL_HEADER_CLASSES . each do |klass |
87- config . send ( "#{ klass ::CONFIG_KEY } =" , OPT_OUT )
88- end
89- end
90-
91- add_configuration ( NOOP_CONFIGURATION , noop_config )
92- end
93-
9485 # Public: perform a basic deep dup. The shallow copy provided by dup/clone
9586 # can lead to modifying parent objects.
9687 def deep_copy ( config )
@@ -115,11 +106,28 @@ def deep_copy_if_hash(value)
115106 end
116107 end
117108
118- attr_writer :hsts , :x_frame_options , :x_content_type_options ,
119- :x_xss_protection , :x_download_options , :x_permitted_cross_domain_policies ,
120- :referrer_policy , :clear_site_data , :expect_certificate_transparency
121-
122- attr_reader :cached_headers , :csp , :cookies , :csp_report_only , :hpkp , :hpkp_report_host
109+ NON_HEADER_ATTRIBUTES = [
110+ :cookies , :hpkp_report_host
111+ ] . freeze
112+
113+ HEADER_ATTRIBUTES_TO_HEADER_CLASSES = {
114+ hsts : StrictTransportSecurity ,
115+ x_frame_options : XFrameOptions ,
116+ x_content_type_options : XContentTypeOptions ,
117+ x_xss_protection : XXssProtection ,
118+ x_download_options : XDownloadOptions ,
119+ x_permitted_cross_domain_policies : XPermittedCrossDomainPolicies ,
120+ referrer_policy : ReferrerPolicy ,
121+ clear_site_data : ClearSiteData ,
122+ expect_certificate_transparency : ExpectCertificateTransparency ,
123+ csp : ContentSecurityPolicy ,
124+ csp_report_only : ContentSecurityPolicy ,
125+ hpkp : PublicKeyPins ,
126+ } . freeze
127+
128+ CONFIG_ATTRIBUTES = ( HEADER_ATTRIBUTES_TO_HEADER_CLASSES . keys + NON_HEADER_ATTRIBUTES ) . freeze
129+
130+ attr_accessor ( *CONFIG_ATTRIBUTES )
123131
124132 @script_hashes = nil
125133 @style_hashes = nil
@@ -154,15 +162,14 @@ def initialize(&block)
154162 instance_eval ( &block ) if block_given?
155163 end
156164
157- # Public: copy everything but the cached headers
165+ # Public: copy everything
158166 #
159167 # Returns a deep-dup'd copy of this configuration.
160168 def dup
161169 copy = self . class . new
162170 copy . cookies = self . class . send ( :deep_copy_if_hash , @cookies )
163171 copy . csp = @csp . dup if @csp
164172 copy . csp_report_only = @csp_report_only . dup if @csp_report_only
165- copy . cached_headers = self . class . send ( :deep_copy_if_hash , @cached_headers )
166173 copy . x_content_type_options = @x_content_type_options
167174 copy . hsts = @hsts
168175 copy . x_frame_options = @x_frame_options
@@ -173,18 +180,26 @@ def dup
173180 copy . expect_certificate_transparency = @expect_certificate_transparency
174181 copy . referrer_policy = @referrer_policy
175182 copy . hpkp = @hpkp
176- copy . hpkp_report_host = @hpkp_report_host
177183 copy
178184 end
179185
186+ def generate_headers ( user_agent )
187+ headers = { }
188+ HEADER_ATTRIBUTES_TO_HEADER_CLASSES . each do |attr , klass |
189+ header_name , value = klass . make_header ( instance_variable_get ( "@#{ attr } " ) , user_agent )
190+ if header_name && value
191+ headers [ header_name ] = value
192+ end
193+ end
194+ headers
195+ end
196+
180197 def opt_out ( header )
181198 send ( "#{ header } =" , OPT_OUT )
182- self . cached_headers . delete ( header )
183199 end
184200
185201 def update_x_frame_options ( value )
186202 @x_frame_options = value
187- self . cached_headers [ XFrameOptions ::CONFIG_KEY ] = XFrameOptions . make_header ( value )
188203 end
189204
190205 # Public: validates all configurations values.
@@ -193,18 +208,9 @@ def update_x_frame_options(value)
193208 #
194209 # Returns nothing
195210 def validate_config!
196- StrictTransportSecurity . validate_config! ( @hsts )
197- ContentSecurityPolicy . validate_config! ( @csp )
198- ContentSecurityPolicy . validate_config! ( @csp_report_only )
199- ReferrerPolicy . validate_config! ( @referrer_policy )
200- XFrameOptions . validate_config! ( @x_frame_options )
201- XContentTypeOptions . validate_config! ( @x_content_type_options )
202- XXssProtection . validate_config! ( @x_xss_protection )
203- XDownloadOptions . validate_config! ( @x_download_options )
204- XPermittedCrossDomainPolicies . validate_config! ( @x_permitted_cross_domain_policies )
205- ClearSiteData . validate_config! ( @clear_site_data )
206- ExpectCertificateTransparency . validate_config! ( @expect_certificate_transparency )
207- PublicKeyPins . validate_config! ( @hpkp )
211+ HEADER_ATTRIBUTES_TO_HEADER_CLASSES . each do |attr , klass |
212+ klass . validate_config! ( instance_variable_get ( "@#{ attr } " ) )
213+ end
208214 Cookie . validate_config! ( @cookies )
209215 end
210216
@@ -247,72 +253,19 @@ def csp_report_only=(new_csp)
247253 end
248254 end
249255
256+ def hpkp_report_host
257+ return nil unless @hpkp && hpkp != OPT_OUT && @hpkp [ :report_uri ]
258+ URI . parse ( @hpkp [ :report_uri ] ) . host
259+ end
260+
250261 protected
251262
252263 def cookies = ( cookies )
253264 @cookies = cookies
254265 end
255266
256- def cached_headers = ( headers )
257- @cached_headers = headers
258- end
259-
260267 def hpkp = ( hpkp )
261268 @hpkp = self . class . send ( :deep_copy_if_hash , hpkp )
262269 end
263-
264- def hpkp_report_host = ( hpkp_report_host )
265- @hpkp_report_host = hpkp_report_host
266- end
267-
268- private
269-
270- def cache_hpkp_report_host
271- has_report_uri = @hpkp && @hpkp != OPT_OUT && @hpkp [ :report_uri ]
272- self . hpkp_report_host = if has_report_uri
273- parsed_report_uri = URI . parse ( @hpkp [ :report_uri ] )
274- parsed_report_uri . host
275- end
276- end
277-
278- # Public: Precompute the header names and values for this configuration.
279- # Ensures that headers generated at configure time, not on demand.
280- #
281- # Returns the cached headers
282- def cache_headers!
283- # generate defaults for the "easy" headers
284- headers = ( ALL_HEADERS_BESIDES_CSP ) . each_with_object ( { } ) do |klass , hash |
285- config = instance_variable_get ( "@#{ klass ::CONFIG_KEY } " )
286- unless config == OPT_OUT
287- hash [ klass ::CONFIG_KEY ] = klass . make_header ( config ) . freeze
288- end
289- end
290-
291- generate_csp_headers ( headers )
292-
293- headers . freeze
294- self . cached_headers = headers
295- end
296-
297- # Private: adds CSP headers for each variation of CSP support.
298- #
299- # headers - generated headers are added to this hash namespaced by The
300- # different variations
301- #
302- # Returns nothing
303- def generate_csp_headers ( headers )
304- generate_csp_headers_for_config ( headers , ContentSecurityPolicyConfig ::CONFIG_KEY , self . csp )
305- generate_csp_headers_for_config ( headers , ContentSecurityPolicyReportOnlyConfig ::CONFIG_KEY , self . csp_report_only )
306- end
307-
308- def generate_csp_headers_for_config ( headers , header_key , csp_config )
309- unless csp_config . opt_out?
310- headers [ header_key ] = { }
311- ContentSecurityPolicy ::VARIATIONS . each_key do |name |
312- csp = ContentSecurityPolicy . make_header ( csp_config , UserAgent . parse ( name ) )
313- headers [ header_key ] [ name ] = csp . freeze
314- end
315- end
316- end
317270 end
318271end
0 commit comments