secure_headers 2.3.0 → 3.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 (127) hide show
  1. checksums.yaml +4 -4
  2. data/.rspec +1 -0
  3. data/.ruby-version +1 -1
  4. data/.travis.yml +3 -0
  5. data/CHANGELOG.md +205 -0
  6. data/Gemfile +9 -10
  7. data/Guardfile +12 -0
  8. data/README.md +169 -301
  9. data/Rakefile +2 -36
  10. data/lib/secure_headers/configuration.rb +189 -0
  11. data/lib/secure_headers/headers/content_security_policy.rb +362 -213
  12. data/lib/secure_headers/headers/public_key_pins.rb +43 -58
  13. data/lib/secure_headers/headers/strict_transport_security.rb +21 -48
  14. data/lib/secure_headers/headers/x_content_type_options.rb +18 -32
  15. data/lib/secure_headers/headers/x_download_options.rb +18 -32
  16. data/lib/secure_headers/headers/x_frame_options.rb +24 -33
  17. data/lib/secure_headers/headers/x_permitted_cross_domain_policies.rb +19 -33
  18. data/lib/secure_headers/headers/x_xss_protection.rb +17 -47
  19. data/lib/secure_headers/middleware.rb +15 -0
  20. data/lib/secure_headers/padrino.rb +1 -2
  21. data/lib/secure_headers/railtie.rb +9 -6
  22. data/lib/secure_headers/view_helper.rb +27 -44
  23. data/lib/secure_headers.rb +229 -162
  24. data/secure_headers.gemspec +7 -11
  25. data/spec/lib/secure_headers/configuration_spec.rb +80 -0
  26. data/spec/lib/secure_headers/headers/content_security_policy_spec.rb +175 -262
  27. data/spec/lib/secure_headers/headers/public_key_pins_spec.rb +17 -17
  28. data/spec/lib/secure_headers/headers/strict_transport_security_spec.rb +11 -43
  29. data/spec/lib/secure_headers/headers/x_content_type_options_spec.rb +13 -18
  30. data/spec/lib/secure_headers/headers/x_download_options_spec.rb +13 -17
  31. data/spec/lib/secure_headers/headers/x_frame_options_spec.rb +15 -17
  32. data/spec/lib/secure_headers/headers/x_permitted_cross_domain_policies_spec.rb +22 -39
  33. data/spec/lib/secure_headers/headers/x_xss_protection_spec.rb +22 -30
  34. data/spec/lib/secure_headers/middleware_spec.rb +40 -0
  35. data/spec/lib/secure_headers_spec.rb +202 -326
  36. data/spec/spec_helper.rb +32 -28
  37. data/upgrading-to-3-0.md +37 -0
  38. metadata +14 -94
  39. data/fixtures/rails_3_2_12/.rspec +0 -1
  40. data/fixtures/rails_3_2_12/Gemfile +0 -6
  41. data/fixtures/rails_3_2_12/README.rdoc +0 -261
  42. data/fixtures/rails_3_2_12/Rakefile +0 -7
  43. data/fixtures/rails_3_2_12/app/controllers/application_controller.rb +0 -4
  44. data/fixtures/rails_3_2_12/app/controllers/other_things_controller.rb +0 -5
  45. data/fixtures/rails_3_2_12/app/controllers/things_controller.rb +0 -5
  46. data/fixtures/rails_3_2_12/app/models/.gitkeep +0 -0
  47. data/fixtures/rails_3_2_12/app/views/layouts/application.html.erb +0 -11
  48. data/fixtures/rails_3_2_12/app/views/other_things/index.html.erb +0 -2
  49. data/fixtures/rails_3_2_12/app/views/things/index.html.erb +0 -1
  50. data/fixtures/rails_3_2_12/config/application.rb +0 -14
  51. data/fixtures/rails_3_2_12/config/boot.rb +0 -6
  52. data/fixtures/rails_3_2_12/config/environment.rb +0 -5
  53. data/fixtures/rails_3_2_12/config/environments/test.rb +0 -37
  54. data/fixtures/rails_3_2_12/config/initializers/secure_headers.rb +0 -17
  55. data/fixtures/rails_3_2_12/config/routes.rb +0 -4
  56. data/fixtures/rails_3_2_12/config/script_hashes.yml +0 -5
  57. data/fixtures/rails_3_2_12/config.ru +0 -7
  58. data/fixtures/rails_3_2_12/lib/assets/.gitkeep +0 -0
  59. data/fixtures/rails_3_2_12/lib/tasks/.gitkeep +0 -0
  60. data/fixtures/rails_3_2_12/log/.gitkeep +0 -0
  61. data/fixtures/rails_3_2_12/spec/controllers/other_things_controller_spec.rb +0 -83
  62. data/fixtures/rails_3_2_12/spec/controllers/things_controller_spec.rb +0 -54
  63. data/fixtures/rails_3_2_12/spec/spec_helper.rb +0 -15
  64. data/fixtures/rails_3_2_12/vendor/assets/javascripts/.gitkeep +0 -0
  65. data/fixtures/rails_3_2_12/vendor/assets/stylesheets/.gitkeep +0 -0
  66. data/fixtures/rails_3_2_12/vendor/plugins/.gitkeep +0 -0
  67. data/fixtures/rails_3_2_12_no_init/.rspec +0 -1
  68. data/fixtures/rails_3_2_12_no_init/Gemfile +0 -5
  69. data/fixtures/rails_3_2_12_no_init/README.rdoc +0 -261
  70. data/fixtures/rails_3_2_12_no_init/Rakefile +0 -7
  71. data/fixtures/rails_3_2_12_no_init/app/controllers/application_controller.rb +0 -4
  72. data/fixtures/rails_3_2_12_no_init/app/controllers/other_things_controller.rb +0 -20
  73. data/fixtures/rails_3_2_12_no_init/app/controllers/things_controller.rb +0 -5
  74. data/fixtures/rails_3_2_12_no_init/app/models/.gitkeep +0 -0
  75. data/fixtures/rails_3_2_12_no_init/app/views/layouts/application.html.erb +0 -12
  76. data/fixtures/rails_3_2_12_no_init/app/views/other_things/index.html.erb +0 -1
  77. data/fixtures/rails_3_2_12_no_init/app/views/things/index.html.erb +0 -0
  78. data/fixtures/rails_3_2_12_no_init/config/application.rb +0 -17
  79. data/fixtures/rails_3_2_12_no_init/config/boot.rb +0 -6
  80. data/fixtures/rails_3_2_12_no_init/config/environment.rb +0 -5
  81. data/fixtures/rails_3_2_12_no_init/config/environments/test.rb +0 -37
  82. data/fixtures/rails_3_2_12_no_init/config/routes.rb +0 -4
  83. data/fixtures/rails_3_2_12_no_init/config.ru +0 -4
  84. data/fixtures/rails_3_2_12_no_init/lib/assets/.gitkeep +0 -0
  85. data/fixtures/rails_3_2_12_no_init/lib/tasks/.gitkeep +0 -0
  86. data/fixtures/rails_3_2_12_no_init/log/.gitkeep +0 -0
  87. data/fixtures/rails_3_2_12_no_init/spec/controllers/other_things_controller_spec.rb +0 -56
  88. data/fixtures/rails_3_2_12_no_init/spec/controllers/things_controller_spec.rb +0 -54
  89. data/fixtures/rails_3_2_12_no_init/spec/spec_helper.rb +0 -5
  90. data/fixtures/rails_3_2_12_no_init/vendor/assets/javascripts/.gitkeep +0 -0
  91. data/fixtures/rails_3_2_12_no_init/vendor/assets/stylesheets/.gitkeep +0 -0
  92. data/fixtures/rails_3_2_12_no_init/vendor/plugins/.gitkeep +0 -0
  93. data/fixtures/rails_4_1_8/Gemfile +0 -5
  94. data/fixtures/rails_4_1_8/README.rdoc +0 -28
  95. data/fixtures/rails_4_1_8/Rakefile +0 -6
  96. data/fixtures/rails_4_1_8/app/controllers/application_controller.rb +0 -4
  97. data/fixtures/rails_4_1_8/app/controllers/concerns/.keep +0 -0
  98. data/fixtures/rails_4_1_8/app/controllers/other_things_controller.rb +0 -5
  99. data/fixtures/rails_4_1_8/app/controllers/things_controller.rb +0 -5
  100. data/fixtures/rails_4_1_8/app/models/.keep +0 -0
  101. data/fixtures/rails_4_1_8/app/models/concerns/.keep +0 -0
  102. data/fixtures/rails_4_1_8/app/views/layouts/application.html.erb +0 -11
  103. data/fixtures/rails_4_1_8/app/views/other_things/index.html.erb +0 -2
  104. data/fixtures/rails_4_1_8/app/views/things/index.html.erb +0 -1
  105. data/fixtures/rails_4_1_8/config/application.rb +0 -15
  106. data/fixtures/rails_4_1_8/config/boot.rb +0 -4
  107. data/fixtures/rails_4_1_8/config/environment.rb +0 -5
  108. data/fixtures/rails_4_1_8/config/environments/test.rb +0 -10
  109. data/fixtures/rails_4_1_8/config/initializers/secure_headers.rb +0 -17
  110. data/fixtures/rails_4_1_8/config/routes.rb +0 -4
  111. data/fixtures/rails_4_1_8/config/script_hashes.yml +0 -5
  112. data/fixtures/rails_4_1_8/config/secrets.yml +0 -22
  113. data/fixtures/rails_4_1_8/config.ru +0 -4
  114. data/fixtures/rails_4_1_8/lib/assets/.keep +0 -0
  115. data/fixtures/rails_4_1_8/lib/tasks/.keep +0 -0
  116. data/fixtures/rails_4_1_8/log/.keep +0 -0
  117. data/fixtures/rails_4_1_8/spec/controllers/other_things_controller_spec.rb +0 -83
  118. data/fixtures/rails_4_1_8/spec/controllers/things_controller_spec.rb +0 -59
  119. data/fixtures/rails_4_1_8/spec/spec_helper.rb +0 -15
  120. data/fixtures/rails_4_1_8/vendor/assets/javascripts/.keep +0 -0
  121. data/fixtures/rails_4_1_8/vendor/assets/stylesheets/.keep +0 -0
  122. data/lib/secure_headers/hash_helper.rb +0 -7
  123. data/lib/secure_headers/header.rb +0 -5
  124. data/lib/secure_headers/headers/content_security_policy/script_hash_middleware.rb +0 -22
  125. data/lib/secure_headers/version.rb +0 -3
  126. data/lib/tasks/tasks.rake +0 -48
  127. data/spec/lib/secure_headers/headers/content_security_policy/script_hash_middleware_spec.rb +0 -47
@@ -1,364 +1,240 @@
1
1
  require 'spec_helper'
2
2
 
3
- describe SecureHeaders do
4
- class DummyClass
5
- include ::SecureHeaders
6
- end
7
-
8
- subject {DummyClass.new}
9
- let(:headers) {double}
10
- let(:response) {double(:headers => headers)}
11
- let(:max_age) {99}
12
- let(:request) {double(:ssl? => true, :url => 'https://example.com')}
13
-
14
- before(:each) do
15
- stub_user_agent(nil)
16
- allow(headers).to receive(:[])
17
- allow(subject).to receive(:response).and_return(response)
18
- allow(subject).to receive(:request).and_return(request)
19
- end
20
-
21
- def stub_user_agent val
22
- allow(request).to receive_message_chain(:env, :[]).and_return(val)
23
- end
24
-
25
- def reset_config
26
- ::SecureHeaders::Configuration.configure do |config|
27
- config.hpkp = nil
28
- config.hsts = nil
29
- config.x_frame_options = nil
30
- config.x_content_type_options = nil
31
- config.x_xss_protection = nil
32
- config.csp = nil
33
- config.x_download_options = nil
34
- config.x_permitted_cross_domain_policies = nil
35
- end
36
- end
37
-
38
- def set_security_headers(subject)
39
- subject.set_csp_header
40
- subject.set_hpkp_header
41
- subject.set_hsts_header
42
- subject.set_x_frame_options_header
43
- subject.set_x_content_type_options_header
44
- subject.set_x_xss_protection_header
45
- subject.set_x_download_options_header
46
- subject.set_x_permitted_cross_domain_policies_header
47
- end
48
-
49
- describe "#set_header" do
50
- it "accepts name/value pairs" do
51
- should_assign_header("X-Hipster-Ipsum", "kombucha")
52
- subject.send(:set_header, "X-Hipster-Ipsum", "kombucha")
53
- end
54
-
55
- it "accepts header objects" do
56
- should_assign_header("Strict-Transport-Security", SecureHeaders::StrictTransportSecurity::Constants::DEFAULT_VALUE)
57
- subject.send(:set_header, SecureHeaders::StrictTransportSecurity.new)
58
- end
59
- end
3
+ module SecureHeaders
4
+ describe SecureHeaders do
5
+ example_hpkp_config = {
6
+ max_age: 1_000_000,
7
+ include_subdomains: true,
8
+ report_uri: '//example.com/uri-directive',
9
+ pins: [
10
+ { sha256: 'abc' },
11
+ { sha256: '123' }
12
+ ]
13
+ }
14
+
15
+ example_hpkp_config_value = %(max-age=1000000; pin-sha256="abc"; pin-sha256="123"; report-uri="//example.com/uri-directive"; includeSubDomains)
16
+
17
+ before(:each) do
18
+ reset_config
19
+ @request = Rack::Request.new("HTTP_X_FORWARDED_SSL" => "on")
20
+ end
21
+
22
+ it "raises a NotYetConfiguredError if default has not been set" do
23
+ expect do
24
+ SecureHeaders.header_hash_for(@request)
25
+ end.to raise_error(Configuration::NotYetConfiguredError)
26
+ end
27
+
28
+ it "raises a NotYetConfiguredError if trying to opt-out of unconfigured headers" do
29
+ expect do
30
+ SecureHeaders.opt_out_of_header(@request, CSP::CONFIG_KEY)
31
+ end.to raise_error(Configuration::NotYetConfiguredError)
32
+ end
33
+
34
+ describe "#header_hash_for" do
35
+ it "allows you to opt out of individual headers" do
36
+ Configuration.default
37
+ SecureHeaders.opt_out_of_header(@request, CSP::CONFIG_KEY)
38
+ hash = SecureHeaders.header_hash_for(@request)
39
+ expect(hash['Content-Security-Policy-Report-Only']).to be_nil
40
+ expect(hash['Content-Security-Policy']).to be_nil
41
+ end
60
42
 
61
- describe "#set_security_headers" do
62
- USER_AGENTS.each do |name, useragent|
63
- it "sets all default headers for #{name} (smoke test)" do
64
- stub_user_agent(useragent)
65
- number_of_headers = 7
66
- expect(subject).to receive(:set_header).exactly(number_of_headers).times # a request for a given header
67
- subject.set_csp_header
68
- subject.set_x_frame_options_header
69
- subject.set_hsts_header
70
- subject.set_hpkp_header
71
- subject.set_x_xss_protection_header
72
- subject.set_x_content_type_options_header
73
- subject.set_x_download_options_header
74
- subject.set_x_permitted_cross_domain_policies_header
43
+ it "allows you to opt out entirely" do
44
+ Configuration.default
45
+ SecureHeaders.opt_out_of_all_protection(@request)
46
+ hash = SecureHeaders.header_hash_for(@request)
47
+ ALL_HEADER_CLASSES.each do |klass|
48
+ expect(hash[klass::CONFIG_KEY]).to be_nil
49
+ end
75
50
  end
76
- end
77
51
 
78
- it "does not set the X-Content-Type-Options header if disabled" do
79
- stub_user_agent(USER_AGENTS[:ie])
80
- should_not_assign_header(X_CONTENT_TYPE_OPTIONS_HEADER_NAME)
81
- subject.set_x_content_type_options_header(false)
82
- end
52
+ it "allows you to override X-Frame-Options settings" do
53
+ Configuration.default
54
+ SecureHeaders.override_x_frame_options(@request, XFrameOptions::DENY)
55
+ hash = SecureHeaders.header_hash_for(@request)
56
+ expect(hash[XFrameOptions::HEADER_NAME]).to eq(XFrameOptions::DENY)
57
+ end
83
58
 
84
- it "does not set the X-XSS-Protection header if disabled" do
85
- should_not_assign_header(X_XSS_PROTECTION_HEADER_NAME)
86
- subject.set_x_xss_protection_header(false)
87
- end
59
+ it "allows you to override opting out" do
60
+ Configuration.default do |config|
61
+ config.x_frame_options = OPT_OUT
62
+ config.csp = OPT_OUT
63
+ end
88
64
 
89
- it "does not set the X-Download-Options header if disabled" do
90
- should_not_assign_header(XDO_HEADER_NAME)
91
- subject.set_x_download_options_header(false)
92
- end
65
+ SecureHeaders.override_x_frame_options(@request, XFrameOptions::SAMEORIGIN)
66
+ SecureHeaders.override_content_security_policy_directives(@request, default_src: %w(https:), script_src: %w('self'))
93
67
 
94
- it "does not set the X-Frame-Options header if disabled" do
95
- should_not_assign_header(XFO_HEADER_NAME)
96
- subject.set_x_frame_options_header(false)
97
- end
68
+ hash = SecureHeaders.header_hash_for(@request)
69
+ expect(hash[CSP::HEADER_NAME]).to eq("default-src https:; script-src 'self'")
70
+ expect(hash[XFrameOptions::HEADER_NAME]).to eq(XFrameOptions::SAMEORIGIN)
71
+ end
98
72
 
99
- it "does not set the X-Permitted-Cross-Domain-Policies header if disabled" do
100
- should_not_assign_header(XPCDP_HEADER_NAME)
101
- subject.set_x_permitted_cross_domain_policies_header(false)
102
- end
73
+ it "produces a hash of headers with default config" do
74
+ Configuration.default
75
+ hash = SecureHeaders.header_hash_for(@request)
76
+ expect_default_values(hash)
77
+ end
103
78
 
104
- it "does not set the HSTS header if disabled" do
105
- should_not_assign_header(HSTS_HEADER_NAME)
106
- subject.set_hsts_header(false)
107
- end
79
+ it "does not set the HSTS header if request is over HTTP" do
80
+ plaintext_request = Rack::Request.new({})
81
+ Configuration.default do |config|
82
+ config.hsts = "max-age=123456"
83
+ end
84
+ expect(SecureHeaders.header_hash_for(plaintext_request)[StrictTransportSecurity::HEADER_NAME]).to be_nil
85
+ end
108
86
 
109
- it "does not set the HSTS header if request is over HTTP" do
110
- allow(subject).to receive_message_chain(:request, :ssl?).and_return(false)
111
- should_not_assign_header(HSTS_HEADER_NAME)
112
- subject.set_hsts_header({:include_subdomains => true})
113
- end
87
+ it "does not set the HPKP header if request is over HTTP" do
88
+ plaintext_request = Rack::Request.new({})
89
+ Configuration.default do |config|
90
+ config.hpkp = example_hpkp_config
91
+ end
114
92
 
115
- it "does not set the HPKP header if disabled" do
116
- should_not_assign_header(HPKP_HEADER_NAME)
117
- subject.set_hpkp_header
118
- end
93
+ expect(SecureHeaders.header_hash_for(plaintext_request)[PublicKeyPins::HEADER_NAME]).to be_nil
94
+ end
119
95
 
120
- it "does not set the HPKP header if request is over HTTP" do
121
- allow(subject).to receive_message_chain(:request, :ssl?).and_return(false)
122
- should_not_assign_header(HPKP_HEADER_NAME)
123
- subject.set_hpkp_header(:max_age => 1234)
124
- end
96
+ context "content security policy" do
97
+ it "appends a value to csp directive" do
98
+ Configuration.default do |config|
99
+ config.csp = {
100
+ default_src: %w('self'),
101
+ script_src: %w(mycdn.com 'unsafe-inline')
102
+ }
103
+ end
104
+
105
+ SecureHeaders.append_content_security_policy_directives(@request, script_src: %w(anothercdn.com))
106
+ hash = SecureHeaders.header_hash_for(@request)
107
+ expect(hash[CSP::HEADER_NAME]).to eq("default-src 'self'; script-src mycdn.com 'unsafe-inline' anothercdn.com")
108
+ end
125
109
 
126
- it "does not set the CSP header if disabled" do
127
- stub_user_agent(USER_AGENTS[:chrome])
128
- should_not_assign_header(HEADER_NAME)
129
- subject.set_csp_header(false)
130
- end
110
+ it "overrides individual directives" do
111
+ Configuration.default do |config|
112
+ config.csp = {
113
+ default_src: %w('self')
114
+ }
115
+ end
116
+ SecureHeaders.override_content_security_policy_directives(@request, default_src: %w('none'))
117
+ hash = SecureHeaders.header_hash_for(@request)
118
+ expect(hash[CSP::HEADER_NAME]).to eq("default-src 'none'")
119
+ end
131
120
 
132
- it "saves the options to the env when using script hashes" do
133
- opts = {
134
- :default_src => 'self',
135
- :script_hash_middleware => true
136
- }
137
- stub_user_agent(USER_AGENTS[:chrome])
121
+ it "overrides non-existant directives" do
122
+ Configuration.default
123
+ SecureHeaders.override_content_security_policy_directives(@request, img_src: [ContentSecurityPolicy::DATA_PROTOCOL])
124
+ hash = SecureHeaders.header_hash_for(@request)
125
+ expect(hash[CSP::HEADER_NAME]).to eq("default-src https:; img-src data:")
126
+ end
138
127
 
139
- expect(SecureHeaders::ContentSecurityPolicy).to receive(:add_to_env)
140
- subject.set_csp_header(opts)
141
- end
128
+ it "does not append a nonce when the browser does not support it" do
129
+ Configuration.default do |config|
130
+ config.csp = {
131
+ default_src: %w('self'),
132
+ script_src: %w(mycdn.com 'unsafe-inline'),
133
+ style_src: %w('self')
134
+ }
135
+ end
136
+
137
+ request = Rack::Request.new(@request.env.merge("HTTP_USER_AGENT" => USER_AGENTS[:safari5]))
138
+ nonce = SecureHeaders.content_security_policy_script_nonce(request)
139
+ hash = SecureHeaders.header_hash_for(request)
140
+ expect(hash[CSP::HEADER_NAME]).to eq("default-src 'self'; script-src mycdn.com 'unsafe-inline'; style-src 'self'")
141
+ end
142
142
 
143
- context "when disabled by configuration settings" do
144
- it "does not set any headers when disabled" do
145
- ::SecureHeaders::Configuration.configure do |config|
146
- config.hsts = false
147
- config.hpkp = false
148
- config.x_frame_options = false
149
- config.x_content_type_options = false
150
- config.x_xss_protection = false
151
- config.csp = false
152
- config.x_download_options = false
153
- config.x_permitted_cross_domain_policies = false
143
+ it "appends a nonce to the script-src when used" do
144
+ Configuration.default do |config|
145
+ config.csp = {
146
+ default_src: %w('self'),
147
+ script_src: %w(mycdn.com),
148
+ style_src: %w('self')
149
+ }
150
+ end
151
+
152
+ request = Rack::Request.new(@request.env.merge("HTTP_USER_AGENT" => USER_AGENTS[:chrome]))
153
+ nonce = SecureHeaders.content_security_policy_script_nonce(request)
154
+
155
+ # simulate the nonce being used multiple times in a request:
156
+ SecureHeaders.content_security_policy_script_nonce(request)
157
+ SecureHeaders.content_security_policy_script_nonce(request)
158
+ SecureHeaders.content_security_policy_script_nonce(request)
159
+
160
+ hash = SecureHeaders.header_hash_for(request)
161
+ expect(hash['Content-Security-Policy']).to eq("default-src 'self'; script-src mycdn.com 'nonce-#{nonce}'; style-src 'self'")
154
162
  end
155
- expect(subject).not_to receive(:set_header)
156
- set_security_headers(subject)
157
- reset_config
158
163
  end
159
164
  end
160
- end
161
-
162
- describe "SecureHeaders#header_hash" do
163
- def expect_default_values(hash)
164
- expect(hash[XFO_HEADER_NAME]).to eq(SecureHeaders::XFrameOptions::Constants::DEFAULT_VALUE)
165
- expect(hash[XDO_HEADER_NAME]).to eq(SecureHeaders::XDownloadOptions::Constants::DEFAULT_VALUE)
166
- expect(hash[HSTS_HEADER_NAME]).to eq(SecureHeaders::StrictTransportSecurity::Constants::DEFAULT_VALUE)
167
- expect(hash[X_XSS_PROTECTION_HEADER_NAME]).to eq(SecureHeaders::XXssProtection::Constants::DEFAULT_VALUE)
168
- expect(hash[X_CONTENT_TYPE_OPTIONS_HEADER_NAME]).to eq(SecureHeaders::XContentTypeOptions::Constants::DEFAULT_VALUE)
169
- expect(hash[XPCDP_HEADER_NAME]).to eq(SecureHeaders::XPermittedCrossDomainPolicies::Constants::DEFAULT_VALUE)
170
- end
171
-
172
- it "produces a hash of headers given a hash as config" do
173
- hash = SecureHeaders::header_hash(:csp => {:default_src => 'none', :img_src => "data:", :disable_fill_missing => true})
174
- expect(hash['Content-Security-Policy-Report-Only']).to eq("default-src 'none'; img-src data:;")
175
- expect_default_values(hash)
176
- end
177
165
 
178
- it "produces a hash with a mix of config values, override values, and default values" do
179
- ::SecureHeaders::Configuration.configure do |config|
180
- config.hsts = { :max_age => '123456'}
181
- config.hpkp = {
182
- :enforce => true,
183
- :max_age => 1000000,
184
- :include_subdomains => true,
185
- :report_uri => '//example.com/uri-directive',
186
- :pins => [
187
- {:sha256 => 'abc'},
188
- {:sha256 => '123'}
189
- ]
190
- }
166
+ context "validation" do
167
+ it "validates your hsts config upon configuration" do
168
+ expect do
169
+ Configuration.default do |config|
170
+ config.hsts = 'lol'
171
+ end
172
+ end.to raise_error(STSConfigError)
191
173
  end
192
174
 
193
- hash = SecureHeaders::header_hash(:csp => {:default_src => 'none', :img_src => "data:", :disable_fill_missing => true})
194
- ::SecureHeaders::Configuration.configure do |config|
195
- config.hsts = nil
196
- config.hpkp = nil
175
+ it "validates your csp config upon configuration" do
176
+ expect do
177
+ Configuration.default do |config|
178
+ config.csp = { CSP::DEFAULT_SRC => '123456' }
179
+ end
180
+ end.to raise_error(ContentSecurityPolicyConfigError)
197
181
  end
198
182
 
199
- expect(hash['Content-Security-Policy-Report-Only']).to eq("default-src 'none'; img-src data:;")
200
- expect(hash[XFO_HEADER_NAME]).to eq(SecureHeaders::XFrameOptions::Constants::DEFAULT_VALUE)
201
- expect(hash[HSTS_HEADER_NAME]).to eq("max-age=123456")
202
- expect(hash[HPKP_HEADER_NAME]).to eq(%{max-age=1000000; pin-sha256="abc"; pin-sha256="123"; report-uri="//example.com/uri-directive"; includeSubDomains})
203
- end
204
-
205
- it "produces a hash of headers with default config" do
206
- hash = SecureHeaders::header_hash
207
- expect(hash['Content-Security-Policy-Report-Only']).to eq(SecureHeaders::ContentSecurityPolicy::Constants::DEFAULT_CSP_HEADER)
208
- expect_default_values(hash)
209
- end
210
- end
211
-
212
- describe "#set_x_frame_options_header" do
213
- it "sets the X-Frame-Options header" do
214
- should_assign_header(XFO_HEADER_NAME, SecureHeaders::XFrameOptions::Constants::DEFAULT_VALUE)
215
- subject.set_x_frame_options_header
216
- end
217
-
218
- it "allows a custom X-Frame-Options header" do
219
- should_assign_header(XFO_HEADER_NAME, "DENY")
220
- subject.set_x_frame_options_header(:value => 'DENY')
221
- end
222
- end
223
-
224
- describe "#set_x_download_options_header" do
225
- it "sets the X-Download-Options header" do
226
- should_assign_header(XDO_HEADER_NAME, SecureHeaders::XDownloadOptions::Constants::DEFAULT_VALUE)
227
- subject.set_x_download_options_header
228
- end
229
-
230
- it "allows a custom X-Download-Options header" do
231
- should_assign_header(XDO_HEADER_NAME, "noopen")
232
- subject.set_x_download_options_header(:value => 'noopen')
233
- end
234
- end
235
-
236
- describe "#set_strict_transport_security" do
237
- it "sets the Strict-Transport-Security header" do
238
- should_assign_header(HSTS_HEADER_NAME, SecureHeaders::StrictTransportSecurity::Constants::DEFAULT_VALUE)
239
- subject.set_hsts_header
240
- end
241
-
242
- it "allows you to specific a custom max-age value" do
243
- should_assign_header(HSTS_HEADER_NAME, 'max-age=1234')
244
- subject.set_hsts_header(:max_age => 1234)
245
- end
246
-
247
- it "allows you to specify includeSubdomains" do
248
- should_assign_header(HSTS_HEADER_NAME, "max-age=#{HSTS_MAX_AGE}; includeSubdomains")
249
- subject.set_hsts_header(:max_age => HSTS_MAX_AGE, :include_subdomains => true)
250
- end
251
-
252
- it "allows you to specify preload" do
253
- should_assign_header(HSTS_HEADER_NAME, "max-age=#{HSTS_MAX_AGE}; includeSubdomains; preload")
254
- subject.set_hsts_header(:max_age => HSTS_MAX_AGE, :include_subdomains => true, :preload => true)
255
- end
256
- end
257
-
258
- describe "#set_public_key_pins" do
259
- it "sets the Public-Key-Pins header" do
260
- should_assign_header(HPKP_HEADER_NAME + "-Report-Only", "max-age=1234")
261
- subject.set_hpkp_header(:max_age => 1234)
262
- end
263
-
264
- it "allows you to enforce public key pinning" do
265
- should_assign_header(HPKP_HEADER_NAME, "max-age=1234")
266
- subject.set_hpkp_header(:max_age => 1234, :enforce => true)
267
- end
268
-
269
- it "allows you to specific a custom max-age value" do
270
- should_assign_header(HPKP_HEADER_NAME + "-Report-Only", 'max-age=1234')
271
- subject.set_hpkp_header(:max_age => 1234)
272
- end
273
-
274
- it "allows you to specify includeSubdomains" do
275
- should_assign_header(HPKP_HEADER_NAME, "max-age=1234; includeSubDomains")
276
- subject.set_hpkp_header(:max_age => 1234, :include_subdomains => true, :enforce => true)
277
- end
278
-
279
- it "allows you to specify a report-uri" do
280
- should_assign_header(HPKP_HEADER_NAME, "max-age=1234; report-uri=\"https://foobar.com\"")
281
- subject.set_hpkp_header(:max_age => 1234, :report_uri => "https://foobar.com", :enforce => true)
282
- end
283
-
284
- it "allows you to specify a report-uri with app_name" do
285
- should_assign_header(HPKP_HEADER_NAME, "max-age=1234; report-uri=\"https://foobar.com?enforce=true&app_name=my_app\"")
286
- subject.set_hpkp_header(:max_age => 1234, :report_uri => "https://foobar.com", :app_name => "my_app", :tag_report_uri => true, :enforce => true)
287
- end
288
- end
289
-
290
- describe "#set_x_xss_protection" do
291
- it "sets the X-XSS-Protection header" do
292
- should_assign_header(X_XSS_PROTECTION_HEADER_NAME, SecureHeaders::XXssProtection::Constants::DEFAULT_VALUE)
293
- subject.set_x_xss_protection_header
294
- end
295
-
296
- it "sets a custom X-XSS-Protection header" do
297
- should_assign_header(X_XSS_PROTECTION_HEADER_NAME, '0')
298
- subject.set_x_xss_protection_header("0")
299
- end
300
-
301
- it "sets the block flag" do
302
- should_assign_header(X_XSS_PROTECTION_HEADER_NAME, '1; mode=block')
303
- subject.set_x_xss_protection_header(:mode => 'block', :value => 1)
304
- end
305
- end
306
-
307
- describe "#set_x_content_type_options" do
308
- USER_AGENTS.each do |useragent|
309
- context "when using #{useragent}" do
310
- before(:each) do
311
- stub_user_agent(USER_AGENTS[useragent])
312
- end
313
-
314
- it "sets the X-Content-Type-Options header" do
315
- should_assign_header(X_CONTENT_TYPE_OPTIONS_HEADER_NAME, SecureHeaders::XContentTypeOptions::Constants::DEFAULT_VALUE)
316
- subject.set_x_content_type_options_header
317
- end
183
+ it "raises errors for unknown directives" do
184
+ expect do
185
+ Configuration.default do |config|
186
+ config.csp = { made_up_directive: '123456' }
187
+ end
188
+ end.to raise_error(ContentSecurityPolicyConfigError)
189
+ end
318
190
 
319
- it "lets you override X-Content-Type-Options" do
320
- should_assign_header(X_CONTENT_TYPE_OPTIONS_HEADER_NAME, 'nosniff')
321
- subject.set_x_content_type_options_header(:value => 'nosniff')
322
- end
191
+ it "validates your xfo config upon configuration" do
192
+ expect do
193
+ Configuration.default do |config|
194
+ config.x_frame_options = "NOPE"
195
+ end
196
+ end.to raise_error(XFOConfigError)
323
197
  end
324
- end
325
- end
326
198
 
327
- describe "#set_csp_header" do
328
- context "when using Firefox" do
329
- it "sets CSP headers" do
330
- stub_user_agent(USER_AGENTS[:firefox])
331
- should_assign_header(HEADER_NAME + "-Report-Only", DEFAULT_CSP_HEADER)
332
- subject.set_csp_header
199
+ it "validates your xcto config upon configuration" do
200
+ expect do
201
+ Configuration.default do |config|
202
+ config.x_content_type_options = "lol"
203
+ end
204
+ end.to raise_error(XContentTypeOptionsConfigError)
333
205
  end
334
- end
335
206
 
336
- context "when using Chrome" do
337
- it "sets default CSP header" do
338
- stub_user_agent(USER_AGENTS[:chrome])
339
- should_assign_header(HEADER_NAME + "-Report-Only", DEFAULT_CSP_HEADER)
340
- subject.set_csp_header
207
+ it "validates your x_xss config upon configuration" do
208
+ expect do
209
+ Configuration.default do |config|
210
+ config.x_xss_protection = "lol"
211
+ end
212
+ end.to raise_error(XXssProtectionConfigError)
341
213
  end
342
- end
343
214
 
344
- context "when using a browser besides chrome/firefox" do
345
- it "sets the CSP header" do
346
- stub_user_agent(USER_AGENTS[:opera])
347
- should_assign_header(HEADER_NAME + "-Report-Only", DEFAULT_CSP_HEADER)
348
- subject.set_csp_header
215
+ it "validates your xdo config upon configuration" do
216
+ expect do
217
+ Configuration.default do |config|
218
+ config.x_download_options = "lol"
219
+ end
220
+ end.to raise_error(XDOConfigError)
349
221
  end
350
- end
351
- end
352
222
 
353
- describe "#set_x_permitted_cross_domain_policies_header" do
354
- it "sets the X-Permitted-Cross-Domain-Policies header" do
355
- should_assign_header(XPCDP_HEADER_NAME, SecureHeaders::XPermittedCrossDomainPolicies::Constants::DEFAULT_VALUE)
356
- subject.set_x_permitted_cross_domain_policies_header
357
- end
223
+ it "validates your x_permitted_cross_domain_policies config upon configuration" do
224
+ expect do
225
+ Configuration.default do |config|
226
+ config.x_permitted_cross_domain_policies = "lol"
227
+ end
228
+ end.to raise_error(XPCDPConfigError)
229
+ end
358
230
 
359
- it "allows a custom X-Permitted-Cross-Domain-Policies header" do
360
- should_assign_header(XPCDP_HEADER_NAME, "master-only")
361
- subject.set_x_permitted_cross_domain_policies_header(:value => 'master-only')
231
+ it "validates your hpkp config upon configuration" do
232
+ expect do
233
+ Configuration.default do |config|
234
+ config.hpkp = "lol"
235
+ end
236
+ end.to raise_error(PublicKeyPinsConfigError)
237
+ end
362
238
  end
363
239
  end
364
240
  end
data/spec/spec_helper.rb CHANGED
@@ -1,40 +1,44 @@
1
1
  require 'rubygems'
2
2
  require 'rspec'
3
+ require 'rack'
4
+ require 'pry-nav'
3
5
 
4
6
  require File.join(File.dirname(__FILE__), '..', 'lib', 'secure_headers')
5
7
 
6
- begin
7
- require 'coveralls'
8
- Coveralls.wear!
9
- rescue LoadError
10
- # damn you 1.8.7
11
- end
12
-
13
- include ::SecureHeaders::PublicKeyPins::Constants
14
- include ::SecureHeaders::StrictTransportSecurity::Constants
15
- include ::SecureHeaders::ContentSecurityPolicy::Constants
16
- include ::SecureHeaders::XFrameOptions::Constants
17
- include ::SecureHeaders::XXssProtection::Constants
18
- include ::SecureHeaders::XContentTypeOptions::Constants
19
- include ::SecureHeaders::XDownloadOptions::Constants
20
- include ::SecureHeaders::XPermittedCrossDomainPolicies::Constants
8
+ ENV["RAILS_ENV"] = "test"
21
9
 
22
10
  USER_AGENTS = {
23
- :firefox => 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:14.0) Gecko/20100101 Firefox/14.0.1',
24
- :chrome => 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_6_8) AppleWebKit/536.5 (KHTML, like Gecko) Chrome/19.0.1084.56 Safari/536.5',
25
- :ie => 'Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.1; Trident/5.0)',
26
- :opera => 'Opera/9.80 (Windows NT 6.1; U; es-ES) Presto/2.9.181 Version/12.00',
27
- :ios5 => "Mozilla/5.0 (iPhone; CPU iPhone OS 5_0 like Mac OS X) AppleWebKit/534.46 (KHTML, like Gecko) Version/5.1 Mobile/9A334 Safari/7534.48.3",
28
- :ios6 => "Mozilla/5.0 (iPhone; CPU iPhone OS 614 like Mac OS X) AppleWebKit/536.26 (KHTML like Gecko) Version/6.0 Mobile/10B350 Safari/8536.25",
29
- :safari5 => "Mozilla/5.0 (iPad; CPU OS 5_1 like Mac OS X) AppleWebKit/534.46 (KHTML, like Gecko ) Version/5.1 Mobile/9B176 Safari/7534.48.3",
30
- :safari5_1 => "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_3) AppleWebKit/534.55.3 (KHTML, like Gecko) Version/5.1.3 Safari/534.53.10",
31
- :safari6 => "Mozilla/5.0 (Macintosh; Intel Mac OS X 1084) AppleWebKit/536.30.1 (KHTML like Gecko) Version/6.0.5 Safari/536.30.1"
11
+ firefox: 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:14.0) Gecko/20100101 Firefox/14.0.1',
12
+ chrome: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_6_8) AppleWebKit/536.5 (KHTML, like Gecko) Chrome/19.0.1084.56 Safari/536.5',
13
+ ie: 'Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.1; Trident/5.0)',
14
+ opera: 'Opera/9.80 (Windows NT 6.1; U; es-ES) Presto/2.9.181 Version/12.00',
15
+ ios5: "Mozilla/5.0 (iPhone; CPU iPhone OS 5_0 like Mac OS X) AppleWebKit/534.46 (KHTML, like Gecko) Version/5.1 Mobile/9A334 Safari/7534.48.3",
16
+ ios6: "Mozilla/5.0 (iPhone; CPU iPhone OS 614 like Mac OS X) AppleWebKit/536.26 (KHTML like Gecko) Version/6.0 Mobile/10B350 Safari/8536.25",
17
+ safari5: "Mozilla/5.0 (iPad; CPU OS 5_1 like Mac OS X) AppleWebKit/534.46 (KHTML, like Gecko ) Version/5.1 Mobile/9B176 Safari/7534.48.3",
18
+ safari5_1: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_3) AppleWebKit/534.55.3 (KHTML, like Gecko) Version/5.1.3 Safari/534.53.10",
19
+ safari6: "Mozilla/5.0 (Macintosh; Intel Mac OS X 1084) AppleWebKit/536.30.1 (KHTML like Gecko) Version/6.0.5 Safari/536.30.1"
32
20
  }
33
21
 
34
- def should_assign_header name, value
35
- expect(response.headers).to receive(:[]=).with(name, value)
22
+ def expect_default_values(hash)
23
+ expect(hash[SecureHeaders::CSP::HEADER_NAME]).to eq(SecureHeaders::CSP::DEFAULT_VALUE)
24
+ expect(hash[SecureHeaders::XFrameOptions::HEADER_NAME]).to eq(SecureHeaders::XFrameOptions::DEFAULT_VALUE)
25
+ expect(hash[SecureHeaders::XDownloadOptions::HEADER_NAME]).to eq(SecureHeaders::XDownloadOptions::DEFAULT_VALUE)
26
+ expect(hash[SecureHeaders::StrictTransportSecurity::HEADER_NAME]).to eq(SecureHeaders::StrictTransportSecurity::DEFAULT_VALUE)
27
+ expect(hash[SecureHeaders::XXssProtection::HEADER_NAME]).to eq(SecureHeaders::XXssProtection::DEFAULT_VALUE)
28
+ expect(hash[SecureHeaders::XContentTypeOptions::HEADER_NAME]).to eq(SecureHeaders::XContentTypeOptions::DEFAULT_VALUE)
29
+ expect(hash[SecureHeaders::XPermittedCrossDomainPolicies::HEADER_NAME]).to eq(SecureHeaders::XPermittedCrossDomainPolicies::DEFAULT_VALUE)
30
+ end
31
+
32
+ module SecureHeaders
33
+ class Configuration
34
+ class << self
35
+ def clear_configurations
36
+ @configurations = nil
37
+ end
38
+ end
39
+ end
36
40
  end
37
41
 
38
- def should_not_assign_header name
39
- expect(response.headers).not_to receive(:[]=).with(name, anything)
42
+ def reset_config
43
+ SecureHeaders::Configuration.clear_configurations
40
44
  end