secure_headers 2.5.3 → 3.0.0.pre

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 (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