secure_headers 3.4.0 → 3.6.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.
@@ -10,22 +10,50 @@ require "secure_headers/headers/x_content_type_options"
10
10
  require "secure_headers/headers/x_download_options"
11
11
  require "secure_headers/headers/x_permitted_cross_domain_policies"
12
12
  require "secure_headers/headers/referrer_policy"
13
+ require "secure_headers/headers/clear_site_data"
13
14
  require "secure_headers/middleware"
14
15
  require "secure_headers/railtie"
15
16
  require "secure_headers/view_helper"
16
17
  require "useragent"
18
+ require "singleton"
17
19
 
18
20
  # All headers (except for hpkp) have a default value. Provide SecureHeaders::OPT_OUT
19
21
  # or ":optout_of_protection" as a config value to disable a given header
20
22
  module SecureHeaders
21
- OPT_OUT = :opt_out_of_protection
23
+ class NoOpHeaderConfig
24
+ include Singleton
25
+
26
+ def boom(arg = nil)
27
+ raise "Illegal State: attempted to modify NoOpHeaderConfig. Create a new config instead."
28
+ end
29
+
30
+ def to_h
31
+ {}
32
+ end
33
+
34
+ def dup
35
+ self.class.instance
36
+ end
37
+
38
+ def opt_out?
39
+ true
40
+ end
41
+
42
+ alias_method :[], :boom
43
+ alias_method :[]=, :boom
44
+ alias_method :keys, :boom
45
+ end
46
+
47
+ OPT_OUT = NoOpHeaderConfig.instance
22
48
  SECURE_HEADERS_CONFIG = "secure_headers_request_config".freeze
23
49
  NONCE_KEY = "secure_headers_content_security_policy_nonce".freeze
24
50
  HTTPS = "https".freeze
25
51
  CSP = ContentSecurityPolicy
26
52
 
27
53
  ALL_HEADER_CLASSES = [
28
- ContentSecurityPolicy,
54
+ ClearSiteData,
55
+ ContentSecurityPolicyConfig,
56
+ ContentSecurityPolicyReportOnlyConfig,
29
57
  StrictTransportSecurity,
30
58
  PublicKeyPins,
31
59
  ReferrerPolicy,
@@ -36,7 +64,10 @@ module SecureHeaders
36
64
  XXssProtection
37
65
  ].freeze
38
66
 
39
- ALL_HEADERS_BESIDES_CSP = (ALL_HEADER_CLASSES - [CSP]).freeze
67
+ ALL_HEADERS_BESIDES_CSP = (
68
+ ALL_HEADER_CLASSES -
69
+ [ContentSecurityPolicyConfig, ContentSecurityPolicyReportOnlyConfig]
70
+ ).freeze
40
71
 
41
72
  # Headers set on http requests (excludes STS and HPKP)
42
73
  HTTP_HEADER_CLASSES =
@@ -50,13 +81,25 @@ module SecureHeaders
50
81
  #
51
82
  # additions - a hash containing directives. e.g.
52
83
  # 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 = {}
84
+ def override_content_security_policy_directives(request, additions, target = nil)
85
+ config, target = config_and_target(request, target)
86
+
87
+ if [:both, :enforced].include?(target)
88
+ if config.csp.opt_out?
89
+ config.csp = ContentSecurityPolicyConfig.new({})
90
+ end
91
+
92
+ config.csp.merge!(additions)
93
+ end
94
+
95
+ if [:both, :report_only].include?(target)
96
+ if config.csp_report_only.opt_out?
97
+ config.csp_report_only = ContentSecurityPolicyReportOnlyConfig.new({})
98
+ end
99
+
100
+ config.csp_report_only.merge!(additions)
57
101
  end
58
102
 
59
- config.dynamic_csp = config.current_csp.merge(additions)
60
103
  override_secure_headers_request_config(request, config)
61
104
  end
62
105
 
@@ -66,12 +109,25 @@ module SecureHeaders
66
109
  #
67
110
  # additions - a hash containing directives. e.g.
68
111
  # 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)
112
+ def append_content_security_policy_directives(request, additions, target = nil)
113
+ config, target = config_and_target(request, target)
114
+
115
+ if [:both, :enforced].include?(target) && !config.csp.opt_out?
116
+ config.csp.append(additions)
117
+ end
118
+
119
+ if [:both, :report_only].include?(target) && !config.csp_report_only.opt_out?
120
+ config.csp_report_only.append(additions)
121
+ end
122
+
72
123
  override_secure_headers_request_config(request, config)
73
124
  end
74
125
 
126
+ def use_content_security_policy_named_append(request, name)
127
+ additions = SecureHeaders::Configuration.named_appends(name).call(request)
128
+ append_content_security_policy_directives(request, additions)
129
+ end
130
+
75
131
  # Public: override X-Frame-Options settings for this request.
76
132
  #
77
133
  # value - deny, sameorigin, or allowall
@@ -107,12 +163,28 @@ module SecureHeaders
107
163
  # returned is meant to be merged into the header value from `@app.call(env)`
108
164
  # in Rack middleware.
109
165
  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)
166
+ config = config_for(request, prevent_dup = true)
167
+ headers = config.cached_headers
168
+ user_agent = UserAgent.parse(request.user_agent)
169
+
170
+ if !config.csp.opt_out? && config.csp.modified?
171
+ headers = update_cached_csp(config.csp, headers, user_agent)
113
172
  end
114
173
 
115
- use_cached_headers(config.cached_headers, request)
174
+ if !config.csp_report_only.opt_out? && config.csp_report_only.modified?
175
+ headers = update_cached_csp(config.csp_report_only, headers, user_agent)
176
+ end
177
+
178
+ header_classes_for(request).each_with_object({}) do |klass, hash|
179
+ if header = headers[klass::CONFIG_KEY]
180
+ header_name, value = if [ContentSecurityPolicyConfig, ContentSecurityPolicyReportOnlyConfig].include?(klass)
181
+ csp_header_for_ua(header, user_agent)
182
+ else
183
+ header
184
+ end
185
+ hash[header_name] = value
186
+ end
187
+ end
116
188
  end
117
189
 
118
190
  # Public: specify which named override will be used for this request.
@@ -133,7 +205,7 @@ module SecureHeaders
133
205
  #
134
206
  # Returns the nonce
135
207
  def content_security_policy_script_nonce(request)
136
- content_security_policy_nonce(request, CSP::SCRIPT_SRC)
208
+ content_security_policy_nonce(request, ContentSecurityPolicy::SCRIPT_SRC)
137
209
  end
138
210
 
139
211
  # Public: gets or creates a nonce for CSP.
@@ -142,7 +214,7 @@ module SecureHeaders
142
214
  #
143
215
  # Returns the nonce
144
216
  def content_security_policy_style_nonce(request)
145
- content_security_policy_nonce(request, CSP::STYLE_SRC)
217
+ content_security_policy_nonce(request, ContentSecurityPolicy::STYLE_SRC)
146
218
  end
147
219
 
148
220
  # Public: Retreives the config for a given header type:
@@ -150,11 +222,15 @@ module SecureHeaders
150
222
  # Checks to see if there is an override for this request, then
151
223
  # Checks to see if a named override is used for this request, then
152
224
  # Falls back to the global config
153
- def config_for(request)
225
+ def config_for(request, prevent_dup = false)
154
226
  config = request.env[SECURE_HEADERS_CONFIG] ||
155
227
  Configuration.get(Configuration::DEFAULT_CONFIG)
156
228
 
157
- if config.frozen?
229
+
230
+ # Global configs are frozen, per-request configs are not. When we're not
231
+ # making modifications to the config, prevent_dup ensures we don't dup
232
+ # the object unnecessarily. It's not necessarily frozen to begin with.
233
+ if config.frozen? && !prevent_dup
158
234
  config.dup
159
235
  else
160
236
  config
@@ -162,13 +238,38 @@ module SecureHeaders
162
238
  end
163
239
 
164
240
  private
241
+ TARGETS = [:both, :enforced, :report_only]
242
+ def raise_on_unknown_target(target)
243
+ unless TARGETS.include?(target)
244
+ raise "Unrecognized target: #{target}. Must be [:both, :enforced, :report_only]"
245
+ end
246
+ end
247
+
248
+ def config_and_target(request, target)
249
+ config = config_for(request)
250
+ target = guess_target(config) unless target
251
+ raise_on_unknown_target(target)
252
+ [config, target]
253
+ end
254
+
255
+ def guess_target(config)
256
+ if !config.csp.opt_out? && !config.csp_report_only.opt_out?
257
+ :both
258
+ elsif !config.csp.opt_out?
259
+ :enforced
260
+ elsif !config.csp_report_only.opt_out?
261
+ :report_only
262
+ else
263
+ :both
264
+ end
265
+ end
165
266
 
166
267
  # Private: gets or creates a nonce for CSP.
167
268
  #
168
269
  # Returns the nonce
169
270
  def content_security_policy_nonce(request, script_or_style)
170
271
  request.env[NONCE_KEY] ||= SecureRandom.base64(32).chomp
171
- nonce_key = script_or_style == CSP::SCRIPT_SRC ? :script_nonce : :style_nonce
272
+ nonce_key = script_or_style == ContentSecurityPolicy::SCRIPT_SRC ? :script_nonce : :style_nonce
172
273
  append_content_security_policy_directives(request, nonce_key => request.env[NONCE_KEY])
173
274
  request.env[NONCE_KEY]
174
275
  end
@@ -193,21 +294,12 @@ module SecureHeaders
193
294
  end
194
295
  end
195
296
 
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
297
+ def update_cached_csp(config, headers, user_agent)
298
+ headers = Configuration.send(:deep_copy, headers)
299
+ headers[config.class::CONFIG_KEY] = {}
300
+ variation = ContentSecurityPolicy.ua_to_variation(user_agent)
301
+ headers[config.class::CONFIG_KEY][variation] = ContentSecurityPolicy.make_header(config, user_agent)
302
+ headers
211
303
  end
212
304
 
213
305
  # Private: chooses the applicable CSP header for the provided user agent.
@@ -215,26 +307,8 @@ module SecureHeaders
215
307
  # headers - a hash of header_config_key => [header_name, header_value]
216
308
  #
217
309
  # 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
310
+ def csp_header_for_ua(headers, user_agent)
311
+ headers[ContentSecurityPolicy.ua_to_variation(user_agent)]
238
312
  end
239
313
  end
240
314
 
@@ -267,4 +341,8 @@ module SecureHeaders
267
341
  def override_x_frame_options(value)
268
342
  SecureHeaders.override_x_frame_options(request, value)
269
343
  end
344
+
345
+ def use_content_security_policy_named_append(name)
346
+ SecureHeaders.use_content_security_policy_named_append(request, name)
347
+ end
270
348
  end
@@ -1,7 +1,7 @@
1
1
  # -*- encoding: utf-8 -*-
2
2
  Gem::Specification.new do |gem|
3
3
  gem.name = "secure_headers"
4
- gem.version = "3.4.0"
4
+ gem.version = "3.6.0"
5
5
  gem.authors = ["Neil Matatall"]
6
6
  gem.email = ["neil.matatall@gmail.com"]
7
7
  gem.description = 'Security related headers all in one gem.'
@@ -36,8 +36,8 @@ module SecureHeaders
36
36
 
37
37
  config = Configuration.get(:test_override)
38
38
  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}."
39
+ [:csp, :csp_report_only, :cookies].each do |key|
40
+ expect(config.send(key)).to eq(noop.send(key))
41
41
  end
42
42
  end
43
43
 
@@ -65,7 +65,7 @@ module SecureHeaders
65
65
  default = Configuration.get
66
66
  override = Configuration.get(:override)
67
67
 
68
- expect(override.csp).not_to eq(default.csp)
68
+ expect(override.csp.directive_value(:default_src)).not_to be(default.csp.directive_value(:default_src))
69
69
  end
70
70
 
71
71
  it "allows you to override an override" do
@@ -78,9 +78,9 @@ module SecureHeaders
78
78
  end
79
79
 
80
80
  original_override = Configuration.get(:override)
81
- expect(original_override.csp).to eq(default_src: %w('self'))
81
+ expect(original_override.csp.to_h).to eq(default_src: %w('self'))
82
82
  override_config = Configuration.get(:second_override)
83
- expect(override_config.csp).to eq(default_src: %w('self'), script_src: %w(example.org))
83
+ expect(override_config.csp.to_h).to eq(default_src: %w('self'), script_src: %w('self' example.org))
84
84
  end
85
85
 
86
86
  it "deprecates the secure_cookies configuration" do
@@ -0,0 +1,103 @@
1
+ require 'spec_helper'
2
+
3
+ module SecureHeaders
4
+ describe ClearSiteData do
5
+ describe "make_header" do
6
+ it "returns nil with nil config" do
7
+ expect(described_class.make_header).to be_nil
8
+ end
9
+
10
+ it "returns nil with empty config" do
11
+ expect(described_class.make_header([])).to be_nil
12
+ end
13
+
14
+ it "returns nil with opt-out config" do
15
+ expect(described_class.make_header(OPT_OUT)).to be_nil
16
+ end
17
+
18
+ it "returns all types with `true` config" do
19
+ name, value = described_class.make_header(true)
20
+
21
+ expect(name).to eq(ClearSiteData::HEADER_NAME)
22
+ expect(value).to eq(normalize_json(<<-HERE))
23
+ {
24
+ "types": [
25
+ "cache",
26
+ "cookies",
27
+ "storage",
28
+ "executionContexts"
29
+ ]
30
+ }
31
+ HERE
32
+ end
33
+
34
+ it "returns specified types" do
35
+ name, value = described_class.make_header(["foo", "bar"])
36
+
37
+ expect(name).to eq(ClearSiteData::HEADER_NAME)
38
+ expect(value).to eq(normalize_json(<<-HERE))
39
+ {
40
+ "types": [
41
+ "foo",
42
+ "bar"
43
+ ]
44
+ }
45
+ HERE
46
+ end
47
+ end
48
+
49
+ describe "validate_config!" do
50
+ it "succeeds for `true` config" do
51
+ expect do
52
+ described_class.validate_config!(true)
53
+ end.not_to raise_error
54
+ end
55
+
56
+ it "succeeds for `nil` config" do
57
+ expect do
58
+ described_class.validate_config!(nil)
59
+ end.not_to raise_error
60
+ end
61
+
62
+ it "succeeds for opt-out config" do
63
+ expect do
64
+ described_class.validate_config!(OPT_OUT)
65
+ end.not_to raise_error
66
+ end
67
+
68
+ it "succeeds for empty config" do
69
+ expect do
70
+ described_class.validate_config!([])
71
+ end.not_to raise_error
72
+ end
73
+
74
+ it "succeeds for Array of Strings config" do
75
+ expect do
76
+ described_class.validate_config!(["foo"])
77
+ end.not_to raise_error
78
+ end
79
+
80
+ it "fails for Array of non-String config" do
81
+ expect do
82
+ described_class.validate_config!([1])
83
+ end.to raise_error(ClearSiteDataConfigError)
84
+ end
85
+
86
+ it "fails for non-serializable config" do
87
+ expect do
88
+ described_class.validate_config!(["hi \255"])
89
+ end.to raise_error(ClearSiteDataConfigError)
90
+ end
91
+
92
+ it "fails for other types of config" do
93
+ expect do
94
+ described_class.validate_config!(:cookies)
95
+ end.to raise_error(ClearSiteDataConfigError)
96
+ end
97
+ end
98
+
99
+ def normalize_json(json)
100
+ JSON.dump(JSON.parse(json))
101
+ end
102
+ end
103
+ end
@@ -14,11 +14,11 @@ module SecureHeaders
14
14
 
15
15
  describe "#name" do
16
16
  context "when in report-only mode" do
17
- specify { expect(ContentSecurityPolicy.new(default_opts.merge(report_only: true)).name).to eq(ContentSecurityPolicy::HEADER_NAME + "-Report-Only") }
17
+ specify { expect(ContentSecurityPolicy.new(default_opts.merge(report_only: true)).name).to eq(ContentSecurityPolicyReportOnlyConfig::HEADER_NAME) }
18
18
  end
19
19
 
20
20
  context "when in enforce mode" do
21
- specify { expect(ContentSecurityPolicy.new(default_opts).name).to eq(ContentSecurityPolicy::HEADER_NAME) }
21
+ specify { expect(ContentSecurityPolicy.new(default_opts).name).to eq(ContentSecurityPolicyConfig::HEADER_NAME) }
22
22
  end
23
23
  end
24
24
 
@@ -107,6 +107,11 @@ module SecureHeaders
107
107
  expect(firefox_transitional).not_to match(/frame-src/)
108
108
  end
109
109
 
110
+ it "supports strict-dynamic" do
111
+ csp = ContentSecurityPolicy.new({default_src: %w('self'), script_src: [ContentSecurityPolicy::STRICT_DYNAMIC], script_nonce: 123456}, USER_AGENTS[:chrome])
112
+ expect(csp.value).to eq("default-src 'self'; script-src 'strict-dynamic' 'nonce-123456'")
113
+ end
114
+
110
115
  context "browser sniffing" do
111
116
  let (:complex_opts) do
112
117
  (ContentSecurityPolicy::ALL_DIRECTIVES - [:frame_src]).each_with_object({}) do |directive, hash|
@@ -149,6 +154,13 @@ module SecureHeaders
149
154
  policy = ContentSecurityPolicy.new(complex_opts, USER_AGENTS[:safari6])
150
155
  expect(policy.value).to eq("default-src default-src.com; connect-src connect-src.com; font-src font-src.com; frame-src child-src.com; img-src img-src.com; media-src media-src.com; object-src object-src.com; sandbox sandbox.com; script-src script-src.com 'unsafe-inline'; style-src style-src.com; report-uri report-uri.com")
151
156
  end
157
+
158
+ it "falls back to standard Firefox defaults when the useragent version is not present" do
159
+ ua = USER_AGENTS[:firefox].dup
160
+ allow(ua).to receive(:version).and_return(nil)
161
+ policy = ContentSecurityPolicy.new(complex_opts, ua)
162
+ expect(policy.value).to eq("default-src default-src.com; base-uri base-uri.com; connect-src connect-src.com; font-src font-src.com; form-action form-action.com; frame-ancestors frame-ancestors.com; frame-src child-src.com; img-src img-src.com; media-src media-src.com; object-src object-src.com; sandbox sandbox.com; script-src script-src.com 'nonce-123456'; style-src style-src.com; upgrade-insecure-requests; report-uri report-uri.com")
163
+ end
152
164
  end
153
165
  end
154
166
  end
@@ -40,70 +40,75 @@ module SecureHeaders
40
40
  report_uri: %w(https://example.com/uri-directive)
41
41
  }
42
42
 
43
- CSP.validate_config!(config)
43
+ ContentSecurityPolicy.validate_config!(ContentSecurityPolicyConfig.new(config))
44
44
  end
45
45
 
46
46
  it "requires a :default_src value" do
47
47
  expect do
48
- CSP.validate_config!(script_src: %('self'))
48
+ ContentSecurityPolicy.validate_config!(ContentSecurityPolicyConfig.new(script_src: %('self')))
49
49
  end.to raise_error(ContentSecurityPolicyConfigError)
50
50
  end
51
51
 
52
52
  it "requires :report_only to be a truthy value" do
53
53
  expect do
54
- CSP.validate_config!(default_opts.merge(report_only: "steve"))
54
+ ContentSecurityPolicy.validate_config!(ContentSecurityPolicyConfig.new(default_opts.merge(report_only: "steve")))
55
55
  end.to raise_error(ContentSecurityPolicyConfigError)
56
56
  end
57
57
 
58
58
  it "requires :preserve_schemes to be a truthy value" do
59
59
  expect do
60
- CSP.validate_config!(default_opts.merge(preserve_schemes: "steve"))
60
+ ContentSecurityPolicy.validate_config!(ContentSecurityPolicyConfig.new(default_opts.merge(preserve_schemes: "steve")))
61
61
  end.to raise_error(ContentSecurityPolicyConfigError)
62
62
  end
63
63
 
64
64
  it "requires :block_all_mixed_content to be a boolean value" do
65
65
  expect do
66
- CSP.validate_config!(default_opts.merge(block_all_mixed_content: "steve"))
66
+ ContentSecurityPolicy.validate_config!(ContentSecurityPolicyConfig.new(default_opts.merge(block_all_mixed_content: "steve")))
67
67
  end.to raise_error(ContentSecurityPolicyConfigError)
68
68
  end
69
69
 
70
70
  it "requires :upgrade_insecure_requests to be a boolean value" do
71
71
  expect do
72
- CSP.validate_config!(default_opts.merge(upgrade_insecure_requests: "steve"))
72
+ ContentSecurityPolicy.validate_config!(ContentSecurityPolicyConfig.new(default_opts.merge(upgrade_insecure_requests: "steve")))
73
73
  end.to raise_error(ContentSecurityPolicyConfigError)
74
74
  end
75
75
 
76
76
  it "requires all source lists to be an array of strings" do
77
77
  expect do
78
- CSP.validate_config!(default_src: "steve")
78
+ ContentSecurityPolicy.validate_config!(ContentSecurityPolicyConfig.new(default_src: "steve"))
79
79
  end.to raise_error(ContentSecurityPolicyConfigError)
80
80
  end
81
81
 
82
82
  it "allows nil values" do
83
83
  expect do
84
- CSP.validate_config!(default_src: %w('self'), script_src: ["https:", nil])
84
+ ContentSecurityPolicy.validate_config!(ContentSecurityPolicyConfig.new(default_src: %w('self'), script_src: ["https:", nil]))
85
85
  end.to_not raise_error
86
86
  end
87
87
 
88
88
  it "rejects unknown directives / config" do
89
89
  expect do
90
- CSP.validate_config!(default_src: %w('self'), default_src_totally_mispelled: "steve")
90
+ ContentSecurityPolicy.validate_config!(ContentSecurityPolicyConfig.new(default_src: %w('self'), default_src_totally_mispelled: "steve"))
91
91
  end.to raise_error(ContentSecurityPolicyConfigError)
92
92
  end
93
93
 
94
94
  # this is mostly to ensure people don't use the antiquated shorthands common in other configs
95
95
  it "performs light validation on source lists" do
96
96
  expect do
97
- CSP.validate_config!(default_src: %w(self none inline eval))
97
+ ContentSecurityPolicy.validate_config!(ContentSecurityPolicyConfig.new(default_src: %w(self none inline eval)))
98
98
  end.to raise_error(ContentSecurityPolicyConfigError)
99
99
  end
100
100
  end
101
101
 
102
102
  describe "#combine_policies" do
103
103
  it "combines the default-src value with the override if the directive was unconfigured" do
104
- combined_config = CSP.combine_policies(Configuration.default.csp, script_src: %w(anothercdn.com))
104
+ Configuration.default do |config|
105
+ config.csp = {
106
+ default_src: %w(https:)
107
+ }
108
+ end
109
+ combined_config = ContentSecurityPolicy.combine_policies(Configuration.get.csp.to_h, script_src: %w(anothercdn.com))
105
110
  csp = ContentSecurityPolicy.new(combined_config)
106
- expect(csp.name).to eq(CSP::HEADER_NAME)
111
+ expect(csp.name).to eq(ContentSecurityPolicyConfig::HEADER_NAME)
107
112
  expect(csp.value).to eq("default-src https:; script-src https: anothercdn.com")
108
113
  end
109
114
 
@@ -115,7 +120,7 @@ module SecureHeaders
115
120
  }.freeze
116
121
  end
117
122
  report_uri = "https://report-uri.io/asdf"
118
- combined_config = CSP.combine_policies(Configuration.get.csp, report_uri: [report_uri])
123
+ combined_config = ContentSecurityPolicy.combine_policies(Configuration.get.csp.to_h, report_uri: [report_uri])
119
124
  csp = ContentSecurityPolicy.new(combined_config, USER_AGENTS[:firefox])
120
125
  expect(csp.value).to include("report-uri #{report_uri}")
121
126
  end
@@ -127,12 +132,12 @@ module SecureHeaders
127
132
  report_only: false
128
133
  }.freeze
129
134
  end
130
- non_default_source_additions = CSP::NON_FETCH_SOURCES.each_with_object({}) do |directive, hash|
135
+ non_default_source_additions = ContentSecurityPolicy::NON_FETCH_SOURCES.each_with_object({}) do |directive, hash|
131
136
  hash[directive] = %w("http://example.org)
132
137
  end
133
- combined_config = CSP.combine_policies(Configuration.get.csp, non_default_source_additions)
138
+ combined_config = ContentSecurityPolicy.combine_policies(Configuration.get.csp.to_h, non_default_source_additions)
134
139
 
135
- CSP::NON_FETCH_SOURCES.each do |directive|
140
+ ContentSecurityPolicy::NON_FETCH_SOURCES.each do |directive|
136
141
  expect(combined_config[directive]).to eq(%w("http://example.org))
137
142
  end
138
143
 
@@ -146,9 +151,9 @@ module SecureHeaders
146
151
  report_only: false
147
152
  }
148
153
  end
149
- combined_config = CSP.combine_policies(Configuration.get.csp, report_only: true)
154
+ combined_config = ContentSecurityPolicy.combine_policies(Configuration.get.csp.to_h, report_only: true)
150
155
  csp = ContentSecurityPolicy.new(combined_config, USER_AGENTS[:firefox])
151
- expect(csp.name).to eq(CSP::REPORT_ONLY)
156
+ expect(csp.name).to eq(ContentSecurityPolicyReportOnlyConfig::HEADER_NAME)
152
157
  end
153
158
 
154
159
  it "overrides the :block_all_mixed_content flag" do
@@ -158,7 +163,7 @@ module SecureHeaders
158
163
  block_all_mixed_content: false
159
164
  }
160
165
  end
161
- combined_config = CSP.combine_policies(Configuration.get.csp, block_all_mixed_content: true)
166
+ combined_config = ContentSecurityPolicy.combine_policies(Configuration.get.csp.to_h, block_all_mixed_content: true)
162
167
  csp = ContentSecurityPolicy.new(combined_config)
163
168
  expect(csp.value).to eq("default-src https:; block-all-mixed-content")
164
169
  end
@@ -168,23 +173,9 @@ module SecureHeaders
168
173
  config.csp = OPT_OUT
169
174
  end
170
175
  expect do
171
- CSP.combine_policies(Configuration.get.csp, script_src: %w(anothercdn.com))
176
+ ContentSecurityPolicy.combine_policies(Configuration.get.csp.to_h, script_src: %w(anothercdn.com))
172
177
  end.to raise_error(ContentSecurityPolicyConfigError)
173
178
  end
174
179
  end
175
-
176
- describe "#idempotent_additions?" do
177
- specify { expect(ContentSecurityPolicy.idempotent_additions?(OPT_OUT, script_src: %w(b.com))).to be false }
178
- specify { expect(ContentSecurityPolicy.idempotent_additions?({script_src: %w(a.com b.com)}, script_src: %w(c.com))).to be false }
179
- specify { expect(ContentSecurityPolicy.idempotent_additions?({script_src: %w(a.com b.com)}, style_src: %w(b.com))).to be false }
180
- specify { expect(ContentSecurityPolicy.idempotent_additions?({script_src: %w(a.com b.com)}, script_src: %w(a.com b.com c.com))).to be false }
181
-
182
- specify { expect(ContentSecurityPolicy.idempotent_additions?({script_src: %w(a.com b.com)}, script_src: %w(b.com))).to be true }
183
- specify { expect(ContentSecurityPolicy.idempotent_additions?({script_src: %w(a.com b.com)}, script_src: %w(b.com a.com))).to be true }
184
- specify { expect(ContentSecurityPolicy.idempotent_additions?({script_src: %w(a.com b.com)}, script_src: %w())).to be true }
185
- specify { expect(ContentSecurityPolicy.idempotent_additions?({script_src: %w(a.com b.com)}, script_src: [nil])).to be true }
186
- specify { expect(ContentSecurityPolicy.idempotent_additions?({script_src: %w(a.com b.com)}, style_src: [nil])).to be true }
187
- specify { expect(ContentSecurityPolicy.idempotent_additions?({script_src: %w(a.com b.com)}, style_src: nil)).to be true }
188
- end
189
180
  end
190
181
  end
@@ -18,6 +18,24 @@ module SecureHeaders
18
18
  end.not_to raise_error
19
19
  end
20
20
 
21
+ it "accepts 'same-origin'" do
22
+ expect do
23
+ ReferrerPolicy.validate_config!("same-origin")
24
+ end.not_to raise_error
25
+ end
26
+
27
+ it "accepts 'strict-origin'" do
28
+ expect do
29
+ ReferrerPolicy.validate_config!("strict-origin")
30
+ end.not_to raise_error
31
+ end
32
+
33
+ it "accepts 'strict-origin-when-cross-origin'" do
34
+ expect do
35
+ ReferrerPolicy.validate_config!("strict-origin-when-cross-origin")
36
+ end.not_to raise_error
37
+ end
38
+
21
39
  it "accepts 'origin'" do
22
40
  expect do
23
41
  ReferrerPolicy.validate_config!("origin")