secure_headers 3.1.2 → 3.3.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.
@@ -1,6 +1,6 @@
1
1
  module SecureHeaders
2
2
  class Middleware
3
- SECURE_COOKIE_REGEXP = /;\s*secure\s*(;|$)/i.freeze
3
+ HPKP_SAME_HOST_WARNING = "[WARNING] HPKP report host should not be the same as the request host. See https://github.com/twitter/secureheaders/issues/166"
4
4
 
5
5
  def initialize(app)
6
6
  @app = app
@@ -12,7 +12,11 @@ module SecureHeaders
12
12
  status, headers, response = @app.call(env)
13
13
 
14
14
  config = SecureHeaders.config_for(req)
15
- flag_cookies_as_secure!(headers) if config.secure_cookies
15
+ if config.hpkp_report_host == req.host
16
+ Kernel.warn(HPKP_SAME_HOST_WARNING)
17
+ end
18
+
19
+ flag_cookies!(headers, override_secure(env, config.cookies)) if config.cookies
16
20
  headers.merge!(SecureHeaders.header_hash_for(req))
17
21
  [status, headers, response]
18
22
  end
@@ -20,19 +24,35 @@ module SecureHeaders
20
24
  private
21
25
 
22
26
  # inspired by https://github.com/tobmatth/rack-ssl-enforcer/blob/6c014/lib/rack/ssl-enforcer.rb#L183-L194
23
- def flag_cookies_as_secure!(headers)
27
+ def flag_cookies!(headers, config)
24
28
  if cookies = headers['Set-Cookie']
25
29
  # Support Rails 2.3 / Rack 1.1 arrays as headers
26
30
  cookies = cookies.split("\n") unless cookies.is_a?(Array)
27
31
 
28
32
  headers['Set-Cookie'] = cookies.map do |cookie|
29
- if cookie !~ SECURE_COOKIE_REGEXP
30
- "#{cookie}; secure"
31
- else
32
- cookie
33
- end
33
+ SecureHeaders::Cookie.new(cookie, config).to_s
34
34
  end.join("\n")
35
35
  end
36
36
  end
37
+
38
+ # disable Secure cookies for non-https requests
39
+ def override_secure(env, config = {})
40
+ if scheme(env) != 'https'
41
+ config.merge!(secure: false)
42
+ end
43
+
44
+ config
45
+ end
46
+
47
+ # derived from https://github.com/tobmatth/rack-ssl-enforcer/blob/6c014/lib/rack/ssl-enforcer.rb#L119
48
+ def scheme(env)
49
+ if env['HTTPS'] == 'on' || env['HTTP_X_SSL_REQUEST'] == 'on'
50
+ 'https'
51
+ elsif env['HTTP_X_FORWARDED_PROTO']
52
+ env['HTTP_X_FORWARDED_PROTO'].split(',')[0]
53
+ else
54
+ env['rack.url_scheme']
55
+ end
56
+ end
37
57
  end
38
58
  end
@@ -7,12 +7,16 @@ if defined?(Rails::Railtie)
7
7
  'X-Permitted-Cross-Domain-Policies', 'X-Download-Options',
8
8
  'X-Content-Type-Options', 'Strict-Transport-Security',
9
9
  'Content-Security-Policy', 'Content-Security-Policy-Report-Only',
10
- 'Public-Key-Pins', 'Public-Key-Pins-Report-Only']
10
+ 'Public-Key-Pins', 'Public-Key-Pins-Report-Only', 'Referrer-Policy']
11
11
 
12
12
  initializer "secure_headers.middleware" do
13
13
  Rails.application.config.middleware.insert_before 0, SecureHeaders::Middleware
14
14
  end
15
15
 
16
+ rake_tasks do
17
+ load File.expand_path(File.join('..', '..', 'lib', 'tasks', 'tasks.rake'), File.dirname(__FILE__))
18
+ end
19
+
16
20
  initializer "secure_headers.action_controller" do
17
21
  ActiveSupport.on_load(:action_controller) do
18
22
  include SecureHeaders
@@ -0,0 +1,94 @@
1
+ module SecureHeaders
2
+ class CookiesConfig
3
+
4
+ attr_reader :config
5
+
6
+ def initialize(config)
7
+ @config = config
8
+ end
9
+
10
+ def validate!
11
+ return if config.nil? || config == SecureHeaders::OPT_OUT
12
+
13
+ validate_config!
14
+ validate_secure_config! if config[:secure]
15
+ validate_httponly_config! if config[:httponly]
16
+ validate_samesite_config! if config[:samesite]
17
+ end
18
+
19
+ private
20
+
21
+ def validate_config!
22
+ raise CookiesConfigError.new("config must be a hash.") unless is_hash?(config)
23
+ end
24
+
25
+ def validate_secure_config!
26
+ validate_hash_or_boolean!(:secure)
27
+ validate_exclusive_use_of_hash_constraints!(config[:secure], :secure)
28
+ end
29
+
30
+ def validate_httponly_config!
31
+ validate_hash_or_boolean!(:httponly)
32
+ validate_exclusive_use_of_hash_constraints!(config[:httponly], :httponly)
33
+ end
34
+
35
+ def validate_samesite_config!
36
+ raise CookiesConfigError.new("samesite cookie config must be a hash") unless is_hash?(config[:samesite])
37
+
38
+ validate_samesite_boolean_config!
39
+ validate_samesite_hash_config!
40
+ end
41
+
42
+ # when configuring with booleans, only one enforcement is permitted
43
+ def validate_samesite_boolean_config!
44
+ if config[:samesite].key?(:lax) && config[:samesite][:lax].is_a?(TrueClass) && config[:samesite].key?(:strict)
45
+ raise CookiesConfigError.new("samesite cookie config is invalid, combination use of booleans and Hash to configure lax and strict enforcement is not permitted.")
46
+ elsif config[:samesite].key?(:strict) && config[:samesite][:strict].is_a?(TrueClass) && config[:samesite].key?(:lax)
47
+ raise CookiesConfigError.new("samesite cookie config is invalid, combination use of booleans and Hash to configure lax and strict enforcement is not permitted.")
48
+ end
49
+ end
50
+
51
+ def validate_samesite_hash_config!
52
+ # validate Hash-based samesite configuration
53
+ if is_hash?(config[:samesite][:lax])
54
+ validate_exclusive_use_of_hash_constraints!(config[:samesite][:lax], 'samesite lax')
55
+
56
+ if is_hash?(config[:samesite][:strict])
57
+ validate_exclusive_use_of_hash_constraints!(config[:samesite][:strict], 'samesite strict')
58
+ validate_exclusive_use_of_samesite_enforcement!(:only)
59
+ validate_exclusive_use_of_samesite_enforcement!(:except)
60
+ end
61
+ end
62
+ end
63
+
64
+ def validate_hash_or_boolean!(attribute)
65
+ if !(is_hash?(config[attribute]) || is_boolean?(config[attribute]))
66
+ raise CookiesConfigError.new("#{attribute} cookie config must be a hash or boolean")
67
+ end
68
+ end
69
+
70
+ # validate exclusive use of only or except but not both at the same time
71
+ def validate_exclusive_use_of_hash_constraints!(conf, attribute)
72
+ return unless is_hash?(conf)
73
+
74
+ if conf.key?(:only) && conf.key?(:except)
75
+ raise CookiesConfigError.new("#{attribute} cookie config is invalid, simultaneous use of conditional arguments `only` and `except` is not permitted.")
76
+ end
77
+ end
78
+
79
+ # validate exclusivity of only and except members within strict and lax
80
+ def validate_exclusive_use_of_samesite_enforcement!(attribute)
81
+ if (intersection = (config[:samesite][:lax].fetch(attribute, []) & config[:samesite][:strict].fetch(attribute, []))).any?
82
+ raise CookiesConfigError.new("samesite cookie config is invalid, cookie(s) #{intersection.join(', ')} cannot be enforced as lax and strict")
83
+ end
84
+ end
85
+
86
+ def is_hash?(obj)
87
+ obj && obj.is_a?(Hash)
88
+ end
89
+
90
+ def is_boolean?(obj)
91
+ obj && (obj.is_a?(TrueClass) || obj.is_a?(FalseClass))
92
+ end
93
+ end
94
+ end
@@ -1,5 +1,10 @@
1
1
  module SecureHeaders
2
2
  module ViewHelpers
3
+ include SecureHeaders::HashHelper
4
+ SECURE_HEADERS_RAKE_TASK = "rake secure_headers:generate_hashes"
5
+
6
+ class UnexpectedHashedScriptException < StandardError; end
7
+
3
8
  # Public: create a style tag using the content security policy nonce.
4
9
  # Instructs secure_headers to append a nonce to style/script-src directives.
5
10
  #
@@ -29,8 +34,67 @@ module SecureHeaders
29
34
  end
30
35
  end
31
36
 
37
+ ##
38
+ # Checks to see if the hashed code is expected and adds the hash source
39
+ # value to the current CSP.
40
+ #
41
+ # By default, in development/test/etc. an exception will be raised.
42
+ def hashed_javascript_tag(raise_error_on_unrecognized_hash = nil, &block)
43
+ hashed_tag(
44
+ :script,
45
+ :script_src,
46
+ Configuration.instance_variable_get(:@script_hashes),
47
+ raise_error_on_unrecognized_hash,
48
+ block
49
+ )
50
+ end
51
+
52
+ def hashed_style_tag(raise_error_on_unrecognized_hash = nil, &block)
53
+ hashed_tag(
54
+ :style,
55
+ :style_src,
56
+ Configuration.instance_variable_get(:@style_hashes),
57
+ raise_error_on_unrecognized_hash,
58
+ block
59
+ )
60
+ end
61
+
32
62
  private
33
63
 
64
+ def hashed_tag(type, directive, hashes, raise_error_on_unrecognized_hash, block)
65
+ if raise_error_on_unrecognized_hash.nil?
66
+ raise_error_on_unrecognized_hash = ENV["RAILS_ENV"] != "production"
67
+ end
68
+
69
+ content = capture(&block)
70
+ file_path = File.join('app', 'views', self.instance_variable_get(:@virtual_path) + '.html.erb')
71
+
72
+ if raise_error_on_unrecognized_hash
73
+ hash_value = hash_source(content)
74
+ message = unexpected_hash_error_message(file_path, content, hash_value)
75
+
76
+ if hashes.nil? || hashes[file_path].nil? || !hashes[file_path].include?(hash_value)
77
+ raise UnexpectedHashedScriptException.new(message)
78
+ end
79
+ end
80
+
81
+ SecureHeaders.append_content_security_policy_directives(request, directive => hashes[file_path])
82
+
83
+ content_tag type, content
84
+ end
85
+
86
+ def unexpected_hash_error_message(file_path, content, hash_value)
87
+ <<-EOF
88
+ \n\n*** WARNING: Unrecognized hash in #{file_path}!!! Value: #{hash_value} ***
89
+ #{content}
90
+ *** Run #{SECURE_HEADERS_RAKE_TASK} or add the following to config/script_hashes.yml:***
91
+ #{file_path}:
92
+ - #{hash_value}\n\n
93
+ NOTE: dynamic javascript is not supported using script hash integration
94
+ on purpose. It defeats the point of using it in the first place.
95
+ EOF
96
+ end
97
+
34
98
  def nonced_tag(type, content_or_options, block)
35
99
  options = {}
36
100
  content = if block
@@ -1,4 +1,6 @@
1
1
  require "secure_headers/configuration"
2
+ require "secure_headers/hash_helper"
3
+ require "secure_headers/headers/cookie"
2
4
  require "secure_headers/headers/public_key_pins"
3
5
  require "secure_headers/headers/content_security_policy"
4
6
  require "secure_headers/headers/x_frame_options"
@@ -7,6 +9,7 @@ require "secure_headers/headers/x_xss_protection"
7
9
  require "secure_headers/headers/x_content_type_options"
8
10
  require "secure_headers/headers/x_download_options"
9
11
  require "secure_headers/headers/x_permitted_cross_domain_policies"
12
+ require "secure_headers/headers/referrer_policy"
10
13
  require "secure_headers/middleware"
11
14
  require "secure_headers/railtie"
12
15
  require "secure_headers/view_helper"
@@ -25,6 +28,7 @@ module SecureHeaders
25
28
  ContentSecurityPolicy,
26
29
  StrictTransportSecurity,
27
30
  PublicKeyPins,
31
+ ReferrerPolicy,
28
32
  XContentTypeOptions,
29
33
  XDownloadOptions,
30
34
  XFrameOptions,
@@ -0,0 +1,81 @@
1
+ INLINE_SCRIPT_REGEX = /(<script(\s*(?!src)([\w\-])+=([\"\'])[^\"\']+\4)*\s*>)(.*?)<\/script>/mx
2
+ INLINE_STYLE_REGEX = /(<style[^>]*>)(.*?)<\/style>/mx
3
+ INLINE_HASH_SCRIPT_HELPER_REGEX = /<%=\s?hashed_javascript_tag(.*?)\s+do\s?%>(.*?)<%\s*end\s*%>/mx
4
+ INLINE_HASH_STYLE_HELPER_REGEX = /<%=\s?hashed_style_tag(.*?)\s+do\s?%>(.*?)<%\s*end\s*%>/mx
5
+
6
+ namespace :secure_headers do
7
+ include SecureHeaders::HashHelper
8
+
9
+ def is_erb?(filename)
10
+ filename =~ /\.erb\Z/
11
+ end
12
+
13
+ def is_mustache?(filename)
14
+ filename =~ /\.mustache\Z/
15
+ end
16
+
17
+ def dynamic_content?(filename, inline_script)
18
+ (is_mustache?(filename) && inline_script =~ /\{\{.*\}\}/) ||
19
+ (is_erb?(filename) && inline_script =~ /<%.*%>/)
20
+ end
21
+
22
+ def find_inline_content(filename, regex, hashes)
23
+ file = File.read(filename)
24
+ file.scan(regex) do # TODO don't use gsub
25
+ inline_script = Regexp.last_match.captures.last
26
+ if dynamic_content?(filename, inline_script)
27
+ puts "Looks like there's some dynamic content inside of a tag :-/"
28
+ puts "That pretty much means the hash value will never match."
29
+ puts "Code: " + inline_script
30
+ puts "=" * 20
31
+ end
32
+
33
+ hashes << hash_source(inline_script)
34
+ end
35
+ end
36
+
37
+ def generate_inline_script_hashes(filename)
38
+ hashes = []
39
+
40
+ [INLINE_SCRIPT_REGEX, INLINE_HASH_SCRIPT_HELPER_REGEX].each do |regex|
41
+ find_inline_content(filename, regex, hashes)
42
+ end
43
+
44
+ hashes
45
+ end
46
+
47
+ def generate_inline_style_hashes(filename)
48
+ hashes = []
49
+
50
+ [INLINE_STYLE_REGEX, INLINE_HASH_STYLE_HELPER_REGEX].each do |regex|
51
+ find_inline_content(filename, regex, hashes)
52
+ end
53
+
54
+ hashes
55
+ end
56
+
57
+ task :generate_hashes do |t, args|
58
+ script_hashes = {
59
+ "scripts" => {},
60
+ "styles" => {}
61
+ }
62
+
63
+ Dir.glob("app/{views,templates}/**/*.{erb,mustache}") do |filename|
64
+ hashes = generate_inline_script_hashes(filename)
65
+ if hashes.any?
66
+ script_hashes["scripts"][filename] = hashes
67
+ end
68
+
69
+ hashes = generate_inline_style_hashes(filename)
70
+ if hashes.any?
71
+ script_hashes["styles"][filename] = hashes
72
+ end
73
+ end
74
+
75
+ File.open(SecureHeaders::Configuration::HASH_CONFIG_FILE, 'w') do |file|
76
+ file.write(script_hashes.to_yaml)
77
+ end
78
+
79
+ puts "Script hashes from " + script_hashes.keys.size.to_s + " files added to #{SecureHeaders::Configuration::HASH_CONFIG_FILE}"
80
+ end
81
+ 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.1.2"
4
+ gem.version = "3.3.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,7 +36,7 @@ module SecureHeaders
36
36
 
37
37
  config = Configuration.get(:test_override)
38
38
  noop = Configuration.get(Configuration::NOOP_CONFIGURATION)
39
- [:csp, :dynamic_csp, :secure_cookies].each do |key|
39
+ [:csp, :dynamic_csp, :cookies].each do |key|
40
40
  expect(config.send(key)).to eq(noop.send(key)), "Value not copied: #{key}."
41
41
  end
42
42
  end
@@ -82,5 +82,13 @@ module SecureHeaders
82
82
  override_config = Configuration.get(:second_override)
83
83
  expect(override_config.csp).to eq(default_src: %w('self'), script_src: %w(example.org))
84
84
  end
85
+
86
+ 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
92
+ end
85
93
  end
86
94
  end
@@ -104,6 +104,11 @@ module SecureHeaders
104
104
  expect(policy.value).to eq("default-src 'self'; base-uri 'self'; connect-src 'self'; font-src 'self'; form-action 'self'; frame-ancestors 'self'; frame-src 'self'; img-src 'self'; media-src 'self'; object-src 'self'; sandbox 'self'; script-src 'self' 'nonce-123456'; style-src 'self'; upgrade-insecure-requests; report-uri 'self'")
105
105
  end
106
106
 
107
+ it "adds 'unsafe-inline', filters base-uri, blocked-all-mixed-content, upgrade-insecure-requests, child-src, form-action, frame-ancestors, nonce sources, hash sources, and plugin-types for Edge" do
108
+ policy = ContentSecurityPolicy.new(complex_opts, USER_AGENTS[:edge])
109
+ expect(policy.value).to eq("default-src 'self'; connect-src 'self'; font-src 'self'; frame-src 'self'; img-src 'self'; media-src 'self'; object-src 'self'; sandbox 'self'; script-src 'self' 'unsafe-inline'; style-src 'self'; report-uri 'self'")
110
+ end
111
+
107
112
  it "adds 'unsafe-inline', filters base-uri, blocked-all-mixed-content, upgrade-insecure-requests, child-src, form-action, frame-ancestors, nonce sources, hash sources, and plugin-types for safari" do
108
113
  policy = ContentSecurityPolicy.new(complex_opts, USER_AGENTS[:safari6])
109
114
  expect(policy.value).to eq("default-src 'self'; connect-src 'self'; font-src 'self'; frame-src 'self'; img-src 'self'; media-src 'self'; object-src 'self'; sandbox 'self'; script-src 'self' 'unsafe-inline'; style-src 'self'; report-uri 'self'")
@@ -0,0 +1,164 @@
1
+ require 'spec_helper'
2
+
3
+ module SecureHeaders
4
+ describe Cookie do
5
+ let(:raw_cookie) { "_session=thisisatest" }
6
+
7
+ it "does not tamper with cookies when unconfigured" do
8
+ cookie = Cookie.new(raw_cookie, {})
9
+ expect(cookie.to_s).to eq(raw_cookie)
10
+ end
11
+
12
+ it "preserves existing attributes" do
13
+ cookie = Cookie.new("_session=thisisatest; secure", secure: true)
14
+ expect(cookie.to_s).to eq("_session=thisisatest; secure")
15
+ end
16
+
17
+ it "prevents duplicate flagging of attributes" do
18
+ cookie = Cookie.new("_session=thisisatest; secure", secure: true)
19
+ expect(cookie.to_s.scan(/secure/i).count).to eq(1)
20
+ end
21
+
22
+ context "Secure cookies" do
23
+ context "when configured with a boolean" do
24
+ it "flags cookies as Secure" do
25
+ cookie = Cookie.new(raw_cookie, secure: true)
26
+ expect(cookie.to_s).to eq("_session=thisisatest; secure")
27
+ end
28
+ end
29
+
30
+ context "when configured with a Hash" do
31
+ it "flags cookies as Secure when whitelisted" do
32
+ cookie = Cookie.new(raw_cookie, secure: { only: ["_session"]})
33
+ expect(cookie.to_s).to eq("_session=thisisatest; secure")
34
+ end
35
+
36
+ it "does not flag cookies as Secure when excluded" do
37
+ cookie = Cookie.new(raw_cookie, secure: { except: ["_session"] })
38
+ expect(cookie.to_s).to eq("_session=thisisatest")
39
+ end
40
+ end
41
+ end
42
+
43
+ context "HttpOnly cookies" do
44
+ context "when configured with a boolean" do
45
+ it "flags cookies as HttpOnly" do
46
+ cookie = Cookie.new(raw_cookie, httponly: true)
47
+ expect(cookie.to_s).to eq("_session=thisisatest; HttpOnly")
48
+ end
49
+ end
50
+
51
+ context "when configured with a Hash" do
52
+ it "flags cookies as HttpOnly when whitelisted" do
53
+ cookie = Cookie.new(raw_cookie, httponly: { only: ["_session"]})
54
+ expect(cookie.to_s).to eq("_session=thisisatest; HttpOnly")
55
+ end
56
+
57
+ it "does not flag cookies as HttpOnly when excluded" do
58
+ cookie = Cookie.new(raw_cookie, httponly: { except: ["_session"] })
59
+ expect(cookie.to_s).to eq("_session=thisisatest")
60
+ end
61
+ end
62
+ end
63
+
64
+ context "SameSite cookies" do
65
+ it "flags SameSite=Lax" do
66
+ cookie = Cookie.new(raw_cookie, samesite: { lax: { only: ["_session"] } })
67
+ expect(cookie.to_s).to eq("_session=thisisatest; SameSite=Lax")
68
+ end
69
+
70
+ it "flags SameSite=Lax when configured with a boolean" do
71
+ cookie = Cookie.new(raw_cookie, samesite: { lax: true})
72
+ expect(cookie.to_s).to eq("_session=thisisatest; SameSite=Lax")
73
+ end
74
+
75
+ it "does not flag cookies as SameSite=Lax when excluded" do
76
+ cookie = Cookie.new(raw_cookie, samesite: { lax: { except: ["_session"] } })
77
+ expect(cookie.to_s).to eq("_session=thisisatest")
78
+ end
79
+
80
+ it "flags SameSite=Strict" do
81
+ cookie = Cookie.new(raw_cookie, samesite: { strict: { only: ["_session"] } })
82
+ expect(cookie.to_s).to eq("_session=thisisatest; SameSite=Strict")
83
+ end
84
+
85
+ it "does not flag cookies as SameSite=Strict when excluded" do
86
+ cookie = Cookie.new(raw_cookie, samesite: { strict: { except: ["_session"] } })
87
+ expect(cookie.to_s).to eq("_session=thisisatest")
88
+ end
89
+
90
+ it "flags SameSite=Strict when configured with a boolean" do
91
+ cookie = Cookie.new(raw_cookie, samesite: { strict: true})
92
+ expect(cookie.to_s).to eq("_session=thisisatest; SameSite=Strict")
93
+ end
94
+
95
+ it "flags properly when both lax and strict are configured" do
96
+ raw_cookie = "_session=thisisatest"
97
+ cookie = Cookie.new(raw_cookie, samesite: { strict: { only: ["_session"] }, lax: { only: ["_additional_session"] } })
98
+ expect(cookie.to_s).to eq("_session=thisisatest; SameSite=Strict")
99
+ end
100
+
101
+ it "ignores configuration if the cookie is already flagged" do
102
+ raw_cookie = "_session=thisisatest; SameSite=Strict"
103
+ cookie = Cookie.new(raw_cookie, samesite: { lax: true })
104
+ expect(cookie.to_s).to eq(raw_cookie)
105
+ end
106
+ end
107
+ end
108
+
109
+ context "with an invalid configuration" do
110
+ it "raises an exception when not configured with a Hash" do
111
+ expect do
112
+ Cookie.validate_config!("configuration")
113
+ end.to raise_error(CookiesConfigError)
114
+ end
115
+
116
+ it "raises an exception when configured without a boolean/Hash" do
117
+ expect do
118
+ Cookie.validate_config!(secure: "true")
119
+ end.to raise_error(CookiesConfigError)
120
+ end
121
+
122
+ it "raises an exception when both only and except filters are provided" do
123
+ expect do
124
+ Cookie.validate_config!(secure: { only: [], except: [] })
125
+ end.to raise_error(CookiesConfigError)
126
+ end
127
+
128
+ it "raises an exception when SameSite is not configured with a Hash" do
129
+ expect do
130
+ Cookie.validate_config!(samesite: true)
131
+ end.to raise_error(CookiesConfigError)
132
+ end
133
+
134
+ it "raises an exception when SameSite lax and strict enforcement modes are configured with booleans" do
135
+ expect do
136
+ Cookie.validate_config!(samesite: { lax: true, strict: true})
137
+ end.to raise_error(CookiesConfigError)
138
+ end
139
+
140
+ it "raises an exception when SameSite lax and strict enforcement modes are configured with booleans" do
141
+ expect do
142
+ Cookie.validate_config!(samesite: { lax: true, strict: { only: ["_anything"] } })
143
+ end.to raise_error(CookiesConfigError)
144
+ end
145
+
146
+ it "raises an exception when both only and except filters are provided to SameSite configurations" do
147
+ expect do
148
+ Cookie.validate_config!(samesite: { lax: { only: ["_anything"], except: ["_anythingelse"] } })
149
+ end.to raise_error(CookiesConfigError)
150
+ end
151
+
152
+ it "raises an exception when both lax and strict only filters are provided to SameSite configurations" do
153
+ expect do
154
+ Cookie.validate_config!(samesite: { lax: { only: ["_anything"] }, strict: { only: ["_anything"] } })
155
+ end.to raise_error(CookiesConfigError)
156
+ end
157
+
158
+ it "raises an exception when both lax and strict only filters are provided to SameSite configurations" do
159
+ expect do
160
+ Cookie.validate_config!(samesite: { lax: { except: ["_anything"] }, strict: { except: ["_anything"] } })
161
+ end.to raise_error(CookiesConfigError)
162
+ end
163
+ end
164
+ end
@@ -0,0 +1,54 @@
1
+ require 'spec_helper'
2
+
3
+ module SecureHeaders
4
+ describe ReferrerPolicy do
5
+ specify { expect(ReferrerPolicy.make_header).to eq([ReferrerPolicy::HEADER_NAME, "origin-when-cross-origin"]) }
6
+ specify { expect(ReferrerPolicy.make_header('no-referrer')).to eq([ReferrerPolicy::HEADER_NAME, "no-referrer"]) }
7
+
8
+ context "valid configuration values" do
9
+ it "accepts 'no-referrer'" do
10
+ expect do
11
+ ReferrerPolicy.validate_config!("no-referrer")
12
+ end.not_to raise_error
13
+ end
14
+
15
+ it "accepts 'no-referrer-when-downgrade'" do
16
+ expect do
17
+ ReferrerPolicy.validate_config!("no-referrer-when-downgrade")
18
+ end.not_to raise_error
19
+ end
20
+
21
+ it "accepts 'origin'" do
22
+ expect do
23
+ ReferrerPolicy.validate_config!("origin")
24
+ end.not_to raise_error
25
+ end
26
+
27
+ it "accepts 'origin-when-cross-origin'" do
28
+ expect do
29
+ ReferrerPolicy.validate_config!("origin-when-cross-origin")
30
+ end.not_to raise_error
31
+ end
32
+
33
+ it "accepts 'unsafe-url'" do
34
+ expect do
35
+ ReferrerPolicy.validate_config!("unsafe-url")
36
+ end.not_to raise_error
37
+ end
38
+
39
+ it "accepts nil" do
40
+ expect do
41
+ ReferrerPolicy.validate_config!(nil)
42
+ end.not_to raise_error
43
+ end
44
+ end
45
+
46
+ context 'invlaid configuration values' do
47
+ it "doesn't accept invalid values" do
48
+ expect do
49
+ ReferrerPolicy.validate_config!("open")
50
+ end.to raise_error(ReferrerPolicyConfigError)
51
+ end
52
+ end
53
+ end
54
+ end