api-auth 1.0.1 → 1.0.2

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore CHANGED
@@ -1,3 +1,5 @@
1
+ .rvmrc
2
+
1
3
  # rcov generated
2
4
  coverage
3
5
 
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- api-auth (1.0.1)
4
+ api-auth (1.0.2)
5
5
 
6
6
  GEM
7
7
  remote: http://rubygems.org/
data/VERSION CHANGED
@@ -1 +1 @@
1
- 1.0.1
1
+ 1.0.2
data/lib/api_auth.rb CHANGED
@@ -9,6 +9,7 @@ require 'api_auth/request_drivers/curb'
9
9
  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
+ require 'api_auth/request_drivers/rack'
12
13
 
13
14
  require 'api_auth/headers'
14
15
  require 'api_auth/base'
data/lib/api_auth/base.rb CHANGED
@@ -60,7 +60,7 @@ module ApiAuth
60
60
  def request_too_old?(request)
61
61
  headers = Headers.new(request)
62
62
  # 900 seconds is 15 minutes
63
- Time.parse(headers.timestamp).utc < (Time.current.utc - 900)
63
+ Time.parse(headers.timestamp).utc < (Time.now.utc - 900)
64
64
  end
65
65
 
66
66
  def md5_mismatch?(request)
@@ -25,6 +25,8 @@ module ApiAuth
25
25
  end
26
26
  when /ActionDispatch::Request/
27
27
  @request = ActionDispatchRequest.new(request)
28
+ when /Rack::Request/
29
+ @request = RackRequest.new(request)
28
30
  else
29
31
  raise UnknownHTTPRequest, "#{request.class.to_s} is not yet supported."
30
32
  end
@@ -0,0 +1,85 @@
1
+ module ApiAuth
2
+
3
+ module RequestDrivers # :nodoc:
4
+
5
+ class RackRequest # :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.env.merge!({ "Authorization" => header })
17
+ @headers = fetch_headers
18
+ @request
19
+ end
20
+
21
+ def calculated_md5
22
+ if @request.body
23
+ body = @request.body.read
24
+ else
25
+ body = ''
26
+ end
27
+ Digest::MD5.base64digest(body)
28
+ end
29
+
30
+ def populate_content_md5
31
+ if ['POST', 'PUT'].include?(@request.request_method)
32
+ @request.env["Content-MD5"] = calculated_md5
33
+ end
34
+ end
35
+
36
+ def md5_mismatch?
37
+ if ['POST', 'PUT'].include?(@request.request_method)
38
+ calculated_md5 != content_md5
39
+ else
40
+ false
41
+ end
42
+ end
43
+
44
+ def fetch_headers
45
+ capitalize_keys @request.env
46
+ end
47
+
48
+ def content_type
49
+ value = find_header(%w(CONTENT-TYPE CONTENT_TYPE HTTP_CONTENT_TYPE))
50
+ value.nil? ? "" : value
51
+ end
52
+
53
+ def content_md5
54
+ value = find_header(%w(CONTENT-MD5 CONTENT_MD5))
55
+ value.nil? ? "" : value
56
+ end
57
+
58
+ def request_uri
59
+ @request.url
60
+ end
61
+
62
+ def set_date
63
+ @request.env.merge!({ "DATE" => Time.now.utc.httpdate })
64
+ end
65
+
66
+ def timestamp
67
+ value = find_header(%w(DATE HTTP_DATE))
68
+ value.nil? ? "" : value
69
+ end
70
+
71
+ def authorization_header
72
+ find_header %w(Authorization AUTHORIZATION HTTP_AUTHORIZATION)
73
+ end
74
+
75
+ private
76
+
77
+ def find_header(keys)
78
+ keys.map {|key| @headers[key] }.compact.first
79
+ end
80
+
81
+ end
82
+
83
+ end
84
+
85
+ end
@@ -329,6 +329,79 @@ describe "ApiAuth" do
329
329
 
330
330
  end
331
331
 
332
+ describe "with Rack::Request" do
333
+
334
+ before(:each) do
335
+ headers = { 'Content-MD5' => "1B2M2Y8AsgTpgAmY7PhCfg==",
336
+ 'Content-Type' => "text/plain",
337
+ 'Date' => Time.now.utc.httpdate }
338
+ @request = Rack::Request.new(Rack::MockRequest.env_for("/resource.xml?foo=bar&bar=foo", :method => :put).merge!(headers))
339
+ @signed_request = ApiAuth.sign!(@request, @access_id, @secret_key)
340
+ end
341
+
342
+ it "should return a Rack::Request object after signing it" do
343
+ ApiAuth.sign!(@request, @access_id, @secret_key).class.to_s.should match("Rack::Request")
344
+ end
345
+
346
+ describe "md5 header" do
347
+ context "not already provided" do
348
+ it "should calculate for empty string" do
349
+ headers = { 'Content-Type' => "text/plain",
350
+ 'Date' => "Mon, 23 Jan 1984 03:29:56 GMT" }
351
+ request = Rack::Request.new(Rack::MockRequest.env_for("/resource.xml?foo=bar&bar=foo", :method => :put).merge!(headers))
352
+ signed_request = ApiAuth.sign!(request, @access_id, @secret_key)
353
+ signed_request.env['Content-MD5'].should == Digest::MD5.base64digest('')
354
+ end
355
+
356
+ it "should calculate for real content" do
357
+ headers = { 'Content-Type' => "text/plain",
358
+ 'Date' => "Mon, 23 Jan 1984 03:29:56 GMT" }
359
+ request = Rack::Request.new(Rack::MockRequest.env_for("/resource.xml?foo=bar&bar=foo", :method => :put, :input => "hellow\nworld").merge!(headers))
360
+ signed_request = ApiAuth.sign!(request, @access_id, @secret_key)
361
+ signed_request.env['Content-MD5'].should == Digest::MD5.base64digest("hellow\nworld")
362
+ end
363
+ end
364
+
365
+ it "should leave the content-md5 alone if provided" do
366
+ @signed_request.env['Content-MD5'].should == "1B2M2Y8AsgTpgAmY7PhCfg=="
367
+ end
368
+ end
369
+
370
+ it "should sign the request" do
371
+ @signed_request.env['Authorization'].should == "APIAuth 1044:#{hmac(@secret_key, @request)}"
372
+ end
373
+
374
+ it "should authenticate a valid request" do
375
+ ApiAuth.authentic?(@signed_request, @secret_key).should be_true
376
+ end
377
+
378
+ it "should NOT authenticate a non-valid request" do
379
+ ApiAuth.authentic?(@signed_request, @secret_key+'j').should be_false
380
+ end
381
+
382
+ it "should NOT authenticate a mismatched content-md5 when body has changed" do
383
+ headers = { 'Content-Type' => "text/plain",
384
+ 'Date' => "Mon, 23 Jan 1984 03:29:56 GMT" }
385
+ request = Rack::Request.new(Rack::MockRequest.env_for("/resource.xml?foo=bar&bar=foo", :method => :put, :input => "hellow\nworld").merge!(headers))
386
+ signed_request = ApiAuth.sign!(request, @access_id, @secret_key)
387
+ changed_request = Rack::Request.new(Rack::MockRequest.env_for("/resource.xml?foo=bar&bar=foo", :method => :put, :input => "goodbye").merge!(headers))
388
+ signed_request.env['rack.input'] = changed_request.env['rack.input']
389
+ signed_request.env['CONTENT_LENGTH'] = changed_request.env['CONTENT_LENGTH']
390
+ ApiAuth.authentic?(signed_request, @secret_key).should be_false
391
+ end
392
+
393
+ it "should NOT authenticate an expired request" do
394
+ @request.env['Date'] = 16.minutes.ago.utc.httpdate
395
+ signed_request = ApiAuth.sign!(@request, @access_id, @secret_key)
396
+ ApiAuth.authentic?(signed_request, @secret_key).should be_false
397
+ end
398
+
399
+ it "should retrieve the access_id" do
400
+ ApiAuth.access_id(@signed_request).should == "1044"
401
+ end
402
+
403
+ end
404
+
332
405
  end
333
406
 
334
407
  end
data/spec/headers_spec.rb CHANGED
@@ -182,4 +182,42 @@ describe "ApiAuth::Headers" do
182
182
  end
183
183
  end
184
184
 
185
+ describe "with Rack::Request" do
186
+
187
+ before(:each) do
188
+ headers = { 'Content-MD5' => "e59ff97941044f85df5297e1c302d260",
189
+ 'Content-Type' => "text/plain",
190
+ 'Date' => "Mon, 23 Jan 1984 03:29:56 GMT"
191
+ }
192
+ @request = Rack::Request.new(Rack::MockRequest.env_for("/resource.xml?foo=bar&bar=foo", :method => :put).merge!(headers))
193
+ @headers = ApiAuth::Headers.new(@request)
194
+ end
195
+
196
+ it "should generate the proper canonical string" do
197
+ @headers.canonical_string.should == "text/plain,e59ff97941044f85df5297e1c302d260,http://example.org/resource.xml?foo=bar&bar=foo,Mon, 23 Jan 1984 03:29:56 GMT"
198
+ end
199
+
200
+ it "should set the authorization header" do
201
+ @headers.sign_header("alpha")
202
+ @headers.authorization_header.should == "alpha"
203
+ end
204
+
205
+ it "should set the DATE header if one is not already present" do
206
+ headers = { 'Content-MD5' => "e59ff97941044f85df5297e1c302d260",
207
+ 'Content-Type' => "text/plain" }
208
+ @request = Rack::Request.new(Rack::MockRequest.env_for("/resource.xml?foo=bar&bar=foo", :method => :put).merge!(headers))
209
+ ApiAuth.sign!(@request, "some access id", "some secret key")
210
+ @request.env['DATE'].should_not be_nil
211
+ end
212
+
213
+ it "should not set the DATE header just by asking for the canonical_string" do
214
+ headers = { 'Content-MD5' => "e59ff97941044f85df5297e1c302d260",
215
+ 'Content-Type' => "text/plain" }
216
+ request = Rack::Request.new(Rack::MockRequest.env_for("/resource.xml?foo=bar&bar=foo", :method => :put).merge!(headers))
217
+ headers = ApiAuth::Headers.new(request)
218
+ headers.canonical_string
219
+ request.env['DATE'].should be_nil
220
+ end
221
+ end
222
+
185
223
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: api-auth
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.1
4
+ version: 1.0.2
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-11-30 00:00:00.000000000 Z
12
+ date: 2013-02-06 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rake
@@ -166,6 +166,7 @@ files:
166
166
  - lib/api_auth/request_drivers/action_dispatch.rb
167
167
  - lib/api_auth/request_drivers/curb.rb
168
168
  - lib/api_auth/request_drivers/net_http.rb
169
+ - lib/api_auth/request_drivers/rack.rb
169
170
  - lib/api_auth/request_drivers/rest_client.rb
170
171
  - spec/api_auth_spec.rb
171
172
  - spec/application_helper.rb
@@ -188,7 +189,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
188
189
  version: '0'
189
190
  segments:
190
191
  - 0
191
- hash: -1454681296772684747
192
+ hash: 3429846398106267766
192
193
  required_rubygems_version: !ruby/object:Gem::Requirement
193
194
  none: false
194
195
  requirements:
@@ -197,7 +198,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
197
198
  version: '0'
198
199
  segments:
199
200
  - 0
200
- hash: -1454681296772684747
201
+ hash: 3429846398106267766
201
202
  requirements: []
202
203
  rubyforge_project:
203
204
  rubygems_version: 1.8.24