api-auth 1.1.0 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,9 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "actionpack", "~> 3.2.17"
6
+ gem "activeresource", "~> 3.2.17"
7
+ gem "activesupport", "~> 3.2.17"
8
+
9
+ gemspec :path=>"../"
@@ -0,0 +1,84 @@
1
+ PATH
2
+ remote: ../
3
+ specs:
4
+ api-auth (1.1.0)
5
+
6
+ GEM
7
+ remote: https://rubygems.org/
8
+ specs:
9
+ actionpack (3.2.17)
10
+ activemodel (= 3.2.17)
11
+ activesupport (= 3.2.17)
12
+ builder (~> 3.0.0)
13
+ erubis (~> 2.7.0)
14
+ journey (~> 1.0.4)
15
+ rack (~> 1.4.5)
16
+ rack-cache (~> 1.2)
17
+ rack-test (~> 0.6.1)
18
+ sprockets (~> 2.2.1)
19
+ activemodel (3.2.17)
20
+ activesupport (= 3.2.17)
21
+ builder (~> 3.0.0)
22
+ activeresource (3.2.17)
23
+ activemodel (= 3.2.17)
24
+ activesupport (= 3.2.17)
25
+ activesupport (3.2.17)
26
+ i18n (~> 0.6, >= 0.6.4)
27
+ multi_json (~> 1.0)
28
+ amatch (0.2.11)
29
+ tins (~> 0.3)
30
+ appraisal (0.5.2)
31
+ bundler
32
+ rake
33
+ builder (3.0.4)
34
+ curb (0.8.5)
35
+ diff-lcs (1.1.3)
36
+ erubis (2.7.0)
37
+ hike (1.2.3)
38
+ httpi (2.1.0)
39
+ rack
40
+ rubyntlm (~> 0.3.2)
41
+ i18n (0.6.9)
42
+ journey (1.0.4)
43
+ mime-types (1.25.1)
44
+ multi_json (1.9.2)
45
+ rack (1.4.5)
46
+ rack-cache (1.2)
47
+ rack (>= 0.4)
48
+ rack-test (0.6.2)
49
+ rack (>= 1.0)
50
+ rake (10.1.1)
51
+ rest-client (1.6.7)
52
+ mime-types (>= 1.16)
53
+ rspec (2.4.0)
54
+ rspec-core (~> 2.4.0)
55
+ rspec-expectations (~> 2.4.0)
56
+ rspec-mocks (~> 2.4.0)
57
+ rspec-core (2.4.0)
58
+ rspec-expectations (2.4.0)
59
+ diff-lcs (~> 1.1.2)
60
+ rspec-mocks (2.4.0)
61
+ rubyntlm (0.3.4)
62
+ sprockets (2.2.2)
63
+ hike (~> 1.2)
64
+ multi_json (~> 1.0)
65
+ rack (~> 1.0)
66
+ tilt (~> 1.1, != 1.3.0)
67
+ tilt (1.4.1)
68
+ tins (0.13.2)
69
+
70
+ PLATFORMS
71
+ ruby
72
+
73
+ DEPENDENCIES
74
+ actionpack (~> 3.2.17)
75
+ activeresource (~> 3.2.17)
76
+ activesupport (~> 3.2.17)
77
+ amatch
78
+ api-auth!
79
+ appraisal
80
+ curb (~> 0.8.1)
81
+ httpi
82
+ rake
83
+ rest-client (~> 1.6.0)
84
+ rspec (~> 2.4.0)
@@ -0,0 +1,9 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "actionpack", "~> 4.0.4"
6
+ gem "activeresource", "~> 4.0.0"
7
+ gem "activesupport", "~> 4.0.4"
8
+
9
+ gemspec :path=>"../"
@@ -0,0 +1,81 @@
1
+ PATH
2
+ remote: ../
3
+ specs:
4
+ api-auth (1.1.0)
5
+
6
+ GEM
7
+ remote: https://rubygems.org/
8
+ specs:
9
+ actionpack (4.0.4)
10
+ activesupport (= 4.0.4)
11
+ builder (~> 3.1.0)
12
+ erubis (~> 2.7.0)
13
+ rack (~> 1.5.2)
14
+ rack-test (~> 0.6.2)
15
+ activemodel (4.0.4)
16
+ activesupport (= 4.0.4)
17
+ builder (~> 3.1.0)
18
+ activeresource (4.0.0)
19
+ activemodel (~> 4.0)
20
+ activesupport (~> 4.0)
21
+ rails-observers (~> 0.1.1)
22
+ activesupport (4.0.4)
23
+ i18n (~> 0.6, >= 0.6.9)
24
+ minitest (~> 4.2)
25
+ multi_json (~> 1.3)
26
+ thread_safe (~> 0.1)
27
+ tzinfo (~> 0.3.37)
28
+ amatch (0.2.11)
29
+ tins (~> 0.3)
30
+ appraisal (0.5.2)
31
+ bundler
32
+ rake
33
+ atomic (1.1.16)
34
+ builder (3.1.4)
35
+ curb (0.8.5)
36
+ diff-lcs (1.1.3)
37
+ erubis (2.7.0)
38
+ httpi (2.1.0)
39
+ rack
40
+ rubyntlm (~> 0.3.2)
41
+ i18n (0.6.9)
42
+ mime-types (2.2)
43
+ minitest (4.7.5)
44
+ multi_json (1.9.2)
45
+ rack (1.5.2)
46
+ rack-test (0.6.2)
47
+ rack (>= 1.0)
48
+ rails-observers (0.1.2)
49
+ activemodel (~> 4.0)
50
+ rake (10.1.1)
51
+ rest-client (1.6.7)
52
+ mime-types (>= 1.16)
53
+ rspec (2.4.0)
54
+ rspec-core (~> 2.4.0)
55
+ rspec-expectations (~> 2.4.0)
56
+ rspec-mocks (~> 2.4.0)
57
+ rspec-core (2.4.0)
58
+ rspec-expectations (2.4.0)
59
+ diff-lcs (~> 1.1.2)
60
+ rspec-mocks (2.4.0)
61
+ rubyntlm (0.3.4)
62
+ thread_safe (0.2.0)
63
+ atomic (>= 1.1.7, < 2)
64
+ tins (0.13.2)
65
+ tzinfo (0.3.39)
66
+
67
+ PLATFORMS
68
+ ruby
69
+
70
+ DEPENDENCIES
71
+ actionpack (~> 4.0.4)
72
+ activeresource (~> 4.0.0)
73
+ activesupport (~> 4.0.4)
74
+ amatch
75
+ api-auth!
76
+ appraisal
77
+ curb (~> 0.8.1)
78
+ httpi
79
+ rake
80
+ rest-client (~> 1.6.0)
81
+ rspec (~> 2.4.0)
@@ -10,6 +10,7 @@ require 'api_auth/request_drivers/rest_client'
10
10
  require 'api_auth/request_drivers/action_controller'
11
11
  require 'api_auth/request_drivers/action_dispatch'
12
12
  require 'api_auth/request_drivers/rack'
13
+ require 'api_auth/request_drivers/httpi'
13
14
 
14
15
  require 'api_auth/headers'
15
16
  require 'api_auth/base'
@@ -15,7 +15,7 @@ module ApiAuth
15
15
  # Signs an HTTP request using the client's access id and secret key.
16
16
  # Returns the HTTP request object with the modified headers.
17
17
  #
18
- # request: The request can be a Net::HTTP, ActionController::Request,
18
+ # request: The request can be a Net::HTTP, ActionDispatch::Request,
19
19
  # Curb (Curl::Easy) or a RestClient object.
20
20
  #
21
21
  # access_id: The public unique identifier for the client
@@ -29,6 +29,10 @@ module ApiAuth
29
29
  @request = RackRequest.new(request)
30
30
  when /ActionController::CgiRequest/
31
31
  @request = ActionControllerRequest.new(request)
32
+ when /HTTPI::Request/
33
+ @request = HttpiRequest.new(request)
34
+ when /Sinatra::Request/
35
+ @request = RackRequest.new(request)
32
36
  else
33
37
  raise UnknownHTTPRequest, "#{request.class.to_s} is not yet supported."
34
38
  end
@@ -1,18 +1,31 @@
1
1
  module ApiAuth
2
2
 
3
3
  module Helpers # :nodoc:
4
-
4
+
5
5
  def b64_encode(string)
6
- Base64.strict_encode64(string)
6
+ if Base64.respond_to?(:strict_encode64)
7
+ Base64.strict_encode64(string)
8
+ else
9
+ # Fall back to stripping out newlines on Ruby 1.8.
10
+ Base64.encode64(string).gsub(/\n/, '')
11
+ end
12
+ end
13
+
14
+ def md5_base64digest(string)
15
+ if Digest::MD5.respond_to?(:base64digest)
16
+ Digest::MD5.base64digest(string)
17
+ else
18
+ b64_encode(Digest::MD5.digest(string))
19
+ end
7
20
  end
8
-
21
+
9
22
  # Capitalizes the keys of a hash
10
23
  def capitalize_keys(hsh)
11
24
  capitalized_hash = {}
12
25
  hsh.each_pair {|k,v| capitalized_hash[k.to_s.upcase] = v }
13
26
  capitalized_hash
14
27
  end
15
-
28
+
16
29
  end
17
-
30
+
18
31
  end
@@ -24,7 +24,7 @@ module ApiAuth
24
24
  else
25
25
  body = ''
26
26
  end
27
- Digest::MD5.base64digest(body)
27
+ md5_base64digest(body)
28
28
  end
29
29
 
30
30
  def populate_content_md5
@@ -60,7 +60,7 @@ module ApiAuth
60
60
  end
61
61
 
62
62
  def set_date
63
- @request.env['DATE'] = Time.now.utc.httpdate
63
+ @request.env['HTTP_DATE'] = Time.now.utc.httpdate
64
64
  end
65
65
 
66
66
  def timestamp
@@ -0,0 +1,80 @@
1
+ module ApiAuth
2
+
3
+ module RequestDrivers # :nodoc:
4
+
5
+ class HttpiRequest # :nodoc:
6
+
7
+ include ApiAuth::Helpers
8
+
9
+ def initialize(request)
10
+ @request = request
11
+ @headers = fetch_headers
12
+ true
13
+ end
14
+
15
+ def set_auth_header(header)
16
+ @request.headers["Authorization"] = header
17
+ @headers = fetch_headers
18
+ @request
19
+ end
20
+
21
+ def calculated_md5
22
+ md5_base64digest(@request.body || '')
23
+ end
24
+
25
+ def populate_content_md5
26
+ if @request.body
27
+ @request.headers["Content-MD5"] = calculated_md5
28
+ end
29
+ end
30
+
31
+ def md5_mismatch?
32
+ if @request.body
33
+ calculated_md5 != content_md5
34
+ else
35
+ false
36
+ end
37
+ end
38
+
39
+ def fetch_headers
40
+ capitalize_keys @request.headers
41
+ end
42
+
43
+ def content_type
44
+ value = find_header(%w(CONTENT-TYPE CONTENT_TYPE HTTP_CONTENT_TYPE))
45
+ value.nil? ? "" : value
46
+ end
47
+
48
+ def content_md5
49
+ value = find_header(%w(CONTENT-MD5 CONTENT_MD5))
50
+ value.nil? ? "" : value
51
+ end
52
+
53
+ def request_uri
54
+ @request.url.request_uri
55
+ end
56
+
57
+ def set_date
58
+ @request.headers["DATE"] = Time.now.utc.httpdate
59
+ end
60
+
61
+ def timestamp
62
+ value = find_header(%w(DATE HTTP_DATE))
63
+ value.nil? ? "" : value
64
+ end
65
+
66
+ def authorization_header
67
+ find_header %w(Authorization AUTHORIZATION HTTP_AUTHORIZATION)
68
+ end
69
+
70
+ private
71
+
72
+ def find_header(keys)
73
+ keys.map {|key| @headers[key] }.compact.first
74
+ end
75
+
76
+ end
77
+
78
+ end
79
+
80
+ end
@@ -4,6 +4,8 @@ module ApiAuth
4
4
 
5
5
  class NetHttpRequest # :nodoc:
6
6
 
7
+ include ApiAuth::Helpers
8
+
7
9
  def initialize(request)
8
10
  @request = request
9
11
  @headers = fetch_headers
@@ -17,7 +19,7 @@ module ApiAuth
17
19
  end
18
20
 
19
21
  def calculated_md5
20
- Digest::MD5.base64digest(@request.body || '')
22
+ md5_base64digest(@request.body || '')
21
23
  end
22
24
 
23
25
  def populate_content_md5
@@ -25,7 +25,7 @@ module ApiAuth
25
25
  else
26
26
  body = ''
27
27
  end
28
- Digest::MD5.base64digest(body)
28
+ md5_base64digest(body)
29
29
  end
30
30
 
31
31
  def populate_content_md5
@@ -17,7 +17,6 @@ module ApiAuth
17
17
 
18
18
  def set_auth_header(header)
19
19
  @request.headers.merge!({ "Authorization" => header })
20
- @headers = fetch_headers
21
20
  save_headers # enforce update of processed_headers based on last updated headers
22
21
  @request
23
22
  end
@@ -29,7 +28,7 @@ module ApiAuth
29
28
  else
30
29
  body = ''
31
30
  end
32
- Digest::MD5.base64digest(body)
31
+ md5_base64digest(body)
33
32
  end
34
33
 
35
34
  def populate_content_md5
@@ -47,7 +46,7 @@ module ApiAuth
47
46
  end
48
47
 
49
48
  def fetch_headers
50
- capitalize_keys @request.headers
49
+ capitalize_keys @request.processed_headers
51
50
  end
52
51
 
53
52
  def content_type
@@ -84,7 +83,8 @@ module ApiAuth
84
83
  end
85
84
 
86
85
  def save_headers
87
- @request.processed_headers = @request.make_headers(@headers)
86
+ @request.processed_headers = @request.make_headers(@request.headers)
87
+ @headers = fetch_headers
88
88
  end
89
89
 
90
90
  end
@@ -36,8 +36,8 @@ describe "ApiAuth" do
36
36
  describe "with Net::HTTP" do
37
37
 
38
38
  before(:each) do
39
- @request = Net::HTTP::Put.new("/resource.xml?foo=bar&bar=foo",
40
- 'content-type' => 'text/plain',
39
+ @request = Net::HTTP::Put.new("/resource.xml?foo=bar&bar=foo",
40
+ 'content-type' => 'text/plain',
41
41
  'content-md5' => '1B2M2Y8AsgTpgAmY7PhCfg==',
42
42
  'date' => Time.now.utc.httpdate)
43
43
  @signed_request = ApiAuth.sign!(@request, @access_id, @secret_key)
@@ -54,7 +54,7 @@ describe "ApiAuth" do
54
54
  'content-type' => 'text/plain',
55
55
  'date' => "Mon, 23 Jan 1984 03:29:56 GMT")
56
56
  signed_request = ApiAuth.sign!(request, @access_id, @secret_key)
57
- signed_request['Content-MD5'].should == Digest::MD5.base64digest('')
57
+ signed_request['Content-MD5'].should == "1B2M2Y8AsgTpgAmY7PhCfg=="
58
58
  end
59
59
 
60
60
  it "should calculate for real content" do
@@ -63,7 +63,7 @@ describe "ApiAuth" do
63
63
  'date' => "Mon, 23 Jan 1984 03:29:56 GMT")
64
64
  request.body = "hello\nworld"
65
65
  signed_request = ApiAuth.sign!(request, @access_id, @secret_key)
66
- signed_request['Content-MD5'].should == Digest::MD5.base64digest("hello\nworld")
66
+ signed_request['Content-MD5'].should == "kZXQvrKoieG+Be1rsZVINw=="
67
67
  end
68
68
  end
69
69
 
@@ -99,7 +99,7 @@ describe "ApiAuth" do
99
99
  signed_request = ApiAuth.sign!(@request, @access_id, @secret_key)
100
100
  ApiAuth.authentic?(signed_request, @secret_key).should be_false
101
101
  end
102
-
102
+
103
103
  it "should retrieve the access_id" do
104
104
  ApiAuth.access_id(@signed_request).should == "1044"
105
105
  end
@@ -112,7 +112,7 @@ describe "ApiAuth" do
112
112
  headers = { 'Content-MD5' => "1B2M2Y8AsgTpgAmY7PhCfg==",
113
113
  'Content-Type' => "text/plain",
114
114
  'Date' => Time.now.utc.httpdate }
115
- @request = RestClient::Request.new(:url => "/resource.xml?foo=bar&bar=foo",
115
+ @request = RestClient::Request.new(:url => "/resource.xml?foo=bar&bar=foo",
116
116
  :headers => headers,
117
117
  :method => :put)
118
118
  @signed_request = ApiAuth.sign!(@request, @access_id, @secret_key)
@@ -131,7 +131,7 @@ describe "ApiAuth" do
131
131
  :headers => headers,
132
132
  :method => :put)
133
133
  signed_request = ApiAuth.sign!(request, @access_id, @secret_key)
134
- signed_request.headers['Content-MD5'].should == Digest::MD5.base64digest('')
134
+ signed_request.headers['Content-MD5'].should == "1B2M2Y8AsgTpgAmY7PhCfg=="
135
135
  end
136
136
 
137
137
  it "should calculate for real content" do
@@ -142,7 +142,7 @@ describe "ApiAuth" do
142
142
  :method => :put,
143
143
  :payload => "hellow\nworld")
144
144
  signed_request = ApiAuth.sign!(request, @access_id, @secret_key)
145
- signed_request.headers['Content-MD5'].should == Digest::MD5.base64digest("hellow\nworld")
145
+ signed_request.headers['Content-MD5'].should == "G0grublI06013h58g9j8Vw=="
146
146
  end
147
147
  end
148
148
 
@@ -180,7 +180,7 @@ describe "ApiAuth" do
180
180
  signed_request = ApiAuth.sign!(@request, @access_id, @secret_key)
181
181
  ApiAuth.authentic?(signed_request, @secret_key).should be_false
182
182
  end
183
-
183
+
184
184
  it "should retrieve the access_id" do
185
185
  ApiAuth.access_id(@signed_request).should == "1044"
186
186
  end
@@ -236,17 +236,19 @@ describe "ApiAuth" do
236
236
  signed_request = ApiAuth.sign!(@request, @access_id, @secret_key)
237
237
  ApiAuth.authentic?(signed_request, @secret_key).should be_false
238
238
  end
239
-
239
+
240
240
  it "should retrieve the access_id" do
241
241
  ApiAuth.access_id(@signed_request).should == "1044"
242
242
  end
243
243
 
244
244
  end
245
245
 
246
- describe "with ActionController" do
246
+ describe "with ActionController/ActionDispatch" do
247
+
248
+ let(:request_klass){ ActionDispatch::Request rescue ActionController::Request }
247
249
 
248
250
  before(:each) do
249
- @request = ActionController::Request.new(
251
+ @request = request_klass.new(
250
252
  'PATH_INFO' => '/resource.xml',
251
253
  'QUERY_STRING' => 'foo=bar&bar=foo',
252
254
  'REQUEST_METHOD' => 'PUT',
@@ -256,25 +258,25 @@ describe "ApiAuth" do
256
258
  @signed_request = ApiAuth.sign!(@request, @access_id, @secret_key)
257
259
  end
258
260
 
259
- it "should return a ActionController::Request object after signing it" do
260
- ApiAuth.sign!(@request, @access_id, @secret_key).class.to_s.should match("ActionController::Request")
261
+ it "should return a ActionDispatch::Request object after signing it" do
262
+ ApiAuth.sign!(@request, @access_id, @secret_key).class.to_s.should match(request_klass.to_s)
261
263
  end
262
264
 
263
265
  describe "md5 header" do
264
266
  context "not already provided" do
265
267
  it "should calculate for empty string" do
266
- request = ActionController::Request.new(
268
+ request = request_klass.new(
267
269
  'PATH_INFO' => '/resource.xml',
268
270
  'QUERY_STRING' => 'foo=bar&bar=foo',
269
271
  'REQUEST_METHOD' => 'PUT',
270
272
  'CONTENT_TYPE' => 'text/plain',
271
273
  'HTTP_DATE' => 'Mon, 23 Jan 1984 03:29:56 GMT')
272
274
  signed_request = ApiAuth.sign!(request, @access_id, @secret_key)
273
- signed_request.env['Content-MD5'].should == Digest::MD5.base64digest('')
275
+ signed_request.env['Content-MD5'].should == "1B2M2Y8AsgTpgAmY7PhCfg=="
274
276
  end
275
277
 
276
278
  it "should calculate for real content" do
277
- request = ActionController::Request.new(
279
+ request = request_klass.new(
278
280
  'PATH_INFO' => '/resource.xml',
279
281
  'QUERY_STRING' => 'foo=bar&bar=foo',
280
282
  'REQUEST_METHOD' => 'PUT',
@@ -282,7 +284,7 @@ describe "ApiAuth" do
282
284
  'HTTP_DATE' => 'Mon, 23 Jan 1984 03:29:56 GMT',
283
285
  'RAW_POST_DATA' => "hello\nworld")
284
286
  signed_request = ApiAuth.sign!(request, @access_id, @secret_key)
285
- signed_request.env['Content-MD5'].should == Digest::MD5.base64digest("hello\nworld")
287
+ signed_request.env['Content-MD5'].should == "kZXQvrKoieG+Be1rsZVINw=="
286
288
  end
287
289
 
288
290
  end
@@ -305,7 +307,7 @@ describe "ApiAuth" do
305
307
  end
306
308
 
307
309
  it "should NOT authenticate a mismatched content-md5 when body has changed" do
308
- request = ActionController::Request.new(
310
+ request = request_klass.new(
309
311
  'PATH_INFO' => '/resource.xml',
310
312
  'QUERY_STRING' => 'foo=bar&bar=foo',
311
313
  'REQUEST_METHOD' => 'PUT',
@@ -350,7 +352,7 @@ describe "ApiAuth" do
350
352
  'Date' => "Mon, 23 Jan 1984 03:29:56 GMT" }
351
353
  request = Rack::Request.new(Rack::MockRequest.env_for("/resource.xml?foo=bar&bar=foo", :method => :put).merge!(headers))
352
354
  signed_request = ApiAuth.sign!(request, @access_id, @secret_key)
353
- signed_request.env['Content-MD5'].should == Digest::MD5.base64digest('')
355
+ signed_request.env['Content-MD5'].should == "1B2M2Y8AsgTpgAmY7PhCfg=="
354
356
  end
355
357
 
356
358
  it "should calculate for real content" do
@@ -358,7 +360,7 @@ describe "ApiAuth" do
358
360
  'Date' => "Mon, 23 Jan 1984 03:29:56 GMT" }
359
361
  request = Rack::Request.new(Rack::MockRequest.env_for("/resource.xml?foo=bar&bar=foo", :method => :put, :input => "hellow\nworld").merge!(headers))
360
362
  signed_request = ApiAuth.sign!(request, @access_id, @secret_key)
361
- signed_request.env['Content-MD5'].should == Digest::MD5.base64digest("hellow\nworld")
363
+ signed_request.env['Content-MD5'].should == "G0grublI06013h58g9j8Vw=="
362
364
  end
363
365
  end
364
366
 
@@ -395,13 +397,86 @@ describe "ApiAuth" do
395
397
  signed_request = ApiAuth.sign!(@request, @access_id, @secret_key)
396
398
  ApiAuth.authentic?(signed_request, @secret_key).should be_false
397
399
  end
398
-
400
+
399
401
  it "should retrieve the access_id" do
400
402
  ApiAuth.access_id(@signed_request).should == "1044"
401
403
  end
402
404
 
403
405
  end
404
406
 
407
+ describe "with HTTPI" do
408
+ before(:each) do
409
+ @request = HTTPI::Request.new("http://localhost/resource.xml?foo=bar&bar=foo")
410
+ @request.headers.merge!({
411
+ 'content-type' => 'text/plain',
412
+ 'content-md5' => '1B2M2Y8AsgTpgAmY7PhCfg==',
413
+ 'date' => Time.now.utc.httpdate
414
+ })
415
+ @headers = ApiAuth::Headers.new(@request)
416
+ @signed_request = ApiAuth.sign!(@request, @access_id, @secret_key)
417
+ end
418
+
419
+ it "should return a HTTPI object after signing it" do
420
+ ApiAuth.sign!(@request, @access_id, @secret_key).class.to_s.should match("HTTPI::Request")
421
+ end
422
+
423
+ describe "md5 header" do
424
+ context "not already provided" do
425
+ it "should calculate for empty string" do
426
+ request = Net::HTTP::Put.new("/resource.xml?foo=bar&bar=foo",
427
+ 'content-type' => 'text/plain',
428
+ 'date' => "Mon, 23 Jan 1984 03:29:56 GMT")
429
+ signed_request = ApiAuth.sign!(request, @access_id, @secret_key)
430
+ signed_request['Content-MD5'].should == "1B2M2Y8AsgTpgAmY7PhCfg=="
431
+ end
432
+
433
+ it "should calculate for real content" do
434
+ request = Net::HTTP::Put.new("/resource.xml?foo=bar&bar=foo",
435
+ 'content-type' => 'text/plain',
436
+ 'date' => "Mon, 23 Jan 1984 03:29:56 GMT")
437
+ request.body = "hello\nworld"
438
+ signed_request = ApiAuth.sign!(request, @access_id, @secret_key)
439
+ signed_request['Content-MD5'].should == "kZXQvrKoieG+Be1rsZVINw=="
440
+ end
441
+ end
442
+
443
+ it "should leave the content-md5 alone if provided" do
444
+ @signed_request.headers['Content-MD5'].should == '1B2M2Y8AsgTpgAmY7PhCfg=='
445
+ end
446
+ end
447
+
448
+ it "should sign the request" do
449
+ @signed_request.headers['Authorization'].should == "APIAuth 1044:#{hmac(@secret_key, @request)}"
450
+ end
451
+
452
+ it "should authenticate a valid request" do
453
+ ApiAuth.authentic?(@signed_request, @secret_key).should be_true
454
+ end
455
+
456
+ it "should NOT authenticate a non-valid request" do
457
+ ApiAuth.authentic?(@signed_request, @secret_key+'j').should be_false
458
+ end
459
+
460
+ it "should NOT authenticate a mismatched content-md5 when body has changed" do
461
+ request = Net::HTTP::Put.new("/resource.xml?foo=bar&bar=foo",
462
+ 'content-type' => 'text/plain',
463
+ 'date' => "Mon, 23 Jan 1984 03:29:56 GMT")
464
+ request.body = "hello\nworld"
465
+ signed_request = ApiAuth.sign!(request, @access_id, @secret_key)
466
+ signed_request.body = "goodbye"
467
+ ApiAuth.authentic?(signed_request, @secret_key).should be_false
468
+ end
469
+
470
+ it "should NOT authenticate an expired request" do
471
+ @request.headers['Date'] = 16.minutes.ago.utc.httpdate
472
+ signed_request = ApiAuth.sign!(@request, @access_id, @secret_key)
473
+ ApiAuth.authentic?(signed_request, @secret_key).should be_false
474
+ end
475
+
476
+ it "should retrieve the access_id" do
477
+ ApiAuth.access_id(@signed_request).should == "1044"
478
+ end
479
+ end
405
480
  end
406
481
 
407
482
  end