api-auth 1.1.0 → 1.2.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.
@@ -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