@@ -14,17 +14,25 @@ class << self
1414 #
1515 # Returns the newly created config.
1616 def default ( &block )
17- config = new ( & block )
18- add_configuration ( DEFAULT_CONFIG , config )
17+ # Define a built-in override that clears all configuration options and
18+ # results in no security headers being set.
1919 override ( NOOP_OVERRIDE ) do |config |
2020 CONFIG_ATTRIBUTES . each do |attr |
2121 config . instance_variable_set ( "@#{ attr } " , OPT_OUT )
2222 end
2323 end
24- config
24+
25+ new_config = new ( &block ) . freeze
26+ new_config . validate_config!
27+ @default_config = new_config
2528 end
2629 alias_method :configure , :default
2730
31+ def get
32+ raise NotYetConfiguredError , "Default policy not yet supplied" unless @default_config
33+ @default_config
34+ end
35+
2836 # Public: create a named configuration that overrides the default config.
2937 #
3038 # name - use an idenfier for the override config.
@@ -34,6 +42,7 @@ def default(&block)
3442 # Returns: the newly created config
3543 def override ( name , &block )
3644 @overrides ||= { }
45+ raise "Provide a configuration block" unless block_given?
3746 @overrides [ name ] = block
3847 end
3948
@@ -42,46 +51,19 @@ def overrides(name)
4251 @overrides [ name ]
4352 end
4453
45- # Public: retrieve a global configuration object
46- #
47- # Returns the configuration with a given name or raises a
48- # NotYetConfiguredError if `default` has not been called.
49- def get ( name = DEFAULT_CONFIG )
50- if @configurations . nil?
51- raise NotYetConfiguredError , "Default policy not yet supplied"
52- end
53- @configurations [ name ]
54- end
55-
5654 def named_appends ( name )
5755 @appends ||= { }
5856 @appends [ name ]
5957 end
6058
61- def named_append ( name , target = nil , &block )
59+ def named_append ( name , &block )
6260 @appends ||= { }
6361 raise "Provide a configuration block" unless block_given?
6462 @appends [ name ] = block
6563 end
6664
6765 private
6866
69- # Private: add a valid configuration to the global set of named configs.
70- #
71- # config - the config to store
72- # name - the lookup value for this config
73- #
74- # Raises errors if the config is invalid or if a config named `name`
75- # already exists.
76- #
77- # Returns the config, if valid
78- def add_configuration ( name , config )
79- config . validate_config!
80- @configurations ||= { }
81- config . freeze
82- @configurations [ name ] = config
83- end
84-
8567 # Public: perform a basic deep dup. The shallow copy provided by dup/clone
8668 # can lead to modifying parent objects.
8769 def deep_copy ( config )
@@ -106,11 +88,7 @@ def deep_copy_if_hash(value)
10688 end
10789 end
10890
109- NON_HEADER_ATTRIBUTES = [
110- :cookies , :hpkp_report_host
111- ] . freeze
112-
113- HEADER_ATTRIBUTES_TO_HEADER_CLASSES = {
91+ CONFIG_ATTRIBUTES_TO_HEADER_CLASSES = {
11492 hsts : StrictTransportSecurity ,
11593 x_frame_options : XFrameOptions ,
11694 x_content_type_options : XContentTypeOptions ,
@@ -123,11 +101,18 @@ def deep_copy_if_hash(value)
123101 csp : ContentSecurityPolicy ,
124102 csp_report_only : ContentSecurityPolicy ,
125103 hpkp : PublicKeyPins ,
104+ cookies : Cookie ,
126105 } . freeze
127106
128- CONFIG_ATTRIBUTES = ( HEADER_ATTRIBUTES_TO_HEADER_CLASSES . keys + NON_HEADER_ATTRIBUTES ) . freeze
107+ CONFIG_ATTRIBUTES = CONFIG_ATTRIBUTES_TO_HEADER_CLASSES . keys . freeze
129108
130- attr_accessor ( *CONFIG_ATTRIBUTES )
109+ # The list of attributes that must respond to a `validate_config!` method
110+ VALIDATABLE_ATTRIBUTES = CONFIG_ATTRIBUTES
111+
112+ # The list of attributes that must respond to a `make_header` method
113+ HEADERABLE_ATTRIBUTES = ( CONFIG_ATTRIBUTES - [ :cookies ] ) . freeze
114+
115+ attr_accessor ( *CONFIG_ATTRIBUTES_TO_HEADER_CLASSES . keys )
131116
132117 @script_hashes = nil
133118 @style_hashes = nil
@@ -144,7 +129,6 @@ def initialize(&block)
144129 @clear_site_data = nil
145130 @csp = nil
146131 @csp_report_only = nil
147- @hpkp_report_host = nil
148132 @hpkp = nil
149133 @hsts = nil
150134 @x_content_type_options = nil
@@ -185,7 +169,8 @@ def dup
185169
186170 def generate_headers ( user_agent )
187171 headers = { }
188- HEADER_ATTRIBUTES_TO_HEADER_CLASSES . each do |attr , klass |
172+ HEADERABLE_ATTRIBUTES . each do |attr |
173+ klass = CONFIG_ATTRIBUTES_TO_HEADER_CLASSES [ attr ]
189174 header_name , value = klass . make_header ( instance_variable_get ( "@#{ attr } " ) , user_agent )
190175 if header_name && value
191176 headers [ header_name ] = value
@@ -208,26 +193,26 @@ def update_x_frame_options(value)
208193 #
209194 # Returns nothing
210195 def validate_config!
211- HEADER_ATTRIBUTES_TO_HEADER_CLASSES . each do |attr , klass |
196+ VALIDATABLE_ATTRIBUTES . each do |attr |
197+ klass = CONFIG_ATTRIBUTES_TO_HEADER_CLASSES [ attr ]
212198 klass . validate_config! ( instance_variable_get ( "@#{ attr } " ) )
213199 end
214- Cookie . validate_config! ( @cookies )
215200 end
216201
217202 def secure_cookies = ( secure_cookies )
218203 raise ArgumentError , "#{ Kernel . caller . first } : `#secure_cookies=` is no longer supported. Please use `#cookies=` to configure secure cookies instead."
219204 end
220205
221206 def csp = ( new_csp )
222- if new_csp . respond_to? ( :opt_out? )
223- @csp = new_csp . dup
207+ case new_csp
208+ when OPT_OUT
209+ @csp = new_csp
210+ when ContentSecurityPolicyConfig
211+ @csp = new_csp
212+ when Hash
213+ @csp = ContentSecurityPolicyConfig . new ( new_csp )
224214 else
225- if new_csp [ :report_only ]
226- # invalid configuration implies that CSPRO should be set, CSP should not - so opt out
227- raise ArgumentError , "#{ Kernel . caller . first } : `#csp=` was supplied a config with report_only: true. Use #csp_report_only="
228- else
229- @csp = ContentSecurityPolicyConfig . new ( new_csp )
230- end
215+ raise ArgumentError , "Must provide either an existing CSP config or a CSP config hash"
231216 end
232217 end
233218
@@ -238,34 +223,23 @@ def csp=(new_csp)
238223 # configuring csp_report_only, the code will assume you mean to only use
239224 # report-only mode and you will be opted-out of enforce mode.
240225 def csp_report_only = ( new_csp )
241- @csp_report_only = begin
242- if new_csp . is_a? ( ContentSecurityPolicyConfig )
243- new_csp . make_report_only
244- elsif new_csp . respond_to? ( :opt_out? )
245- new_csp . dup
246- else
247- if new_csp [ :report_only ] == false # nil is a valid value on which we do not want to raise
248- raise ContentSecurityPolicyConfigError , "`#csp_report_only=` was supplied a config with report_only: false. Use #csp="
249- else
250- ContentSecurityPolicyReportOnlyConfig . new ( new_csp )
251- end
252- end
226+ case new_csp
227+ when OPT_OUT
228+ @csp_report_only = new_csp
229+ when ContentSecurityPolicyReportOnlyConfig
230+ @csp_report_only = new_csp . dup
231+ when ContentSecurityPolicyConfig
232+ @csp_report_only = new_csp . make_report_only
233+ when Hash
234+ @csp_report_only = ContentSecurityPolicyReportOnlyConfig . new ( new_csp )
235+ else
236+ raise ArgumentError , "Must provide either an existing CSP config or a CSP config hash"
253237 end
254238 end
255239
256240 def hpkp_report_host
257241 return nil unless @hpkp && hpkp != OPT_OUT && @hpkp [ :report_uri ]
258242 URI . parse ( @hpkp [ :report_uri ] ) . host
259243 end
260-
261- protected
262-
263- def cookies = ( cookies )
264- @cookies = cookies
265- end
266-
267- def hpkp = ( hpkp )
268- @hpkp = self . class . send ( :deep_copy_if_hash , hpkp )
269- end
270244 end
271245end
0 commit comments