api-auth 1.0.1 → 1.0.2
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.
- data/.gitignore +2 -0
- data/Gemfile.lock +1 -1
- data/VERSION +1 -1
- data/lib/api_auth.rb +1 -0
- data/lib/api_auth/base.rb +1 -1
- data/lib/api_auth/headers.rb +2 -0
- data/lib/api_auth/request_drivers/rack.rb +85 -0
- data/spec/api_auth_spec.rb +73 -0
- data/spec/headers_spec.rb +38 -0
- metadata +5 -4
data/Gemfile.lock
CHANGED
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
1.0.
|
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.
|
63
|
+
Time.parse(headers.timestamp).utc < (Time.now.utc - 900)
|
64
64
|
end
|
65
65
|
|
66
66
|
def md5_mismatch?(request)
|
data/lib/api_auth/headers.rb
CHANGED
@@ -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
|
data/spec/api_auth_spec.rb
CHANGED
@@ -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.
|
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:
|
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:
|
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:
|
201
|
+
hash: 3429846398106267766
|
201
202
|
requirements: []
|
202
203
|
rubyforge_project:
|
203
204
|
rubygems_version: 1.8.24
|