bixby-auth 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,660 @@
1
+ # encoding: UTF-8
2
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
3
+
4
+ describe "ApiAuth" do
5
+
6
+ describe "generating secret keys" do
7
+
8
+ it "should generate secret keys" do
9
+ ApiAuth.generate_secret_key
10
+ end
11
+
12
+ it "should generate secret keys that are 88 characters" do
13
+ ApiAuth.generate_secret_key.size.should be(88)
14
+ end
15
+
16
+ it "should generate keys that have a Hamming Distance of at least 65" do
17
+ key1 = ApiAuth.generate_secret_key
18
+ key2 = ApiAuth.generate_secret_key
19
+ Amatch::Hamming.new(key1).match(key2).should be > 65
20
+ end
21
+
22
+ end
23
+
24
+ describe "signing requests" do
25
+
26
+ def hmac(secret_key, request)
27
+ canonical_string = ApiAuth::Headers.new(request).canonical_string
28
+ digest = OpenSSL::Digest.new('sha1')
29
+ ApiAuth.b64_encode(OpenSSL::HMAC.digest(digest, secret_key, canonical_string))
30
+ end
31
+
32
+ before(:all) do
33
+ @access_id = "1044"
34
+ @secret_key = ApiAuth.generate_secret_key
35
+ end
36
+
37
+ describe "with Net::HTTP" do
38
+
39
+ before(:each) do
40
+ @request = Net::HTTP::Put.new("/resource.xml?foo=bar&bar=foo",
41
+ 'content-type' => 'text/plain',
42
+ 'content-md5' => '1B2M2Y8AsgTpgAmY7PhCfg==',
43
+ 'date' => Time.now.utc.httpdate)
44
+ @signed_request = ApiAuth.sign!(@request, @access_id, @secret_key)
45
+ end
46
+
47
+ it "should return a Net::HTTP object after signing it" do
48
+ ApiAuth.sign!(@request, @access_id, @secret_key).class.to_s.should match("Net::HTTP")
49
+ end
50
+
51
+ describe "md5 header" do
52
+ context "not already provided" do
53
+ it "should calculate for empty string" do
54
+ request = Net::HTTP::Put.new("/resource.xml?foo=bar&bar=foo",
55
+ 'content-type' => 'text/plain',
56
+ 'date' => Time.now.utc.httpdate)
57
+ signed_request = ApiAuth.sign!(request, @access_id, @secret_key)
58
+ signed_request['Content-MD5'].should == "1B2M2Y8AsgTpgAmY7PhCfg=="
59
+ end
60
+
61
+ it "should calculate for real content" do
62
+ request = Net::HTTP::Put.new("/resource.xml?foo=bar&bar=foo",
63
+ 'content-type' => 'text/plain',
64
+ 'date' => Time.now.utc.httpdate)
65
+ request.body = "hello\nworld"
66
+ signed_request = ApiAuth.sign!(request, @access_id, @secret_key)
67
+ signed_request['Content-MD5'].should == "kZXQvrKoieG+Be1rsZVINw=="
68
+ end
69
+
70
+ it "should calculate for real multipart content" do
71
+ request = Net::HTTP::Put.new("/resource.xml?foo=bar&bar=foo",
72
+ 'content-type' => 'text/plain',
73
+ 'date' => Time.now.utc.httpdate)
74
+ request.body_stream = File.new('spec/fixtures/upload.png')
75
+ signed_request = ApiAuth.sign!(request, @access_id, @secret_key)
76
+ signed_request['Content-MD5'].should == "k4U8MTA3RHDcewBzymVNEQ=="
77
+ end
78
+ end
79
+
80
+ it "should leave the content-md5 alone if provided" do
81
+ @signed_request['Content-MD5'].should == '1B2M2Y8AsgTpgAmY7PhCfg=='
82
+ end
83
+ end
84
+
85
+ it "should sign the request" do
86
+ @signed_request['Authorization'].should == "APIAuth 1044:#{hmac(@secret_key, @request)}"
87
+ end
88
+
89
+ it "should authenticate a valid request" do
90
+ ApiAuth.authentic?(@signed_request, @secret_key).should be_true
91
+ end
92
+
93
+ it "should NOT authenticate a non-valid request" do
94
+ ApiAuth.authentic?(@signed_request, @secret_key+'j').should be_false
95
+ end
96
+
97
+ it "should NOT authenticate a mismatched content-md5 when body has changed" do
98
+ request = Net::HTTP::Put.new("/resource.xml?foo=bar&bar=foo",
99
+ 'content-type' => 'text/plain',
100
+ 'date' => Time.now.utc.httpdate)
101
+ request.body = "hello\nworld"
102
+ signed_request = ApiAuth.sign!(request, @access_id, @secret_key)
103
+ signed_request.body = "goodbye"
104
+ ApiAuth.authentic?(signed_request, @secret_key).should be_false
105
+ end
106
+
107
+ it "should NOT authenticate an expired request" do
108
+ @request['Date'] = 16.minutes.ago.utc.httpdate
109
+ signed_request = ApiAuth.sign!(@request, @access_id, @secret_key)
110
+ expect{ ApiAuth.authentic?(signed_request, @secret_key)}.to raise_error(ApiAuth::RequestTooOld)
111
+ end
112
+
113
+ it "should NOT authenticate a request with an invalid date" do
114
+ @request['Date'] = "٢٠١٤-٠٩-٠٨ ١٦:٣١:١٤ +٠٣٠٠"
115
+ signed_request = ApiAuth.sign!(@request, @access_id, @secret_key)
116
+ ApiAuth.authentic?(signed_request, @secret_key).should be_false
117
+ end
118
+
119
+ it "should retrieve the access_id" do
120
+ ApiAuth.access_id(@signed_request).should == "1044"
121
+ end
122
+
123
+ end
124
+
125
+ describe "with RestClient" do
126
+
127
+ before(:each) do
128
+ headers = { 'Content-MD5' => "1B2M2Y8AsgTpgAmY7PhCfg==",
129
+ 'Content-Type' => "text/plain",
130
+ 'Date' => Time.now.utc.httpdate }
131
+ @request = RestClient::Request.new(:url => "/resource.xml?foo=bar&bar=foo",
132
+ :headers => headers,
133
+ :method => :put)
134
+ @signed_request = ApiAuth.sign!(@request, @access_id, @secret_key)
135
+ end
136
+
137
+ it "should return a RestClient object after signing it" do
138
+ ApiAuth.sign!(@request, @access_id, @secret_key).class.to_s.should match("RestClient")
139
+ end
140
+
141
+ describe "md5 header" do
142
+ context "not already provided" do
143
+ it "should calculate for empty string" do
144
+ headers = { 'Content-Type' => "text/plain",
145
+ 'Date' => Time.now.utc.httpdate }
146
+ request = RestClient::Request.new(:url => "/resource.xml?foo=bar&bar=foo",
147
+ :headers => headers,
148
+ :method => :put)
149
+ signed_request = ApiAuth.sign!(request, @access_id, @secret_key)
150
+ signed_request.headers['Content-MD5'].should == "1B2M2Y8AsgTpgAmY7PhCfg=="
151
+ end
152
+
153
+ it "should calculate for real content" do
154
+ headers = { 'Content-Type' => "text/plain",
155
+ 'Date' => Time.now.utc.httpdate }
156
+ request = RestClient::Request.new(:url => "/resource.xml?foo=bar&bar=foo",
157
+ :headers => headers,
158
+ :method => :put,
159
+ :payload => "hellow\nworld")
160
+ signed_request = ApiAuth.sign!(request, @access_id, @secret_key)
161
+ signed_request.headers['Content-MD5'].should == "G0grublI06013h58g9j8Vw=="
162
+ end
163
+ end
164
+
165
+ it "should leave the content-md5 alone if provided" do
166
+ @signed_request.headers['Content-MD5'].should == "1B2M2Y8AsgTpgAmY7PhCfg=="
167
+ end
168
+ end
169
+
170
+ it "should sign the request" do
171
+ @signed_request.headers['Authorization'].should == "APIAuth 1044:#{hmac(@secret_key, @request)}"
172
+ end
173
+
174
+ it "should sign the request using the generated md5 header" do
175
+ date = Time.now.utc.httpdate
176
+ headers1 = { 'Content-MD5' => "1B2M2Y8AsgTpgAmY7PhCfg==",
177
+ 'Content-Type' => "text/plain",
178
+ 'Date' => date }
179
+ request1 = RestClient::Request.new(:url => "/resource.xml?foo=bar&bar=foo",
180
+ :headers => headers1,
181
+ :method => :put)
182
+ headers2 = { 'Content-Type' => "text/plain",
183
+ 'Date' => date }
184
+ request2 = RestClient::Request.new(:url => "/resource.xml?foo=bar&bar=foo",
185
+ :headers => headers2,
186
+ :method => :put)
187
+
188
+ ApiAuth.sign!(request1, @access_id, @secret_key)
189
+ ApiAuth.sign!(request2, @access_id, @secret_key)
190
+
191
+ request2.headers['Authorization'].should == request1.headers['Authorization']
192
+ end
193
+
194
+ it "should sign the request using the generated Date header" do
195
+ headers1 = { 'Content-MD5' => "1B2M2Y8AsgTpgAmY7PhCfg==",
196
+ 'Content-Type' => "text/plain"}
197
+ request1 = RestClient::Request.new(:url => "/resource.xml?foo=bar&bar=foo",
198
+ :headers => headers1,
199
+ :method => :put)
200
+ ApiAuth.sign!(request1, @access_id, @secret_key)
201
+ headers2 = { 'Content-MD5' => "1B2M2Y8AsgTpgAmY7PhCfg==",
202
+ 'Content-Type' => "text/plain",
203
+ 'Date' => request1.headers['DATE'] }
204
+ request2 = RestClient::Request.new(:url => "/resource.xml?foo=bar&bar=foo",
205
+ :headers => headers2,
206
+ :method => :put)
207
+
208
+ ApiAuth.sign!(request2, @access_id, @secret_key)
209
+
210
+ request2.headers['Authorization'].should == request1.headers['Authorization']
211
+ end
212
+
213
+ it "should authenticate a valid request" do
214
+ ApiAuth.authentic?(@signed_request, @secret_key).should be_true
215
+ end
216
+
217
+ it "should NOT authenticate a non-valid request" do
218
+ ApiAuth.authentic?(@signed_request, @secret_key+'j').should be_false
219
+ end
220
+
221
+ it "should NOT authenticate a mismatched content-md5 when body has changed" do
222
+ headers = { 'Content-Type' => "text/plain",
223
+ 'Date' => Time.now.utc.httpdate }
224
+ request = RestClient::Request.new(:url => "/resource.xml?foo=bar&bar=foo",
225
+ :headers => headers,
226
+ :method => :put,
227
+ :payload => "hello\nworld")
228
+ signed_request = ApiAuth.sign!(request, @access_id, @secret_key)
229
+ signed_request.instance_variable_set("@payload", RestClient::Payload.generate('goodbye'))
230
+ ApiAuth.authentic?(signed_request, @secret_key).should be_false
231
+ end
232
+
233
+ it "should NOT authenticate an expired request" do
234
+ headers = @request.headers.dup.merge({"Date" => 16.minutes.ago.utc.httpdate})
235
+ headers.delete("Authorization")
236
+ request = RestClient::Request.new(:url => "/resource.xml?foo=bar&bar=foo",
237
+ :headers => headers,
238
+ :method => :put)
239
+ signed_request = ApiAuth.sign!(request, @access_id, @secret_key)
240
+ expect{ ApiAuth.authentic?(signed_request, @secret_key)}.to raise_error(ApiAuth::RequestTooOld)
241
+ end
242
+
243
+ it "should NOT authenticate a request with an invalid date" do
244
+ @request.headers['Date'] = "٢٠١٤-٠٩-٠٨ ١٦:٣١:١٤ +٠٣٠٠"
245
+ signed_request = ApiAuth.sign!(@request, @access_id, @secret_key)
246
+ ApiAuth.authentic?(signed_request, @secret_key).should be_false
247
+ end
248
+
249
+ it "should retrieve the access_id" do
250
+ ApiAuth.access_id(@signed_request).should == "1044"
251
+ end
252
+
253
+ end
254
+
255
+ describe "with Curb" do
256
+
257
+ before(:each) do
258
+ headers = { 'Content-MD5' => "e59ff97941044f85df5297e1c302d260",
259
+ 'Content-Type' => "text/plain",
260
+ 'Date' => Time.now.utc.httpdate }
261
+ @request = Curl::Easy.new("/resource.xml?foo=bar&bar=foo") do |curl|
262
+ curl.headers = headers
263
+ end
264
+ @signed_request = ApiAuth.sign!(@request, @access_id, @secret_key)
265
+ end
266
+
267
+ it "should return a Curl::Easy object after signing it" do
268
+ ApiAuth.sign!(@request, @access_id, @secret_key).class.to_s.should match("Curl::Easy")
269
+ end
270
+
271
+ describe "md5 header" do
272
+ it "should not calculate and add the content-md5 header if not provided" do
273
+ headers = { 'Content-Type' => "text/plain",
274
+ 'Date' => Time.now.utc.httpdate }
275
+ request = Curl::Easy.new("/resource.xml?foo=bar&bar=foo") do |curl|
276
+ curl.headers = headers
277
+ end
278
+ signed_request = ApiAuth.sign!(request, @access_id, @secret_key)
279
+ signed_request.headers['Content-MD5'].should == nil
280
+ end
281
+
282
+ it "should leave the content-md5 alone if provided" do
283
+ @signed_request.headers['Content-MD5'].should == "e59ff97941044f85df5297e1c302d260"
284
+ end
285
+ end
286
+
287
+ it "should sign the request" do
288
+ @signed_request.headers['Authorization'].should == "APIAuth 1044:#{hmac(@secret_key, @request)}"
289
+ end
290
+
291
+ it "should authenticate a valid request" do
292
+ ApiAuth.authentic?(@signed_request, @secret_key).should be_true
293
+ end
294
+
295
+ it "should NOT authenticate a non-valid request" do
296
+ ApiAuth.authentic?(@signed_request, @secret_key+'j').should be_false
297
+ end
298
+
299
+ it "should NOT authenticate an expired request" do
300
+ @request.headers['Date'] = 16.minutes.ago.utc.httpdate
301
+ signed_request = ApiAuth.sign!(@request, @access_id, @secret_key)
302
+ expect{ ApiAuth.authentic?(signed_request, @secret_key)}.to raise_error(ApiAuth::RequestTooOld)
303
+ end
304
+
305
+ it "should NOT authenticate a request with an invalid date" do
306
+ @request.headers['Date'] = "٢٠١٤-٠٩-٠٨ ١٦:٣١:١٤ +٠٣٠٠"
307
+ signed_request = ApiAuth.sign!(@request, @access_id, @secret_key)
308
+ ApiAuth.authentic?(signed_request, @secret_key).should be_false
309
+ end
310
+
311
+ it "should retrieve the access_id" do
312
+ ApiAuth.access_id(@signed_request).should == "1044"
313
+ end
314
+
315
+ end
316
+
317
+ describe "with ActionController/ActionDispatch" do
318
+
319
+ let(:request_klass){ ActionDispatch::Request rescue ActionController::Request }
320
+
321
+ before(:each) do
322
+ @request = request_klass.new(
323
+ 'PATH_INFO' => '/resource.xml',
324
+ 'QUERY_STRING' => 'foo=bar&bar=foo',
325
+ 'REQUEST_METHOD' => 'PUT',
326
+ 'CONTENT_MD5' => '1B2M2Y8AsgTpgAmY7PhCfg==',
327
+ 'CONTENT_TYPE' => 'text/plain',
328
+ 'HTTP_DATE' => Time.now.utc.httpdate,
329
+ 'rack.input' => StringIO.new)
330
+ @signed_request = ApiAuth.sign!(@request, @access_id, @secret_key)
331
+ end
332
+
333
+ it "should return a ActionDispatch::Request object after signing it" do
334
+ ApiAuth.sign!(@request, @access_id, @secret_key).class.to_s.should match(request_klass.to_s)
335
+ end
336
+
337
+ describe "md5 header" do
338
+ context "not already provided" do
339
+ it "should calculate for empty string" do
340
+ request = request_klass.new(
341
+ 'PATH_INFO' => '/resource.xml',
342
+ 'QUERY_STRING' => 'foo=bar&bar=foo',
343
+ 'REQUEST_METHOD' => 'PUT',
344
+ 'CONTENT_TYPE' => 'text/plain',
345
+ 'HTTP_DATE' => 'Mon, 23 Jan 1984 03:29:56 GMT',
346
+ 'rack.input' => StringIO.new)
347
+ signed_request = ApiAuth.sign!(request, @access_id, @secret_key)
348
+ signed_request.env['Content-MD5'].should == "1B2M2Y8AsgTpgAmY7PhCfg=="
349
+ end
350
+
351
+ it "should calculate for real content" do
352
+ request = request_klass.new(
353
+ 'PATH_INFO' => '/resource.xml',
354
+ 'QUERY_STRING' => 'foo=bar&bar=foo',
355
+ 'REQUEST_METHOD' => 'PUT',
356
+ 'CONTENT_TYPE' => 'text/plain',
357
+ 'HTTP_DATE' => 'Mon, 23 Jan 1984 03:29:56 GMT',
358
+ 'rack.input' => StringIO.new("hello\nworld"),
359
+ 'CONTENT_LENGTH' => '11')
360
+ signed_request = ApiAuth.sign!(request, @access_id, @secret_key)
361
+ signed_request.env['Content-MD5'].should == "kZXQvrKoieG+Be1rsZVINw=="
362
+ end
363
+
364
+ end
365
+
366
+ it "should leave the content-md5 alone if provided" do
367
+ @signed_request.env['CONTENT_MD5'].should == '1B2M2Y8AsgTpgAmY7PhCfg=='
368
+ end
369
+ end
370
+
371
+ it "should sign the request" do
372
+ @signed_request.env['Authorization'].should == "APIAuth 1044:#{hmac(@secret_key, @request)}"
373
+ end
374
+
375
+ it "should authenticate a valid request" do
376
+ ApiAuth.authentic?(@signed_request, @secret_key).should be_true
377
+ end
378
+
379
+ it "should NOT authenticate a non-valid request" do
380
+ ApiAuth.authentic?(@signed_request, @secret_key+'j').should be_false
381
+ end
382
+
383
+ it "should NOT authenticate a mismatched content-md5 when body has changed" do
384
+ request = request_klass.new(
385
+ 'PATH_INFO' => '/resource.xml',
386
+ 'QUERY_STRING' => 'foo=bar&bar=foo',
387
+ 'REQUEST_METHOD' => 'PUT',
388
+ 'CONTENT_TYPE' => 'text/plain',
389
+ 'HTTP_DATE' => Time.now.utc.httpdate,
390
+ 'rack.input' => StringIO.new("hello\nworld"))
391
+ signed_request = ApiAuth.sign!(request, @access_id, @secret_key)
392
+ signed_request.instance_variable_get("@env")["rack.input"] = StringIO.new("goodbye")
393
+ ApiAuth.authentic?(signed_request, @secret_key).should be_false
394
+ end
395
+
396
+ it "should NOT authenticate an expired request" do
397
+ @request.env['HTTP_DATE'] = 16.minutes.ago.utc.httpdate
398
+ signed_request = ApiAuth.sign!(@request, @access_id, @secret_key)
399
+ expect{ ApiAuth.authentic?(signed_request, @secret_key)}.to raise_error(ApiAuth::RequestTooOld)
400
+ end
401
+
402
+ it "should NOT authenticate a request with an invalid date" do
403
+ @request.env['Date'] = "٢٠١٤-٠٩-٠٨ ١٦:٣١:١٤ +٠٣٠٠"
404
+ signed_request = ApiAuth.sign!(@request, @access_id, @secret_key)
405
+ ApiAuth.authentic?(signed_request, @secret_key).should be_false
406
+ end
407
+
408
+ it "should retrieve the access_id" do
409
+ ApiAuth.access_id(@signed_request).should == "1044"
410
+ end
411
+
412
+ end
413
+
414
+ describe "with Rack::Request" do
415
+
416
+ before(:each) do
417
+ headers = { 'Content-MD5' => "1B2M2Y8AsgTpgAmY7PhCfg==",
418
+ 'Content-Type' => "text/plain",
419
+ 'Date' => Time.now.utc.httpdate }
420
+ @request = Rack::Request.new(Rack::MockRequest.env_for("/resource.xml?foo=bar&bar=foo", :method => :put).merge!(headers))
421
+ @signed_request = ApiAuth.sign!(@request, @access_id, @secret_key)
422
+ end
423
+
424
+ it "should return a Rack::Request object after signing it" do
425
+ ApiAuth.sign!(@request, @access_id, @secret_key).class.to_s.should match("Rack::Request")
426
+ end
427
+
428
+ describe "md5 header" do
429
+ context "not already provided" do
430
+ it "should calculate for empty string" do
431
+ headers = { 'Content-Type' => "text/plain",
432
+ 'Date' => Time.now.utc.httpdate }
433
+ request = Rack::Request.new(Rack::MockRequest.env_for("/resource.xml?foo=bar&bar=foo", :method => :put).merge!(headers))
434
+ signed_request = ApiAuth.sign!(request, @access_id, @secret_key)
435
+ signed_request.env['Content-MD5'].should == "1B2M2Y8AsgTpgAmY7PhCfg=="
436
+ end
437
+
438
+ it "should calculate for real content" do
439
+ headers = { 'Content-Type' => "text/plain",
440
+ 'Date' => Time.now.utc.httpdate }
441
+ request = Rack::Request.new(Rack::MockRequest.env_for("/resource.xml?foo=bar&bar=foo", :method => :put, :input => "hellow\nworld").merge!(headers))
442
+ signed_request = ApiAuth.sign!(request, @access_id, @secret_key)
443
+ signed_request.env['Content-MD5'].should == "G0grublI06013h58g9j8Vw=="
444
+ end
445
+ end
446
+
447
+ it "should leave the content-md5 alone if provided" do
448
+ @signed_request.env['Content-MD5'].should == "1B2M2Y8AsgTpgAmY7PhCfg=="
449
+ end
450
+ end
451
+
452
+ it "should sign the request" do
453
+ @signed_request.env['Authorization'].should == "APIAuth 1044:#{hmac(@secret_key, @request)}"
454
+ end
455
+
456
+ it "should authenticate a valid request" do
457
+ ApiAuth.authentic?(@signed_request, @secret_key).should be_true
458
+ end
459
+
460
+ it "should NOT authenticate a non-valid request" do
461
+ ApiAuth.authentic?(@signed_request, @secret_key+'j').should be_false
462
+ end
463
+
464
+ it "should NOT authenticate a mismatched content-md5 when body has changed" do
465
+ headers = { 'Content-Type' => "text/plain",
466
+ 'Date' => Time.now.utc.httpdate }
467
+ request = Rack::Request.new(Rack::MockRequest.env_for("/resource.xml?foo=bar&bar=foo", :method => :put, :input => "hellow\nworld").merge!(headers))
468
+ signed_request = ApiAuth.sign!(request, @access_id, @secret_key)
469
+ changed_request = Rack::Request.new(Rack::MockRequest.env_for("/resource.xml?foo=bar&bar=foo", :method => :put, :input => "goodbye").merge!(headers))
470
+ signed_request.env['rack.input'] = changed_request.env['rack.input']
471
+ signed_request.env['CONTENT_LENGTH'] = changed_request.env['CONTENT_LENGTH']
472
+ ApiAuth.authentic?(signed_request, @secret_key).should be_false
473
+ end
474
+
475
+ it "should NOT authenticate an expired request" do
476
+ @request.env['Date'] = 16.minutes.ago.utc.httpdate
477
+ signed_request = ApiAuth.sign!(@request, @access_id, @secret_key)
478
+ expect{ ApiAuth.authentic?(signed_request, @secret_key)}.to raise_error(ApiAuth::RequestTooOld)
479
+ end
480
+
481
+ it "should NOT authenticate a request with an invalid date" do
482
+ @request.env['Date'] = "٢٠١٤-٠٩-٠٨ ١٦:٣١:١٤ +٠٣٠٠"
483
+ signed_request = ApiAuth.sign!(@request, @access_id, @secret_key)
484
+ ApiAuth.authentic?(signed_request, @secret_key).should be_false
485
+ end
486
+
487
+ it "should retrieve the access_id" do
488
+ ApiAuth.access_id(@signed_request).should == "1044"
489
+ end
490
+
491
+ end
492
+
493
+ describe "with HTTPI" do
494
+ before(:each) do
495
+ @request = HTTPI::Request.new("http://localhost/resource.xml?foo=bar&bar=foo")
496
+ @request.headers.merge!({
497
+ 'content-type' => 'text/plain',
498
+ 'content-md5' => '1B2M2Y8AsgTpgAmY7PhCfg==',
499
+ 'date' => Time.now.utc.httpdate
500
+ })
501
+ @headers = ApiAuth::Headers.new(@request)
502
+ @signed_request = ApiAuth.sign!(@request, @access_id, @secret_key)
503
+ end
504
+
505
+ it "should return a HTTPI object after signing it" do
506
+ ApiAuth.sign!(@request, @access_id, @secret_key).class.to_s.should match("HTTPI::Request")
507
+ end
508
+
509
+ describe "md5 header" do
510
+ context "not already provided" do
511
+ it "should calculate for empty string" do
512
+ request = Net::HTTP::Put.new("/resource.xml?foo=bar&bar=foo",
513
+ 'content-type' => 'text/plain',
514
+ 'date' => Time.now.utc.httpdate)
515
+ signed_request = ApiAuth.sign!(request, @access_id, @secret_key)
516
+ signed_request['Content-MD5'].should == "1B2M2Y8AsgTpgAmY7PhCfg=="
517
+ end
518
+
519
+ it "should calculate for real content" do
520
+ request = Net::HTTP::Put.new("/resource.xml?foo=bar&bar=foo",
521
+ 'content-type' => 'text/plain',
522
+ 'date' => Time.now.utc.httpdate)
523
+ request.body = "hello\nworld"
524
+ signed_request = ApiAuth.sign!(request, @access_id, @secret_key)
525
+ signed_request['Content-MD5'].should == "kZXQvrKoieG+Be1rsZVINw=="
526
+ end
527
+ end
528
+
529
+ it "should leave the content-md5 alone if provided" do
530
+ @signed_request.headers['Content-MD5'].should == '1B2M2Y8AsgTpgAmY7PhCfg=='
531
+ end
532
+ end
533
+
534
+ it "should sign the request" do
535
+ @signed_request.headers['Authorization'].should == "APIAuth 1044:#{hmac(@secret_key, @request)}"
536
+ end
537
+
538
+ it "should authenticate a valid request" do
539
+ ApiAuth.authentic?(@signed_request, @secret_key).should be_true
540
+ end
541
+
542
+ it "should NOT authenticate a non-valid request" do
543
+ ApiAuth.authentic?(@signed_request, @secret_key+'j').should be_false
544
+ end
545
+
546
+ it "should NOT authenticate a mismatched content-md5 when body has changed" do
547
+ request = Net::HTTP::Put.new("/resource.xml?foo=bar&bar=foo",
548
+ 'content-type' => 'text/plain',
549
+ 'date' => Time.now.utc.httpdate)
550
+ request.body = "hello\nworld"
551
+ signed_request = ApiAuth.sign!(request, @access_id, @secret_key)
552
+ signed_request.body = "goodbye"
553
+ ApiAuth.authentic?(signed_request, @secret_key).should be_false
554
+ end
555
+
556
+ it "should NOT authenticate an expired request" do
557
+ @request.headers['Date'] = 16.minutes.ago.utc.httpdate
558
+ signed_request = ApiAuth.sign!(@request, @access_id, @secret_key)
559
+ expect{ ApiAuth.authentic?(signed_request, @secret_key)}.to raise_error(ApiAuth::RequestTooOld)
560
+ end
561
+
562
+ it "should NOT authenticate a request with an invalid date" do
563
+ @request.headers['Date'] = "٢٠١٤-٠٩-٠٨ ١٦:٣١:١٤ +٠٣٠٠"
564
+ signed_request = ApiAuth.sign!(@request, @access_id, @secret_key)
565
+ ApiAuth.authentic?(signed_request, @secret_key).should be_false
566
+ end
567
+
568
+ it "should retrieve the access_id" do
569
+ ApiAuth.access_id(@signed_request).should == "1044"
570
+ end
571
+ end
572
+
573
+ describe "with Bixby::SignedJsonRequest" do
574
+
575
+ before(:each) do
576
+ @json_req = Bixby::JsonRequest.new("foo", "bar")
577
+ @request = Bixby::SignedJsonRequest.new(@json_req)
578
+ @request.headers.merge!({
579
+ 'Content-Type' => 'text/plain',
580
+ 'content-md5' => '1B2M2Y8AsgTpgAmY7PhCfg==',
581
+ 'date' => ApiAuth::Helpers.time_as_httpdate
582
+ })
583
+ @request.body = ""
584
+ @headers = ApiAuth::Headers.new(@request)
585
+ @signed_request = ApiAuth.sign!(@request, @access_id, @secret_key)
586
+ end
587
+
588
+ it "should return a Bixby::SignedJsonRequest object after signing it" do
589
+ ApiAuth.sign!(@request, @access_id, @secret_key).class.to_s.should match("Bixby::SignedJsonRequest")
590
+ end
591
+
592
+ describe "md5 header" do
593
+ context "not already provided" do
594
+ it "should calculate for empty string" do
595
+ request = Bixby::SignedJsonRequest.new(@json_req)
596
+ request.headers.merge!({
597
+ 'Content-Type' => 'text/plain',
598
+ 'Date' => Time.now.utc.httpdate
599
+ })
600
+ request.body = ""
601
+ signed_request = ApiAuth.sign!(request, @access_id, @secret_key)
602
+ signed_request.headers['Content-MD5'].should == Digest::MD5.base64digest('')
603
+ end
604
+
605
+ it "should calculate for real content" do
606
+ request = Bixby::SignedJsonRequest.new(@json_req)
607
+ request.headers.merge!({
608
+ 'Content-Type' => 'text/plain',
609
+ 'Date' => Time.now.utc.httpdate
610
+ })
611
+ request.body = "hello\nworld"
612
+ signed_request = ApiAuth.sign!(request, @access_id, @secret_key)
613
+ signed_request.headers['Content-MD5'].should == Digest::MD5.base64digest("hello\nworld")
614
+ end
615
+ end
616
+
617
+ it "should leave the Content-MD5 alone if provided" do
618
+ @signed_request.headers['Content-MD5'].should == '1B2M2Y8AsgTpgAmY7PhCfg=='
619
+ end
620
+ end
621
+
622
+ it "should sign the request" do
623
+ @signed_request.headers['Authorization'].should == "APIAuth 1044:#{hmac(@secret_key, @request)}"
624
+ end
625
+
626
+ it "should authenticate a valid request" do
627
+ ApiAuth.authentic?(@signed_request, @secret_key).should be_true
628
+ end
629
+
630
+ it "should NOT authenticate a non-valid request" do
631
+ ApiAuth.authentic?(@signed_request, @secret_key+'j').should be_false
632
+ end
633
+
634
+ it "should NOT authenticate a mismatched content-md5 when body has changed" do
635
+ request = Bixby::SignedJsonRequest.new(@json_req)
636
+ request.headers.merge!({
637
+ 'Content-Type' => 'text/plain',
638
+ 'Date' => Time.now.utc.httpdate
639
+ })
640
+ request.body = "hello\nworld"
641
+ signed_request = ApiAuth.sign!(request, @access_id, @secret_key)
642
+ signed_request.body = "goodbye"
643
+ ApiAuth.authentic?(signed_request, @secret_key).should be_false
644
+ end
645
+
646
+ it "should NOT authenticate an expired request" do
647
+ @request.headers['Date'] = 16.minutes.ago.utc.httpdate
648
+ signed_request = ApiAuth.sign!(@request, @access_id, @secret_key)
649
+ expect{ ApiAuth.authentic?(signed_request, @secret_key)}.to raise_error(ApiAuth::RequestTooOld)
650
+ end
651
+
652
+ it "should retrieve the access_id" do
653
+ ApiAuth.access_id(@signed_request).should == "1044"
654
+ end
655
+
656
+ end
657
+
658
+ end
659
+
660
+ end
@@ -0,0 +1,2 @@
1
+ module ApplicationHelper
2
+ end
Binary file