secure_headers 3.4.0 → 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. checksums.yaml +4 -4
  2. data/.rspec +2 -0
  3. data/.rubocop.yml +3 -0
  4. data/.ruby-version +1 -1
  5. data/.travis.yml +8 -4
  6. data/CHANGELOG.md +113 -1
  7. data/CODE_OF_CONDUCT.md +46 -0
  8. data/CONTRIBUTING.md +41 -0
  9. data/Gemfile +8 -4
  10. data/Guardfile +1 -0
  11. data/LICENSE +4 -199
  12. data/README.md +63 -333
  13. data/Rakefile +22 -18
  14. data/docs/HPKP.md +17 -0
  15. data/docs/cookies.md +64 -0
  16. data/docs/hashes.md +64 -0
  17. data/docs/named_overrides_and_appends.md +107 -0
  18. data/docs/per_action_configuration.md +133 -0
  19. data/docs/sinatra.md +25 -0
  20. data/lib/secure_headers/configuration.rb +91 -44
  21. data/lib/secure_headers/hash_helper.rb +2 -1
  22. data/lib/secure_headers/headers/clear_site_data.rb +55 -0
  23. data/lib/secure_headers/headers/content_security_policy.rb +110 -51
  24. data/lib/secure_headers/headers/content_security_policy_config.rb +162 -0
  25. data/lib/secure_headers/headers/cookie.rb +21 -3
  26. data/lib/secure_headers/headers/expect_certificate_transparency.rb +70 -0
  27. data/lib/secure_headers/headers/policy_management.rb +143 -69
  28. data/lib/secure_headers/headers/public_key_pins.rb +5 -4
  29. data/lib/secure_headers/headers/referrer_policy.rb +5 -1
  30. data/lib/secure_headers/headers/strict_transport_security.rb +2 -1
  31. data/lib/secure_headers/headers/x_content_type_options.rb +2 -1
  32. data/lib/secure_headers/headers/x_download_options.rb +3 -2
  33. data/lib/secure_headers/headers/x_frame_options.rb +2 -1
  34. data/lib/secure_headers/headers/x_permitted_cross_domain_policies.rb +3 -2
  35. data/lib/secure_headers/headers/x_xss_protection.rb +2 -1
  36. data/lib/secure_headers/middleware.rb +10 -9
  37. data/lib/secure_headers/railtie.rb +7 -6
  38. data/lib/secure_headers/utils/cookies_config.rb +13 -12
  39. data/lib/secure_headers/view_helper.rb +12 -3
  40. data/lib/secure_headers.rb +137 -55
  41. data/lib/tasks/tasks.rake +2 -1
  42. data/secure_headers.gemspec +13 -3
  43. data/spec/lib/secure_headers/configuration_spec.rb +13 -12
  44. data/spec/lib/secure_headers/headers/clear_site_data_spec.rb +87 -0
  45. data/spec/lib/secure_headers/headers/content_security_policy_spec.rb +59 -26
  46. data/spec/lib/secure_headers/headers/cookie_spec.rb +38 -20
  47. data/spec/lib/secure_headers/headers/expect_certificate_spec.rb +42 -0
  48. data/spec/lib/secure_headers/headers/policy_management_spec.rb +78 -40
  49. data/spec/lib/secure_headers/headers/public_key_pins_spec.rb +7 -6
  50. data/spec/lib/secure_headers/headers/referrer_policy_spec.rb +22 -3
  51. data/spec/lib/secure_headers/headers/strict_transport_security_spec.rb +5 -4
  52. data/spec/lib/secure_headers/headers/x_content_type_options_spec.rb +2 -1
  53. data/spec/lib/secure_headers/headers/x_download_options_spec.rb +3 -2
  54. data/spec/lib/secure_headers/headers/x_frame_options_spec.rb +2 -1
  55. data/spec/lib/secure_headers/headers/x_permitted_cross_domain_policies_spec.rb +4 -3
  56. data/spec/lib/secure_headers/headers/x_xss_protection_spec.rb +4 -3
  57. data/spec/lib/secure_headers/middleware_spec.rb +39 -19
  58. data/spec/lib/secure_headers/view_helpers_spec.rb +21 -8
  59. data/spec/lib/secure_headers_spec.rb +304 -56
  60. data/spec/spec_helper.rb +13 -24
  61. data/upgrading-to-4-0.md +55 -0
  62. metadata +29 -7
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module SecureHeaders
2
3
  module ViewHelpers
3
4
  include SecureHeaders::HashHelper
@@ -34,6 +35,14 @@ module SecureHeaders
34
35
  end
35
36
  end
36
37
 
38
+ def content_security_policy_script_nonce
39
+ content_security_policy_nonce(:script)
40
+ end
41
+
42
+ def content_security_policy_style_nonce
43
+ content_security_policy_nonce(:style)
44
+ end
45
+
37
46
  ##
38
47
  # Checks to see if the hashed code is expected and adds the hash source
39
48
  # value to the current CSP.
@@ -67,7 +76,7 @@ module SecureHeaders
67
76
  end
68
77
 
69
78
  content = capture(&block)
70
- file_path = File.join('app', 'views', self.instance_variable_get(:@virtual_path) + '.html.erb')
79
+ file_path = File.join("app", "views", self.instance_variable_get(:@virtual_path) + ".html.erb")
71
80
 
72
81
  if raise_error_on_unrecognized_hash
73
82
  hash_value = hash_source(content)
@@ -87,9 +96,9 @@ module SecureHeaders
87
96
  <<-EOF
88
97
  \n\n*** WARNING: Unrecognized hash in #{file_path}!!! Value: #{hash_value} ***
89
98
  #{content}
90
- *** Run #{SECURE_HEADERS_RAKE_TASK} or add the following to config/script_hashes.yml:***
99
+ *** Run #{SECURE_HEADERS_RAKE_TASK} or add the following to config/secure_headers_generated_hashes.yml:***
91
100
  #{file_path}:
92
- - #{hash_value}\n\n
101
+ - \"#{hash_value}\"\n\n
93
102
  NOTE: dynamic javascript is not supported using script hash integration
94
103
  on purpose. It defeats the point of using it in the first place.
95
104
  EOF
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  require "secure_headers/configuration"
2
3
  require "secure_headers/hash_helper"
3
4
  require "secure_headers/headers/cookie"
@@ -10,22 +11,52 @@ require "secure_headers/headers/x_content_type_options"
10
11
  require "secure_headers/headers/x_download_options"
11
12
  require "secure_headers/headers/x_permitted_cross_domain_policies"
12
13
  require "secure_headers/headers/referrer_policy"
14
+ require "secure_headers/headers/clear_site_data"
15
+ require "secure_headers/headers/expect_certificate_transparency"
13
16
  require "secure_headers/middleware"
14
17
  require "secure_headers/railtie"
15
18
  require "secure_headers/view_helper"
16
19
  require "useragent"
20
+ require "singleton"
17
21
 
18
22
  # All headers (except for hpkp) have a default value. Provide SecureHeaders::OPT_OUT
19
23
  # or ":optout_of_protection" as a config value to disable a given header
20
24
  module SecureHeaders
21
- OPT_OUT = :opt_out_of_protection
25
+ class NoOpHeaderConfig
26
+ include Singleton
27
+
28
+ def boom(*args)
29
+ raise "Illegal State: attempted to modify NoOpHeaderConfig. Create a new config instead."
30
+ end
31
+
32
+ def to_h
33
+ {}
34
+ end
35
+
36
+ def dup
37
+ self.class.instance
38
+ end
39
+
40
+ def opt_out?
41
+ true
42
+ end
43
+
44
+ alias_method :[], :boom
45
+ alias_method :[]=, :boom
46
+ alias_method :keys, :boom
47
+ end
48
+
49
+ OPT_OUT = NoOpHeaderConfig.instance
22
50
  SECURE_HEADERS_CONFIG = "secure_headers_request_config".freeze
23
51
  NONCE_KEY = "secure_headers_content_security_policy_nonce".freeze
24
52
  HTTPS = "https".freeze
25
53
  CSP = ContentSecurityPolicy
26
54
 
27
55
  ALL_HEADER_CLASSES = [
28
- ContentSecurityPolicy,
56
+ ExpectCertificateTransparency,
57
+ ClearSiteData,
58
+ ContentSecurityPolicyConfig,
59
+ ContentSecurityPolicyReportOnlyConfig,
29
60
  StrictTransportSecurity,
30
61
  PublicKeyPins,
31
62
  ReferrerPolicy,
@@ -36,7 +67,10 @@ module SecureHeaders
36
67
  XXssProtection
37
68
  ].freeze
38
69
 
39
- ALL_HEADERS_BESIDES_CSP = (ALL_HEADER_CLASSES - [CSP]).freeze
70
+ ALL_HEADERS_BESIDES_CSP = (
71
+ ALL_HEADER_CLASSES -
72
+ [ContentSecurityPolicyConfig, ContentSecurityPolicyReportOnlyConfig]
73
+ ).freeze
40
74
 
41
75
  # Headers set on http requests (excludes STS and HPKP)
42
76
  HTTP_HEADER_CLASSES =
@@ -50,13 +84,25 @@ module SecureHeaders
50
84
  #
51
85
  # additions - a hash containing directives. e.g.
52
86
  # script_src: %w(another-host.com)
53
- def override_content_security_policy_directives(request, additions)
54
- config = config_for(request)
55
- if config.current_csp == OPT_OUT
56
- config.dynamic_csp = {}
87
+ def override_content_security_policy_directives(request, additions, target = nil)
88
+ config, target = config_and_target(request, target)
89
+
90
+ if [:both, :enforced].include?(target)
91
+ if config.csp.opt_out?
92
+ config.csp = ContentSecurityPolicyConfig.new({})
93
+ end
94
+
95
+ config.csp.merge!(additions)
96
+ end
97
+
98
+ if [:both, :report_only].include?(target)
99
+ if config.csp_report_only.opt_out?
100
+ config.csp_report_only = ContentSecurityPolicyReportOnlyConfig.new({})
101
+ end
102
+
103
+ config.csp_report_only.merge!(additions)
57
104
  end
58
105
 
59
- config.dynamic_csp = config.current_csp.merge(additions)
60
106
  override_secure_headers_request_config(request, config)
61
107
  end
62
108
 
@@ -66,12 +112,25 @@ module SecureHeaders
66
112
  #
67
113
  # additions - a hash containing directives. e.g.
68
114
  # script_src: %w(another-host.com)
69
- def append_content_security_policy_directives(request, additions)
70
- config = config_for(request)
71
- config.dynamic_csp = CSP.combine_policies(config.current_csp, additions)
115
+ def append_content_security_policy_directives(request, additions, target = nil)
116
+ config, target = config_and_target(request, target)
117
+
118
+ if [:both, :enforced].include?(target) && !config.csp.opt_out?
119
+ config.csp.append(additions)
120
+ end
121
+
122
+ if [:both, :report_only].include?(target) && !config.csp_report_only.opt_out?
123
+ config.csp_report_only.append(additions)
124
+ end
125
+
72
126
  override_secure_headers_request_config(request, config)
73
127
  end
74
128
 
129
+ def use_content_security_policy_named_append(request, name)
130
+ additions = SecureHeaders::Configuration.named_appends(name).call(request)
131
+ append_content_security_policy_directives(request, additions)
132
+ end
133
+
75
134
  # Public: override X-Frame-Options settings for this request.
76
135
  #
77
136
  # value - deny, sameorigin, or allowall
@@ -107,12 +166,29 @@ module SecureHeaders
107
166
  # returned is meant to be merged into the header value from `@app.call(env)`
108
167
  # in Rack middleware.
109
168
  def header_hash_for(request)
110
- config = config_for(request)
111
- unless ContentSecurityPolicy.idempotent_additions?(config.csp, config.current_csp)
112
- config.rebuild_csp_header_cache!(request.user_agent)
169
+ prevent_dup = true
170
+ config = config_for(request, prevent_dup)
171
+ headers = config.cached_headers
172
+ user_agent = UserAgent.parse(request.user_agent)
173
+
174
+ if !config.csp.opt_out? && config.csp.modified?
175
+ headers = update_cached_csp(config.csp, headers, user_agent)
113
176
  end
114
177
 
115
- use_cached_headers(config.cached_headers, request)
178
+ if !config.csp_report_only.opt_out? && config.csp_report_only.modified?
179
+ headers = update_cached_csp(config.csp_report_only, headers, user_agent)
180
+ end
181
+
182
+ header_classes_for(request).each_with_object({}) do |klass, hash|
183
+ if header = headers[klass::CONFIG_KEY]
184
+ header_name, value = if klass == ContentSecurityPolicyConfig || klass == ContentSecurityPolicyReportOnlyConfig
185
+ csp_header_for_ua(header, user_agent)
186
+ else
187
+ header
188
+ end
189
+ hash[header_name] = value
190
+ end
191
+ end
116
192
  end
117
193
 
118
194
  # Public: specify which named override will be used for this request.
@@ -133,7 +209,7 @@ module SecureHeaders
133
209
  #
134
210
  # Returns the nonce
135
211
  def content_security_policy_script_nonce(request)
136
- content_security_policy_nonce(request, CSP::SCRIPT_SRC)
212
+ content_security_policy_nonce(request, ContentSecurityPolicy::SCRIPT_SRC)
137
213
  end
138
214
 
139
215
  # Public: gets or creates a nonce for CSP.
@@ -142,7 +218,7 @@ module SecureHeaders
142
218
  #
143
219
  # Returns the nonce
144
220
  def content_security_policy_style_nonce(request)
145
- content_security_policy_nonce(request, CSP::STYLE_SRC)
221
+ content_security_policy_nonce(request, ContentSecurityPolicy::STYLE_SRC)
146
222
  end
147
223
 
148
224
  # Public: Retreives the config for a given header type:
@@ -150,11 +226,15 @@ module SecureHeaders
150
226
  # Checks to see if there is an override for this request, then
151
227
  # Checks to see if a named override is used for this request, then
152
228
  # Falls back to the global config
153
- def config_for(request)
229
+ def config_for(request, prevent_dup = false)
154
230
  config = request.env[SECURE_HEADERS_CONFIG] ||
155
231
  Configuration.get(Configuration::DEFAULT_CONFIG)
156
232
 
157
- if config.frozen?
233
+
234
+ # Global configs are frozen, per-request configs are not. When we're not
235
+ # making modifications to the config, prevent_dup ensures we don't dup
236
+ # the object unnecessarily. It's not necessarily frozen to begin with.
237
+ if config.frozen? && !prevent_dup
158
238
  config.dup
159
239
  else
160
240
  config
@@ -162,13 +242,38 @@ module SecureHeaders
162
242
  end
163
243
 
164
244
  private
245
+ TARGETS = [:both, :enforced, :report_only]
246
+ def raise_on_unknown_target(target)
247
+ unless TARGETS.include?(target)
248
+ raise "Unrecognized target: #{target}. Must be [:both, :enforced, :report_only]"
249
+ end
250
+ end
251
+
252
+ def config_and_target(request, target)
253
+ config = config_for(request)
254
+ target = guess_target(config) unless target
255
+ raise_on_unknown_target(target)
256
+ [config, target]
257
+ end
258
+
259
+ def guess_target(config)
260
+ if !config.csp.opt_out? && !config.csp_report_only.opt_out?
261
+ :both
262
+ elsif !config.csp.opt_out?
263
+ :enforced
264
+ elsif !config.csp_report_only.opt_out?
265
+ :report_only
266
+ else
267
+ :both
268
+ end
269
+ end
165
270
 
166
271
  # Private: gets or creates a nonce for CSP.
167
272
  #
168
273
  # Returns the nonce
169
274
  def content_security_policy_nonce(request, script_or_style)
170
275
  request.env[NONCE_KEY] ||= SecureRandom.base64(32).chomp
171
- nonce_key = script_or_style == CSP::SCRIPT_SRC ? :script_nonce : :style_nonce
276
+ nonce_key = script_or_style == ContentSecurityPolicy::SCRIPT_SRC ? :script_nonce : :style_nonce
172
277
  append_content_security_policy_directives(request, nonce_key => request.env[NONCE_KEY])
173
278
  request.env[NONCE_KEY]
174
279
  end
@@ -193,21 +298,12 @@ module SecureHeaders
193
298
  end
194
299
  end
195
300
 
196
- # Private: takes a precomputed hash of headers and returns the Headers
197
- # customized for the request.
198
- #
199
- # Returns a hash of header names / values valid for a given request.
200
- def use_cached_headers(headers, request)
201
- header_classes_for(request).each_with_object({}) do |klass, hash|
202
- if header = headers[klass::CONFIG_KEY]
203
- header_name, value = if klass == CSP
204
- csp_header_for_ua(header, request)
205
- else
206
- header
207
- end
208
- hash[header_name] = value
209
- end
210
- end
301
+ def update_cached_csp(config, headers, user_agent)
302
+ headers = Configuration.send(:deep_copy, headers)
303
+ headers[config.class::CONFIG_KEY] = {}
304
+ variation = ContentSecurityPolicy.ua_to_variation(user_agent)
305
+ headers[config.class::CONFIG_KEY][variation] = ContentSecurityPolicy.make_header(config, user_agent)
306
+ headers
211
307
  end
212
308
 
213
309
  # Private: chooses the applicable CSP header for the provided user agent.
@@ -215,26 +311,8 @@ module SecureHeaders
215
311
  # headers - a hash of header_config_key => [header_name, header_value]
216
312
  #
217
313
  # Returns a CSP [header, value] array
218
- def csp_header_for_ua(headers, request)
219
- headers[CSP.ua_to_variation(UserAgent.parse(request.user_agent))]
220
- end
221
-
222
- # Private: optionally build a header with a given configure
223
- #
224
- # klass - corresponding Class for a given header
225
- # config - A string, symbol, or hash config for the header
226
- # user_agent - A string representing the UA (only used for CSP feature sniffing)
227
- #
228
- # Returns a 2 element array [header_name, header_value] or nil if config
229
- # is OPT_OUT
230
- def make_header(klass, header_config, user_agent = nil)
231
- unless header_config == OPT_OUT
232
- if klass == CSP
233
- klass.make_header(header_config, user_agent)
234
- else
235
- klass.make_header(header_config)
236
- end
237
- end
314
+ def csp_header_for_ua(headers, user_agent)
315
+ headers[ContentSecurityPolicy.ua_to_variation(user_agent)]
238
316
  end
239
317
  end
240
318
 
@@ -267,4 +345,8 @@ module SecureHeaders
267
345
  def override_x_frame_options(value)
268
346
  SecureHeaders.override_x_frame_options(request, value)
269
347
  end
348
+
349
+ def use_content_security_policy_named_append(name)
350
+ SecureHeaders.use_content_security_policy_named_append(request, name)
351
+ end
270
352
  end
data/lib/tasks/tasks.rake CHANGED
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  INLINE_SCRIPT_REGEX = /(<script(\s*(?!src)([\w\-])+=([\"\'])[^\"\']+\4)*\s*>)(.*?)<\/script>/mx unless defined? INLINE_SCRIPT_REGEX
2
3
  INLINE_STYLE_REGEX = /(<style[^>]*>)(.*?)<\/style>/mx unless defined? INLINE_STYLE_REGEX
3
4
  INLINE_HASH_SCRIPT_HELPER_REGEX = /<%=\s?hashed_javascript_tag(.*?)\s+do\s?%>(.*?)<%\s*end\s*%>/mx unless defined? INLINE_HASH_SCRIPT_HELPER_REGEX
@@ -73,7 +74,7 @@ namespace :secure_headers do
73
74
  end
74
75
  end
75
76
 
76
- File.open(SecureHeaders::Configuration::HASH_CONFIG_FILE, 'w') do |file|
77
+ File.open(SecureHeaders::Configuration::HASH_CONFIG_FILE, "w") do |file|
77
78
  file.write(script_hashes.to_yaml)
78
79
  end
79
80
 
@@ -1,10 +1,11 @@
1
1
  # -*- encoding: utf-8 -*-
2
+ # frozen_string_literal: true
2
3
  Gem::Specification.new do |gem|
3
4
  gem.name = "secure_headers"
4
- gem.version = "3.4.0"
5
+ gem.version = "4.0.0"
5
6
  gem.authors = ["Neil Matatall"]
6
7
  gem.email = ["neil.matatall@gmail.com"]
7
- gem.description = 'Security related headers all in one gem.'
8
+ gem.description = "Manages application of security headers with many safe defaults."
8
9
  gem.summary = 'Add easily configured security headers to responses
9
10
  including content-security-policy, x-frame-options,
10
11
  strict-transport-security, etc.'
@@ -15,5 +16,14 @@ Gem::Specification.new do |gem|
15
16
  gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
16
17
  gem.require_paths = ["lib"]
17
18
  gem.add_development_dependency "rake"
18
- gem.add_dependency "useragent"
19
+ gem.add_dependency "useragent", ">= 0.15.0"
20
+
21
+ # TODO: delete this after 4.1 is cut or a number of 4.0.x releases have occurred
22
+ gem.post_install_message = <<-POST_INSTALL
23
+
24
+ **********
25
+ :wave: secure_headers 4.0 introduces a lot of breaking changes (in the name of security!). It's highly likely you will need to update your secure_headers cookie configuration to avoid breaking things. See the upgrade guide for details: https://github.com/twitter/secureheaders/blob/master/upgrading-to-4-0.md
26
+ **********
27
+
28
+ POST_INSTALL
19
29
  end
@@ -1,4 +1,5 @@
1
- require 'spec_helper'
1
+ # frozen_string_literal: true
2
+ require "spec_helper"
2
3
 
3
4
  module SecureHeaders
4
5
  describe Configuration do
@@ -36,8 +37,8 @@ module SecureHeaders
36
37
 
37
38
  config = Configuration.get(:test_override)
38
39
  noop = Configuration.get(Configuration::NOOP_CONFIGURATION)
39
- [:csp, :dynamic_csp, :cookies].each do |key|
40
- expect(config.send(key)).to eq(noop.send(key)), "Value not copied: #{key}."
40
+ [:csp, :csp_report_only, :cookies].each do |key|
41
+ expect(config.send(key)).to eq(noop.send(key))
41
42
  end
42
43
  end
43
44
 
@@ -65,12 +66,12 @@ module SecureHeaders
65
66
  default = Configuration.get
66
67
  override = Configuration.get(:override)
67
68
 
68
- expect(override.csp).not_to eq(default.csp)
69
+ expect(override.csp.directive_value(:default_src)).not_to be(default.csp.directive_value(:default_src))
69
70
  end
70
71
 
71
72
  it "allows you to override an override" do
72
73
  Configuration.override(:override) do |config|
73
- config.csp = { default_src: %w('self')}
74
+ config.csp = { default_src: %w('self'), script_src: %w('self')}
74
75
  end
75
76
 
76
77
  Configuration.override(:second_override, :override) do |config|
@@ -78,17 +79,17 @@ module SecureHeaders
78
79
  end
79
80
 
80
81
  original_override = Configuration.get(:override)
81
- expect(original_override.csp).to eq(default_src: %w('self'))
82
+ expect(original_override.csp.to_h).to eq(default_src: %w('self'), script_src: %w('self'))
82
83
  override_config = Configuration.get(:second_override)
83
- expect(override_config.csp).to eq(default_src: %w('self'), script_src: %w(example.org))
84
+ expect(override_config.csp.to_h).to eq(default_src: %w('self'), script_src: %w('self' example.org))
84
85
  end
85
86
 
86
87
  it "deprecates the secure_cookies configuration" do
87
- expect(Kernel).to receive(:warn).with(/\[DEPRECATION\]/)
88
-
89
- Configuration.default do |config|
90
- config.secure_cookies = true
91
- end
88
+ expect {
89
+ Configuration.default do |config|
90
+ config.secure_cookies = true
91
+ end
92
+ }.to raise_error(ArgumentError)
92
93
  end
93
94
  end
94
95
  end
@@ -0,0 +1,87 @@
1
+ # frozen_string_literal: true
2
+ require "spec_helper"
3
+
4
+ module SecureHeaders
5
+ describe ClearSiteData do
6
+ describe "make_header" do
7
+ it "returns nil with nil config" do
8
+ expect(described_class.make_header).to be_nil
9
+ end
10
+
11
+ it "returns nil with empty config" do
12
+ expect(described_class.make_header([])).to be_nil
13
+ end
14
+
15
+ it "returns nil with opt-out config" do
16
+ expect(described_class.make_header(OPT_OUT)).to be_nil
17
+ end
18
+
19
+ it "returns all types with `true` config" do
20
+ name, value = described_class.make_header(true)
21
+
22
+ expect(name).to eq(ClearSiteData::HEADER_NAME)
23
+ expect(value).to eq(
24
+ %("cache", "cookies", "storage", "executionContexts")
25
+ )
26
+ end
27
+
28
+ it "returns specified types" do
29
+ name, value = described_class.make_header(["foo", "bar"])
30
+
31
+ expect(name).to eq(ClearSiteData::HEADER_NAME)
32
+ expect(value).to eq(%("foo", "bar"))
33
+ end
34
+ end
35
+
36
+ describe "validate_config!" do
37
+ it "succeeds for `true` config" do
38
+ expect do
39
+ described_class.validate_config!(true)
40
+ end.not_to raise_error
41
+ end
42
+
43
+ it "succeeds for `nil` config" do
44
+ expect do
45
+ described_class.validate_config!(nil)
46
+ end.not_to raise_error
47
+ end
48
+
49
+ it "succeeds for opt-out config" do
50
+ expect do
51
+ described_class.validate_config!(OPT_OUT)
52
+ end.not_to raise_error
53
+ end
54
+
55
+ it "succeeds for empty config" do
56
+ expect do
57
+ described_class.validate_config!([])
58
+ end.not_to raise_error
59
+ end
60
+
61
+ it "succeeds for Array of Strings config" do
62
+ expect do
63
+ described_class.validate_config!(["foo"])
64
+ end.not_to raise_error
65
+ end
66
+
67
+ it "fails for Array of non-String config" do
68
+ expect do
69
+ described_class.validate_config!([1])
70
+ end.to raise_error(ClearSiteDataConfigError)
71
+ end
72
+
73
+ it "fails for other types of config" do
74
+ expect do
75
+ described_class.validate_config!(:cookies)
76
+ end.to raise_error(ClearSiteDataConfigError)
77
+ end
78
+ end
79
+
80
+ describe "make_header_value" do
81
+ it "returns a string of quoted values that are comma separated" do
82
+ value = described_class.make_header_value(["foo", "bar"])
83
+ expect(value).to eq(%("foo", "bar"))
84
+ end
85
+ end
86
+ end
87
+ end