secure_headers 2.5.3 → 3.0.0.pre

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of secure_headers might be problematic. Click here for more details.

Files changed (125) hide show
  1. checksums.yaml +4 -4
  2. data/.rspec +1 -0
  3. data/.travis.yml +2 -1
  4. data/Gemfile +9 -16
  5. data/README.md +154 -331
  6. data/Rakefile +2 -36
  7. data/lib/secure_headers/configuration.rb +189 -0
  8. data/lib/secure_headers/headers/content_security_policy.rb +341 -254
  9. data/lib/secure_headers/headers/public_key_pins.rb +43 -58
  10. data/lib/secure_headers/headers/strict_transport_security.rb +21 -49
  11. data/lib/secure_headers/headers/x_content_type_options.rb +18 -33
  12. data/lib/secure_headers/headers/x_download_options.rb +18 -33
  13. data/lib/secure_headers/headers/x_frame_options.rb +24 -34
  14. data/lib/secure_headers/headers/x_permitted_cross_domain_policies.rb +19 -34
  15. data/lib/secure_headers/headers/x_xss_protection.rb +17 -48
  16. data/lib/secure_headers/middleware.rb +15 -0
  17. data/lib/secure_headers/padrino.rb +1 -2
  18. data/lib/secure_headers/railtie.rb +9 -6
  19. data/lib/secure_headers/view_helper.rb +27 -43
  20. data/lib/secure_headers.rb +254 -61
  21. data/secure_headers.gemspec +7 -12
  22. data/spec/lib/secure_headers/configuration_spec.rb +80 -0
  23. data/spec/lib/secure_headers/headers/content_security_policy_spec.rb +111 -276
  24. data/spec/lib/secure_headers/headers/public_key_pins_spec.rb +17 -17
  25. data/spec/lib/secure_headers/headers/strict_transport_security_spec.rb +11 -43
  26. data/spec/lib/secure_headers/headers/x_content_type_options_spec.rb +11 -18
  27. data/spec/lib/secure_headers/headers/x_download_options_spec.rb +13 -17
  28. data/spec/lib/secure_headers/headers/x_frame_options_spec.rb +15 -17
  29. data/spec/lib/secure_headers/headers/x_permitted_cross_domain_policies_spec.rb +22 -39
  30. data/spec/lib/secure_headers/headers/x_xss_protection_spec.rb +20 -30
  31. data/spec/lib/secure_headers/middleware_spec.rb +40 -0
  32. data/spec/lib/secure_headers_spec.rb +201 -339
  33. data/spec/spec_helper.rb +30 -30
  34. data/upgrading-to-3-0.md +35 -0
  35. metadata +14 -100
  36. data/fixtures/rails_3_2_22/.rspec +0 -1
  37. data/fixtures/rails_3_2_22/Gemfile +0 -6
  38. data/fixtures/rails_3_2_22/README.rdoc +0 -261
  39. data/fixtures/rails_3_2_22/Rakefile +0 -7
  40. data/fixtures/rails_3_2_22/app/controllers/application_controller.rb +0 -4
  41. data/fixtures/rails_3_2_22/app/controllers/other_things_controller.rb +0 -5
  42. data/fixtures/rails_3_2_22/app/controllers/things_controller.rb +0 -5
  43. data/fixtures/rails_3_2_22/app/models/.gitkeep +0 -0
  44. data/fixtures/rails_3_2_22/app/views/layouts/application.html.erb +0 -11
  45. data/fixtures/rails_3_2_22/app/views/other_things/index.html.erb +0 -2
  46. data/fixtures/rails_3_2_22/app/views/things/index.html.erb +0 -1
  47. data/fixtures/rails_3_2_22/config/application.rb +0 -14
  48. data/fixtures/rails_3_2_22/config/boot.rb +0 -6
  49. data/fixtures/rails_3_2_22/config/environment.rb +0 -5
  50. data/fixtures/rails_3_2_22/config/environments/test.rb +0 -37
  51. data/fixtures/rails_3_2_22/config/initializers/secure_headers.rb +0 -16
  52. data/fixtures/rails_3_2_22/config/routes.rb +0 -4
  53. data/fixtures/rails_3_2_22/config/script_hashes.yml +0 -5
  54. data/fixtures/rails_3_2_22/config.ru +0 -7
  55. data/fixtures/rails_3_2_22/lib/assets/.gitkeep +0 -0
  56. data/fixtures/rails_3_2_22/lib/tasks/.gitkeep +0 -0
  57. data/fixtures/rails_3_2_22/log/.gitkeep +0 -0
  58. data/fixtures/rails_3_2_22/spec/controllers/other_things_controller_spec.rb +0 -83
  59. data/fixtures/rails_3_2_22/spec/controllers/things_controller_spec.rb +0 -54
  60. data/fixtures/rails_3_2_22/spec/spec_helper.rb +0 -15
  61. data/fixtures/rails_3_2_22/vendor/assets/javascripts/.gitkeep +0 -0
  62. data/fixtures/rails_3_2_22/vendor/assets/stylesheets/.gitkeep +0 -0
  63. data/fixtures/rails_3_2_22/vendor/plugins/.gitkeep +0 -0
  64. data/fixtures/rails_3_2_22_no_init/.rspec +0 -1
  65. data/fixtures/rails_3_2_22_no_init/Gemfile +0 -6
  66. data/fixtures/rails_3_2_22_no_init/README.rdoc +0 -261
  67. data/fixtures/rails_3_2_22_no_init/Rakefile +0 -7
  68. data/fixtures/rails_3_2_22_no_init/app/controllers/application_controller.rb +0 -4
  69. data/fixtures/rails_3_2_22_no_init/app/controllers/other_things_controller.rb +0 -20
  70. data/fixtures/rails_3_2_22_no_init/app/controllers/things_controller.rb +0 -5
  71. data/fixtures/rails_3_2_22_no_init/app/models/.gitkeep +0 -0
  72. data/fixtures/rails_3_2_22_no_init/app/views/layouts/application.html.erb +0 -12
  73. data/fixtures/rails_3_2_22_no_init/app/views/other_things/index.html.erb +0 -1
  74. data/fixtures/rails_3_2_22_no_init/app/views/things/index.html.erb +0 -0
  75. data/fixtures/rails_3_2_22_no_init/config/application.rb +0 -17
  76. data/fixtures/rails_3_2_22_no_init/config/boot.rb +0 -6
  77. data/fixtures/rails_3_2_22_no_init/config/environment.rb +0 -5
  78. data/fixtures/rails_3_2_22_no_init/config/environments/test.rb +0 -37
  79. data/fixtures/rails_3_2_22_no_init/config/routes.rb +0 -4
  80. data/fixtures/rails_3_2_22_no_init/config.ru +0 -4
  81. data/fixtures/rails_3_2_22_no_init/lib/assets/.gitkeep +0 -0
  82. data/fixtures/rails_3_2_22_no_init/lib/tasks/.gitkeep +0 -0
  83. data/fixtures/rails_3_2_22_no_init/log/.gitkeep +0 -0
  84. data/fixtures/rails_3_2_22_no_init/spec/controllers/other_things_controller_spec.rb +0 -56
  85. data/fixtures/rails_3_2_22_no_init/spec/controllers/things_controller_spec.rb +0 -54
  86. data/fixtures/rails_3_2_22_no_init/spec/spec_helper.rb +0 -5
  87. data/fixtures/rails_3_2_22_no_init/vendor/assets/javascripts/.gitkeep +0 -0
  88. data/fixtures/rails_3_2_22_no_init/vendor/assets/stylesheets/.gitkeep +0 -0
  89. data/fixtures/rails_3_2_22_no_init/vendor/plugins/.gitkeep +0 -0
  90. data/fixtures/rails_4_1_8/Gemfile +0 -5
  91. data/fixtures/rails_4_1_8/README.rdoc +0 -28
  92. data/fixtures/rails_4_1_8/Rakefile +0 -6
  93. data/fixtures/rails_4_1_8/app/controllers/application_controller.rb +0 -4
  94. data/fixtures/rails_4_1_8/app/controllers/concerns/.keep +0 -0
  95. data/fixtures/rails_4_1_8/app/controllers/other_things_controller.rb +0 -5
  96. data/fixtures/rails_4_1_8/app/controllers/things_controller.rb +0 -5
  97. data/fixtures/rails_4_1_8/app/models/.keep +0 -0
  98. data/fixtures/rails_4_1_8/app/models/concerns/.keep +0 -0
  99. data/fixtures/rails_4_1_8/app/views/layouts/application.html.erb +0 -11
  100. data/fixtures/rails_4_1_8/app/views/other_things/index.html.erb +0 -2
  101. data/fixtures/rails_4_1_8/app/views/things/index.html.erb +0 -1
  102. data/fixtures/rails_4_1_8/config/application.rb +0 -15
  103. data/fixtures/rails_4_1_8/config/boot.rb +0 -4
  104. data/fixtures/rails_4_1_8/config/environment.rb +0 -5
  105. data/fixtures/rails_4_1_8/config/environments/test.rb +0 -10
  106. data/fixtures/rails_4_1_8/config/initializers/secure_headers.rb +0 -16
  107. data/fixtures/rails_4_1_8/config/routes.rb +0 -4
  108. data/fixtures/rails_4_1_8/config/script_hashes.yml +0 -5
  109. data/fixtures/rails_4_1_8/config/secrets.yml +0 -22
  110. data/fixtures/rails_4_1_8/config.ru +0 -4
  111. data/fixtures/rails_4_1_8/lib/assets/.keep +0 -0
  112. data/fixtures/rails_4_1_8/lib/tasks/.keep +0 -0
  113. data/fixtures/rails_4_1_8/log/.keep +0 -0
  114. data/fixtures/rails_4_1_8/spec/controllers/other_things_controller_spec.rb +0 -83
  115. data/fixtures/rails_4_1_8/spec/controllers/things_controller_spec.rb +0 -59
  116. data/fixtures/rails_4_1_8/spec/spec_helper.rb +0 -15
  117. data/fixtures/rails_4_1_8/vendor/assets/javascripts/.keep +0 -0
  118. data/fixtures/rails_4_1_8/vendor/assets/stylesheets/.keep +0 -0
  119. data/lib/secure_headers/controller_extension.rb +0 -158
  120. data/lib/secure_headers/hash_helper.rb +0 -7
  121. data/lib/secure_headers/header.rb +0 -5
  122. data/lib/secure_headers/headers/content_security_policy/script_hash_middleware.rb +0 -22
  123. data/lib/secure_headers/version.rb +0 -3
  124. data/lib/tasks/tasks.rake +0 -48
  125. data/spec/lib/secure_headers/headers/content_security_policy/script_hash_middleware_spec.rb +0 -46
@@ -1,378 +1,240 @@
1
1
  require 'spec_helper'
2
2
 
3
- describe SecureHeaders do
4
- subject {DummyClass.new}
5
- let(:headers) {double}
6
- let(:response) {double(:headers => headers)}
7
- let(:max_age) {99}
8
- let(:request) {double(:ssl? => true, :url => 'https://example.com')}
9
-
10
- before(:each) do
11
- reset_config
12
- stub_user_agent(nil)
13
- allow(headers).to receive(:[])
14
- allow(subject).to receive(:response).and_return(response)
15
- allow(subject).to receive(:request).and_return(request)
16
- end
17
-
18
- def stub_user_agent val
19
- allow(request).to receive_message_chain(:env, :[]).and_return(val)
20
- end
21
-
22
- def reset_config
23
- ::SecureHeaders::Configuration.default do |config|
24
- config.hpkp = nil
25
- config.hsts = nil
26
- config.x_frame_options = nil
27
- config.x_content_type_options = nil
28
- config.x_xss_protection = nil
29
- config.csp = nil
30
- config.x_download_options = nil
31
- config.x_permitted_cross_domain_policies = nil
32
- end
33
- end
34
-
35
- def set_security_headers(subject)
36
- subject.set_csp_header
37
- subject.set_hpkp_header
38
- subject.set_hsts_header
39
- subject.set_x_frame_options_header
40
- subject.set_x_content_type_options_header
41
- subject.set_x_xss_protection_header
42
- subject.set_x_download_options_header
43
- subject.set_x_permitted_cross_domain_policies_header
44
- end
45
-
46
- describe "#set_header" do
47
- it "accepts name/value pairs" do
48
- should_assign_header("X-Hipster-Ipsum", "kombucha")
49
- subject.send(:set_header, "X-Hipster-Ipsum", "kombucha")
50
- end
51
-
52
- it "accepts header objects" do
53
- should_assign_header("Strict-Transport-Security", SecureHeaders::StrictTransportSecurity::Constants::DEFAULT_VALUE)
54
- subject.send(:set_header, SecureHeaders::StrictTransportSecurity.new)
55
- end
56
- end
57
-
58
- describe "#set_security_headers" do
59
- USER_AGENTS.each do |name, useragent|
60
- it "sets all default headers for #{name} (smoke test)" do
61
- stub_user_agent(useragent)
62
- number_of_headers = 7
63
- expect(subject).to receive(:set_header).exactly(number_of_headers).times # a request for a given header
64
- subject.set_csp_header
65
- subject.set_x_frame_options_header
66
- subject.set_hsts_header
67
- subject.set_hpkp_header
68
- subject.set_x_xss_protection_header
69
- subject.set_x_content_type_options_header
70
- subject.set_x_download_options_header
71
- subject.set_x_permitted_cross_domain_policies_header
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
72
41
  end
73
- end
74
-
75
- it "does not set the X-Content-Type-Options header if disabled" do
76
- stub_user_agent(USER_AGENTS[:ie])
77
- should_not_assign_header(X_CONTENT_TYPE_OPTIONS_HEADER_NAME)
78
- subject.set_x_content_type_options_header(false)
79
- end
80
-
81
- it "does not set the X-XSS-Protection header if disabled" do
82
- should_not_assign_header(X_XSS_PROTECTION_HEADER_NAME)
83
- subject.set_x_xss_protection_header(false)
84
- end
85
-
86
- it "does not set the X-Download-Options header if disabled" do
87
- should_not_assign_header(XDO_HEADER_NAME)
88
- subject.set_x_download_options_header(false)
89
- end
90
-
91
- it "does not set the X-Frame-Options header if disabled" do
92
- should_not_assign_header(XFO_HEADER_NAME)
93
- subject.set_x_frame_options_header(false)
94
- end
95
-
96
- it "does not set the X-Permitted-Cross-Domain-Policies header if disabled" do
97
- should_not_assign_header(XPCDP_HEADER_NAME)
98
- subject.set_x_permitted_cross_domain_policies_header(false)
99
- end
100
-
101
- it "does not set the HSTS header if disabled" do
102
- should_not_assign_header(HSTS_HEADER_NAME)
103
- subject.set_hsts_header(false)
104
- end
105
42
 
106
- it "does not set the HSTS header if request is over HTTP" do
107
- allow(subject).to receive_message_chain(:request, :ssl?).and_return(false)
108
- should_not_assign_header(HSTS_HEADER_NAME)
109
- subject.set_hsts_header({:include_subdomains => true})
110
- end
111
-
112
- it "does not set the HPKP header if disabled" do
113
- should_not_assign_header(HPKP_HEADER_NAME)
114
- subject.set_hpkp_header
115
- end
116
-
117
- it "does not set the HPKP header if request is over HTTP" do
118
- allow(subject).to receive_message_chain(:request, :ssl?).and_return(false)
119
- should_not_assign_header(HPKP_HEADER_NAME)
120
- subject.set_hpkp_header(:max_age => 1234)
121
- end
122
-
123
- it "does not set the CSP header if disabled" do
124
- stub_user_agent(USER_AGENTS[:chrome])
125
- should_not_assign_header(HEADER_NAME)
126
- subject.set_csp_header(false)
127
- end
128
-
129
- it "saves the options to the env when using script hashes" do
130
- opts = {
131
- :default_src => "'self'",
132
- :script_hash_middleware => true
133
- }
134
- stub_user_agent(USER_AGENTS[:chrome])
135
-
136
- expect(SecureHeaders::ContentSecurityPolicy).to receive(:add_to_env)
137
- subject.set_csp_header(opts)
138
- end
139
-
140
- context "when disabled by configuration settings" do
141
- it "does not set any headers when disabled" do
142
- ::SecureHeaders::Configuration.default do |config|
143
- config.hsts = false
144
- config.hpkp = false
145
- config.x_frame_options = false
146
- config.x_content_type_options = false
147
- config.x_xss_protection = false
148
- config.csp = false
149
- config.x_download_options = false
150
- config.x_permitted_cross_domain_policies = false
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
151
49
  end
152
- expect(subject).not_to receive(:set_header)
153
- set_security_headers(subject)
154
- reset_config
155
50
  end
156
- end
157
- end
158
51
 
159
- describe "SecureHeaders#header_hash" do
160
- def expect_default_values(hash)
161
- expect(hash[XFO_HEADER_NAME]).to eq(SecureHeaders::XFrameOptions::Constants::DEFAULT_VALUE)
162
- expect(hash[XDO_HEADER_NAME]).to eq(SecureHeaders::XDownloadOptions::Constants::DEFAULT_VALUE)
163
- expect(hash[HSTS_HEADER_NAME]).to eq(SecureHeaders::StrictTransportSecurity::Constants::DEFAULT_VALUE)
164
- expect(hash[X_XSS_PROTECTION_HEADER_NAME]).to eq(SecureHeaders::XXssProtection::Constants::DEFAULT_VALUE)
165
- expect(hash[X_CONTENT_TYPE_OPTIONS_HEADER_NAME]).to eq(SecureHeaders::XContentTypeOptions::Constants::DEFAULT_VALUE)
166
- expect(hash[XPCDP_HEADER_NAME]).to eq(SecureHeaders::XPermittedCrossDomainPolicies::Constants::DEFAULT_VALUE)
167
- 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
168
58
 
169
- it "produces a hash of headers given a hash as config" do
170
- hash = SecureHeaders::header_hash(:csp => {:default_src => "'none'", :img_src => "data:"})
171
- expect(hash['Content-Security-Policy-Report-Only']).to eq("default-src 'none'; img-src data:;")
172
- expect_default_values(hash)
173
- 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
174
64
 
175
- it "allows opting out" do
176
- hash = SecureHeaders::header_hash(:csp => false, :hpkp => false)
177
- expect(hash['Content-Security-Policy-Report-Only']).to be_nil
178
- expect(hash['Content-Security-Policy']).to be_nil
179
- 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'))
180
67
 
181
- it "allows opting out with config" do
182
- ::SecureHeaders::Configuration.default do |config|
183
- config.hsts = false
184
- config.csp = false
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)
185
71
  end
186
72
 
187
- hash = SecureHeaders::header_hash
188
- expect(hash['Content-Security-Policy-Report-Only']).to be_nil
189
- expect(hash['Content-Security-Policy']).to be_nil
190
- end
191
-
192
- it "produces a hash with a mix of config values, override values, and default values" do
193
- ::SecureHeaders::Configuration.default do |config|
194
- config.hsts = { :max_age => '123456'}
195
- config.hpkp = {
196
- :enforce => true,
197
- :max_age => 1000000,
198
- :include_subdomains => true,
199
- :report_uri => '//example.com/uri-directive',
200
- :pins => [
201
- {:sha256 => 'abc'},
202
- {:sha256 => '123'}
203
- ]
204
- }
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)
205
77
  end
206
78
 
207
- hash = SecureHeaders::header_hash(:csp => {:default_src => "'none'", :img_src => "data:"})
208
- ::SecureHeaders::Configuration.default do |config|
209
- config.hsts = nil
210
- config.hpkp = nil
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
211
85
  end
212
86
 
213
- expect(hash['Content-Security-Policy-Report-Only']).to eq("default-src 'none'; img-src data:;")
214
- expect(hash[XFO_HEADER_NAME]).to eq(SecureHeaders::XFrameOptions::Constants::DEFAULT_VALUE)
215
- expect(hash[HSTS_HEADER_NAME]).to eq("max-age=123456")
216
- expect(hash[HPKP_HEADER_NAME]).to eq(%{max-age=1000000; pin-sha256="abc"; pin-sha256="123"; report-uri="//example.com/uri-directive"; includeSubDomains})
217
- end
218
-
219
- it "produces a hash of headers with default config" do
220
- hash = SecureHeaders::header_hash
221
- expect(hash['Content-Security-Policy-Report-Only']).to eq(SecureHeaders::ContentSecurityPolicy::Constants::DEFAULT_CSP_HEADER)
222
- expect_default_values(hash)
223
- end
224
- end
225
-
226
- describe "#set_x_frame_options_header" do
227
- it "sets the X-Frame-Options header" do
228
- should_assign_header(XFO_HEADER_NAME, SecureHeaders::XFrameOptions::Constants::DEFAULT_VALUE)
229
- subject.set_x_frame_options_header
230
- end
231
-
232
- it "allows a custom X-Frame-Options header" do
233
- should_assign_header(XFO_HEADER_NAME, "DENY")
234
- subject.set_x_frame_options_header(:value => 'DENY')
235
- end
236
- end
237
-
238
- describe "#set_x_download_options_header" do
239
- it "sets the X-Download-Options header" do
240
- should_assign_header(XDO_HEADER_NAME, SecureHeaders::XDownloadOptions::Constants::DEFAULT_VALUE)
241
- subject.set_x_download_options_header
242
- end
243
-
244
- it "allows a custom X-Download-Options header" do
245
- should_assign_header(XDO_HEADER_NAME, "noopen")
246
- subject.set_x_download_options_header(:value => 'noopen')
247
- end
248
- end
249
-
250
- describe "#set_strict_transport_security" do
251
- it "sets the Strict-Transport-Security header" do
252
- should_assign_header(HSTS_HEADER_NAME, SecureHeaders::StrictTransportSecurity::Constants::DEFAULT_VALUE)
253
- subject.set_hsts_header
254
- end
255
-
256
- it "allows you to specific a custom max-age value" do
257
- should_assign_header(HSTS_HEADER_NAME, 'max-age=1234')
258
- subject.set_hsts_header(:max_age => 1234)
259
- end
260
-
261
- it "allows you to specify includeSubdomains" do
262
- should_assign_header(HSTS_HEADER_NAME, "max-age=#{HSTS_MAX_AGE}; includeSubdomains")
263
- subject.set_hsts_header(:max_age => HSTS_MAX_AGE, :include_subdomains => true)
264
- end
265
-
266
- it "allows you to specify preload" do
267
- should_assign_header(HSTS_HEADER_NAME, "max-age=#{HSTS_MAX_AGE}; includeSubdomains; preload")
268
- subject.set_hsts_header(:max_age => HSTS_MAX_AGE, :include_subdomains => true, :preload => true)
269
- end
270
- end
271
-
272
- describe "#set_public_key_pins" do
273
- it "sets the Public-Key-Pins header" do
274
- should_assign_header(HPKP_HEADER_NAME + "-Report-Only", "max-age=1234")
275
- subject.set_hpkp_header(:max_age => 1234)
276
- end
277
-
278
- it "allows you to enforce public key pinning" do
279
- should_assign_header(HPKP_HEADER_NAME, "max-age=1234")
280
- subject.set_hpkp_header(:max_age => 1234, :enforce => true)
281
- 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
282
92
 
283
- it "allows you to specific a custom max-age value" do
284
- should_assign_header(HPKP_HEADER_NAME + "-Report-Only", 'max-age=1234')
285
- subject.set_hpkp_header(:max_age => 1234)
286
- end
93
+ expect(SecureHeaders.header_hash_for(plaintext_request)[PublicKeyPins::HEADER_NAME]).to be_nil
94
+ end
287
95
 
288
- it "allows you to specify includeSubdomains" do
289
- should_assign_header(HPKP_HEADER_NAME, "max-age=1234; includeSubDomains")
290
- subject.set_hpkp_header(:max_age => 1234, :include_subdomains => true, :enforce => true)
291
- 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
292
109
 
293
- it "allows you to specify a report-uri" do
294
- should_assign_header(HPKP_HEADER_NAME, "max-age=1234; report-uri=\"https://foobar.com\"")
295
- subject.set_hpkp_header(:max_age => 1234, :report_uri => "https://foobar.com", :enforce => true)
296
- 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
297
120
 
298
- it "allows you to specify a report-uri with app_name" do
299
- should_assign_header(HPKP_HEADER_NAME, "max-age=1234; report-uri=\"https://foobar.com?enforce=true&app_name=my_app\"")
300
- subject.set_hpkp_header(:max_age => 1234, :report_uri => "https://foobar.com", :app_name => "my_app", :tag_report_uri => true, :enforce => true)
301
- end
302
- end
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
303
127
 
304
- describe "#set_x_xss_protection" do
305
- it "sets the X-XSS-Protection header" do
306
- should_assign_header(X_XSS_PROTECTION_HEADER_NAME, SecureHeaders::XXssProtection::Constants::DEFAULT_VALUE)
307
- subject.set_x_xss_protection_header
308
- 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
309
142
 
310
- it "sets a custom X-XSS-Protection header" do
311
- should_assign_header(X_XSS_PROTECTION_HEADER_NAME, '0')
312
- subject.set_x_xss_protection_header("0")
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'")
162
+ end
163
+ end
313
164
  end
314
165
 
315
- it "sets the block flag" do
316
- should_assign_header(X_XSS_PROTECTION_HEADER_NAME, '1; mode=block')
317
- subject.set_x_xss_protection_header(:mode => 'block', :value => 1)
318
- end
319
- end
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)
173
+ end
320
174
 
321
- describe "#set_x_content_type_options" do
322
- USER_AGENTS.each do |useragent|
323
- context "when using #{useragent}" do
324
- before(:each) do
325
- stub_user_agent(USER_AGENTS[useragent])
326
- end
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)
181
+ end
327
182
 
328
- it "sets the X-Content-Type-Options header" do
329
- should_assign_header(X_CONTENT_TYPE_OPTIONS_HEADER_NAME, SecureHeaders::XContentTypeOptions::Constants::DEFAULT_VALUE)
330
- subject.set_x_content_type_options_header
331
- 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
332
190
 
333
- it "lets you override X-Content-Type-Options" do
334
- should_assign_header(X_CONTENT_TYPE_OPTIONS_HEADER_NAME, 'nosniff')
335
- subject.set_x_content_type_options_header(:value => 'nosniff')
336
- 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)
337
197
  end
338
- end
339
- end
340
198
 
341
- describe "#set_csp_header" do
342
- context "when using Firefox" do
343
- it "sets CSP headers" do
344
- stub_user_agent(USER_AGENTS[:firefox])
345
- should_assign_header(HEADER_NAME + "-Report-Only", DEFAULT_CSP_HEADER)
346
- 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)
347
205
  end
348
- end
349
206
 
350
- context "when using Chrome" do
351
- it "sets default CSP header" do
352
- stub_user_agent(USER_AGENTS[:chrome])
353
- should_assign_header(HEADER_NAME + "-Report-Only", DEFAULT_CSP_HEADER)
354
- 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)
355
213
  end
356
- end
357
214
 
358
- context "when using a browser besides chrome/firefox" do
359
- it "sets the CSP header" do
360
- stub_user_agent(USER_AGENTS[:opera])
361
- should_assign_header(HEADER_NAME + "-Report-Only", DEFAULT_CSP_HEADER)
362
- 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)
363
221
  end
364
- end
365
- end
366
222
 
367
- describe "#set_x_permitted_cross_domain_policies_header" do
368
- it "sets the X-Permitted-Cross-Domain-Policies header" do
369
- should_assign_header(XPCDP_HEADER_NAME, SecureHeaders::XPermittedCrossDomainPolicies::Constants::DEFAULT_VALUE)
370
- subject.set_x_permitted_cross_domain_policies_header
371
- 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
372
230
 
373
- it "allows a custom X-Permitted-Cross-Domain-Policies header" do
374
- should_assign_header(XPCDP_HEADER_NAME, "master-only")
375
- 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
376
238
  end
377
239
  end
378
240
  end
data/spec/spec_helper.rb CHANGED
@@ -1,44 +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
- class DummyClass
35
- include ::SecureHeaders
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)
36
30
  end
37
31
 
38
- def should_assign_header name, value
39
- expect(response.headers).to receive(:[]=).with(name, value)
32
+ module SecureHeaders
33
+ class Configuration
34
+ class << self
35
+ def clear_configurations
36
+ @configurations = nil
37
+ end
38
+ end
39
+ end
40
40
  end
41
41
 
42
- def should_not_assign_header name
43
- expect(response.headers).not_to receive(:[]=).with(name, anything)
42
+ def reset_config
43
+ SecureHeaders::Configuration.clear_configurations
44
44
  end