escher 0.2.1 → 0.3.1
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.
- checksums.yaml +4 -4
- data/escher.gemspec +5 -1
- data/lib/escher/auth.rb +341 -0
- data/lib/escher/request/base.rb +29 -0
- data/lib/escher/request/factory.rb +24 -0
- data/lib/escher/request/hash_request.rb +67 -0
- data/lib/escher/request/legacy_request.rb +157 -0
- data/lib/escher/request/rack_request.rb +42 -0
- data/lib/escher/version.rb +2 -2
- data/lib/escher.rb +6 -2
- data/spec/escher/auth_spec.rb +408 -0
- data/spec/escher/request/factory_spec.rb +20 -0
- data/spec/escher/request/hash_request_spec.rb +121 -0
- data/spec/escher/request/rack_request_spec.rb +114 -0
- metadata +45 -21
- data/lib/escher/base.rb +0 -324
- data/lib/escher/request.rb +0 -83
- data/spec/escher_spec.rb +0 -358
data/lib/escher.rb
CHANGED
@@ -0,0 +1,408 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module Escher
|
4
|
+
describe Auth do
|
5
|
+
|
6
|
+
test_suites = {
|
7
|
+
# 'get-header-key-duplicate',
|
8
|
+
# 'get-header-value-order',
|
9
|
+
aws4: %w(
|
10
|
+
get-header-value-trim
|
11
|
+
get-relative
|
12
|
+
get-relative-relative
|
13
|
+
get-slash
|
14
|
+
get-slash-dot-slash
|
15
|
+
get-slash-pointless-dot
|
16
|
+
get-slashes
|
17
|
+
get-space
|
18
|
+
get-unreserved
|
19
|
+
get-utf8
|
20
|
+
get-vanilla
|
21
|
+
get-vanilla-empty-query-key
|
22
|
+
get-vanilla-query
|
23
|
+
get-vanilla-query-order-key
|
24
|
+
get-vanilla-query-order-key-case
|
25
|
+
get-vanilla-query-order-value
|
26
|
+
get-vanilla-query-unreserved
|
27
|
+
get-vanilla-ut8-query
|
28
|
+
post-header-key-case
|
29
|
+
post-header-key-sort
|
30
|
+
post-header-value-case
|
31
|
+
post-vanilla
|
32
|
+
post-vanilla-empty-query-value
|
33
|
+
post-vanilla-query
|
34
|
+
post-vanilla-query-nonunreserved
|
35
|
+
post-vanilla-query-space
|
36
|
+
post-x-www-form-urlencoded
|
37
|
+
post-x-www-form-urlencoded-parameters
|
38
|
+
),
|
39
|
+
emarsys: %w(
|
40
|
+
get-header-key-duplicate
|
41
|
+
post-header-key-order
|
42
|
+
post-header-value-spaces
|
43
|
+
post-header-value-spaces-within-quotes
|
44
|
+
)
|
45
|
+
}
|
46
|
+
|
47
|
+
ESCHER_AWS4_OPTIONS = {
|
48
|
+
algo_prefix: 'AWS4', vendor_key: 'AWS4', hash_algo: 'SHA256', auth_header_name: 'Authorization', date_header_name: 'Date'
|
49
|
+
}
|
50
|
+
|
51
|
+
ESCHER_MIXED_OPTIONS = {
|
52
|
+
algo_prefix: 'EMS', vendor_key: 'EMS', hash_algo: 'SHA256', auth_header_name: 'Authorization', date_header_name: 'Date', clock_skew: 10
|
53
|
+
}
|
54
|
+
|
55
|
+
ESCHER_EMARSYS_OPTIONS = ESCHER_MIXED_OPTIONS.merge(auth_header_name: 'X-Ems-Auth', date_header_name: 'X-Ems-Date')
|
56
|
+
|
57
|
+
GOOD_AUTH_HEADER = 'AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20110909/us-east-1/host/aws4_request, SignedHeaders=date;host, Signature=b27ccfbfa7df52a200ff74193ca6e32d4b48b8856fab7ebf1c595d0670a7e470'
|
58
|
+
|
59
|
+
|
60
|
+
|
61
|
+
def key_db
|
62
|
+
{
|
63
|
+
'AKIDEXAMPLE' => 'wJalrXUtnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY',
|
64
|
+
'th3K3y' => 'very_secure',
|
65
|
+
}
|
66
|
+
end
|
67
|
+
|
68
|
+
|
69
|
+
|
70
|
+
def credential_scope
|
71
|
+
%w(us-east-1 host aws4_request)
|
72
|
+
end
|
73
|
+
|
74
|
+
|
75
|
+
|
76
|
+
def client
|
77
|
+
{:api_key_id => 'AKIDEXAMPLE', :api_secret => 'wJalrXUtnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY'}
|
78
|
+
end
|
79
|
+
|
80
|
+
|
81
|
+
|
82
|
+
test_suites.each do |suite, tests|
|
83
|
+
tests.each do |test|
|
84
|
+
it "should calculate canonicalized request for #{test} in #{suite}" do
|
85
|
+
escher = described_class.new('us-east-1/host/aws4_request', ESCHER_AWS4_OPTIONS)
|
86
|
+
method, request_uri, body, headers = read_request(suite, test)
|
87
|
+
headers_to_sign = headers.map { |k| k[0].downcase }
|
88
|
+
path, query_parts = escher.parse_uri(request_uri)
|
89
|
+
canonicalized_request = escher.canonicalize(method, path, query_parts, body, headers, headers_to_sign)
|
90
|
+
check_canonicalized_request(canonicalized_request, suite, test)
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
|
96
|
+
test_suites.each do |suite, tests|
|
97
|
+
tests.each do |test|
|
98
|
+
it "should calculate string to sign for #{test} in #{suite}" do
|
99
|
+
method, request_uri, body, headers, date = read_request(suite, test)
|
100
|
+
escher = described_class.new('us-east-1/host/aws4_request', ESCHER_AWS4_OPTIONS.merge(current_time: Time.parse(date)))
|
101
|
+
headers_to_sign = headers.map { |k| k[0].downcase }
|
102
|
+
path, query_parts = escher.parse_uri(request_uri)
|
103
|
+
canonicalized_request = escher.canonicalize(method, path, query_parts, body, headers, headers_to_sign)
|
104
|
+
string_to_sign = escher.get_string_to_sign(canonicalized_request)
|
105
|
+
expect(string_to_sign).to eq(fixture(suite, test, 'sts'))
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
|
111
|
+
test_suites.each do |suite, tests|
|
112
|
+
tests.each do |test|
|
113
|
+
it "should calculate auth header for #{test} in #{suite}" do
|
114
|
+
method, request_uri, body, headers, date = read_request(suite, test)
|
115
|
+
escher = described_class.new('us-east-1/host/aws4_request', ESCHER_AWS4_OPTIONS.merge(current_time: Time.parse(date)))
|
116
|
+
headers_to_sign = headers.map { |k| k[0].downcase }
|
117
|
+
request = {
|
118
|
+
method: method,
|
119
|
+
uri: request_uri,
|
120
|
+
body: body,
|
121
|
+
headers: headers,
|
122
|
+
}
|
123
|
+
signed_headers = escher.sign!(request, client, headers_to_sign)[:headers].map { |k, v| {k.downcase => v} }.reduce({}, &:merge)
|
124
|
+
expect(signed_headers['authorization']).to eq(fixture(suite, test, 'authz'))
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
|
130
|
+
it 'should sign request' do
|
131
|
+
escher = described_class.new('us-east-1/iam/aws4_request', ESCHER_EMARSYS_OPTIONS.merge(current_time: Time.parse('20110909T233600Z')))
|
132
|
+
client = {:api_key_id => 'AKIDEXAMPLE', :api_secret => 'wJalrXUtnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY'}
|
133
|
+
|
134
|
+
input_headers = [
|
135
|
+
['host', 'iam.amazonaws.com'],
|
136
|
+
['content-type', 'application/x-www-form-urlencoded; charset=utf-8'],
|
137
|
+
]
|
138
|
+
|
139
|
+
expected_headers = {
|
140
|
+
'content-type' => 'application/x-www-form-urlencoded; charset=utf-8',
|
141
|
+
'host' => 'iam.amazonaws.com',
|
142
|
+
'x-ems-date' => '20110909T233600Z',
|
143
|
+
'x-ems-auth' => 'EMS-HMAC-SHA256 Credential=AKIDEXAMPLE/20110909/us-east-1/iam/aws4_request, SignedHeaders=content-type;host;x-ems-date, Signature=f36c21c6e16a71a6e8dc56673ad6354aeef49c577a22fd58a190b5fcf8891dbd',
|
144
|
+
}
|
145
|
+
headers_to_sign = %w(content-type)
|
146
|
+
|
147
|
+
request = {
|
148
|
+
method: 'POST',
|
149
|
+
uri: '/',
|
150
|
+
body: 'Action=ListUsers&Version=2010-05-08',
|
151
|
+
headers: input_headers,
|
152
|
+
}
|
153
|
+
|
154
|
+
downcase = escher.sign!(request, client, headers_to_sign)[:headers].map { |k, v| {k.downcase => v} }.reduce({}, &:merge)
|
155
|
+
expect(downcase).to eq expected_headers
|
156
|
+
end
|
157
|
+
|
158
|
+
|
159
|
+
it 'should generate presigned url' do
|
160
|
+
escher = described_class.new('us-east-1/host/aws4_request', ESCHER_MIXED_OPTIONS.merge(current_time: Time.parse('2011/05/11 12:00:00 UTC')))
|
161
|
+
expected_url =
|
162
|
+
'http://example.com/something?foo=bar&' + 'baz=barbaz&' +
|
163
|
+
'X-EMS-Algorithm=EMS-HMAC-SHA256&' +
|
164
|
+
'X-EMS-Credentials=th3K3y%2F20110511%2Fus-east-1%2Fhost%2Faws4_request&' +
|
165
|
+
'X-EMS-Date=20110511T120000Z&' +
|
166
|
+
'X-EMS-Expires=123456&' +
|
167
|
+
'X-EMS-SignedHeaders=host&' +
|
168
|
+
'X-EMS-Signature=fbc9dbb91670e84d04ad2ae7505f4f52ab3ff9e192b8233feeae57e9022c2b67'
|
169
|
+
|
170
|
+
client = {:api_key_id => 'th3K3y', :api_secret => 'very_secure'}
|
171
|
+
expect(escher.generate_signed_url('http://example.com/something?foo=bar&baz=barbaz', client, 123456)).to eq expected_url
|
172
|
+
end
|
173
|
+
|
174
|
+
|
175
|
+
it 'should validate presigned url' do
|
176
|
+
escher = described_class.new('us-east-1/host/aws4_request', ESCHER_MIXED_OPTIONS.merge(current_time: Time.parse('2011/05/12 21:59:00 UTC')))
|
177
|
+
presigned_uri =
|
178
|
+
'/something?foo=bar&' + 'baz=barbaz&' +
|
179
|
+
'X-EMS-Algorithm=EMS-HMAC-SHA256&' +
|
180
|
+
'X-EMS-Credentials=th3K3y%2F20110511%2Fus-east-1%2Fhost%2Faws4_request&' +
|
181
|
+
'X-EMS-Date=20110511T120000Z&' +
|
182
|
+
'X-EMS-Expires=123456&' +
|
183
|
+
'X-EMS-SignedHeaders=host&' +
|
184
|
+
'X-EMS-Signature=fbc9dbb91670e84d04ad2ae7505f4f52ab3ff9e192b8233feeae57e9022c2b67'
|
185
|
+
|
186
|
+
client = {:api_key_id => 'th3K3y', :api_secret => 'very_secure'}
|
187
|
+
expect { escher.authenticate({
|
188
|
+
:method => 'GET',
|
189
|
+
:headers => [%w(host example.com)],
|
190
|
+
:uri => presigned_uri,
|
191
|
+
:body => 'IRRELEVANT'
|
192
|
+
}, key_db) }.not_to raise_error
|
193
|
+
end
|
194
|
+
|
195
|
+
|
196
|
+
it 'should validate expiration' do
|
197
|
+
escher = described_class.new('us-east-1/host/aws4_request', ESCHER_MIXED_OPTIONS.merge(current_time: Time.parse('2011/05/12 22:20:00 UTC')))
|
198
|
+
presigned_uri =
|
199
|
+
'/something?foo=bar&' + 'baz=barbaz&' +
|
200
|
+
'X-EMS-Algorithm=EMS-HMAC-SHA256&' +
|
201
|
+
'X-EMS-Credentials=th3K3y%2F20110511%2Fus-east-1%2Fhost%2Faws4_request&' +
|
202
|
+
'X-EMS-Date=20110511T120000Z&' +
|
203
|
+
'X-EMS-Expires=123456&' +
|
204
|
+
'X-EMS-SignedHeaders=host&' +
|
205
|
+
'X-EMS-Signature=fbc9dbb91670e84d04ad2ae7505f4f52ab3ff9e192b8233feeae57e9022c2b67'
|
206
|
+
|
207
|
+
client = {:api_key_id => 'th3K3y', :api_secret => 'very_secure'}
|
208
|
+
expect { escher.authenticate({
|
209
|
+
:method => 'GET',
|
210
|
+
:headers => [%w(host example.com)],
|
211
|
+
:uri => presigned_uri,
|
212
|
+
:body => 'IRRELEVANT'
|
213
|
+
}, key_db) }.to raise_error(EscherError, 'The request date is not within the accepted time range')
|
214
|
+
end
|
215
|
+
|
216
|
+
|
217
|
+
it 'should validate request' do
|
218
|
+
headers = [
|
219
|
+
%w(Host host.foo.com),
|
220
|
+
['Date', 'Mon, 09 Sep 2011 23:36:00 GMT'],
|
221
|
+
['Authorization', GOOD_AUTH_HEADER],
|
222
|
+
]
|
223
|
+
expect { call_validate(headers) }.not_to raise_error
|
224
|
+
end
|
225
|
+
|
226
|
+
|
227
|
+
it 'should authenticate' do
|
228
|
+
headers = [
|
229
|
+
%w(Host host.foo.com),
|
230
|
+
['Date', 'Mon, 09 Sep 2011 23:36:00 GMT'],
|
231
|
+
['Authorization', GOOD_AUTH_HEADER],
|
232
|
+
]
|
233
|
+
expect(call_validate(headers)).to eq 'AKIDEXAMPLE'
|
234
|
+
end
|
235
|
+
|
236
|
+
|
237
|
+
it 'should detect if signatures do not match' do
|
238
|
+
headers = [
|
239
|
+
%w(Host host.foo.com),
|
240
|
+
['Date', 'Mon, 09 Sep 2011 23:36:00 GMT'],
|
241
|
+
['Authorization', 'AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20110909/us-east-1/host/aws4_request, SignedHeaders=date;host, Signature=ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff'],
|
242
|
+
]
|
243
|
+
expect { call_validate(headers) }.to raise_error(EscherError, 'The signatures do not match')
|
244
|
+
end
|
245
|
+
|
246
|
+
|
247
|
+
it 'should detect if dates are not on the same day' do
|
248
|
+
yesterday = '08'
|
249
|
+
headers = [
|
250
|
+
%w(Host host.foo.com),
|
251
|
+
['Date', "Mon, #{yesterday} Sep 2011 23:36:00 GMT"],
|
252
|
+
['Authorization', GOOD_AUTH_HEADER],
|
253
|
+
]
|
254
|
+
expect { call_validate(headers) }.to raise_error(EscherError, 'Invalid request date')
|
255
|
+
end
|
256
|
+
|
257
|
+
|
258
|
+
it 'should detect if date is not within the 15 minutes range' do
|
259
|
+
long_ago = '00'
|
260
|
+
headers = [
|
261
|
+
%w(Host host.foo.com),
|
262
|
+
['Date', "Mon, 09 Sep 2011 23:#{long_ago}:00 GMT"],
|
263
|
+
['Authorization', GOOD_AUTH_HEADER],
|
264
|
+
]
|
265
|
+
expect { call_validate(headers) }.to raise_error(EscherError, 'The request date is not within the accepted time range')
|
266
|
+
end
|
267
|
+
|
268
|
+
|
269
|
+
it 'should detect missing host header' do
|
270
|
+
headers = [
|
271
|
+
['Date', 'Mon, 09 Sep 2011 23:36:00 GMT'],
|
272
|
+
['Authorization', GOOD_AUTH_HEADER],
|
273
|
+
]
|
274
|
+
expect { call_validate(headers) }.to raise_error(EscherError, 'Missing header: Host')
|
275
|
+
end
|
276
|
+
|
277
|
+
|
278
|
+
it 'should detect missing date header' do
|
279
|
+
headers = [
|
280
|
+
%w(Host host.foo.com),
|
281
|
+
['Authorization', GOOD_AUTH_HEADER],
|
282
|
+
]
|
283
|
+
expect { call_validate(headers) }.to raise_error(EscherError, 'Missing header: Date')
|
284
|
+
end
|
285
|
+
|
286
|
+
|
287
|
+
it 'should detect missing auth header' do
|
288
|
+
headers = [
|
289
|
+
%w(Host host.foo.com),
|
290
|
+
['Date', 'Mon, 09 Sep 2011 23:36:00 GMT'],
|
291
|
+
]
|
292
|
+
expect { call_validate(headers) }.to raise_error(EscherError, 'Missing header: Authorization')
|
293
|
+
end
|
294
|
+
|
295
|
+
|
296
|
+
it 'should detect malformed auth header' do
|
297
|
+
headers = [
|
298
|
+
%w(Host host.foo.com),
|
299
|
+
['Date', 'Mon, 09 Sep 2011 23:36:00 GMT'],
|
300
|
+
['Authorization', 'AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20110909/us-east-1/host/aws4_request, SignedHeaders=date;host, Signature=UNPARSABLE'],
|
301
|
+
]
|
302
|
+
expect { call_validate(headers) }.to raise_error(EscherError, 'Malformed authorization header')
|
303
|
+
end
|
304
|
+
|
305
|
+
|
306
|
+
it 'should detect malformed credential scope' do
|
307
|
+
headers = [
|
308
|
+
%w(Host host.foo.com),
|
309
|
+
['Date', 'Mon, 09 Sep 2011 23:36:00 GMT'],
|
310
|
+
['Authorization', 'AWS4-HMAC-SHA256 Credential=BAD-CREDENTIAL-SCOPE, SignedHeaders=date;host, Signature=b27ccfbfa7df52a200ff74193ca6e32d4b48b8856fab7ebf1c595d0670a7e470'],
|
311
|
+
]
|
312
|
+
expect { call_validate(headers) }.to raise_error(EscherError, 'Malformed authorization header')
|
313
|
+
end
|
314
|
+
|
315
|
+
|
316
|
+
it 'should check mandatory signed headers: host' do
|
317
|
+
headers = [
|
318
|
+
%w(Host host.foo.com),
|
319
|
+
['Date', 'Mon, 09 Sep 2011 23:36:00 GMT'],
|
320
|
+
['Authorization', 'AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20110909/us-east-1/host/aws4_request, SignedHeaders=date, Signature=b27ccfbfa7df52a200ff74193ca6e32d4b48b8856fab7ebf1c595d0670a7e470'],
|
321
|
+
]
|
322
|
+
expect { call_validate(headers) }.to raise_error(EscherError, 'Host header is not signed')
|
323
|
+
end
|
324
|
+
|
325
|
+
|
326
|
+
it 'should check mandatory signed headers: date' do
|
327
|
+
headers = [
|
328
|
+
%w(Host host.foo.com),
|
329
|
+
['Date', 'Mon, 09 Sep 2011 23:36:00 GMT'],
|
330
|
+
['Authorization', 'AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20110909/us-east-1/host/aws4_request, SignedHeaders=host, Signature=b27ccfbfa7df52a200ff74193ca6e32d4b48b8856fab7ebf1c595d0670a7e470'],
|
331
|
+
]
|
332
|
+
expect { call_validate(headers) }.to raise_error(EscherError, 'Date header is not signed')
|
333
|
+
end
|
334
|
+
|
335
|
+
|
336
|
+
it 'should check algorithm' do
|
337
|
+
headers = [
|
338
|
+
%w(Host host.foo.com),
|
339
|
+
['Date', 'Mon, 09 Sep 2011 23:36:00 GMT'],
|
340
|
+
['Authorization', 'AWS4-HMAC-INVALID Credential=AKIDEXAMPLE/20110909/us-east-1/host/aws4_request, SignedHeaders=date;host, Signature=b27ccfbfa7df52a200ff74193ca6e32d4b48b8856fab7ebf1c595d0670a7e470'],
|
341
|
+
]
|
342
|
+
expect { call_validate(headers) }.to raise_error(EscherError, 'Only SHA256 and SHA512 hash algorithms are allowed')
|
343
|
+
end
|
344
|
+
|
345
|
+
|
346
|
+
it 'should check credential scope' do
|
347
|
+
headers = [
|
348
|
+
%w(Host host.foo.com),
|
349
|
+
['Date', 'Mon, 09 Sep 2011 23:36:00 GMT'],
|
350
|
+
['Authorization', 'AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20110909/us-east-1/INVALID/aws4_request, SignedHeaders=date;host, Signature=b27ccfbfa7df52a200ff74193ca6e32d4b48b8856fab7ebf1c595d0670a7e470'],
|
351
|
+
]
|
352
|
+
expect { call_validate(headers) }.to raise_error(EscherError, 'Invalid credentials')
|
353
|
+
end
|
354
|
+
|
355
|
+
|
356
|
+
it 'should convert dates' do
|
357
|
+
date_str = 'Fri, 09 Sep 2011 23:36:00 GMT'
|
358
|
+
expect(described_class.new('irrelevant', date_header_name: 'date', current_time: Time.parse(date_str)).format_date_for_header).to eq date_str
|
359
|
+
end
|
360
|
+
|
361
|
+
|
362
|
+
|
363
|
+
def call_validate(headers)
|
364
|
+
escher = described_class.new('us-east-1/host/aws4_request', ESCHER_AWS4_OPTIONS.merge(current_time: Time.parse('Mon, 09 Sep 2011 23:40:00 GMT')))
|
365
|
+
escher.authenticate({
|
366
|
+
:method => 'GET',
|
367
|
+
:headers => headers,
|
368
|
+
:uri => '/',
|
369
|
+
:body => '',
|
370
|
+
}, key_db)
|
371
|
+
end
|
372
|
+
|
373
|
+
|
374
|
+
|
375
|
+
def fixture(suite, test, extension)
|
376
|
+
open("spec/#{suite}_testsuite/#{test}.#{extension}").read
|
377
|
+
end
|
378
|
+
|
379
|
+
|
380
|
+
|
381
|
+
def get_host(headers)
|
382
|
+
headers.detect { |header| header[0].downcase == 'host' }[1]
|
383
|
+
end
|
384
|
+
|
385
|
+
|
386
|
+
|
387
|
+
def get_date(headers)
|
388
|
+
headers.detect { |header| header[0].downcase == 'date' }[1]
|
389
|
+
end
|
390
|
+
|
391
|
+
|
392
|
+
|
393
|
+
def read_request(suite, test, extension = 'req')
|
394
|
+
lines = (fixture(suite, test, extension) + "\n").lines.map(&:chomp)
|
395
|
+
method, uri = lines[0].split ' '
|
396
|
+
headers = lines[1..-3].map { |header| k, v = header.split(':', 2); [k, v] }
|
397
|
+
body = lines[-1]
|
398
|
+
[method, uri, body, headers, get_date(headers), get_host(headers)]
|
399
|
+
end
|
400
|
+
|
401
|
+
|
402
|
+
|
403
|
+
def check_canonicalized_request(creq, suite, test)
|
404
|
+
expect(creq).to eq(fixture(suite, test, 'creq'))
|
405
|
+
end
|
406
|
+
|
407
|
+
end
|
408
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'rack/request'
|
3
|
+
|
4
|
+
describe Escher::Request::Factory do
|
5
|
+
|
6
|
+
describe ".from_request" do
|
7
|
+
{{uri: "request uri"} => Escher::Request::HashRequest,
|
8
|
+
Struct.new(:uri) => Escher::Request::LegacyRequest,
|
9
|
+
Rack::Request.new({}) => Escher::Request::RackRequest}.each do |request, expected_class|
|
10
|
+
|
11
|
+
it "should return a #{expected_class.name} when the request to be wrapped is a #{request.class.name}" do
|
12
|
+
expect(expected_class).to receive(:new).with(request).and_return "#{expected_class.name} wrapping request"
|
13
|
+
|
14
|
+
expect(described_class.from_request request).to eq "#{expected_class.name} wrapping request"
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,121 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Escher::Request::HashRequest do
|
4
|
+
|
5
|
+
let(:request) { {headers: [], uri: '/'} }
|
6
|
+
subject { described_class.new request }
|
7
|
+
|
8
|
+
|
9
|
+
describe "#request" do
|
10
|
+
it "should return the underlying request object" do
|
11
|
+
expect(subject.request).to eq request
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
|
16
|
+
describe "#headers" do
|
17
|
+
it "should return the request headers" do
|
18
|
+
request[:headers] = [['HOST', 'some host'],
|
19
|
+
['SOME_HEADER', 'some header']]
|
20
|
+
|
21
|
+
expect(subject.headers).to eq [['HOST', 'some host'],
|
22
|
+
['SOME-HEADER', 'some header']]
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
|
27
|
+
describe "#has_header?" do
|
28
|
+
it "should return true if request has specified header, false otherwise" do
|
29
|
+
request[:headers] = [['HOST', 'some host']]
|
30
|
+
|
31
|
+
expect(subject.has_header? 'host').to be_truthy
|
32
|
+
expect(subject.has_header? 'no-such-header').to be_falsey
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
|
37
|
+
describe "#header" do
|
38
|
+
it "should return the value for the requested header" do
|
39
|
+
request[:headers] = [['HOST', 'some host']]
|
40
|
+
|
41
|
+
expect(subject.header 'host').to eq 'some host'
|
42
|
+
end
|
43
|
+
|
44
|
+
it "should return nil if no such header exists" do
|
45
|
+
expect(subject.header 'host').to be_nil
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
|
50
|
+
describe "#set_header" do
|
51
|
+
it "should add the specified header to the request" do
|
52
|
+
subject.set_header 'TEST_HEADER', 'test value'
|
53
|
+
|
54
|
+
expect(subject.has_header? 'test-header').to be_truthy
|
55
|
+
expect(subject.header 'test-header').to eq 'test value'
|
56
|
+
end
|
57
|
+
|
58
|
+
it "should return nil if no such header exists" do
|
59
|
+
expect(subject.header 'no-such-header').to be_nil
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
|
64
|
+
describe "#method" do
|
65
|
+
it "should return the request method" do
|
66
|
+
request[:method] = 'GET'
|
67
|
+
|
68
|
+
expect(subject.method).to eq 'GET'
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
|
73
|
+
describe "#body" do
|
74
|
+
it "should return the request body" do
|
75
|
+
request[:body] = 'request body'
|
76
|
+
|
77
|
+
expect(subject.body).to eq 'request body'
|
78
|
+
end
|
79
|
+
|
80
|
+
it "should return empty string for no body" do
|
81
|
+
expect(subject.body).to eq ''
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
|
86
|
+
describe "#path" do
|
87
|
+
it "should return the request path" do
|
88
|
+
request[:uri] = '/resources/id?search=query'
|
89
|
+
|
90
|
+
expect(subject.path).to eq '/resources/id'
|
91
|
+
end
|
92
|
+
|
93
|
+
it "should return the original path unnormalized" do
|
94
|
+
request[:uri] = '//'
|
95
|
+
|
96
|
+
expect(subject.path).to eq '//'
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
|
101
|
+
describe "#query_values" do
|
102
|
+
it "should return the request query parameters as an array of key-value pairs" do
|
103
|
+
request[:uri] = '/resources/id?search=query¶m=value'
|
104
|
+
|
105
|
+
expect(subject.query_values).to eq [['search', 'query'], ['param', 'value']]
|
106
|
+
end
|
107
|
+
|
108
|
+
it "should return the query parameters regardless of fragments" do
|
109
|
+
request[:uri] = "/?@\#$%^&+=/,?><`\";:\\|][{}"
|
110
|
+
|
111
|
+
expect(subject.query_values).to eq [["@\#$%^"], ["+", "/,?><`\";:\\|][{}"]]
|
112
|
+
end
|
113
|
+
|
114
|
+
it "should return an empty array if the request has no query parameters" do
|
115
|
+
request[:uri] = '/resources/id'
|
116
|
+
|
117
|
+
expect(subject.query_values).to eq []
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
end
|
@@ -0,0 +1,114 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'rack/request'
|
3
|
+
|
4
|
+
describe Escher::Request::RackRequest do
|
5
|
+
|
6
|
+
let(:request_params) { {"PATH_INFO" => "/", } }
|
7
|
+
let(:request) { Rack::Request.new request_params }
|
8
|
+
|
9
|
+
subject { described_class.new request }
|
10
|
+
|
11
|
+
|
12
|
+
describe "#request" do
|
13
|
+
it "should return the underlying request object" do
|
14
|
+
expect(subject.request).to eq request
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
|
19
|
+
describe "#headers" do
|
20
|
+
it "should return only the HTTP request headers" do
|
21
|
+
request_params.merge! 'HTTP_HOST' => 'some host',
|
22
|
+
'SOME_HEADER' => 'some header'
|
23
|
+
|
24
|
+
expect(subject.headers).to eq [['HOST', 'some host']]
|
25
|
+
end
|
26
|
+
|
27
|
+
it "should replace underscores with dashes in the header name" do
|
28
|
+
request_params.merge! 'HTTP_HOST_NAME' => 'some host'
|
29
|
+
|
30
|
+
expect(subject.headers).to eq [['HOST-NAME', 'some host']]
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
|
35
|
+
describe "#has_header?" do
|
36
|
+
it "should return true if request has specified header, false otherwise" do
|
37
|
+
request_params.merge! 'HTTP_HOST_NAME' => 'some host'
|
38
|
+
|
39
|
+
expect(subject.has_header? 'host-name').to be_truthy
|
40
|
+
expect(subject.has_header? 'no-such-header').to be_falsey
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
|
45
|
+
describe "#header" do
|
46
|
+
it "should return the value for the requested header" do
|
47
|
+
request_params.merge! 'HTTP_HOST' => 'some host'
|
48
|
+
|
49
|
+
expect(subject.header 'host').to eq 'some host'
|
50
|
+
end
|
51
|
+
|
52
|
+
it "should return nil if no such header exists" do
|
53
|
+
expect(subject.header 'host').to be_nil
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
|
58
|
+
describe "#method" do
|
59
|
+
it "should return the request method" do
|
60
|
+
request_params.merge! 'REQUEST_METHOD' => 'GET'
|
61
|
+
|
62
|
+
expect(subject.method).to eq 'GET'
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
|
67
|
+
describe "#body" do
|
68
|
+
it "should return the request body" do
|
69
|
+
request_params.merge! 'rack.input' => 'request body'
|
70
|
+
|
71
|
+
expect(subject.body).to eq 'request body'
|
72
|
+
end
|
73
|
+
|
74
|
+
it "should return empty string for no body" do
|
75
|
+
expect(subject.body).to eq ''
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
|
80
|
+
describe "#path" do
|
81
|
+
it "should return the request path" do
|
82
|
+
request_params.merge! 'REQUEST_PATH' => '/resources/id///'
|
83
|
+
|
84
|
+
expect(subject.path).to eq '/resources/id///'
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
|
89
|
+
describe "#query_values" do
|
90
|
+
it "should return the request query parameters as an array of key-value pairs" do
|
91
|
+
request_params.merge! 'QUERY_STRING' => 'search=query¶m=value'
|
92
|
+
|
93
|
+
expect(subject.query_values).to eq [['search', 'query'], ['param', 'value']]
|
94
|
+
end
|
95
|
+
|
96
|
+
it "should return the query parameters regardless of fragments" do
|
97
|
+
request_params.merge! 'QUERY_STRING' => "@\#$%^&+=/,?><`\";:\\|][{}"
|
98
|
+
|
99
|
+
expect(subject.query_values).to eq [["@\#$%^"], ["+", "/,?><`\";:\\|][{}"]]
|
100
|
+
end
|
101
|
+
|
102
|
+
it "should return an empty array if the request has no query parameters" do
|
103
|
+
expect(subject.query_values).to eq []
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
|
108
|
+
describe "#set_header" do
|
109
|
+
it "should ignore calls" do
|
110
|
+
expect { subject.set_header 'test-header', 'test value' }.not_to raise_error
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
end
|