http-security 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (73) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +19 -0
  3. data/.rspec +1 -0
  4. data/.travis.yml +21 -0
  5. data/.yardopts +1 -0
  6. data/ChangeLog.md +17 -0
  7. data/Gemfile +17 -0
  8. data/LICENSE.txt +20 -0
  9. data/README.md +90 -0
  10. data/Rakefile +34 -0
  11. data/http-security.gemspec +23 -0
  12. data/lib/http/security.rb +2 -0
  13. data/lib/http/security/exceptions.rb +8 -0
  14. data/lib/http/security/headers.rb +12 -0
  15. data/lib/http/security/headers/cache_control.rb +36 -0
  16. data/lib/http/security/headers/content_security_policy.rb +71 -0
  17. data/lib/http/security/headers/content_security_policy_report_only.rb +10 -0
  18. data/lib/http/security/headers/pragma.rb +24 -0
  19. data/lib/http/security/headers/public_key_pins.rb +60 -0
  20. data/lib/http/security/headers/public_key_pins_report_only.rb +10 -0
  21. data/lib/http/security/headers/set_cookie.rb +75 -0
  22. data/lib/http/security/headers/strict_transport_security.rb +29 -0
  23. data/lib/http/security/headers/x_content_type_options.rb +24 -0
  24. data/lib/http/security/headers/x_frame_options.rb +39 -0
  25. data/lib/http/security/headers/x_permitted_cross_domain_policies.rb +47 -0
  26. data/lib/http/security/headers/x_xss_protection.rb +34 -0
  27. data/lib/http/security/http_date.rb +13 -0
  28. data/lib/http/security/malformed_header.rb +33 -0
  29. data/lib/http/security/parsers.rb +14 -0
  30. data/lib/http/security/parsers/cache_control.rb +62 -0
  31. data/lib/http/security/parsers/content_security_policy.rb +128 -0
  32. data/lib/http/security/parsers/content_security_policy_report_only.rb +10 -0
  33. data/lib/http/security/parsers/expires.rb +19 -0
  34. data/lib/http/security/parsers/parser.rb +408 -0
  35. data/lib/http/security/parsers/pragma.rb +25 -0
  36. data/lib/http/security/parsers/public_key_pins.rb +43 -0
  37. data/lib/http/security/parsers/public_key_pins_report_only.rb +10 -0
  38. data/lib/http/security/parsers/set_cookie.rb +62 -0
  39. data/lib/http/security/parsers/strict_transport_security.rb +42 -0
  40. data/lib/http/security/parsers/x_content_type_options.rb +19 -0
  41. data/lib/http/security/parsers/x_frame_options.rb +47 -0
  42. data/lib/http/security/parsers/x_permitted_cross_domain_policies.rb +33 -0
  43. data/lib/http/security/parsers/x_xss_protection.rb +27 -0
  44. data/lib/http/security/response.rb +323 -0
  45. data/lib/http/security/version.rb +5 -0
  46. data/spec/data/alexa.csv +100 -0
  47. data/spec/headers/cache_control_spec.rb +40 -0
  48. data/spec/headers/content_security_policy_spec.rb +46 -0
  49. data/spec/headers/pragma_spec.rb +26 -0
  50. data/spec/headers/public_key_pins_spec.rb +68 -0
  51. data/spec/headers/set_cookie_spec.rb +122 -0
  52. data/spec/headers/strict_transport_security_spec.rb +39 -0
  53. data/spec/headers/x_content_type_options_spec.rb +26 -0
  54. data/spec/headers/x_frame_options_spec.rb +86 -0
  55. data/spec/headers/x_permitted_cross_domain_policies_spec.rb +108 -0
  56. data/spec/headers/x_xss_protection_spec.rb +59 -0
  57. data/spec/parsers/cache_control_spec.rb +26 -0
  58. data/spec/parsers/content_security_policy_report_only_spec.rb +48 -0
  59. data/spec/parsers/content_security_policy_spec.rb +74 -0
  60. data/spec/parsers/expires_spec.rb +71 -0
  61. data/spec/parsers/parser_spec.rb +317 -0
  62. data/spec/parsers/pragma_spec.rb +10 -0
  63. data/spec/parsers/public_key_pins_spec.rb +81 -0
  64. data/spec/parsers/set_cookie_spec.rb +55 -0
  65. data/spec/parsers/strict_transport_security_spec.rb +62 -0
  66. data/spec/parsers/x_content_type_options_spec.rb +10 -0
  67. data/spec/parsers/x_frame_options_spec.rb +24 -0
  68. data/spec/parsers/x_permitted_cross_domain_policies_spec.rb +34 -0
  69. data/spec/parsers/x_xss_protection_spec.rb +39 -0
  70. data/spec/response_spec.rb +262 -0
  71. data/spec/spec_helper.rb +13 -0
  72. data/tasks/alexa.rb +40 -0
  73. metadata +171 -0
@@ -0,0 +1,317 @@
1
+ require "spec_helper"
2
+ require 'http/security/parsers/parser'
3
+
4
+ describe Parsers::Parser do
5
+ describe "#http_date" do
6
+ subject { super().http_date }
7
+
8
+ it "parses rfc1123-date" do
9
+ date = "Thu, 04 Dec 2015 16:00:00 GMT"
10
+
11
+ expect(subject.parse(date)).to eq(date: date)
12
+ end
13
+
14
+ it "parses rfc850-date" do
15
+ date = "Thursday, 04-Dec-15 16:00:00 GMT"
16
+
17
+ expect(subject.parse(date)).to eq(date: date)
18
+ end
19
+
20
+ it "parses rfc1123-date" do
21
+ date = "Thu Dec 04 16:00:00 2015"
22
+
23
+ expect(subject.parse(date)).to eq(date: date)
24
+ end
25
+ end
26
+
27
+ describe "#header_extension" do
28
+ subject { super().header_extension }
29
+
30
+ let(:name) { 'foo' }
31
+
32
+ context "when parsing a token" do
33
+ it "should tag the token name" do
34
+ expect(subject.parse(name)).to eq(name: name)
35
+ end
36
+ end
37
+
38
+ context "when parsing a token and a value" do
39
+ let(:value) { 'bar' }
40
+
41
+ it "should tag the token and value" do
42
+ expect(subject.parse("#{name}=#{value}")).to eq(
43
+ name: name,
44
+ value: value
45
+ )
46
+ end
47
+ end
48
+ end
49
+
50
+ describe "#uri" do
51
+ let(:transform) { Parsers::Parser::Transform.new }
52
+ subject { super().uri }
53
+
54
+ it "parses a uri without a scheme specified" do
55
+ uri = "www.example.com"
56
+ expect(transform.apply(subject.parse(uri))).to eq(URI.parse(uri))
57
+ end
58
+
59
+ it "parses a uri with a scheme specified" do
60
+ uri = "https://www.example.com"
61
+ expect(transform.apply(subject.parse(uri))).to eq(URI.parse(uri))
62
+ end
63
+
64
+ it "parses a uri with a path specified" do
65
+ uri = "http://www.example.com/about"
66
+ expect(transform.apply(subject.parse(uri))).to eq(URI.parse(uri))
67
+ end
68
+
69
+ it "parses a uri with parameters" do
70
+ uri = "http://www.example.com/about?parameter1=val1&parameter2=val2"
71
+ expect(transform.apply(subject.parse(uri))).to eq(URI.parse(uri))
72
+ end
73
+
74
+ it "parses a uri with a redirect address in its parameters" do
75
+ uri = "http://www.example.com/url?sa=X&q=http://example2.com/article/headline_20101013&ct=ga&cad=:n1:n2:t1286988171:&cd=yQ=AG-Tx"
76
+ expect(transform.apply(subject.parse(uri))).to eq(URI.parse(uri))
77
+ end
78
+
79
+ it "parses a uri with fragments" do
80
+ uri = "http://www.example.com/url#fragment"
81
+ expect(transform.apply(subject.parse(uri))).to eq(URI.parse(uri))
82
+ end
83
+
84
+ it "parses an ftp uri" do
85
+ uri = "ftp://ftp.is.co.za/rfc/rfc1808.txt"
86
+ expect(transform.apply(subject.parse(uri))).to eq(URI.parse(uri))
87
+ end
88
+
89
+ it "parses a uri with a port" do
90
+ uri = "telnet://192.0.2.16:80/"
91
+ expect(transform.apply(subject.parse(uri))).to eq(URI.parse(uri))
92
+ end
93
+ end
94
+
95
+ describe described_class::Transform do
96
+ describe "boolean" do
97
+ it "should map '0' to false" do
98
+ expect(subject.apply({boolean: '0'})).to be false
99
+ end
100
+
101
+ it "should map 'no' to false" do
102
+ expect(subject.apply({boolean: 'no'})).to be false
103
+ end
104
+
105
+ it "should map 'false' to false" do
106
+ expect(subject.apply({boolean: 'false'})).to be false
107
+ end
108
+
109
+ it "should map '1' to true" do
110
+ expect(subject.apply({boolean: '1'})).to be true
111
+ end
112
+
113
+ it "should map 'yes' to true" do
114
+ expect(subject.apply({boolean: 'yes'})).to be true
115
+ end
116
+
117
+ it "should map 'true' to false" do
118
+ expect(subject.apply({boolean: 'true'})).to be true
119
+ end
120
+ end
121
+
122
+ describe "numeric" do
123
+ it "should coerce Strings to Integer values" do
124
+ expect(subject.apply({numeric: '42'})).to be 42
125
+ end
126
+ end
127
+
128
+ describe "escaped_char" do
129
+ context "when the escaped char is a control character" do
130
+ let(:char) { 'n' }
131
+
132
+ it "should map it to the control character" do
133
+ expect(subject.apply({escaped_char: 'n'})).to be == "\n"
134
+ end
135
+ end
136
+
137
+ context "when the escaped char is a printable character" do
138
+ let(:char) { 'x' }
139
+
140
+ it "should return the printable character" do
141
+ expect(subject.apply({escaped_char: char})).to be == char
142
+ end
143
+ end
144
+ end
145
+
146
+ describe "string" do
147
+ context "when given one String" do
148
+ let(:string) { "foo bar" }
149
+
150
+ it "should return the String" do
151
+ expect(subject.apply({string: string})).to be == string
152
+ end
153
+ end
154
+
155
+ context "when given multiple Strings" do
156
+ let(:strings) { ['foo', "\n", 'bar'] }
157
+
158
+ it "should join the Strings" do
159
+ expect(subject.apply({string: strings})).to be == strings.join
160
+ end
161
+ end
162
+ end
163
+
164
+ describe "date" do
165
+ let(:string) { 'Tue, 24 Mar 2015 00:00:00 GMT' }
166
+ let(:date) { Date.parse(string) }
167
+
168
+ it "should return an HTTPDate" do
169
+ expect(subject.apply({date: string})).to be_kind_of(HTTPDate)
170
+ end
171
+
172
+ it "should coerce Strings to Date values" do
173
+ expect(subject.apply({date: string})).to be == date
174
+ end
175
+ end
176
+
177
+ describe "uri" do
178
+ let(:string) { 'https://www.example.com/?foo=bar' }
179
+ let(:uri) { URI(string) }
180
+
181
+ it "should parse Strings as URIs" do
182
+ expect(subject.apply({uri: string})).to be == uri
183
+ end
184
+ end
185
+
186
+ describe "list" do
187
+ context "when given a single element" do
188
+ let(:element) { 'foo' }
189
+
190
+ it "should return an Array of the element" do
191
+ expect(subject.apply({list: element})).to be == [element]
192
+ end
193
+ end
194
+
195
+ context "when given multiple elements" do
196
+ let(:elements) { %w[foo bar baz] }
197
+
198
+ it "should return the elements" do
199
+ expect(subject.apply({list: elements})).to be == elements
200
+ end
201
+ end
202
+ end
203
+
204
+ describe "key" do
205
+ let(:name) { 'foo' }
206
+ let(:key) { :foo }
207
+
208
+ it "should return a Hash with the name and true" do
209
+ expect(subject.apply({key: name})).to be == {key => true}
210
+ end
211
+
212
+ context "when given mixed-case String" do
213
+ let(:name) { 'fooBar' }
214
+ let(:key) { :foobar }
215
+
216
+ it "should downcase the String" do
217
+ expect(subject.apply({key: name})).to be == {key => true}
218
+ end
219
+ end
220
+
221
+ context "when given a hyphenated String" do
222
+ let(:name) { 'foo-bar' }
223
+ let(:key) { :foo_bar }
224
+
225
+ it "should replace the hyphens with underscores" do
226
+ expect(subject.apply({key: name})).to be == {key => true}
227
+ end
228
+ end
229
+ end
230
+
231
+ describe "key with simple value" do
232
+ let(:name) { 'foo' }
233
+ let(:key) { :foo }
234
+ let(:value) { 'bar' }
235
+
236
+ it "should return a Hash of the name and value" do
237
+ expect(subject.apply({key: name, value: value})).to be == {
238
+ key => value
239
+ }
240
+ end
241
+ end
242
+
243
+ describe "key with values" do
244
+ let(:name) { 'foo' }
245
+ let(:key) { :foo }
246
+ let(:value) { {'x' => 1} }
247
+
248
+ it "should return a Hash of the name and value" do
249
+ expect(subject.apply({key: name, values: value})).to be == {
250
+ key => value
251
+ }
252
+ end
253
+ end
254
+
255
+ describe "name" do
256
+ let(:name) { 'foo' }
257
+
258
+ it "should return a Hash with the name and true" do
259
+ expect(subject.apply({name: name})).to be == {name => true}
260
+ end
261
+ end
262
+
263
+ describe "name with simple value" do
264
+ let(:name) { 'foo' }
265
+ let(:value) { 'bar' }
266
+
267
+ it "should return a Hash of the name and value" do
268
+ expect(subject.apply({name: name, value: value})).to be == {
269
+ name => value
270
+ }
271
+ end
272
+ end
273
+
274
+ describe "name with a tree of values" do
275
+ let(:name) { 'foo' }
276
+ let(:tree) { {'x' => 1} }
277
+
278
+ it "should return a Hash of the name and values" do
279
+ expect(subject.apply({name: name, values: tree})).to be == {
280
+ name => tree
281
+ }
282
+ end
283
+ end
284
+
285
+ describe "directives" do
286
+ context "when given a single Hash" do
287
+ let(:hash) { {foo: 'bar'} }
288
+
289
+ it "should return the Hash" do
290
+ expect(subject.apply({directives: hash})).to be == hash
291
+ end
292
+ end
293
+
294
+ context "when given multiple Hashes" do
295
+ let(:hash1) { {foo: 'bar'} }
296
+ let(:hash2) { {baz: 'quix'} }
297
+ let(:hashes) { [hash1, hash2] }
298
+ let(:hash) { hash1.merge(hash2) }
299
+
300
+ it "should return a merged Hash" do
301
+ expect(subject.apply({directives: hashes})).to be == hash
302
+ end
303
+
304
+ context "when the Hashes share key names" do
305
+ let(:hash1) { {foo: '1', bar: '2'} }
306
+ let(:hash2) { {bar: '3', baz: '4'} }
307
+ let(:hashes) { [hash1, hash2] }
308
+ let(:hash) { {foo: '1', bar: ['2', '3'], baz: '4'} }
309
+
310
+ it "should combine the values into an Array" do
311
+ expect(subject.apply({directives: hashes})).to be == hash
312
+ end
313
+ end
314
+ end
315
+ end
316
+ end
317
+ end
@@ -0,0 +1,10 @@
1
+ require "spec_helper"
2
+ require "http/security/parsers/pragma"
3
+
4
+ describe Parsers::Pragma do
5
+ it "accepts no-cache" do
6
+ header = "no-cache"
7
+
8
+ expect(subject.parse(header)).to be == {no_cache: true}
9
+ end
10
+ end
@@ -0,0 +1,81 @@
1
+ require 'spec_helper'
2
+ require 'http/security/parsers/public_key_pins'
3
+
4
+ describe Parsers::PublicKeyPins do
5
+ let(:pin_sha256) { 'klO23nT2ehFDXCfx3eHTDRESMz3asj1muO+4aIdjiuY=' }
6
+ let(:header) { "pin-sha256=\"#{pin_sha256}\"" }
7
+
8
+ it "parses the one pin-sha256=..." do
9
+ expect(subject.parse(header)).to be == {pin_sha256: pin_sha256}
10
+ end
11
+
12
+ context "when given multiple pin-sha256= directives" do
13
+ let(:primary) { pin_sha256 }
14
+ let(:secondary) { 'M8HztCzM3elUxkcjR2S5P4hhyBNf6lHkmjAHKhpGPWE=' }
15
+ let(:header) { "pin-sha256=\"#{primary}\"; pin-sha256=\"#{secondary}\"" }
16
+
17
+ it "parses the pin-sha256=... directives into an Array" do
18
+ expect(subject.parse(header)).to be == {pin_sha256: [primary, secondary]}
19
+ end
20
+ end
21
+
22
+ context "when given pin- directives with unsupported hash algorithms" do
23
+ let(:pin_sha9000) { "foo" }
24
+ let(:header) { "#{super()}; pin-sha9000=\"#{pin_sha9000}\"" }
25
+
26
+ it "parses the unsupported pin- directives" do
27
+ expect(subject.parse(header)).to be == {
28
+ pin_sha256: pin_sha256,
29
+ 'pin-sha9000' => pin_sha9000
30
+ }
31
+ end
32
+ end
33
+
34
+ context "when the max-age= directive is present" do
35
+ let(:max_age) { 31536000 }
36
+ let(:header) { "pin-sha256=\"#{pin_sha256}\"; max-age=#{max_age}" }
37
+
38
+ it "accepts pin-sha256=...; max-age=..." do
39
+ expect(subject.parse(header)).to be == {
40
+ pin_sha256: pin_sha256,
41
+ max_age: max_age
42
+ }
43
+ end
44
+
45
+ context "when the includeSubdomains directive is present" do
46
+ let(:header) { "#{super()}; includeSubdomains" }
47
+
48
+ it "accepts pin-sha256=...; max-age=...; includeSubdomains" do
49
+ expect(subject.parse(header)).to be == {
50
+ pin_sha256: pin_sha256,
51
+ max_age: max_age,
52
+ includesubdomains: true
53
+ }
54
+ end
55
+ end
56
+
57
+ context "when the report-uri directive is present" do
58
+ let(:report_uri) { URI('https://www.example.com/') }
59
+ let(:header) { "#{super()}; report-uri=\"#{report_uri}\"" }
60
+
61
+ it "accepts pin-sha256=...; max-age=...; includeSubdomains" do
62
+ expect(subject.parse(header)).to be == {
63
+ pin_sha256: pin_sha256,
64
+ max_age: max_age,
65
+ report_uri: report_uri
66
+ }
67
+ end
68
+ end
69
+ end
70
+
71
+ context "when the strict directive is present" do
72
+ let(:header) { "#{super()}; strict" }
73
+
74
+ it "accepts pin-sha256=...; strict" do
75
+ expect(subject.parse(header)).to be == {
76
+ pin_sha256: pin_sha256,
77
+ strict: true
78
+ }
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,55 @@
1
+ require "spec_helper"
2
+ require "http/security/parsers/set_cookie"
3
+
4
+ describe Parsers::SetCookie do
5
+ it "accepts 'name=value'" do
6
+ expect(subject.parse('foo=bar')).to be == [{cookie: {foo: 'bar'}}]
7
+ end
8
+
9
+ it "accepts 'name=value; Expires=...'" do
10
+ expires = "Wed, 09 Jun 2021 10:18:14 GMT"
11
+
12
+ expect(subject.parse("foo=bar; Expires=#{expires}")).to be == [{
13
+ cookie: {foo: 'bar'},
14
+ expires: Date.parse(expires)
15
+ }]
16
+ end
17
+
18
+ it "accepts 'name=value; Path=...; Expires=...; Secure; Domain=...; HttpOnly'" do
19
+ path = '/accounts'
20
+ expires = "Wed, 09 Jun 2021 10:18:14 GMT"
21
+ domain = '.example.com'
22
+
23
+ expect(subject.parse("foo=bar; Path=#{path}; Expires=#{expires}; Secure; Domain=#{domain}; HttpOnly")).to be == [{
24
+ cookie: {foo: 'bar'},
25
+ path: path,
26
+ expires: Date.parse(expires),
27
+ secure: 'Secure',
28
+ domain: domain,
29
+ http_only: 'HttpOnly'
30
+ }]
31
+ end
32
+
33
+ it "accepts multiple cookie values" do
34
+ path = '/'
35
+ domain = '.twitter.com'
36
+ expires = 'Sat, 19 Nov 2016 00:27:36 GMT'
37
+
38
+ expect(subject.parse("foo=bar; Path=#{path}; Domain=#{domain}; Secure; HTTPOnly, bar=baz; Domain=#{domain}; Path=#{path}; Expires=#{expires}")).to be == [
39
+ {
40
+ cookie: {foo: 'bar'},
41
+ path: path,
42
+ domain: domain,
43
+ secure: 'Secure',
44
+ http_only: 'HTTPOnly'
45
+ },
46
+
47
+ {
48
+ cookie: {bar: 'baz'},
49
+ domain: domain,
50
+ path: path,
51
+ expires: Date.parse(expires)
52
+ }
53
+ ]
54
+ end
55
+ end