api-auth 0.9.0 → 0.9.1
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/Gemfile.lock +43 -0
- data/README.md +107 -25
- data/Rakefile +2 -2
- data/VERSION +1 -0
- data/api_auth.gemspec +75 -0
- data/lib/api-auth.rb +2 -14
- data/lib/api_auth.rb +14 -0
- data/lib/api_auth/base.rb +81 -0
- data/lib/api_auth/errors.rb +9 -0
- data/lib/api_auth/headers.rb +53 -0
- data/lib/api_auth/helpers.rb +19 -0
- data/lib/api_auth/railtie.rb +120 -0
- data/lib/api_auth/request_drivers/action_controller.rb +62 -0
- data/lib/api_auth/request_drivers/curb.rb +62 -0
- data/lib/api_auth/request_drivers/net_http.rb +60 -0
- data/lib/api_auth/request_drivers/rest_client.rb +62 -0
- data/spec/{api-auth_spec.rb → api_auth_spec.rb} +0 -0
- data/spec/spec_helper.rb +1 -1
- metadata +54 -89
data/Gemfile.lock
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
GEM
|
2
|
+
remote: http://rubygems.org/
|
3
|
+
specs:
|
4
|
+
actionpack (2.3.10)
|
5
|
+
activesupport (= 2.3.10)
|
6
|
+
rack (~> 1.1.0)
|
7
|
+
activeresource (2.3.10)
|
8
|
+
activesupport (= 2.3.10)
|
9
|
+
activesupport (2.3.10)
|
10
|
+
amatch (0.2.5)
|
11
|
+
curb (0.7.10)
|
12
|
+
diff-lcs (1.1.2)
|
13
|
+
git (1.2.5)
|
14
|
+
jeweler (1.5.2)
|
15
|
+
bundler (~> 1.0.0)
|
16
|
+
git (>= 1.2.5)
|
17
|
+
rake
|
18
|
+
mime-types (1.16)
|
19
|
+
rack (1.1.0)
|
20
|
+
rake (0.8.7)
|
21
|
+
rest-client (1.6.1)
|
22
|
+
mime-types (>= 1.16)
|
23
|
+
rspec (2.4.0)
|
24
|
+
rspec-core (~> 2.4.0)
|
25
|
+
rspec-expectations (~> 2.4.0)
|
26
|
+
rspec-mocks (~> 2.4.0)
|
27
|
+
rspec-core (2.4.0)
|
28
|
+
rspec-expectations (2.4.0)
|
29
|
+
diff-lcs (~> 1.1.2)
|
30
|
+
rspec-mocks (2.4.0)
|
31
|
+
|
32
|
+
PLATFORMS
|
33
|
+
ruby
|
34
|
+
|
35
|
+
DEPENDENCIES
|
36
|
+
actionpack (~> 2.3.2)
|
37
|
+
activeresource (~> 2.3.2)
|
38
|
+
amatch (~> 0.2.5)
|
39
|
+
bundler (~> 1.0.0)
|
40
|
+
curb (~> 0.7.7)
|
41
|
+
jeweler (~> 1.5.2)
|
42
|
+
rest-client (~> 1.6.0)
|
43
|
+
rspec (~> 2.4.0)
|
data/README.md
CHANGED
@@ -1,11 +1,11 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
ApiAuth
|
2
|
+
=======
|
3
3
|
|
4
4
|
Logins and passwords are for humans. Communication between applications need to
|
5
5
|
be protected through different means.
|
6
6
|
|
7
|
-
|
8
|
-
HTTP-based applications. It implements the same authentication methods (HMAC)
|
7
|
+
ApiAuth is a Ruby gem designed to be used both in your client and server
|
8
|
+
HTTP-based applications. It implements the same authentication methods (HMAC-SHA1)
|
9
9
|
used by Amazon Web Services.
|
10
10
|
|
11
11
|
The gem will sign your requests on the client side and authenticate that
|
@@ -14,28 +14,25 @@ Rails ActiveResource, it will integrate with that. It will even generate the
|
|
14
14
|
secret keys necessary for your clients to sign their requests.
|
15
15
|
|
16
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.
|
18
|
-
OpenSSL bindings will suffice.
|
17
|
+
have to be written in the same language as the clients.
|
19
18
|
|
20
19
|
How it works
|
21
20
|
------------
|
22
21
|
|
23
22
|
1. A canonical string is first created using your HTTP headers containing the
|
24
|
-
content-type, content-MD5, request URI and the
|
25
|
-
|
23
|
+
content-type, content-MD5, request URI and the timestamp. If content-type or
|
24
|
+
content-MD5 are not present, then a blank string is used in their place. If the
|
25
|
+
timestamp isn't present, a valid HTTP date is automatically added to the
|
26
|
+
request. The canonical string string is computed as follows:
|
26
27
|
|
27
|
-
canonical_string =
|
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.
|
28
|
+
canonical_string = 'content-type,content-MD5,request URI,timestamp'
|
32
29
|
|
33
30
|
2. This string is then used to create the signature which is a Base64 encoded
|
34
31
|
SHA1 HMAC, using the client's private secret key.
|
35
32
|
|
36
33
|
3. This signature is then added as the `Authorization` HTTP header in the form:
|
37
34
|
|
38
|
-
Authorization = APIAuth
|
35
|
+
Authorization = APIAuth 'client access id':'signature from step 2'
|
39
36
|
|
40
37
|
5. On the server side, the SHA1 HMAC is computed in the same way using the
|
41
38
|
request headers and the client's secret key, which is known to only
|
@@ -52,18 +49,19 @@ References
|
|
52
49
|
* [HMAC algorithm](http://en.wikipedia.org/wiki/HMAC)
|
53
50
|
* [RFC 2104 (HMAC)](http://tools.ietf.org/html/rfc2104)
|
54
51
|
|
55
|
-
|
56
|
-
|
52
|
+
Install
|
53
|
+
-------
|
57
54
|
|
58
|
-
|
55
|
+
The gem doesn't have any dependencies outside of having a working OpenSSL
|
56
|
+
configuration for your Ruby VM. To install:
|
59
57
|
|
60
|
-
[sudo] gem install
|
58
|
+
[sudo] gem install api_auth
|
61
59
|
|
62
|
-
|
60
|
+
Clients
|
61
|
+
-------
|
63
62
|
|
64
|
-
ApiAuth supports
|
65
|
-
|
66
|
-
other request objects can be added as a request driver.
|
63
|
+
ApiAuth supports many popular HTTP clients. Support for other clients can be
|
64
|
+
added as a request driver.
|
67
65
|
|
68
66
|
Here is the current list of supported request objects:
|
69
67
|
|
@@ -71,16 +69,100 @@ Here is the current list of supported request objects:
|
|
71
69
|
* ActionController::Request
|
72
70
|
* Curb (Curl::Easy)
|
73
71
|
* RestClient
|
72
|
+
|
73
|
+
### HTTP Client Objects ###
|
74
|
+
|
75
|
+
Here's a sample implementation of signing a request created with RestClient. For
|
76
|
+
more examples, please check out the ApiAuth Spec where every supported HTTP
|
77
|
+
client is tested.
|
78
|
+
|
79
|
+
Assuming you have a client access id and secret as follows:
|
80
|
+
|
81
|
+
@access_id = "1044"
|
82
|
+
@secret_key = ApiAuth.generate_secret_key
|
74
83
|
|
75
|
-
|
84
|
+
A typical RestClient PUT request may look like:
|
85
|
+
|
86
|
+
headers = { 'Content-MD5' => "e59ff97941044f85df5297e1c302d260",
|
87
|
+
'Content-Type' => "text/plain",
|
88
|
+
'Date' => "Mon, 23 Jan 1984 03:29:56 GMT" }
|
89
|
+
@request = RestClient::Request.new(:url => "/resource.xml?foo=bar&bar=foo",
|
90
|
+
:headers => headers,
|
91
|
+
:method => :put)
|
92
|
+
|
93
|
+
To sign that request, simply call the `sign!` method as follows:
|
94
|
+
|
95
|
+
@signed_request = ApiAuth.sign!(@request, @access_id, @secret_key)
|
96
|
+
|
97
|
+
The proper `Authorization` request header has now been added to that request
|
98
|
+
object and it's ready to be transmitted. It's recommended that you sign the
|
99
|
+
request as one of the last steps in building the request to ensure the headers
|
100
|
+
don't change after the signing process which would cause the authentication
|
101
|
+
check to fail on the server side.
|
102
|
+
|
103
|
+
### ActiveResource Clients ###
|
104
|
+
|
105
|
+
ApiAuth can transparently protect your ActiveResource communications with a
|
106
|
+
single configuration line:
|
76
107
|
|
77
108
|
class MyResource < ActiveResource::Base
|
78
|
-
with_api_auth(
|
109
|
+
with_api_auth(access_id, secret_key)
|
110
|
+
end
|
111
|
+
|
112
|
+
This will automatically sign all outgoing ActiveResource requests from your app.
|
113
|
+
|
114
|
+
Server
|
115
|
+
------
|
116
|
+
|
117
|
+
ApiAuth provides some built in methods to help you generate API keys for your
|
118
|
+
clients as well as verifying incoming API requests.
|
119
|
+
|
120
|
+
To generate a Base64 encoded API key for a client:
|
121
|
+
|
122
|
+
ApiAuth.generate_secret_key
|
123
|
+
|
124
|
+
To validate whether or not a request is authentic:
|
125
|
+
|
126
|
+
ApiAuth.authentic?(signed_request, secret_key)
|
127
|
+
|
128
|
+
If your server is a Rails app, the signed request will be the `request` object.
|
129
|
+
|
130
|
+
In order to obtain the secret key for the client, you first need to look up the
|
131
|
+
client's access_id. ApiAuth can pull that from the request headers for you:
|
132
|
+
|
133
|
+
ApiAuth.access_id(signed_request)
|
134
|
+
|
135
|
+
Once you've looked up the client's record via the access id, you can then verify
|
136
|
+
whether or not the request is authentic. Typically, the access id for the client
|
137
|
+
will be their record's primary key in the DB that stores the record or some other
|
138
|
+
public unique identifier for the client.
|
139
|
+
|
140
|
+
Here's a sample method that can be used in a `before_filter` if your server is a
|
141
|
+
Rails app:
|
142
|
+
|
143
|
+
before_filter :api_authenticate
|
144
|
+
|
145
|
+
def api_authenticate
|
146
|
+
@current_account = Account.find_by_access_id(ApiAuth.access_id(request))
|
147
|
+
return ApiAuth.authentic?(request, @current_account.secret_key) unless @current_account.nil?
|
148
|
+
false
|
79
149
|
end
|
150
|
+
|
151
|
+
Development
|
152
|
+
-----------
|
80
153
|
|
81
|
-
|
154
|
+
ApiAuth uses bundler for gem dependencies and RSpec for testing. Developing the
|
155
|
+
gem requires that you have all supported HTTP clients installed. Bundler will
|
156
|
+
take care of all that for you.
|
82
157
|
|
158
|
+
To run the tests:
|
83
159
|
|
160
|
+
rake spec
|
161
|
+
|
162
|
+
If you'd like to add support for additional HTTP clients, check out the already
|
163
|
+
implemented drivers in `lib/api_auth/request_drivers` for reference. All of
|
164
|
+
the public methods for each driver are required to be implemented by your driver.
|
165
|
+
|
84
166
|
Authors
|
85
167
|
-------
|
86
168
|
|
data/Rakefile
CHANGED
@@ -12,7 +12,7 @@ require 'rake'
|
|
12
12
|
require 'jeweler'
|
13
13
|
Jeweler::Tasks.new do |gem|
|
14
14
|
gem.name = "api-auth"
|
15
|
-
gem.homepage = "http://github.com/
|
15
|
+
gem.homepage = "http://github.com/mgomes/api_auth"
|
16
16
|
gem.license = "MIT"
|
17
17
|
gem.summary = %Q{Simple HMAC authentication for your APIs}
|
18
18
|
gem.description = %Q{Full HMAC auth implementation for use in your gems and Rails apps.}
|
@@ -39,7 +39,7 @@ Rake::RDocTask.new do |rdoc|
|
|
39
39
|
version = File.exist?('VERSION') ? File.read('VERSION') : ""
|
40
40
|
|
41
41
|
rdoc.rdoc_dir = 'rdoc'
|
42
|
-
rdoc.title = "
|
42
|
+
rdoc.title = "api_auth #{version}"
|
43
43
|
rdoc.rdoc_files.include('README*')
|
44
44
|
rdoc.rdoc_files.include('lib/**/*.rb')
|
45
45
|
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.9.1
|
data/api_auth.gemspec
ADDED
@@ -0,0 +1,75 @@
|
|
1
|
+
# Generated by jeweler
|
2
|
+
# DO NOT EDIT THIS FILE DIRECTLY
|
3
|
+
# Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
|
4
|
+
# -*- encoding: utf-8 -*-
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = %q{api_auth}
|
8
|
+
s.version = "0.9.0"
|
9
|
+
|
10
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
|
+
s.authors = ["Mauricio Gomes"]
|
12
|
+
s.date = %q{2011-01-30}
|
13
|
+
s.description = %q{Full HMAC auth implementation for use in your gems and Rails apps.}
|
14
|
+
s.email = %q{mgomes@geminisbs.com}
|
15
|
+
s.extra_rdoc_files = [
|
16
|
+
"LICENSE.txt",
|
17
|
+
"README.md"
|
18
|
+
]
|
19
|
+
s.files = [
|
20
|
+
".document",
|
21
|
+
".rspec",
|
22
|
+
"Gemfile",
|
23
|
+
"LICENSE.txt",
|
24
|
+
"Rakefile",
|
25
|
+
"lib/api_auth.rb",
|
26
|
+
"spec/api_auth_spec.rb",
|
27
|
+
"spec/spec_helper.rb"
|
28
|
+
]
|
29
|
+
s.homepage = %q{http://github.com/geminisbs/api-auth}
|
30
|
+
s.licenses = ["MIT"]
|
31
|
+
s.require_paths = ["lib"]
|
32
|
+
s.rubygems_version = %q{1.4.1}
|
33
|
+
s.summary = %q{Simple HMAC authentication for your APIs}
|
34
|
+
s.test_files = [
|
35
|
+
"spec/api_auth_spec.rb",
|
36
|
+
"spec/headers_spec.rb",
|
37
|
+
"spec/helpers_spec.rb",
|
38
|
+
"spec/railtie_spec.rb",
|
39
|
+
"spec/spec_helper.rb"
|
40
|
+
]
|
41
|
+
|
42
|
+
if s.respond_to? :specification_version then
|
43
|
+
s.specification_version = 3
|
44
|
+
|
45
|
+
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
46
|
+
s.add_development_dependency(%q<rspec>, ["~> 2.4.0"])
|
47
|
+
s.add_development_dependency(%q<bundler>, ["~> 1.0.0"])
|
48
|
+
s.add_development_dependency(%q<jeweler>, ["~> 1.5.2"])
|
49
|
+
s.add_development_dependency(%q<amatch>, ["~> 0.2.5"])
|
50
|
+
s.add_development_dependency(%q<curb>, ["~> 0.7.7"])
|
51
|
+
s.add_development_dependency(%q<rest-client>, ["~> 1.6.0"])
|
52
|
+
s.add_development_dependency(%q<actionpack>, ["~> 2.3.2"])
|
53
|
+
s.add_development_dependency(%q<activeresource>, ["~> 2.3.2"])
|
54
|
+
else
|
55
|
+
s.add_dependency(%q<rspec>, ["~> 2.4.0"])
|
56
|
+
s.add_dependency(%q<bundler>, ["~> 1.0.0"])
|
57
|
+
s.add_dependency(%q<jeweler>, ["~> 1.5.2"])
|
58
|
+
s.add_dependency(%q<amatch>, ["~> 0.2.5"])
|
59
|
+
s.add_dependency(%q<curb>, ["~> 0.7.7"])
|
60
|
+
s.add_dependency(%q<rest-client>, ["~> 1.6.0"])
|
61
|
+
s.add_dependency(%q<actionpack>, ["~> 2.3.2"])
|
62
|
+
s.add_dependency(%q<activeresource>, ["~> 2.3.2"])
|
63
|
+
end
|
64
|
+
else
|
65
|
+
s.add_dependency(%q<rspec>, ["~> 2.4.0"])
|
66
|
+
s.add_dependency(%q<bundler>, ["~> 1.0.0"])
|
67
|
+
s.add_dependency(%q<jeweler>, ["~> 1.5.2"])
|
68
|
+
s.add_dependency(%q<amatch>, ["~> 0.2.5"])
|
69
|
+
s.add_dependency(%q<curb>, ["~> 0.7.7"])
|
70
|
+
s.add_dependency(%q<rest-client>, ["~> 1.6.0"])
|
71
|
+
s.add_dependency(%q<actionpack>, ["~> 2.3.2"])
|
72
|
+
s.add_dependency(%q<activeresource>, ["~> 2.3.2"])
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
data/lib/api-auth.rb
CHANGED
@@ -1,14 +1,2 @@
|
|
1
|
-
require
|
2
|
-
require
|
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'
|
1
|
+
# So you can require "api-auth" instead of "api_auth"
|
2
|
+
require "api_auth"
|
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,81 @@
|
|
1
|
+
# api-auth is Ruby gem designed to be used both in your client and server
|
2
|
+
# HTTP-based applications. It implements the same authentication methods (HMAC)
|
3
|
+
# used by Amazon Web Services.
|
4
|
+
|
5
|
+
# The gem will sign your requests on the client side and authenticate that
|
6
|
+
# signature on the server side. If your server resources are implemented as a
|
7
|
+
# Rails ActiveResource, it will integrate with that. It will even generate the
|
8
|
+
# secret keys necessary for your clients to sign their requests.
|
9
|
+
module ApiAuth
|
10
|
+
|
11
|
+
class << self
|
12
|
+
|
13
|
+
include Helpers
|
14
|
+
|
15
|
+
# Signs an HTTP request using the client's access id and secret key.
|
16
|
+
# Returns the HTTP request object with the modified headers.
|
17
|
+
#
|
18
|
+
# request: The request can be a Net::HTTP, ActionController::Request,
|
19
|
+
# Curb (Curl::Easy) or a RestClient object.
|
20
|
+
#
|
21
|
+
# access_id: The public unique identifier for the client
|
22
|
+
#
|
23
|
+
# secret_key: assigned secret key that is known to both parties
|
24
|
+
def sign!(request, access_id, secret_key)
|
25
|
+
headers = Headers.new(request)
|
26
|
+
headers.sign_header auth_header(request, access_id, secret_key)
|
27
|
+
end
|
28
|
+
|
29
|
+
# Determines if the request is authentic given the request and the client's
|
30
|
+
# secret key. Returns true if the request is authentic and false otherwise.
|
31
|
+
def authentic?(request, secret_key)
|
32
|
+
return false if secret_key.nil?
|
33
|
+
|
34
|
+
headers = Headers.new(request)
|
35
|
+
if match_data = parse_auth_header(headers.authorization_header)
|
36
|
+
hmac = match_data[2]
|
37
|
+
return hmac == hmac_signature(request, secret_key)
|
38
|
+
end
|
39
|
+
|
40
|
+
false
|
41
|
+
end
|
42
|
+
|
43
|
+
# Returns the access id from the request's authorization header
|
44
|
+
def access_id(request)
|
45
|
+
headers = Headers.new(request)
|
46
|
+
if match_data = parse_auth_header(headers.authorization_header)
|
47
|
+
return match_data[1]
|
48
|
+
end
|
49
|
+
|
50
|
+
nil
|
51
|
+
end
|
52
|
+
|
53
|
+
# Generates a Base64 encoded, randomized secret key
|
54
|
+
#
|
55
|
+
# Store this key along with the access key that will be used for
|
56
|
+
# authenticating the client
|
57
|
+
def generate_secret_key
|
58
|
+
random_bytes = OpenSSL::Random.random_bytes(512)
|
59
|
+
b64_encode(Digest::SHA2.new(512).digest(random_bytes))
|
60
|
+
end
|
61
|
+
|
62
|
+
private
|
63
|
+
|
64
|
+
def hmac_signature(request, secret_key)
|
65
|
+
headers = Headers.new(request)
|
66
|
+
canonical_string = headers.canonical_string
|
67
|
+
digest = OpenSSL::Digest::Digest.new('sha1')
|
68
|
+
b64_encode(OpenSSL::HMAC.digest(digest, secret_key, canonical_string))
|
69
|
+
end
|
70
|
+
|
71
|
+
def auth_header(request, access_id, secret_key)
|
72
|
+
"APIAuth #{access_id}:#{hmac_signature(request, secret_key)}"
|
73
|
+
end
|
74
|
+
|
75
|
+
def parse_auth_header(auth_header)
|
76
|
+
Regexp.new("APIAuth ([^:]+):(.+)$").match(auth_header)
|
77
|
+
end
|
78
|
+
|
79
|
+
end # class methods
|
80
|
+
|
81
|
+
end # ApiAuth
|
@@ -0,0 +1,53 @@
|
|
1
|
+
module ApiAuth
|
2
|
+
|
3
|
+
# Builds the canonical string given a request object.
|
4
|
+
class Headers
|
5
|
+
|
6
|
+
include RequestDrivers
|
7
|
+
|
8
|
+
def initialize(request)
|
9
|
+
@original_request = request
|
10
|
+
|
11
|
+
case request.class.to_s
|
12
|
+
when /Net::HTTP/
|
13
|
+
@request = NetHttpRequest.new(request)
|
14
|
+
when /RestClient/
|
15
|
+
@request = RestClientRequest.new(request)
|
16
|
+
when /Curl::Easy/
|
17
|
+
@request = CurbRequest.new(request)
|
18
|
+
when /ActionController::Request/
|
19
|
+
@request = ActionControllerRequest.new(request)
|
20
|
+
when /ActionController::TestRequest/
|
21
|
+
@request = ActionControllerRequest.new(request)
|
22
|
+
else
|
23
|
+
raise UnknownHTTPRequest, "#{request.class.to_s} is not yet supported."
|
24
|
+
end
|
25
|
+
true
|
26
|
+
end
|
27
|
+
|
28
|
+
# Returns the canonical string computed from the request's headers
|
29
|
+
def canonical_string
|
30
|
+
[ @request.content_type,
|
31
|
+
@request.content_md5,
|
32
|
+
@request.request_uri,
|
33
|
+
@request.timestamp
|
34
|
+
].join(",")
|
35
|
+
end
|
36
|
+
|
37
|
+
# Returns the authorization header from the request's headers
|
38
|
+
def authorization_header
|
39
|
+
@request.authorization_header
|
40
|
+
end
|
41
|
+
|
42
|
+
# Sets the request's authorization header with the passed in value.
|
43
|
+
# The header should be the ApiAuth HMAC signature.
|
44
|
+
#
|
45
|
+
# This will return the original request object with the signed Authorization
|
46
|
+
# header already in place.
|
47
|
+
def sign_header(header)
|
48
|
+
@request.set_auth_header header
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module ApiAuth
|
2
|
+
|
3
|
+
module Helpers # :nodoc:
|
4
|
+
|
5
|
+
# Remove the ending new line character added by default
|
6
|
+
def b64_encode(string)
|
7
|
+
Base64.encode64(string).strip
|
8
|
+
end
|
9
|
+
|
10
|
+
# Capitalizes the keys of a hash
|
11
|
+
def capitalize_keys(hsh)
|
12
|
+
capitalized_hash = {}
|
13
|
+
hsh.each_pair {|k,v| capitalized_hash[k.to_s.upcase] = v }
|
14
|
+
capitalized_hash
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
@@ -0,0 +1,120 @@
|
|
1
|
+
module ApiAuth
|
2
|
+
|
3
|
+
# Integration with Rails
|
4
|
+
#
|
5
|
+
class Rails # :nodoc:
|
6
|
+
|
7
|
+
module ControllerMethods # :nodoc:
|
8
|
+
|
9
|
+
module InstanceMethods # :nodoc:
|
10
|
+
|
11
|
+
def get_api_access_id_from_request
|
12
|
+
ApiAuth.access_id(request)
|
13
|
+
end
|
14
|
+
|
15
|
+
def api_authenticated?(secret_key)
|
16
|
+
ApiAuth.authentic?(request, secret_key)
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
20
|
+
|
21
|
+
unless defined?(ActionController)
|
22
|
+
begin
|
23
|
+
require 'rubygems'
|
24
|
+
gem 'actionpack'
|
25
|
+
gem 'activesupport'
|
26
|
+
require 'action_controller'
|
27
|
+
require 'active_support'
|
28
|
+
rescue
|
29
|
+
nil
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
if defined?(ActionController::Base)
|
34
|
+
ActionController::Base.send(:include, ControllerMethods::InstanceMethods)
|
35
|
+
end
|
36
|
+
|
37
|
+
end # ControllerMethods
|
38
|
+
|
39
|
+
module ActiveResourceExtension # :nodoc:
|
40
|
+
|
41
|
+
module ActiveResourceApiAuth # :nodoc:
|
42
|
+
|
43
|
+
def self.included(base)
|
44
|
+
base.extend(ClassMethods)
|
45
|
+
|
46
|
+
base.class_inheritable_accessor :hmac_access_id
|
47
|
+
base.class_inheritable_accessor :hmac_secret_key
|
48
|
+
base.class_inheritable_accessor :use_hmac
|
49
|
+
end
|
50
|
+
|
51
|
+
module ClassMethods
|
52
|
+
|
53
|
+
def with_api_auth(access_id, secret_key)
|
54
|
+
self.hmac_access_id = access_id
|
55
|
+
self.hmac_secret_key = secret_key
|
56
|
+
self.use_hmac = true
|
57
|
+
|
58
|
+
class << self
|
59
|
+
alias_method_chain :connection, :auth
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def connection_with_auth(refresh = false)
|
64
|
+
c = connection_without_auth(refresh)
|
65
|
+
c.hmac_access_id = self.hmac_access_id
|
66
|
+
c.hmac_secret_key = self.hmac_secret_key
|
67
|
+
c.use_hmac = self.use_hmac
|
68
|
+
c
|
69
|
+
end
|
70
|
+
|
71
|
+
end # class methods
|
72
|
+
|
73
|
+
module InstanceMethods
|
74
|
+
end
|
75
|
+
|
76
|
+
end # BaseApiAuth
|
77
|
+
|
78
|
+
module Connection
|
79
|
+
|
80
|
+
def self.included(base)
|
81
|
+
base.send :alias_method_chain, :request, :auth
|
82
|
+
base.class_eval do
|
83
|
+
attr_accessor :hmac_secret_key, :hmac_access_id, :use_hmac
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def request_with_auth(method, path, *arguments)
|
88
|
+
if use_hmac && hmac_access_id && hmac_secret_key
|
89
|
+
h = arguments.last
|
90
|
+
tmp = "Net::HTTP::#{method.to_s.capitalize}".constantize.new(path, h)
|
91
|
+
ApiAuth.sign!(tmp, hmac_access_id, hmac_secret_key)
|
92
|
+
arguments.last['DATE'] = tmp['DATE']
|
93
|
+
arguments.last['Authorization'] = tmp['Authorization']
|
94
|
+
end
|
95
|
+
|
96
|
+
request_without_auth(method, path, *arguments)
|
97
|
+
end
|
98
|
+
|
99
|
+
end # Connection
|
100
|
+
|
101
|
+
unless defined?(ActiveResource)
|
102
|
+
begin
|
103
|
+
require 'rubygems'
|
104
|
+
gem 'activeresource'
|
105
|
+
require 'active_resource'
|
106
|
+
rescue
|
107
|
+
nil
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
if defined?(ActiveResource)
|
112
|
+
ActiveResource::Base.send(:include, ActiveResourceApiAuth)
|
113
|
+
ActiveResource::Connection.send(:include, Connection)
|
114
|
+
end
|
115
|
+
|
116
|
+
end # ActiveResourceExtension
|
117
|
+
|
118
|
+
end # Rails
|
119
|
+
|
120
|
+
end # ApiAuth
|
@@ -0,0 +1,62 @@
|
|
1
|
+
module ApiAuth
|
2
|
+
|
3
|
+
module RequestDrivers # :nodoc:
|
4
|
+
|
5
|
+
class ActionControllerRequest # :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["Authorization"] = header
|
17
|
+
@headers = fetch_headers
|
18
|
+
@request
|
19
|
+
end
|
20
|
+
|
21
|
+
def fetch_headers
|
22
|
+
capitalize_keys @request.env
|
23
|
+
end
|
24
|
+
|
25
|
+
def content_type
|
26
|
+
value = find_header(%w(CONTENT-TYPE CONTENT_TYPE HTTP_CONTENT_TYPE))
|
27
|
+
value.nil? ? "" : value
|
28
|
+
end
|
29
|
+
|
30
|
+
def content_md5
|
31
|
+
value = find_header(%w(CONTENT-MD5 CONTENT_MD5))
|
32
|
+
value.nil? ? "" : value
|
33
|
+
end
|
34
|
+
|
35
|
+
def request_uri
|
36
|
+
@request.path
|
37
|
+
end
|
38
|
+
|
39
|
+
def timestamp
|
40
|
+
value = find_header(%w(DATE HTTP_DATE))
|
41
|
+
if value.nil?
|
42
|
+
value = Time.now.utc.httpdate
|
43
|
+
@request.env['DATE'] = value
|
44
|
+
end
|
45
|
+
value
|
46
|
+
end
|
47
|
+
|
48
|
+
def authorization_header
|
49
|
+
find_header %w(Authorization AUTHORIZATION HTTP_AUTHORIZATION)
|
50
|
+
end
|
51
|
+
|
52
|
+
private
|
53
|
+
|
54
|
+
def find_header(keys)
|
55
|
+
keys.map {|key| @headers[key] }.compact.first
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
61
|
+
|
62
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
module ApiAuth
|
2
|
+
|
3
|
+
module RequestDrivers # :nodoc:
|
4
|
+
|
5
|
+
class CurbRequest # :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.merge!({ "Authorization" => header })
|
17
|
+
@headers = fetch_headers
|
18
|
+
@request
|
19
|
+
end
|
20
|
+
|
21
|
+
def fetch_headers
|
22
|
+
capitalize_keys @request.headers
|
23
|
+
end
|
24
|
+
|
25
|
+
def content_type
|
26
|
+
value = find_header(%w(CONTENT-TYPE CONTENT_TYPE HTTP_CONTENT_TYPE))
|
27
|
+
value.nil? ? "" : value
|
28
|
+
end
|
29
|
+
|
30
|
+
def content_md5
|
31
|
+
value = find_header(%w(CONTENT-MD5 CONTENT_MD5))
|
32
|
+
value.nil? ? "" : value
|
33
|
+
end
|
34
|
+
|
35
|
+
def request_uri
|
36
|
+
@request.url
|
37
|
+
end
|
38
|
+
|
39
|
+
def timestamp
|
40
|
+
value = find_header(%w(DATE HTTP_DATE))
|
41
|
+
if value.nil?
|
42
|
+
value = Time.now.utc.httpdate
|
43
|
+
@request.headers.merge!({ "DATE" => value })
|
44
|
+
end
|
45
|
+
value
|
46
|
+
end
|
47
|
+
|
48
|
+
def authorization_header
|
49
|
+
find_header %w(Authorization AUTHORIZATION HTTP_AUTHORIZATION)
|
50
|
+
end
|
51
|
+
|
52
|
+
private
|
53
|
+
|
54
|
+
def find_header(keys)
|
55
|
+
keys.map {|key| @headers[key] }.compact.first
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
61
|
+
|
62
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
module ApiAuth
|
2
|
+
|
3
|
+
module RequestDrivers # :nodoc:
|
4
|
+
|
5
|
+
class NetHttpRequest # :nodoc:
|
6
|
+
|
7
|
+
def initialize(request)
|
8
|
+
@request = request
|
9
|
+
@headers = fetch_headers
|
10
|
+
true
|
11
|
+
end
|
12
|
+
|
13
|
+
def set_auth_header(header)
|
14
|
+
@request["Authorization"] = header
|
15
|
+
@headers = fetch_headers
|
16
|
+
@request
|
17
|
+
end
|
18
|
+
|
19
|
+
def fetch_headers
|
20
|
+
@request
|
21
|
+
end
|
22
|
+
|
23
|
+
def content_type
|
24
|
+
value = find_header(%w(CONTENT-TYPE CONTENT_TYPE HTTP_CONTENT_TYPE))
|
25
|
+
value.nil? ? "" : value
|
26
|
+
end
|
27
|
+
|
28
|
+
def content_md5
|
29
|
+
value = find_header(%w(CONTENT-MD5 CONTENT_MD5))
|
30
|
+
value.nil? ? "" : value
|
31
|
+
end
|
32
|
+
|
33
|
+
def request_uri
|
34
|
+
@request.path
|
35
|
+
end
|
36
|
+
|
37
|
+
def timestamp
|
38
|
+
value = find_header(%w(DATE HTTP_DATE))
|
39
|
+
if value.nil?
|
40
|
+
value = Time.now.utc.httpdate
|
41
|
+
@request["DATE"] = value
|
42
|
+
end
|
43
|
+
value
|
44
|
+
end
|
45
|
+
|
46
|
+
def authorization_header
|
47
|
+
find_header %w(Authorization AUTHORIZATION HTTP_AUTHORIZATION)
|
48
|
+
end
|
49
|
+
|
50
|
+
private
|
51
|
+
|
52
|
+
def find_header(keys)
|
53
|
+
keys.map {|key| @headers[key] }.compact.first
|
54
|
+
end
|
55
|
+
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
module ApiAuth
|
2
|
+
|
3
|
+
module RequestDrivers # :nodoc:
|
4
|
+
|
5
|
+
class RestClientRequest # :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.merge!({ "Authorization" => header })
|
17
|
+
@headers = fetch_headers
|
18
|
+
@request
|
19
|
+
end
|
20
|
+
|
21
|
+
def fetch_headers
|
22
|
+
capitalize_keys @request.headers
|
23
|
+
end
|
24
|
+
|
25
|
+
def content_type
|
26
|
+
value = find_header(%w(CONTENT-TYPE CONTENT_TYPE HTTP_CONTENT_TYPE))
|
27
|
+
value.nil? ? "" : value
|
28
|
+
end
|
29
|
+
|
30
|
+
def content_md5
|
31
|
+
value = find_header(%w(CONTENT-MD5 CONTENT_MD5))
|
32
|
+
value.nil? ? "" : value
|
33
|
+
end
|
34
|
+
|
35
|
+
def request_uri
|
36
|
+
@request.url
|
37
|
+
end
|
38
|
+
|
39
|
+
def timestamp
|
40
|
+
value = find_header(%w(DATE HTTP_DATE))
|
41
|
+
if value.nil?
|
42
|
+
value = Time.now.utc.httpdate
|
43
|
+
@request.headers.merge!({ "DATE" => value })
|
44
|
+
end
|
45
|
+
value
|
46
|
+
end
|
47
|
+
|
48
|
+
def authorization_header
|
49
|
+
find_header %w(Authorization AUTHORIZATION HTTP_AUTHORIZATION)
|
50
|
+
end
|
51
|
+
|
52
|
+
private
|
53
|
+
|
54
|
+
def find_header(keys)
|
55
|
+
keys.map {|key| @headers[key] }.compact.first
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
61
|
+
|
62
|
+
end
|
File without changes
|
data/spec/spec_helper.rb
CHANGED
metadata
CHANGED
@@ -1,13 +1,8 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: api-auth
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash: 59
|
5
4
|
prerelease:
|
6
|
-
|
7
|
-
- 0
|
8
|
-
- 9
|
9
|
-
- 0
|
10
|
-
version: 0.9.0
|
5
|
+
version: 0.9.1
|
11
6
|
platform: ruby
|
12
7
|
authors:
|
13
8
|
- Mauricio Gomes
|
@@ -15,137 +10,97 @@ autorequire:
|
|
15
10
|
bindir: bin
|
16
11
|
cert_chain: []
|
17
12
|
|
18
|
-
date: 2011-
|
13
|
+
date: 2011-03-12 00:00:00 -05:00
|
19
14
|
default_executable:
|
20
15
|
dependencies:
|
21
16
|
- !ruby/object:Gem::Dependency
|
22
|
-
|
23
|
-
|
17
|
+
name: rspec
|
18
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
24
19
|
none: false
|
25
20
|
requirements:
|
26
21
|
- - ~>
|
27
22
|
- !ruby/object:Gem::Version
|
28
|
-
hash: 31
|
29
|
-
segments:
|
30
|
-
- 2
|
31
|
-
- 4
|
32
|
-
- 0
|
33
23
|
version: 2.4.0
|
34
|
-
|
24
|
+
type: :development
|
35
25
|
prerelease: false
|
36
|
-
|
26
|
+
version_requirements: *id001
|
37
27
|
- !ruby/object:Gem::Dependency
|
38
|
-
|
39
|
-
|
28
|
+
name: bundler
|
29
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
40
30
|
none: false
|
41
31
|
requirements:
|
42
32
|
- - ~>
|
43
33
|
- !ruby/object:Gem::Version
|
44
|
-
hash: 23
|
45
|
-
segments:
|
46
|
-
- 1
|
47
|
-
- 0
|
48
|
-
- 0
|
49
34
|
version: 1.0.0
|
50
|
-
|
35
|
+
type: :development
|
51
36
|
prerelease: false
|
52
|
-
|
37
|
+
version_requirements: *id002
|
53
38
|
- !ruby/object:Gem::Dependency
|
54
|
-
|
55
|
-
|
39
|
+
name: jeweler
|
40
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
56
41
|
none: false
|
57
42
|
requirements:
|
58
43
|
- - ~>
|
59
44
|
- !ruby/object:Gem::Version
|
60
|
-
hash: 7
|
61
|
-
segments:
|
62
|
-
- 1
|
63
|
-
- 5
|
64
|
-
- 2
|
65
45
|
version: 1.5.2
|
66
|
-
|
46
|
+
type: :development
|
67
47
|
prerelease: false
|
68
|
-
|
48
|
+
version_requirements: *id003
|
69
49
|
- !ruby/object:Gem::Dependency
|
70
|
-
|
71
|
-
|
50
|
+
name: amatch
|
51
|
+
requirement: &id004 !ruby/object:Gem::Requirement
|
72
52
|
none: false
|
73
53
|
requirements:
|
74
54
|
- - ~>
|
75
55
|
- !ruby/object:Gem::Version
|
76
|
-
hash: 29
|
77
|
-
segments:
|
78
|
-
- 0
|
79
|
-
- 2
|
80
|
-
- 5
|
81
56
|
version: 0.2.5
|
82
|
-
|
57
|
+
type: :development
|
83
58
|
prerelease: false
|
84
|
-
|
59
|
+
version_requirements: *id004
|
85
60
|
- !ruby/object:Gem::Dependency
|
86
|
-
|
87
|
-
|
61
|
+
name: curb
|
62
|
+
requirement: &id005 !ruby/object:Gem::Requirement
|
88
63
|
none: false
|
89
64
|
requirements:
|
90
65
|
- - ~>
|
91
66
|
- !ruby/object:Gem::Version
|
92
|
-
hash: 13
|
93
|
-
segments:
|
94
|
-
- 0
|
95
|
-
- 7
|
96
|
-
- 7
|
97
67
|
version: 0.7.7
|
98
|
-
|
68
|
+
type: :development
|
99
69
|
prerelease: false
|
100
|
-
|
70
|
+
version_requirements: *id005
|
101
71
|
- !ruby/object:Gem::Dependency
|
102
|
-
|
103
|
-
|
72
|
+
name: rest-client
|
73
|
+
requirement: &id006 !ruby/object:Gem::Requirement
|
104
74
|
none: false
|
105
75
|
requirements:
|
106
76
|
- - ~>
|
107
77
|
- !ruby/object:Gem::Version
|
108
|
-
hash: 15
|
109
|
-
segments:
|
110
|
-
- 1
|
111
|
-
- 6
|
112
|
-
- 0
|
113
78
|
version: 1.6.0
|
114
|
-
|
79
|
+
type: :development
|
115
80
|
prerelease: false
|
116
|
-
|
81
|
+
version_requirements: *id006
|
117
82
|
- !ruby/object:Gem::Dependency
|
118
|
-
|
119
|
-
|
83
|
+
name: actionpack
|
84
|
+
requirement: &id007 !ruby/object:Gem::Requirement
|
120
85
|
none: false
|
121
86
|
requirements:
|
122
87
|
- - ~>
|
123
88
|
- !ruby/object:Gem::Version
|
124
|
-
hash: 7
|
125
|
-
segments:
|
126
|
-
- 2
|
127
|
-
- 3
|
128
|
-
- 2
|
129
89
|
version: 2.3.2
|
130
|
-
|
90
|
+
type: :development
|
131
91
|
prerelease: false
|
132
|
-
|
92
|
+
version_requirements: *id007
|
133
93
|
- !ruby/object:Gem::Dependency
|
134
|
-
|
135
|
-
|
94
|
+
name: activeresource
|
95
|
+
requirement: &id008 !ruby/object:Gem::Requirement
|
136
96
|
none: false
|
137
97
|
requirements:
|
138
98
|
- - ~>
|
139
99
|
- !ruby/object:Gem::Version
|
140
|
-
hash: 7
|
141
|
-
segments:
|
142
|
-
- 2
|
143
|
-
- 3
|
144
|
-
- 2
|
145
100
|
version: 2.3.2
|
146
|
-
|
101
|
+
type: :development
|
147
102
|
prerelease: false
|
148
|
-
|
103
|
+
version_requirements: *id008
|
149
104
|
description: Full HMAC auth implementation for use in your gems and Rails apps.
|
150
105
|
email: mgomes@geminisbs.com
|
151
106
|
executables: []
|
@@ -159,17 +114,30 @@ files:
|
|
159
114
|
- .document
|
160
115
|
- .rspec
|
161
116
|
- Gemfile
|
117
|
+
- Gemfile.lock
|
162
118
|
- LICENSE.txt
|
119
|
+
- README.md
|
163
120
|
- Rakefile
|
121
|
+
- VERSION
|
122
|
+
- api_auth.gemspec
|
164
123
|
- lib/api-auth.rb
|
165
|
-
-
|
166
|
-
-
|
167
|
-
-
|
124
|
+
- lib/api_auth.rb
|
125
|
+
- lib/api_auth/base.rb
|
126
|
+
- lib/api_auth/errors.rb
|
127
|
+
- lib/api_auth/headers.rb
|
128
|
+
- lib/api_auth/helpers.rb
|
129
|
+
- lib/api_auth/railtie.rb
|
130
|
+
- lib/api_auth/request_drivers/action_controller.rb
|
131
|
+
- lib/api_auth/request_drivers/curb.rb
|
132
|
+
- lib/api_auth/request_drivers/net_http.rb
|
133
|
+
- lib/api_auth/request_drivers/rest_client.rb
|
134
|
+
- spec/api_auth_spec.rb
|
168
135
|
- spec/headers_spec.rb
|
169
136
|
- spec/helpers_spec.rb
|
170
137
|
- spec/railtie_spec.rb
|
138
|
+
- spec/spec_helper.rb
|
171
139
|
has_rdoc: true
|
172
|
-
homepage: http://github.com/
|
140
|
+
homepage: http://github.com/mgomes/api_auth
|
173
141
|
licenses:
|
174
142
|
- MIT
|
175
143
|
post_install_message:
|
@@ -182,7 +150,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
182
150
|
requirements:
|
183
151
|
- - ">="
|
184
152
|
- !ruby/object:Gem::Version
|
185
|
-
hash:
|
153
|
+
hash: -3238234233330839898
|
186
154
|
segments:
|
187
155
|
- 0
|
188
156
|
version: "0"
|
@@ -191,19 +159,16 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
191
159
|
requirements:
|
192
160
|
- - ">="
|
193
161
|
- !ruby/object:Gem::Version
|
194
|
-
hash: 3
|
195
|
-
segments:
|
196
|
-
- 0
|
197
162
|
version: "0"
|
198
163
|
requirements: []
|
199
164
|
|
200
165
|
rubyforge_project:
|
201
|
-
rubygems_version: 1.
|
166
|
+
rubygems_version: 1.5.2
|
202
167
|
signing_key:
|
203
168
|
specification_version: 3
|
204
169
|
summary: Simple HMAC authentication for your APIs
|
205
170
|
test_files:
|
206
|
-
- spec/
|
171
|
+
- spec/api_auth_spec.rb
|
207
172
|
- spec/headers_spec.rb
|
208
173
|
- spec/helpers_spec.rb
|
209
174
|
- spec/railtie_spec.rb
|