api-auth 0.9.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.
- data/.document +5 -0
- data/.rspec +3 -0
- data/Gemfile +12 -0
- data/LICENSE.txt +20 -0
- data/README.md +92 -0
- data/Rakefile +45 -0
- data/lib/api-auth.rb +14 -0
- data/spec/api-auth_spec.rb +138 -0
- data/spec/headers_spec.rb +102 -0
- data/spec/helpers_spec.rb +14 -0
- data/spec/railtie_spec.rb +105 -0
- data/spec/spec_helper.rb +22 -0
- metadata +210 -0
data/.document
ADDED
data/.rspec
ADDED
data/Gemfile
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
source "http://rubygems.org"
|
2
|
+
|
3
|
+
group :development do
|
4
|
+
gem "rspec", "~> 2.4.0"
|
5
|
+
gem "bundler", "~> 1.0.0"
|
6
|
+
gem "jeweler", "~> 1.5.2"
|
7
|
+
gem "amatch", "~> 0.2.5"
|
8
|
+
gem "curb", "~> 0.7.7"
|
9
|
+
gem "rest-client", "~> 1.6.0"
|
10
|
+
gem "actionpack", "~> 2.3.2"
|
11
|
+
gem "activeresource", "~> 2.3.2"
|
12
|
+
end
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2011 Gemini SBS LLC
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,92 @@
|
|
1
|
+
api-auth
|
2
|
+
========
|
3
|
+
|
4
|
+
Logins and passwords are for humans. Communication between applications need to
|
5
|
+
be protected through different means.
|
6
|
+
|
7
|
+
api-auth is Ruby gem designed to be used both in your client and server
|
8
|
+
HTTP-based applications. It implements the same authentication methods (HMAC)
|
9
|
+
used by Amazon Web Services.
|
10
|
+
|
11
|
+
The gem will sign your requests on the client side and authenticate that
|
12
|
+
signature on the server side. If your server resources are implemented as a
|
13
|
+
Rails ActiveResource, it will integrate with that. It will even generate the
|
14
|
+
secret keys necessary for your clients to sign their requests.
|
15
|
+
|
16
|
+
Since it operates entirely using HTTP headers, the server component does not
|
17
|
+
have to be written in the same language as the clients. Any language with
|
18
|
+
OpenSSL bindings will suffice.
|
19
|
+
|
20
|
+
How it works
|
21
|
+
------------
|
22
|
+
|
23
|
+
1. A canonical string is first created using your HTTP headers containing the
|
24
|
+
content-type, content-MD5, request URI and the
|
25
|
+
timestamp. The canonical string string is computed as follows:
|
26
|
+
|
27
|
+
canonical_string = "<content-type>,<content-MD5>,<URI>,<timestamp>"
|
28
|
+
|
29
|
+
If content-type or content-MD5 are not present, then a blank string is used in
|
30
|
+
their place. If the timestamp isn't present, a valid HTTP date is automatically
|
31
|
+
added to the request.
|
32
|
+
|
33
|
+
2. This string is then used to create the signature which is a Base64 encoded
|
34
|
+
SHA1 HMAC, using the client's private secret key.
|
35
|
+
|
36
|
+
3. This signature is then added as the `Authorization` HTTP header in the form:
|
37
|
+
|
38
|
+
Authorization = APIAuth <client access id>:<signature from step 2>
|
39
|
+
|
40
|
+
5. On the server side, the SHA1 HMAC is computed in the same way using the
|
41
|
+
request headers and the client's secret key, which is known to only
|
42
|
+
the client and the server but can be looked up on the server using the client's
|
43
|
+
access id that was attached in the header. The access id can be any integer or
|
44
|
+
string that uniquely identifies the client.
|
45
|
+
|
46
|
+
|
47
|
+
References
|
48
|
+
----------
|
49
|
+
|
50
|
+
* [Hash functions](http://en.wikipedia.org/wiki/Cryptographic_hash_function)
|
51
|
+
* [SHA-1 Hash function](http://en.wikipedia.org/wiki/SHA-1)
|
52
|
+
* [HMAC algorithm](http://en.wikipedia.org/wiki/HMAC)
|
53
|
+
* [RFC 2104 (HMAC)](http://tools.ietf.org/html/rfc2104)
|
54
|
+
|
55
|
+
Usage
|
56
|
+
-----
|
57
|
+
|
58
|
+
### Install ###
|
59
|
+
|
60
|
+
[sudo] gem install api-auth
|
61
|
+
|
62
|
+
### Supported Request Objects ###
|
63
|
+
|
64
|
+
ApiAuth supports most request objects. With the support of
|
65
|
+
ActionController::Request, ApiAuth is fully compatible with Rails. Support for
|
66
|
+
other request objects can be added as a request driver.
|
67
|
+
|
68
|
+
Here is the current list of supported request objects:
|
69
|
+
|
70
|
+
* Net::HTTP
|
71
|
+
* ActionController::Request
|
72
|
+
* Curb (Curl::Easy)
|
73
|
+
* RestClient
|
74
|
+
|
75
|
+
### ActiveResource ###
|
76
|
+
|
77
|
+
class MyResource < ActiveResource::Base
|
78
|
+
with_api_auth(<access_id>, <secret_key>)
|
79
|
+
end
|
80
|
+
|
81
|
+
### Server ###
|
82
|
+
|
83
|
+
|
84
|
+
Authors
|
85
|
+
-------
|
86
|
+
|
87
|
+
* [Mauricio Gomes](http://github.com/mgomes)
|
88
|
+
|
89
|
+
Copyright
|
90
|
+
---------
|
91
|
+
|
92
|
+
Copyright (c) 2011 Gemini SBS LLC. See LICENSE.txt for further details.
|
data/Rakefile
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'bundler'
|
3
|
+
begin
|
4
|
+
Bundler.setup(:default, :development)
|
5
|
+
rescue Bundler::BundlerError => e
|
6
|
+
$stderr.puts e.message
|
7
|
+
$stderr.puts "Run `bundle install` to install missing gems"
|
8
|
+
exit e.status_code
|
9
|
+
end
|
10
|
+
require 'rake'
|
11
|
+
|
12
|
+
require 'jeweler'
|
13
|
+
Jeweler::Tasks.new do |gem|
|
14
|
+
gem.name = "api-auth"
|
15
|
+
gem.homepage = "http://github.com/geminisbs/api-auth"
|
16
|
+
gem.license = "MIT"
|
17
|
+
gem.summary = %Q{Simple HMAC authentication for your APIs}
|
18
|
+
gem.description = %Q{Full HMAC auth implementation for use in your gems and Rails apps.}
|
19
|
+
gem.email = "mgomes@geminisbs.com"
|
20
|
+
gem.authors = ["Mauricio Gomes"]
|
21
|
+
end
|
22
|
+
Jeweler::RubygemsDotOrgTasks.new
|
23
|
+
|
24
|
+
require 'rspec/core'
|
25
|
+
require 'rspec/core/rake_task'
|
26
|
+
RSpec::Core::RakeTask.new(:spec) do |spec|
|
27
|
+
spec.pattern = FileList['spec/**/*_spec.rb']
|
28
|
+
end
|
29
|
+
|
30
|
+
RSpec::Core::RakeTask.new(:rcov) do |spec|
|
31
|
+
spec.pattern = 'spec/**/*_spec.rb'
|
32
|
+
spec.rcov = true
|
33
|
+
end
|
34
|
+
|
35
|
+
task :default => :spec
|
36
|
+
|
37
|
+
require 'rake/rdoctask'
|
38
|
+
Rake::RDocTask.new do |rdoc|
|
39
|
+
version = File.exist?('VERSION') ? File.read('VERSION') : ""
|
40
|
+
|
41
|
+
rdoc.rdoc_dir = 'rdoc'
|
42
|
+
rdoc.title = "api-auth #{version}"
|
43
|
+
rdoc.rdoc_files.include('README*')
|
44
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
45
|
+
end
|
data/lib/api-auth.rb
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'openssl'
|
2
|
+
require 'base64'
|
3
|
+
|
4
|
+
require 'api-auth/errors'
|
5
|
+
require 'api-auth/helpers'
|
6
|
+
|
7
|
+
require 'api-auth/request_drivers/net_http'
|
8
|
+
require 'api-auth/request_drivers/curb'
|
9
|
+
require 'api-auth/request_drivers/rest_client'
|
10
|
+
require 'api-auth/request_drivers/action_controller'
|
11
|
+
|
12
|
+
require 'api-auth/headers'
|
13
|
+
require 'api-auth/base'
|
14
|
+
require 'api-auth/railtie'
|
@@ -0,0 +1,138 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
2
|
+
|
3
|
+
describe "ApiAuth" do
|
4
|
+
|
5
|
+
describe "generating secret keys" do
|
6
|
+
|
7
|
+
it "should generate secret keys" do
|
8
|
+
ApiAuth.generate_secret_key
|
9
|
+
end
|
10
|
+
|
11
|
+
it "should generate secret keys that are 89 characters" do
|
12
|
+
ApiAuth.generate_secret_key.size.should be(89)
|
13
|
+
end
|
14
|
+
|
15
|
+
it "should generate keys that have a Hamming Distance of at least 65" do
|
16
|
+
key1 = ApiAuth.generate_secret_key
|
17
|
+
key2 = ApiAuth.generate_secret_key
|
18
|
+
Amatch::Hamming.new(key1).match(key2).should be > 65
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
22
|
+
|
23
|
+
describe "signing requests" do
|
24
|
+
|
25
|
+
def hmac(secret_key, request)
|
26
|
+
canonical_string = ApiAuth::Headers.new(request).canonical_string
|
27
|
+
digest = OpenSSL::Digest::Digest.new('sha1')
|
28
|
+
ApiAuth.b64_encode(OpenSSL::HMAC.digest(digest, secret_key, canonical_string))
|
29
|
+
end
|
30
|
+
|
31
|
+
before(:all) do
|
32
|
+
@access_id = "1044"
|
33
|
+
@secret_key = ApiAuth.generate_secret_key
|
34
|
+
end
|
35
|
+
|
36
|
+
describe "with Net::HTTP" do
|
37
|
+
|
38
|
+
before(:each) do
|
39
|
+
@request = Net::HTTP::Put.new("/resource.xml?foo=bar&bar=foo",
|
40
|
+
'content-type' => 'text/plain',
|
41
|
+
'content-md5' => 'e59ff97941044f85df5297e1c302d260',
|
42
|
+
'date' => "Mon, 23 Jan 1984 03:29:56 GMT")
|
43
|
+
@signed_request = ApiAuth.sign!(@request, @access_id, @secret_key)
|
44
|
+
end
|
45
|
+
|
46
|
+
it "should return a Net::HTTP object after signing it" do
|
47
|
+
ApiAuth.sign!(@request, @access_id, @secret_key).class.to_s.should match("Net::HTTP")
|
48
|
+
end
|
49
|
+
|
50
|
+
it "should sign the request" do
|
51
|
+
@signed_request['Authorization'].should == "APIAuth 1044:#{hmac(@secret_key, @request)}"
|
52
|
+
end
|
53
|
+
|
54
|
+
it "should authenticate a valid request" do
|
55
|
+
ApiAuth.authentic?(@signed_request, @secret_key).should be_true
|
56
|
+
end
|
57
|
+
|
58
|
+
it "should NOT authenticate a non-valid request" do
|
59
|
+
ApiAuth.authentic?(@signed_request, @secret_key+'j').should be_false
|
60
|
+
end
|
61
|
+
|
62
|
+
it "should retrieve the access_id" do
|
63
|
+
ApiAuth.access_id(@signed_request).should == "1044"
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
67
|
+
|
68
|
+
describe "with RestClient" do
|
69
|
+
|
70
|
+
before(:each) do
|
71
|
+
headers = { 'Content-MD5' => "e59ff97941044f85df5297e1c302d260",
|
72
|
+
'Content-Type' => "text/plain",
|
73
|
+
'Date' => "Mon, 23 Jan 1984 03:29:56 GMT" }
|
74
|
+
@request = RestClient::Request.new(:url => "/resource.xml?foo=bar&bar=foo",
|
75
|
+
:headers => headers,
|
76
|
+
:method => :put)
|
77
|
+
@signed_request = ApiAuth.sign!(@request, @access_id, @secret_key)
|
78
|
+
end
|
79
|
+
|
80
|
+
it "should return a RestClient object after signing it" do
|
81
|
+
ApiAuth.sign!(@request, @access_id, @secret_key).class.to_s.should match("RestClient")
|
82
|
+
end
|
83
|
+
|
84
|
+
it "should sign the request" do
|
85
|
+
@signed_request.headers['Authorization'].should == "APIAuth 1044:#{hmac(@secret_key, @request)}"
|
86
|
+
end
|
87
|
+
|
88
|
+
it "should authenticate a valid request" do
|
89
|
+
ApiAuth.authentic?(@signed_request, @secret_key).should be_true
|
90
|
+
end
|
91
|
+
|
92
|
+
it "should NOT authenticate a non-valid request" do
|
93
|
+
ApiAuth.authentic?(@signed_request, @secret_key+'j').should be_false
|
94
|
+
end
|
95
|
+
|
96
|
+
it "should retrieve the access_id" do
|
97
|
+
ApiAuth.access_id(@signed_request).should == "1044"
|
98
|
+
end
|
99
|
+
|
100
|
+
end
|
101
|
+
|
102
|
+
describe "with Curb" do
|
103
|
+
|
104
|
+
before(:each) do
|
105
|
+
headers = { 'Content-MD5' => "e59ff97941044f85df5297e1c302d260",
|
106
|
+
'Content-Type' => "text/plain",
|
107
|
+
'Date' => "Mon, 23 Jan 1984 03:29:56 GMT" }
|
108
|
+
@request = Curl::Easy.new("/resource.xml?foo=bar&bar=foo") do |curl|
|
109
|
+
curl.headers = headers
|
110
|
+
end
|
111
|
+
@signed_request = ApiAuth.sign!(@request, @access_id, @secret_key)
|
112
|
+
end
|
113
|
+
|
114
|
+
it "should return a Curl::Easy object after signing it" do
|
115
|
+
ApiAuth.sign!(@request, @access_id, @secret_key).class.to_s.should match("Curl::Easy")
|
116
|
+
end
|
117
|
+
|
118
|
+
it "should sign the request" do
|
119
|
+
@signed_request.headers['Authorization'].should == "APIAuth 1044:#{hmac(@secret_key, @request)}"
|
120
|
+
end
|
121
|
+
|
122
|
+
it "should authenticate a valid request" do
|
123
|
+
ApiAuth.authentic?(@signed_request, @secret_key).should be_true
|
124
|
+
end
|
125
|
+
|
126
|
+
it "should NOT authenticate a non-valid request" do
|
127
|
+
ApiAuth.authentic?(@signed_request, @secret_key+'j').should be_false
|
128
|
+
end
|
129
|
+
|
130
|
+
it "should retrieve the access_id" do
|
131
|
+
ApiAuth.access_id(@signed_request).should == "1044"
|
132
|
+
end
|
133
|
+
|
134
|
+
end
|
135
|
+
|
136
|
+
end
|
137
|
+
|
138
|
+
end
|
@@ -0,0 +1,102 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
2
|
+
|
3
|
+
describe "ApiAuth::Headers" do
|
4
|
+
|
5
|
+
CANONICAL_STRING = "text/plain,e59ff97941044f85df5297e1c302d260,/resource.xml?foo=bar&bar=foo,Mon, 23 Jan 1984 03:29:56 GMT"
|
6
|
+
|
7
|
+
describe "with Net::HTTP" do
|
8
|
+
|
9
|
+
before(:each) do
|
10
|
+
@request = Net::HTTP::Put.new("/resource.xml?foo=bar&bar=foo",
|
11
|
+
'content-type' => 'text/plain',
|
12
|
+
'content-md5' => 'e59ff97941044f85df5297e1c302d260',
|
13
|
+
'date' => "Mon, 23 Jan 1984 03:29:56 GMT")
|
14
|
+
@headers = ApiAuth::Headers.new(@request)
|
15
|
+
end
|
16
|
+
|
17
|
+
it "should generate the proper canonical string" do
|
18
|
+
@headers.canonical_string.should == CANONICAL_STRING
|
19
|
+
end
|
20
|
+
|
21
|
+
it "should set the authorization header" do
|
22
|
+
@headers.sign_header("alpha")
|
23
|
+
@headers.authorization_header.should == "alpha"
|
24
|
+
end
|
25
|
+
|
26
|
+
it "should set the DATE header if one is not already present" do
|
27
|
+
@request = Net::HTTP::Put.new("/resource.xml?foo=bar&bar=foo",
|
28
|
+
'content-type' => 'text/plain',
|
29
|
+
'content-md5' => 'e59ff97941044f85df5297e1c302d260')
|
30
|
+
ApiAuth.sign!(@request, "some access id", "some secret key")
|
31
|
+
@request['DATE'].should_not be_nil
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
|
36
|
+
describe "with RestClient" do
|
37
|
+
|
38
|
+
before(:each) do
|
39
|
+
headers = { 'Content-MD5' => "e59ff97941044f85df5297e1c302d260",
|
40
|
+
'Content-Type' => "text/plain",
|
41
|
+
'Date' => "Mon, 23 Jan 1984 03:29:56 GMT" }
|
42
|
+
@request = RestClient::Request.new(:url => "/resource.xml?foo=bar&bar=foo",
|
43
|
+
:headers => headers,
|
44
|
+
:method => :put)
|
45
|
+
@headers = ApiAuth::Headers.new(@request)
|
46
|
+
end
|
47
|
+
|
48
|
+
it "should generate the proper canonical string" do
|
49
|
+
@headers.canonical_string.should == CANONICAL_STRING
|
50
|
+
end
|
51
|
+
|
52
|
+
it "should set the authorization header" do
|
53
|
+
@headers.sign_header("alpha")
|
54
|
+
@headers.authorization_header.should == "alpha"
|
55
|
+
end
|
56
|
+
|
57
|
+
it "should set the DATE header if one is not already present" do
|
58
|
+
headers = { 'Content-MD5' => "e59ff97941044f85df5297e1c302d260",
|
59
|
+
'Content-Type' => "text/plain" }
|
60
|
+
@request = RestClient::Request.new(:url => "/resource.xml?foo=bar&bar=foo",
|
61
|
+
:headers => headers,
|
62
|
+
:method => :put)
|
63
|
+
ApiAuth.sign!(@request, "some access id", "some secret key")
|
64
|
+
@request.headers['DATE'].should_not be_nil
|
65
|
+
end
|
66
|
+
|
67
|
+
end
|
68
|
+
|
69
|
+
describe "with Curb" do
|
70
|
+
|
71
|
+
before(:each) do
|
72
|
+
headers = { 'Content-MD5' => "e59ff97941044f85df5297e1c302d260",
|
73
|
+
'Content-Type' => "text/plain",
|
74
|
+
'Date' => "Mon, 23 Jan 1984 03:29:56 GMT" }
|
75
|
+
@request = Curl::Easy.new("/resource.xml?foo=bar&bar=foo") do |curl|
|
76
|
+
curl.headers = headers
|
77
|
+
end
|
78
|
+
@headers = ApiAuth::Headers.new(@request)
|
79
|
+
end
|
80
|
+
|
81
|
+
it "should generate the proper canonical string" do
|
82
|
+
@headers.canonical_string.should == CANONICAL_STRING
|
83
|
+
end
|
84
|
+
|
85
|
+
it "should set the authorization header" do
|
86
|
+
@headers.sign_header("alpha")
|
87
|
+
@headers.authorization_header.should == "alpha"
|
88
|
+
end
|
89
|
+
|
90
|
+
it "should set the DATE header if one is not already present" do
|
91
|
+
headers = { 'Content-MD5' => "e59ff97941044f85df5297e1c302d260",
|
92
|
+
'Content-Type' => "text/plain" }
|
93
|
+
@request = Curl::Easy.new("/resource.xml?foo=bar&bar=foo") do |curl|
|
94
|
+
curl.headers = headers
|
95
|
+
end
|
96
|
+
ApiAuth.sign!(@request, "some access id", "some secret key")
|
97
|
+
@request.headers['DATE'].should_not be_nil
|
98
|
+
end
|
99
|
+
|
100
|
+
end
|
101
|
+
|
102
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
2
|
+
|
3
|
+
describe "ApiAuth::Helpers" do
|
4
|
+
|
5
|
+
it "should strip the new line character on a Base64 encoding" do
|
6
|
+
ApiAuth.b64_encode("some string").should_not match(/\n/)
|
7
|
+
end
|
8
|
+
|
9
|
+
it "should properly upcase a hash's keys" do
|
10
|
+
hsh = { "JoE" => "rOOLz" }
|
11
|
+
ApiAuth.capitalize_keys(hsh)["JOE"].should == "rOOLz"
|
12
|
+
end
|
13
|
+
|
14
|
+
end
|
@@ -0,0 +1,105 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
2
|
+
|
3
|
+
describe "Rails integration" do
|
4
|
+
|
5
|
+
API_KEY_STORE = { "1044" => "l16imAXie1sRMcJODpOG7UwC1VyoqvO13jejkfpKWX4Z09W8DC9IrU23DvCwMry7pgSFW6c5S1GIfV0OY6F/vUA==" }
|
6
|
+
|
7
|
+
describe "Rails controller integration" do
|
8
|
+
|
9
|
+
class ApplicationController < ActionController::Base
|
10
|
+
|
11
|
+
private
|
12
|
+
|
13
|
+
def require_api_auth
|
14
|
+
if (access_id = get_api_access_id_from_request)
|
15
|
+
return true if api_authenticated?(API_KEY_STORE[access_id])
|
16
|
+
end
|
17
|
+
|
18
|
+
respond_to do |format|
|
19
|
+
format.xml { render :xml => "You are unauthorized to perform this action.", :status => 401 }
|
20
|
+
format.json { render :json => "You are unauthorized to perform this action.", :status => 401 }
|
21
|
+
format.html { render :text => "You are unauthorized to perform this action", :status => 401 }
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
|
27
|
+
class TestController < ApplicationController
|
28
|
+
before_filter :require_api_auth, :only => [:index]
|
29
|
+
|
30
|
+
def index
|
31
|
+
render :text => "OK"
|
32
|
+
end
|
33
|
+
|
34
|
+
def public
|
35
|
+
render :text => "OK"
|
36
|
+
end
|
37
|
+
|
38
|
+
def rescue_action(e); raise(e); end
|
39
|
+
end
|
40
|
+
ActionController::Routing::Routes.draw {|map| map.resources :test }
|
41
|
+
|
42
|
+
it "should permit a request with properly signed headers" do
|
43
|
+
request = ActionController::TestRequest.new
|
44
|
+
request.env['DATE'] = "Mon, 23 Jan 1984 03:29:56 GMT"
|
45
|
+
request.action = 'index'
|
46
|
+
request.path = "/index"
|
47
|
+
ApiAuth.sign!(request, "1044", API_KEY_STORE["1044"])
|
48
|
+
TestController.new.process(request, ActionController::TestResponse.new).code.should == "200"
|
49
|
+
end
|
50
|
+
|
51
|
+
it "should insert a DATE header in the request when one hasn't been specified" do
|
52
|
+
request = ActionController::TestRequest.new
|
53
|
+
request.action = 'index'
|
54
|
+
request.path = "/index"
|
55
|
+
ApiAuth.sign!(request, "1044", API_KEY_STORE["1044"])
|
56
|
+
request.headers['DATE'].should_not be_nil
|
57
|
+
end
|
58
|
+
|
59
|
+
it "should forbid an unsigned request to a protected controller action" do
|
60
|
+
request = ActionController::TestRequest.new
|
61
|
+
request.action = 'index'
|
62
|
+
TestController.new.process(request, ActionController::TestResponse.new).code.should == "401"
|
63
|
+
end
|
64
|
+
|
65
|
+
it "should forbid a request with a bogus signature" do
|
66
|
+
request = ActionController::TestRequest.new
|
67
|
+
request.action = 'index'
|
68
|
+
request.env['Authorization'] = "APIAuth bogus:bogus"
|
69
|
+
TestController.new.process(request, ActionController::TestResponse.new).code.should == "401"
|
70
|
+
end
|
71
|
+
|
72
|
+
it "should allow non-protected controller actions to function as before" do
|
73
|
+
request = ActionController::TestRequest.new
|
74
|
+
request.action = 'public'
|
75
|
+
request.path('/public')
|
76
|
+
TestController.new.process(request, ActionController::TestResponse.new).code.should == "200"
|
77
|
+
end
|
78
|
+
|
79
|
+
end
|
80
|
+
|
81
|
+
describe "Rails ActiveResource integration" do
|
82
|
+
|
83
|
+
class TestResource < ActiveResource::Base
|
84
|
+
with_api_auth "1044", API_KEY_STORE["1044"]
|
85
|
+
self.site = "http://localhost/"
|
86
|
+
end
|
87
|
+
|
88
|
+
it "should send signed requests automagically" do
|
89
|
+
timestamp = Time.parse("Mon, 23 Jan 1984 03:29:56 GMT")
|
90
|
+
Time.should_receive(:now).at_least(1).times.and_return(timestamp)
|
91
|
+
ActiveResource::HttpMock.respond_to do |mock|
|
92
|
+
mock.get "/test_resources/1.xml",
|
93
|
+
{
|
94
|
+
'Authorization' => 'APIAuth 1044:IbTx7VzSOGU55HNbV4y2jZDnVis=',
|
95
|
+
'Accept' => 'application/xml',
|
96
|
+
'DATE' => "Mon, 23 Jan 1984 03:29:56 GMT"
|
97
|
+
},
|
98
|
+
{ :id => "1" }.to_xml(:root => 'test_resource')
|
99
|
+
end
|
100
|
+
TestResource.find(1)
|
101
|
+
end
|
102
|
+
|
103
|
+
end
|
104
|
+
|
105
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
2
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
3
|
+
require 'rspec'
|
4
|
+
require 'api-auth'
|
5
|
+
require 'amatch'
|
6
|
+
require 'rest_client'
|
7
|
+
require 'curb'
|
8
|
+
|
9
|
+
require 'active_support'
|
10
|
+
require 'active_support/test_case'
|
11
|
+
require 'action_controller'
|
12
|
+
require 'action_controller/test_process'
|
13
|
+
require 'active_resource'
|
14
|
+
require 'active_resource/http_mock'
|
15
|
+
|
16
|
+
# Requires supporting files with custom matchers and macros, etc,
|
17
|
+
# in ./support/ and its subdirectories.
|
18
|
+
Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each {|f| require f}
|
19
|
+
|
20
|
+
RSpec.configure do |config|
|
21
|
+
|
22
|
+
end
|
metadata
ADDED
@@ -0,0 +1,210 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: api-auth
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 59
|
5
|
+
prerelease:
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 9
|
9
|
+
- 0
|
10
|
+
version: 0.9.0
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Mauricio Gomes
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2011-01-30 00:00:00 -05:00
|
19
|
+
default_executable:
|
20
|
+
dependencies:
|
21
|
+
- !ruby/object:Gem::Dependency
|
22
|
+
type: :development
|
23
|
+
version_requirements: &id001 !ruby/object:Gem::Requirement
|
24
|
+
none: false
|
25
|
+
requirements:
|
26
|
+
- - ~>
|
27
|
+
- !ruby/object:Gem::Version
|
28
|
+
hash: 31
|
29
|
+
segments:
|
30
|
+
- 2
|
31
|
+
- 4
|
32
|
+
- 0
|
33
|
+
version: 2.4.0
|
34
|
+
requirement: *id001
|
35
|
+
prerelease: false
|
36
|
+
name: rspec
|
37
|
+
- !ruby/object:Gem::Dependency
|
38
|
+
type: :development
|
39
|
+
version_requirements: &id002 !ruby/object:Gem::Requirement
|
40
|
+
none: false
|
41
|
+
requirements:
|
42
|
+
- - ~>
|
43
|
+
- !ruby/object:Gem::Version
|
44
|
+
hash: 23
|
45
|
+
segments:
|
46
|
+
- 1
|
47
|
+
- 0
|
48
|
+
- 0
|
49
|
+
version: 1.0.0
|
50
|
+
requirement: *id002
|
51
|
+
prerelease: false
|
52
|
+
name: bundler
|
53
|
+
- !ruby/object:Gem::Dependency
|
54
|
+
type: :development
|
55
|
+
version_requirements: &id003 !ruby/object:Gem::Requirement
|
56
|
+
none: false
|
57
|
+
requirements:
|
58
|
+
- - ~>
|
59
|
+
- !ruby/object:Gem::Version
|
60
|
+
hash: 7
|
61
|
+
segments:
|
62
|
+
- 1
|
63
|
+
- 5
|
64
|
+
- 2
|
65
|
+
version: 1.5.2
|
66
|
+
requirement: *id003
|
67
|
+
prerelease: false
|
68
|
+
name: jeweler
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
type: :development
|
71
|
+
version_requirements: &id004 !ruby/object:Gem::Requirement
|
72
|
+
none: false
|
73
|
+
requirements:
|
74
|
+
- - ~>
|
75
|
+
- !ruby/object:Gem::Version
|
76
|
+
hash: 29
|
77
|
+
segments:
|
78
|
+
- 0
|
79
|
+
- 2
|
80
|
+
- 5
|
81
|
+
version: 0.2.5
|
82
|
+
requirement: *id004
|
83
|
+
prerelease: false
|
84
|
+
name: amatch
|
85
|
+
- !ruby/object:Gem::Dependency
|
86
|
+
type: :development
|
87
|
+
version_requirements: &id005 !ruby/object:Gem::Requirement
|
88
|
+
none: false
|
89
|
+
requirements:
|
90
|
+
- - ~>
|
91
|
+
- !ruby/object:Gem::Version
|
92
|
+
hash: 13
|
93
|
+
segments:
|
94
|
+
- 0
|
95
|
+
- 7
|
96
|
+
- 7
|
97
|
+
version: 0.7.7
|
98
|
+
requirement: *id005
|
99
|
+
prerelease: false
|
100
|
+
name: curb
|
101
|
+
- !ruby/object:Gem::Dependency
|
102
|
+
type: :development
|
103
|
+
version_requirements: &id006 !ruby/object:Gem::Requirement
|
104
|
+
none: false
|
105
|
+
requirements:
|
106
|
+
- - ~>
|
107
|
+
- !ruby/object:Gem::Version
|
108
|
+
hash: 15
|
109
|
+
segments:
|
110
|
+
- 1
|
111
|
+
- 6
|
112
|
+
- 0
|
113
|
+
version: 1.6.0
|
114
|
+
requirement: *id006
|
115
|
+
prerelease: false
|
116
|
+
name: rest-client
|
117
|
+
- !ruby/object:Gem::Dependency
|
118
|
+
type: :development
|
119
|
+
version_requirements: &id007 !ruby/object:Gem::Requirement
|
120
|
+
none: false
|
121
|
+
requirements:
|
122
|
+
- - ~>
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
hash: 7
|
125
|
+
segments:
|
126
|
+
- 2
|
127
|
+
- 3
|
128
|
+
- 2
|
129
|
+
version: 2.3.2
|
130
|
+
requirement: *id007
|
131
|
+
prerelease: false
|
132
|
+
name: actionpack
|
133
|
+
- !ruby/object:Gem::Dependency
|
134
|
+
type: :development
|
135
|
+
version_requirements: &id008 !ruby/object:Gem::Requirement
|
136
|
+
none: false
|
137
|
+
requirements:
|
138
|
+
- - ~>
|
139
|
+
- !ruby/object:Gem::Version
|
140
|
+
hash: 7
|
141
|
+
segments:
|
142
|
+
- 2
|
143
|
+
- 3
|
144
|
+
- 2
|
145
|
+
version: 2.3.2
|
146
|
+
requirement: *id008
|
147
|
+
prerelease: false
|
148
|
+
name: activeresource
|
149
|
+
description: Full HMAC auth implementation for use in your gems and Rails apps.
|
150
|
+
email: mgomes@geminisbs.com
|
151
|
+
executables: []
|
152
|
+
|
153
|
+
extensions: []
|
154
|
+
|
155
|
+
extra_rdoc_files:
|
156
|
+
- LICENSE.txt
|
157
|
+
- README.md
|
158
|
+
files:
|
159
|
+
- .document
|
160
|
+
- .rspec
|
161
|
+
- Gemfile
|
162
|
+
- LICENSE.txt
|
163
|
+
- Rakefile
|
164
|
+
- lib/api-auth.rb
|
165
|
+
- spec/api-auth_spec.rb
|
166
|
+
- spec/spec_helper.rb
|
167
|
+
- README.md
|
168
|
+
- spec/headers_spec.rb
|
169
|
+
- spec/helpers_spec.rb
|
170
|
+
- spec/railtie_spec.rb
|
171
|
+
has_rdoc: true
|
172
|
+
homepage: http://github.com/geminisbs/api-auth
|
173
|
+
licenses:
|
174
|
+
- MIT
|
175
|
+
post_install_message:
|
176
|
+
rdoc_options: []
|
177
|
+
|
178
|
+
require_paths:
|
179
|
+
- lib
|
180
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
181
|
+
none: false
|
182
|
+
requirements:
|
183
|
+
- - ">="
|
184
|
+
- !ruby/object:Gem::Version
|
185
|
+
hash: 3
|
186
|
+
segments:
|
187
|
+
- 0
|
188
|
+
version: "0"
|
189
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
190
|
+
none: false
|
191
|
+
requirements:
|
192
|
+
- - ">="
|
193
|
+
- !ruby/object:Gem::Version
|
194
|
+
hash: 3
|
195
|
+
segments:
|
196
|
+
- 0
|
197
|
+
version: "0"
|
198
|
+
requirements: []
|
199
|
+
|
200
|
+
rubyforge_project:
|
201
|
+
rubygems_version: 1.4.1
|
202
|
+
signing_key:
|
203
|
+
specification_version: 3
|
204
|
+
summary: Simple HMAC authentication for your APIs
|
205
|
+
test_files:
|
206
|
+
- spec/api-auth_spec.rb
|
207
|
+
- spec/headers_spec.rb
|
208
|
+
- spec/helpers_spec.rb
|
209
|
+
- spec/railtie_spec.rb
|
210
|
+
- spec/spec_helper.rb
|