http-security 0.1.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 (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