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/spec/escher_spec.rb
DELETED
@@ -1,358 +0,0 @@
|
|
1
|
-
require 'spec_helper'
|
2
|
-
|
3
|
-
test_suites = {
|
4
|
-
# 'get-header-key-duplicate',
|
5
|
-
# 'get-header-value-order',
|
6
|
-
aws4: %w(
|
7
|
-
get-header-value-trim
|
8
|
-
get-relative
|
9
|
-
get-relative-relative
|
10
|
-
get-slash
|
11
|
-
get-slash-dot-slash
|
12
|
-
get-slash-pointless-dot
|
13
|
-
get-slashes
|
14
|
-
get-space
|
15
|
-
get-unreserved
|
16
|
-
get-utf8
|
17
|
-
get-vanilla
|
18
|
-
get-vanilla-empty-query-key
|
19
|
-
get-vanilla-query
|
20
|
-
get-vanilla-query-order-key
|
21
|
-
get-vanilla-query-order-key-case
|
22
|
-
get-vanilla-query-order-value
|
23
|
-
get-vanilla-query-unreserved
|
24
|
-
get-vanilla-ut8-query
|
25
|
-
post-header-key-case
|
26
|
-
post-header-key-sort
|
27
|
-
post-header-value-case
|
28
|
-
post-vanilla
|
29
|
-
post-vanilla-empty-query-value
|
30
|
-
post-vanilla-query
|
31
|
-
post-vanilla-query-nonunreserved
|
32
|
-
post-vanilla-query-space
|
33
|
-
post-x-www-form-urlencoded
|
34
|
-
post-x-www-form-urlencoded-parameters
|
35
|
-
),
|
36
|
-
emarsys: %w(
|
37
|
-
get-header-key-duplicate
|
38
|
-
post-header-key-order
|
39
|
-
post-header-value-spaces
|
40
|
-
post-header-value-spaces-within-quotes
|
41
|
-
)
|
42
|
-
}
|
43
|
-
|
44
|
-
ESCHER_AWS4_OPTIONS = {
|
45
|
-
algo_prefix: 'AWS4', vendor_key: 'AWS4', hash_algo: 'SHA256', auth_header_name: 'Authorization', date_header_name: 'Date'
|
46
|
-
}
|
47
|
-
|
48
|
-
ESCHER_MIXED_OPTIONS = {
|
49
|
-
algo_prefix: 'EMS', vendor_key: 'EMS', hash_algo: 'SHA256', auth_header_name: 'Authorization', date_header_name: 'Date', clock_skew: 10
|
50
|
-
}
|
51
|
-
|
52
|
-
ESCHER_EMARSYS_OPTIONS = ESCHER_MIXED_OPTIONS.merge(auth_header_name: 'X-Ems-Auth', date_header_name: 'X-Ems-Date')
|
53
|
-
|
54
|
-
GOOD_AUTH_HEADER = 'AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20110909/us-east-1/host/aws4_request, SignedHeaders=date;host, Signature=b27ccfbfa7df52a200ff74193ca6e32d4b48b8856fab7ebf1c595d0670a7e470'
|
55
|
-
|
56
|
-
# noinspection RubyStringKeysInHashInspection
|
57
|
-
def key_db
|
58
|
-
{
|
59
|
-
'AKIDEXAMPLE' => 'wJalrXUtnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY',
|
60
|
-
'th3K3y' => 'very_secure',
|
61
|
-
}
|
62
|
-
end
|
63
|
-
|
64
|
-
def credential_scope
|
65
|
-
%w(us-east-1 host aws4_request)
|
66
|
-
end
|
67
|
-
|
68
|
-
def client
|
69
|
-
{:api_key_id => 'AKIDEXAMPLE', :api_secret => 'wJalrXUtnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY'}
|
70
|
-
end
|
71
|
-
|
72
|
-
describe 'Escher' do
|
73
|
-
test_suites.each do |suite, tests|
|
74
|
-
tests.each do |test|
|
75
|
-
it "should calculate canonicalized request for #{test} in #{suite}" do
|
76
|
-
escher = Escher.new('us-east-1/host/aws4_request', ESCHER_AWS4_OPTIONS)
|
77
|
-
method, request_uri, body, headers = read_request(suite, test)
|
78
|
-
headers_to_sign = headers.map {|k| k[0].downcase }
|
79
|
-
path, query_parts = escher.parse_uri(request_uri)
|
80
|
-
canonicalized_request = escher.canonicalize(method, path, query_parts, body, headers, headers_to_sign)
|
81
|
-
check_canonicalized_request(canonicalized_request, suite, test)
|
82
|
-
end
|
83
|
-
end
|
84
|
-
end
|
85
|
-
|
86
|
-
test_suites.each do |suite, tests|
|
87
|
-
tests.each do |test|
|
88
|
-
it "should calculate string to sign for #{test} in #{suite}" do
|
89
|
-
method, request_uri, body, headers, date = read_request(suite, test)
|
90
|
-
escher = Escher.new('us-east-1/host/aws4_request', ESCHER_AWS4_OPTIONS.merge(current_time: Time.parse(date)))
|
91
|
-
headers_to_sign = headers.map {|k| k[0].downcase }
|
92
|
-
path, query_parts = escher.parse_uri(request_uri)
|
93
|
-
canonicalized_request = escher.canonicalize(method, path, query_parts, body, headers, headers_to_sign)
|
94
|
-
string_to_sign = escher.get_string_to_sign(canonicalized_request)
|
95
|
-
expect(string_to_sign).to eq(fixture(suite, test, 'sts'))
|
96
|
-
end
|
97
|
-
end
|
98
|
-
end
|
99
|
-
|
100
|
-
test_suites.each do |suite, tests|
|
101
|
-
tests.each do |test|
|
102
|
-
it "should calculate auth header for #{test} in #{suite}" do
|
103
|
-
method, request_uri, body, headers, date = read_request(suite, test)
|
104
|
-
escher = Escher.new('us-east-1/host/aws4_request', ESCHER_AWS4_OPTIONS.merge(current_time: Time.parse(date)))
|
105
|
-
headers_to_sign = headers.map {|k| k[0].downcase }
|
106
|
-
auth_header = escher.generate_auth_header(client, method, request_uri, body, headers, headers_to_sign)
|
107
|
-
expect(auth_header).to eq(fixture(suite, test, 'authz'))
|
108
|
-
end
|
109
|
-
end
|
110
|
-
end
|
111
|
-
|
112
|
-
it 'should sign request' do
|
113
|
-
escher = Escher.new('us-east-1/iam/aws4_request', ESCHER_EMARSYS_OPTIONS.merge(current_time: Time.parse('20110909T233600Z')))
|
114
|
-
client = { :api_key_id => 'AKIDEXAMPLE', :api_secret => 'wJalrXUtnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY' }
|
115
|
-
|
116
|
-
input_headers = {
|
117
|
-
'content-type' => 'application/x-www-form-urlencoded; charset=utf-8'
|
118
|
-
}
|
119
|
-
|
120
|
-
expected_headers = {
|
121
|
-
'content-type' => 'application/x-www-form-urlencoded; charset=utf-8',
|
122
|
-
'host' => 'iam.amazonaws.com',
|
123
|
-
'x-ems-date' => '20110909T233600Z',
|
124
|
-
'x-ems-auth' => 'EMS-HMAC-SHA256 Credential=AKIDEXAMPLE/20110909/us-east-1/iam/aws4_request, SignedHeaders=content-type;host;x-ems-date, Signature=f36c21c6e16a71a6e8dc56673ad6354aeef49c577a22fd58a190b5fcf8891dbd',
|
125
|
-
}
|
126
|
-
headers_to_sign = %w(content-type)
|
127
|
-
|
128
|
-
request = {
|
129
|
-
method: 'POST',
|
130
|
-
uri: 'http://iam.amazonaws.com/',
|
131
|
-
body: 'Action=ListUsers&Version=2010-05-08',
|
132
|
-
headers: input_headers,
|
133
|
-
}
|
134
|
-
|
135
|
-
downcase = escher.sign!(request, client, headers_to_sign)[:headers].map { |k, v| { k.downcase => v } }.reduce({}, &:merge)
|
136
|
-
expect(downcase).to eq expected_headers
|
137
|
-
end
|
138
|
-
|
139
|
-
it 'should generate presigned url' do
|
140
|
-
escher = Escher.new('us-east-1/host/aws4_request', ESCHER_MIXED_OPTIONS.merge(current_time: Time.parse('2011/05/11 12:00:00 UTC')))
|
141
|
-
expected_url =
|
142
|
-
'http://example.com/something?foo=bar&' + 'baz=barbaz&' +
|
143
|
-
'X-EMS-Algorithm=EMS-HMAC-SHA256&' +
|
144
|
-
'X-EMS-Credentials=th3K3y%2F20110511%2Fus-east-1%2Fhost%2Faws4_request&' +
|
145
|
-
'X-EMS-Date=20110511T120000Z&' +
|
146
|
-
'X-EMS-Expires=123456&' +
|
147
|
-
'X-EMS-SignedHeaders=host&' +
|
148
|
-
'X-EMS-Signature=fbc9dbb91670e84d04ad2ae7505f4f52ab3ff9e192b8233feeae57e9022c2b67'
|
149
|
-
|
150
|
-
client = {:api_key_id => 'th3K3y', :api_secret => 'very_secure'}
|
151
|
-
expect(escher.generate_signed_url('http://example.com/something?foo=bar&baz=barbaz', client, 123456)).to eq expected_url
|
152
|
-
end
|
153
|
-
|
154
|
-
it 'should validate presigned url' do
|
155
|
-
escher = Escher.new('us-east-1/host/aws4_request', ESCHER_MIXED_OPTIONS.merge(current_time: Time.parse('2011/05/12 21:59:00 UTC')))
|
156
|
-
presigned_uri =
|
157
|
-
'/something?foo=bar&' + 'baz=barbaz&' +
|
158
|
-
'X-EMS-Algorithm=EMS-HMAC-SHA256&' +
|
159
|
-
'X-EMS-Credentials=th3K3y%2F20110511%2Fus-east-1%2Fhost%2Faws4_request&' +
|
160
|
-
'X-EMS-Date=20110511T120000Z&' +
|
161
|
-
'X-EMS-Expires=123456&' +
|
162
|
-
'X-EMS-SignedHeaders=host&' +
|
163
|
-
'X-EMS-Signature=fbc9dbb91670e84d04ad2ae7505f4f52ab3ff9e192b8233feeae57e9022c2b67'
|
164
|
-
|
165
|
-
client = {:api_key_id => 'th3K3y', :api_secret => 'very_secure'}
|
166
|
-
expect { escher.authenticate({
|
167
|
-
:method => 'GET',
|
168
|
-
:headers => [%w(host example.com)],
|
169
|
-
:uri => presigned_uri,
|
170
|
-
:body => 'IRRELEVANT'
|
171
|
-
}, key_db) }.not_to raise_error
|
172
|
-
end
|
173
|
-
|
174
|
-
it 'should validate expiration' do
|
175
|
-
escher = Escher.new('us-east-1/host/aws4_request', ESCHER_MIXED_OPTIONS.merge(current_time: Time.parse('2011/05/12 22:20:00 UTC')))
|
176
|
-
presigned_uri =
|
177
|
-
'/something?foo=bar&' + 'baz=barbaz&' +
|
178
|
-
'X-EMS-Algorithm=EMS-HMAC-SHA256&' +
|
179
|
-
'X-EMS-Credentials=th3K3y%2F20110511%2Fus-east-1%2Fhost%2Faws4_request&' +
|
180
|
-
'X-EMS-Date=20110511T120000Z&' +
|
181
|
-
'X-EMS-Expires=123456&' +
|
182
|
-
'X-EMS-SignedHeaders=host&' +
|
183
|
-
'X-EMS-Signature=fbc9dbb91670e84d04ad2ae7505f4f52ab3ff9e192b8233feeae57e9022c2b67'
|
184
|
-
|
185
|
-
client = {:api_key_id => 'th3K3y', :api_secret => 'very_secure'}
|
186
|
-
expect { escher.authenticate({
|
187
|
-
:method => 'GET',
|
188
|
-
:headers => [%w(host example.com)],
|
189
|
-
:uri => presigned_uri,
|
190
|
-
:body => 'IRRELEVANT'
|
191
|
-
}, key_db) }.to raise_error(EscherError, 'The request date is not within the accepted time range')
|
192
|
-
end
|
193
|
-
|
194
|
-
it 'should validate request' do
|
195
|
-
headers = [
|
196
|
-
%w(Host host.foo.com),
|
197
|
-
['Date', 'Mon, 09 Sep 2011 23:36:00 GMT'],
|
198
|
-
['Authorization', GOOD_AUTH_HEADER],
|
199
|
-
]
|
200
|
-
expect { call_validate(headers) }.not_to raise_error
|
201
|
-
end
|
202
|
-
|
203
|
-
it 'should authenticate' do
|
204
|
-
headers = [
|
205
|
-
%w(Host host.foo.com),
|
206
|
-
['Date', 'Mon, 09 Sep 2011 23:36:00 GMT'],
|
207
|
-
['Authorization', GOOD_AUTH_HEADER],
|
208
|
-
]
|
209
|
-
expect(call_validate(headers)).to eq 'AKIDEXAMPLE'
|
210
|
-
end
|
211
|
-
|
212
|
-
it 'should detect if signatures do not match' do
|
213
|
-
headers = [
|
214
|
-
%w(Host host.foo.com),
|
215
|
-
['Date', 'Mon, 09 Sep 2011 23:36:00 GMT'],
|
216
|
-
['Authorization', 'AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20110909/us-east-1/host/aws4_request, SignedHeaders=date;host, Signature=ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff'],
|
217
|
-
]
|
218
|
-
expect { call_validate(headers) }.to raise_error(EscherError, 'The signatures do not match')
|
219
|
-
end
|
220
|
-
|
221
|
-
it 'should detect if dates are not on the same day' do
|
222
|
-
yesterday = '08'
|
223
|
-
headers = [
|
224
|
-
%w(Host host.foo.com),
|
225
|
-
['Date', "Mon, #{yesterday} Sep 2011 23:36:00 GMT"],
|
226
|
-
['Authorization', GOOD_AUTH_HEADER],
|
227
|
-
]
|
228
|
-
expect { call_validate(headers) }.to raise_error(EscherError, 'Invalid request date')
|
229
|
-
end
|
230
|
-
|
231
|
-
it 'should detect if date is not within the 15 minutes range' do
|
232
|
-
long_ago = '00'
|
233
|
-
headers = [
|
234
|
-
%w(Host host.foo.com),
|
235
|
-
['Date', "Mon, 09 Sep 2011 23:#{long_ago}:00 GMT"],
|
236
|
-
['Authorization', GOOD_AUTH_HEADER],
|
237
|
-
]
|
238
|
-
expect { call_validate(headers) }.to raise_error(EscherError, 'The request date is not within the accepted time range')
|
239
|
-
end
|
240
|
-
|
241
|
-
it 'should detect missing host header' do
|
242
|
-
headers = [
|
243
|
-
['Date', 'Mon, 09 Sep 2011 23:36:00 GMT'],
|
244
|
-
['Authorization', GOOD_AUTH_HEADER],
|
245
|
-
]
|
246
|
-
expect { call_validate(headers) }.to raise_error(EscherError, 'Missing header: Host')
|
247
|
-
end
|
248
|
-
|
249
|
-
it 'should detect missing date header' do
|
250
|
-
headers = [
|
251
|
-
%w(Host host.foo.com),
|
252
|
-
['Authorization', GOOD_AUTH_HEADER],
|
253
|
-
]
|
254
|
-
expect { call_validate(headers) }.to raise_error(EscherError, 'Missing header: Date')
|
255
|
-
end
|
256
|
-
|
257
|
-
it 'should detect missing auth header' do
|
258
|
-
headers = [
|
259
|
-
%w(Host host.foo.com),
|
260
|
-
['Date', 'Mon, 09 Sep 2011 23:36:00 GMT'],
|
261
|
-
]
|
262
|
-
expect { call_validate(headers) }.to raise_error(EscherError, 'Missing header: Authorization')
|
263
|
-
end
|
264
|
-
|
265
|
-
it 'should detect malformed auth header' do
|
266
|
-
headers = [
|
267
|
-
%w(Host host.foo.com),
|
268
|
-
['Date', 'Mon, 09 Sep 2011 23:36:00 GMT'],
|
269
|
-
['Authorization', 'AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20110909/us-east-1/host/aws4_request, SignedHeaders=date;host, Signature=UNPARSABLE'],
|
270
|
-
]
|
271
|
-
expect { call_validate(headers) }.to raise_error(EscherError, 'Malformed authorization header')
|
272
|
-
end
|
273
|
-
|
274
|
-
it 'should detect malformed credential scope' do
|
275
|
-
headers = [
|
276
|
-
%w(Host host.foo.com),
|
277
|
-
['Date', 'Mon, 09 Sep 2011 23:36:00 GMT'],
|
278
|
-
['Authorization', 'AWS4-HMAC-SHA256 Credential=BAD-CREDENTIAL-SCOPE, SignedHeaders=date;host, Signature=b27ccfbfa7df52a200ff74193ca6e32d4b48b8856fab7ebf1c595d0670a7e470'],
|
279
|
-
]
|
280
|
-
expect { call_validate(headers) }.to raise_error(EscherError, 'Malformed authorization header')
|
281
|
-
end
|
282
|
-
|
283
|
-
it 'should check mandatory signed headers: host' do
|
284
|
-
headers = [
|
285
|
-
%w(Host host.foo.com),
|
286
|
-
['Date', 'Mon, 09 Sep 2011 23:36:00 GMT'],
|
287
|
-
['Authorization', 'AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20110909/us-east-1/host/aws4_request, SignedHeaders=date, Signature=b27ccfbfa7df52a200ff74193ca6e32d4b48b8856fab7ebf1c595d0670a7e470'],
|
288
|
-
]
|
289
|
-
expect { call_validate(headers) }.to raise_error(EscherError, 'Host header is not signed')
|
290
|
-
end
|
291
|
-
|
292
|
-
it 'should check mandatory signed headers: date' do
|
293
|
-
headers = [
|
294
|
-
%w(Host host.foo.com),
|
295
|
-
['Date', 'Mon, 09 Sep 2011 23:36:00 GMT'],
|
296
|
-
['Authorization', 'AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20110909/us-east-1/host/aws4_request, SignedHeaders=host, Signature=b27ccfbfa7df52a200ff74193ca6e32d4b48b8856fab7ebf1c595d0670a7e470'],
|
297
|
-
]
|
298
|
-
expect { call_validate(headers) }.to raise_error(EscherError, 'Date header is not signed')
|
299
|
-
end
|
300
|
-
|
301
|
-
it 'should check algorithm' do
|
302
|
-
headers = [
|
303
|
-
%w(Host host.foo.com),
|
304
|
-
['Date', 'Mon, 09 Sep 2011 23:36:00 GMT'],
|
305
|
-
['Authorization', 'AWS4-HMAC-INVALID Credential=AKIDEXAMPLE/20110909/us-east-1/host/aws4_request, SignedHeaders=date;host, Signature=b27ccfbfa7df52a200ff74193ca6e32d4b48b8856fab7ebf1c595d0670a7e470'],
|
306
|
-
]
|
307
|
-
expect { call_validate(headers) }.to raise_error(EscherError, 'Only SHA256 and SHA512 hash algorithms are allowed')
|
308
|
-
end
|
309
|
-
|
310
|
-
it 'should check credential scope' do
|
311
|
-
headers = [
|
312
|
-
%w(Host host.foo.com),
|
313
|
-
['Date', 'Mon, 09 Sep 2011 23:36:00 GMT'],
|
314
|
-
['Authorization', 'AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20110909/us-east-1/INVALID/aws4_request, SignedHeaders=date;host, Signature=b27ccfbfa7df52a200ff74193ca6e32d4b48b8856fab7ebf1c595d0670a7e470'],
|
315
|
-
]
|
316
|
-
expect { call_validate(headers) }.to raise_error(EscherError, 'Invalid credentials')
|
317
|
-
end
|
318
|
-
|
319
|
-
it 'should convert dates' do
|
320
|
-
date_str = 'Fri, 09 Sep 2011 23:36:00 GMT'
|
321
|
-
expect(Escher.new('irrelevant', date_header_name: 'date', current_time: Time.parse(date_str)).format_date_for_header).to eq date_str
|
322
|
-
end
|
323
|
-
|
324
|
-
def call_validate(headers)
|
325
|
-
escher = Escher.new('us-east-1/host/aws4_request', ESCHER_AWS4_OPTIONS.merge(current_time: Time.parse('Mon, 09 Sep 2011 23:40:00 GMT')))
|
326
|
-
escher.authenticate({
|
327
|
-
:method => 'GET',
|
328
|
-
:headers => headers,
|
329
|
-
:uri => '/',
|
330
|
-
:body => '',
|
331
|
-
}, key_db)
|
332
|
-
end
|
333
|
-
|
334
|
-
end
|
335
|
-
|
336
|
-
def fixture(suite, test, extension)
|
337
|
-
open("spec/#{suite}_testsuite/#{test}.#{extension}").read
|
338
|
-
end
|
339
|
-
|
340
|
-
def get_host(headers)
|
341
|
-
headers.detect {|header| header[0].downcase == 'host'}[1]
|
342
|
-
end
|
343
|
-
|
344
|
-
def get_date(headers)
|
345
|
-
headers.detect {|header| header[0].downcase == 'date'}[1]
|
346
|
-
end
|
347
|
-
|
348
|
-
def read_request(suite, test, extension = 'req')
|
349
|
-
lines = (fixture(suite, test, extension) + "\n").lines.map(&:chomp)
|
350
|
-
method, request_uri = lines[0].split ' '
|
351
|
-
headers = lines[1..-3].map { |header| k, v = header.split(':', 2); [k, v] }
|
352
|
-
request_body = lines[-1]
|
353
|
-
[method, request_uri, request_body, headers, get_date(headers), get_host(headers)]
|
354
|
-
end
|
355
|
-
|
356
|
-
def check_canonicalized_request(creq, suite, test)
|
357
|
-
expect(creq).to eq(fixture(suite, test, 'creq'))
|
358
|
-
end
|