jmoses_api-auth 1.0.4
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/.gitignore +44 -0
- data/.rspec +3 -0
- data/Gemfile +2 -0
- data/Gemfile.lock +46 -0
- data/LICENSE.txt +20 -0
- data/README.md +185 -0
- data/Rakefile +22 -0
- data/VERSION +1 -0
- data/api_auth.gemspec +26 -0
- data/lib/api-auth.rb +2 -0
- data/lib/api_auth/base.rb +97 -0
- data/lib/api_auth/errors.rb +9 -0
- data/lib/api_auth/headers.rb +82 -0
- data/lib/api_auth/helpers.rb +18 -0
- data/lib/api_auth/railtie.rb +129 -0
- data/lib/api_auth/request_drivers/action_controller.rb +85 -0
- data/lib/api_auth/request_drivers/action_dispatch.rb +15 -0
- data/lib/api_auth/request_drivers/curb.rb +70 -0
- data/lib/api_auth/request_drivers/net_http.rb +78 -0
- data/lib/api_auth/request_drivers/rack.rb +85 -0
- data/lib/api_auth/request_drivers/rest_client.rb +93 -0
- data/lib/api_auth.rb +16 -0
- data/spec/api_auth_spec.rb +407 -0
- data/spec/application_helper.rb +2 -0
- data/spec/headers_spec.rb +223 -0
- data/spec/helpers_spec.rb +14 -0
- data/spec/railtie_spec.rb +114 -0
- data/spec/spec_helper.rb +22 -0
- data/spec/test_helper.rb +2 -0
- metadata +212 -0
data/.document
ADDED
data/.gitignore
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
.rvmrc
|
2
|
+
|
3
|
+
# rcov generated
|
4
|
+
coverage
|
5
|
+
|
6
|
+
# rdoc generated
|
7
|
+
rdoc
|
8
|
+
|
9
|
+
# yard generated
|
10
|
+
doc
|
11
|
+
.yardoc
|
12
|
+
|
13
|
+
# bundler
|
14
|
+
.bundle
|
15
|
+
|
16
|
+
# jeweler generated
|
17
|
+
pkg
|
18
|
+
|
19
|
+
# Have editor/IDE/OS specific files you need to ignore? Consider using a global gitignore:
|
20
|
+
#
|
21
|
+
# * Create a file at ~/.gitignore
|
22
|
+
# * Include files you want ignored
|
23
|
+
# * Run: git config --global core.excludesfile ~/.gitignore
|
24
|
+
#
|
25
|
+
# After doing this, these files will be ignored in all your git projects,
|
26
|
+
# saving you from having to 'pollute' every project you touch with them
|
27
|
+
#
|
28
|
+
# Not sure what to needs to be ignored for particular editors/OSes? Here's some ideas to get you started. (Remember, remove the leading # of the line)
|
29
|
+
#
|
30
|
+
# For MacOS:
|
31
|
+
#
|
32
|
+
#.DS_Store
|
33
|
+
#
|
34
|
+
# For TextMate
|
35
|
+
#*.tmproj
|
36
|
+
#tmtags
|
37
|
+
#
|
38
|
+
# For emacs:
|
39
|
+
#*~
|
40
|
+
#\#*
|
41
|
+
#.\#*
|
42
|
+
#
|
43
|
+
# For vim:
|
44
|
+
#*.swp
|
data/.rspec
ADDED
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,46 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
api-auth (1.0.3)
|
5
|
+
|
6
|
+
GEM
|
7
|
+
remote: https://rubygems.org/
|
8
|
+
specs:
|
9
|
+
actionpack (2.3.14)
|
10
|
+
activesupport (= 2.3.14)
|
11
|
+
rack (~> 1.1.0)
|
12
|
+
activeresource (2.3.14)
|
13
|
+
activesupport (= 2.3.14)
|
14
|
+
activesupport (2.3.14)
|
15
|
+
amatch (0.2.10)
|
16
|
+
tins (~> 0.3)
|
17
|
+
curb (0.8.1)
|
18
|
+
diff-lcs (1.1.3)
|
19
|
+
mime-types (1.17.2)
|
20
|
+
rack (1.1.3)
|
21
|
+
rake (0.9.2.2)
|
22
|
+
rest-client (1.6.7)
|
23
|
+
mime-types (>= 1.16)
|
24
|
+
rspec (2.4.0)
|
25
|
+
rspec-core (~> 2.4.0)
|
26
|
+
rspec-expectations (~> 2.4.0)
|
27
|
+
rspec-mocks (~> 2.4.0)
|
28
|
+
rspec-core (2.4.0)
|
29
|
+
rspec-expectations (2.4.0)
|
30
|
+
diff-lcs (~> 1.1.2)
|
31
|
+
rspec-mocks (2.4.0)
|
32
|
+
tins (0.5.5)
|
33
|
+
|
34
|
+
PLATFORMS
|
35
|
+
ruby
|
36
|
+
|
37
|
+
DEPENDENCIES
|
38
|
+
actionpack (~> 2.3.2)
|
39
|
+
activeresource (~> 2.3.2)
|
40
|
+
activesupport (~> 2.3.2)
|
41
|
+
amatch
|
42
|
+
api-auth!
|
43
|
+
curb (~> 0.8.1)
|
44
|
+
rake
|
45
|
+
rest-client (~> 1.6.0)
|
46
|
+
rspec (~> 2.4.0)
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2012 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,185 @@
|
|
1
|
+
# ApiAuth #
|
2
|
+
|
3
|
+
Logins and passwords are for humans. Communication between applications need to
|
4
|
+
be protected through different means.
|
5
|
+
|
6
|
+
ApiAuth is a Ruby gem designed to be used both in your client and server
|
7
|
+
HTTP-based applications. It implements the same authentication methods (HMAC-SHA1)
|
8
|
+
used by Amazon Web Services.
|
9
|
+
|
10
|
+
The gem will sign your requests on the client side and authenticate that
|
11
|
+
signature on the server side. If your server resources are implemented as a
|
12
|
+
Rails ActiveResource, it will integrate with that. It will even generate the
|
13
|
+
secret keys necessary for your clients to sign their requests.
|
14
|
+
|
15
|
+
Since it operates entirely using HTTP headers, the server component does not
|
16
|
+
have to be written in the same language as the clients.
|
17
|
+
|
18
|
+
## How it works ##
|
19
|
+
|
20
|
+
1. A canonical string is first created using your HTTP headers containing the
|
21
|
+
content-type, content-MD5, request URI and the timestamp. If content-type or
|
22
|
+
content-MD5 are not present, then a blank string is used in their place. If the
|
23
|
+
timestamp isn't present, a valid HTTP date is automatically added to the
|
24
|
+
request. The canonical string string is computed as follows:
|
25
|
+
|
26
|
+
canonical_string = 'content-type,content-MD5,request URI,timestamp'
|
27
|
+
|
28
|
+
2. This string is then used to create the signature which is a Base64 encoded
|
29
|
+
SHA1 HMAC, using the client's private secret key.
|
30
|
+
|
31
|
+
3. This signature is then added as the `Authorization` HTTP header in the form:
|
32
|
+
|
33
|
+
Authorization = APIAuth 'client access id':'signature from step 2'
|
34
|
+
|
35
|
+
5. On the server side, the SHA1 HMAC is computed in the same way using the
|
36
|
+
request headers and the client's secret key, which is known to only
|
37
|
+
the client and the server but can be looked up on the server using the client's
|
38
|
+
access id that was attached in the header. The access id can be any integer or
|
39
|
+
string that uniquely identifies the client. The signed request expires after 15
|
40
|
+
minutes in order to avoid replay attacks.
|
41
|
+
|
42
|
+
|
43
|
+
## References ##
|
44
|
+
|
45
|
+
* [Hash functions](http://en.wikipedia.org/wiki/Cryptographic_hash_function)
|
46
|
+
* [SHA-1 Hash function](http://en.wikipedia.org/wiki/SHA-1)
|
47
|
+
* [HMAC algorithm](http://en.wikipedia.org/wiki/HMAC)
|
48
|
+
* [RFC 2104 (HMAC)](http://tools.ietf.org/html/rfc2104)
|
49
|
+
|
50
|
+
## Install ##
|
51
|
+
|
52
|
+
The gem doesn't have any dependencies outside of having a working OpenSSL
|
53
|
+
configuration for your Ruby VM. To install:
|
54
|
+
|
55
|
+
[sudo] gem install api-auth
|
56
|
+
|
57
|
+
Please note the dash in the name versus the underscore.
|
58
|
+
|
59
|
+
## Clients ##
|
60
|
+
|
61
|
+
ApiAuth supports many popular HTTP clients. Support for other clients can be
|
62
|
+
added as a request driver.
|
63
|
+
|
64
|
+
Here is the current list of supported request objects:
|
65
|
+
|
66
|
+
* Net::HTTP
|
67
|
+
* ActionController::Request
|
68
|
+
* Curb (Curl::Easy)
|
69
|
+
* RestClient
|
70
|
+
|
71
|
+
### HTTP Client Objects ###
|
72
|
+
|
73
|
+
Here's a sample implementation of signing a request created with RestClient. For
|
74
|
+
more examples, please check out the ApiAuth Spec where every supported HTTP
|
75
|
+
client is tested.
|
76
|
+
|
77
|
+
Assuming you have a client access id and secret as follows:
|
78
|
+
|
79
|
+
``` ruby
|
80
|
+
@access_id = "1044"
|
81
|
+
@secret_key = ApiAuth.generate_secret_key
|
82
|
+
```
|
83
|
+
|
84
|
+
A typical RestClient PUT request may look like:
|
85
|
+
|
86
|
+
``` ruby
|
87
|
+
headers = { 'Content-MD5' => "e59ff97941044f85df5297e1c302d260",
|
88
|
+
'Content-Type' => "text/plain",
|
89
|
+
'Date' => "Mon, 23 Jan 1984 03:29:56 GMT" }
|
90
|
+
@request = RestClient::Request.new(:url => "/resource.xml?foo=bar&bar=foo",
|
91
|
+
:headers => headers,
|
92
|
+
:method => :put)
|
93
|
+
```
|
94
|
+
|
95
|
+
To sign that request, simply call the `sign!` method as follows:
|
96
|
+
|
97
|
+
``` ruby
|
98
|
+
@signed_request = ApiAuth.sign!(@request, @access_id, @secret_key)
|
99
|
+
```
|
100
|
+
|
101
|
+
The proper `Authorization` request header has now been added to that request
|
102
|
+
object and it's ready to be transmitted. It's recommended that you sign the
|
103
|
+
request as one of the last steps in building the request to ensure the headers
|
104
|
+
don't change after the signing process which would cause the authentication
|
105
|
+
check to fail on the server side.
|
106
|
+
|
107
|
+
### ActiveResource Clients ###
|
108
|
+
|
109
|
+
ApiAuth can transparently protect your ActiveResource communications with a
|
110
|
+
single configuration line:
|
111
|
+
|
112
|
+
``` ruby
|
113
|
+
class MyResource < ActiveResource::Base
|
114
|
+
with_api_auth(access_id, secret_key)
|
115
|
+
end
|
116
|
+
```
|
117
|
+
|
118
|
+
This will automatically sign all outgoing ActiveResource requests from your app.
|
119
|
+
|
120
|
+
## Server ##
|
121
|
+
|
122
|
+
ApiAuth provides some built in methods to help you generate API keys for your
|
123
|
+
clients as well as verifying incoming API requests.
|
124
|
+
|
125
|
+
To generate a Base64 encoded API key for a client:
|
126
|
+
|
127
|
+
``` ruby
|
128
|
+
ApiAuth.generate_secret_key
|
129
|
+
```
|
130
|
+
|
131
|
+
To validate whether or not a request is authentic:
|
132
|
+
|
133
|
+
``` ruby
|
134
|
+
ApiAuth.authentic?(signed_request, secret_key)
|
135
|
+
```
|
136
|
+
|
137
|
+
If your server is a Rails app, the signed request will be the `request` object.
|
138
|
+
|
139
|
+
In order to obtain the secret key for the client, you first need to look up the
|
140
|
+
client's access_id. ApiAuth can pull that from the request headers for you:
|
141
|
+
|
142
|
+
``` ruby
|
143
|
+
ApiAuth.access_id(signed_request)
|
144
|
+
```
|
145
|
+
|
146
|
+
Once you've looked up the client's record via the access id, you can then verify
|
147
|
+
whether or not the request is authentic. Typically, the access id for the client
|
148
|
+
will be their record's primary key in the DB that stores the record or some other
|
149
|
+
public unique identifier for the client.
|
150
|
+
|
151
|
+
Here's a sample method that can be used in a `before_filter` if your server is a
|
152
|
+
Rails app:
|
153
|
+
|
154
|
+
``` ruby
|
155
|
+
before_filter :api_authenticate
|
156
|
+
|
157
|
+
def api_authenticate
|
158
|
+
@current_account = Account.find_by_access_id(ApiAuth.access_id(request))
|
159
|
+
return ApiAuth.authentic?(request, @current_account.secret_key) unless @current_account.nil?
|
160
|
+
false
|
161
|
+
end
|
162
|
+
```
|
163
|
+
|
164
|
+
## Development ##
|
165
|
+
|
166
|
+
ApiAuth uses bundler for gem dependencies and RSpec for testing. Developing the
|
167
|
+
gem requires that you have all supported HTTP clients installed. Bundler will
|
168
|
+
take care of all that for you.
|
169
|
+
|
170
|
+
To run the tests:
|
171
|
+
|
172
|
+
rake spec
|
173
|
+
|
174
|
+
If you'd like to add support for additional HTTP clients, check out the already
|
175
|
+
implemented drivers in `lib/api_auth/request_drivers` for reference. All of
|
176
|
+
the public methods for each driver are required to be implemented by your driver.
|
177
|
+
|
178
|
+
## Authors ##
|
179
|
+
|
180
|
+
* [Mauricio Gomes](http://github.com/mgomes)
|
181
|
+
* [Kevin Glowacz](http://github.com/kjg)
|
182
|
+
|
183
|
+
## Copyright ##
|
184
|
+
|
185
|
+
Copyright (c) 2012 Gemini SBS LLC. See LICENSE.txt for further details.
|
data/Rakefile
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'bundler'
|
2
|
+
Bundler::GemHelper.install_tasks
|
3
|
+
|
4
|
+
require 'rake'
|
5
|
+
require 'rspec/core'
|
6
|
+
require 'rspec/core/rake_task'
|
7
|
+
|
8
|
+
RSpec::Core::RakeTask.new(:spec) do |spec|
|
9
|
+
spec.pattern = FileList['spec/**/*_spec.rb']
|
10
|
+
end
|
11
|
+
|
12
|
+
task :default => :spec
|
13
|
+
|
14
|
+
require 'rake/rdoctask'
|
15
|
+
Rake::RDocTask.new do |rdoc|
|
16
|
+
version = File.exist?('VERSION') ? File.read('VERSION') : ""
|
17
|
+
|
18
|
+
rdoc.rdoc_dir = 'rdoc'
|
19
|
+
rdoc.title = "test #{version}"
|
20
|
+
rdoc.rdoc_files.include('README*')
|
21
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
22
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
1.0.4
|
data/api_auth.gemspec
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
|
4
|
+
Gem::Specification.new do |s|
|
5
|
+
s.name = %q{jmoses_api-auth}
|
6
|
+
s.summary = %q{Simple HMAC authentication for your APIs (fork by jmoses)}
|
7
|
+
s.description = %q{Full HMAC auth implementation for use in your gems and Rails apps.}
|
8
|
+
s.homepage = %q{https://github.com/jmoses/api_auth}
|
9
|
+
s.version = File.read(File.join(File.dirname(__FILE__), 'VERSION'))
|
10
|
+
s.authors = ['Jon Moses', "Mauricio Gomes"]
|
11
|
+
s.email = ['jon@burningbush.us', "mauricio@edge14.com"]
|
12
|
+
|
13
|
+
s.add_development_dependency "rake"
|
14
|
+
s.add_development_dependency "amatch"
|
15
|
+
s.add_development_dependency "rspec", "~> 2.4.0"
|
16
|
+
s.add_development_dependency "actionpack", "~> 2.3.2"
|
17
|
+
s.add_development_dependency "activesupport", "~> 2.3.2"
|
18
|
+
s.add_development_dependency "activeresource", "~> 2.3.2"
|
19
|
+
s.add_development_dependency "rest-client", "~> 1.6.0"
|
20
|
+
s.add_development_dependency "curb", "~> 0.8.1"
|
21
|
+
|
22
|
+
s.files = `git ls-files`.split("\n")
|
23
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
24
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
25
|
+
s.require_paths = ["lib"]
|
26
|
+
end
|
data/lib/api-auth.rb
ADDED
@@ -0,0 +1,97 @@
|
|
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.calculate_md5
|
27
|
+
headers.set_date
|
28
|
+
headers.sign_header auth_header(request, access_id, secret_key)
|
29
|
+
end
|
30
|
+
|
31
|
+
# Determines if the request is authentic given the request and the client's
|
32
|
+
# secret key. Returns true if the request is authentic and false otherwise.
|
33
|
+
def authentic?(request, secret_key)
|
34
|
+
return false if secret_key.nil?
|
35
|
+
|
36
|
+
return !md5_mismatch?(request) && signatures_match?(request, secret_key) && !request_too_old?(request)
|
37
|
+
end
|
38
|
+
|
39
|
+
# Returns the access id from the request's authorization header
|
40
|
+
def access_id(request)
|
41
|
+
headers = Headers.new(request)
|
42
|
+
if match_data = parse_auth_header(headers.authorization_header)
|
43
|
+
return match_data[1]
|
44
|
+
end
|
45
|
+
|
46
|
+
nil
|
47
|
+
end
|
48
|
+
|
49
|
+
# Generates a Base64 encoded, randomized secret key
|
50
|
+
#
|
51
|
+
# Store this key along with the access key that will be used for
|
52
|
+
# authenticating the client
|
53
|
+
def generate_secret_key
|
54
|
+
random_bytes = OpenSSL::Random.random_bytes(512)
|
55
|
+
b64_encode(Digest::SHA2.new(512).digest(random_bytes))
|
56
|
+
end
|
57
|
+
|
58
|
+
private
|
59
|
+
|
60
|
+
def request_too_old?(request)
|
61
|
+
headers = Headers.new(request)
|
62
|
+
# 900 seconds is 15 minutes
|
63
|
+
Time.parse(headers.timestamp).utc < (Time.now.utc - 900)
|
64
|
+
end
|
65
|
+
|
66
|
+
def md5_mismatch?(request)
|
67
|
+
headers = Headers.new(request)
|
68
|
+
headers.md5_mismatch?
|
69
|
+
end
|
70
|
+
|
71
|
+
def signatures_match?(request, secret_key)
|
72
|
+
headers = Headers.new(request)
|
73
|
+
if match_data = parse_auth_header(headers.authorization_header)
|
74
|
+
hmac = match_data[2]
|
75
|
+
return hmac == hmac_signature(request, secret_key)
|
76
|
+
end
|
77
|
+
false
|
78
|
+
end
|
79
|
+
|
80
|
+
def hmac_signature(request, secret_key)
|
81
|
+
headers = Headers.new(request)
|
82
|
+
canonical_string = headers.canonical_string
|
83
|
+
digest = OpenSSL::Digest::Digest.new('sha1')
|
84
|
+
b64_encode(OpenSSL::HMAC.digest(digest, secret_key, canonical_string))
|
85
|
+
end
|
86
|
+
|
87
|
+
def auth_header(request, access_id, secret_key)
|
88
|
+
"APIAuth #{access_id}:#{hmac_signature(request, secret_key)}"
|
89
|
+
end
|
90
|
+
|
91
|
+
def parse_auth_header(auth_header)
|
92
|
+
Regexp.new("APIAuth ([^:]+):(.+)$").match(auth_header)
|
93
|
+
end
|
94
|
+
|
95
|
+
end # class methods
|
96
|
+
|
97
|
+
end # ApiAuth
|
@@ -0,0 +1,82 @@
|
|
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
|
+
if defined?(ActionDispatch)
|
22
|
+
@request = ActionDispatchRequest.new(request)
|
23
|
+
else
|
24
|
+
@request = ActionControllerRequest.new(request)
|
25
|
+
end
|
26
|
+
when /ActionDispatch::Request/
|
27
|
+
@request = ActionDispatchRequest.new(request)
|
28
|
+
when /Rack::Request/
|
29
|
+
@request = RackRequest.new(request)
|
30
|
+
else
|
31
|
+
raise UnknownHTTPRequest, "#{request.class.to_s} is not yet supported."
|
32
|
+
end
|
33
|
+
true
|
34
|
+
end
|
35
|
+
|
36
|
+
# Returns the request timestamp
|
37
|
+
def timestamp
|
38
|
+
@request.timestamp
|
39
|
+
end
|
40
|
+
|
41
|
+
# Returns the canonical string computed from the request's headers
|
42
|
+
def canonical_string
|
43
|
+
[ @request.content_type,
|
44
|
+
@request.content_md5,
|
45
|
+
@request.request_uri.gsub(/http:\/\/[^(,|\?|\/)]*/,''), # remove host
|
46
|
+
@request.timestamp
|
47
|
+
].join(",")
|
48
|
+
end
|
49
|
+
|
50
|
+
# Returns the authorization header from the request's headers
|
51
|
+
def authorization_header
|
52
|
+
@request.authorization_header
|
53
|
+
end
|
54
|
+
|
55
|
+
def set_date
|
56
|
+
@request.set_date if @request.timestamp.blank?
|
57
|
+
end
|
58
|
+
|
59
|
+
def calculate_md5
|
60
|
+
@request.populate_content_md5 if @request.content_md5.blank?
|
61
|
+
end
|
62
|
+
|
63
|
+
def md5_mismatch?
|
64
|
+
if @request.content_md5.blank?
|
65
|
+
false
|
66
|
+
else
|
67
|
+
@request.md5_mismatch?
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
# Sets the request's authorization header with the passed in value.
|
72
|
+
# The header should be the ApiAuth HMAC signature.
|
73
|
+
#
|
74
|
+
# This will return the original request object with the signed Authorization
|
75
|
+
# header already in place.
|
76
|
+
def sign_header(header)
|
77
|
+
@request.set_auth_header header
|
78
|
+
end
|
79
|
+
|
80
|
+
end
|
81
|
+
|
82
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module ApiAuth
|
2
|
+
|
3
|
+
module Helpers # :nodoc:
|
4
|
+
|
5
|
+
def b64_encode(string)
|
6
|
+
Base64.strict_encode64(string)
|
7
|
+
end
|
8
|
+
|
9
|
+
# Capitalizes the keys of a hash
|
10
|
+
def capitalize_keys(hsh)
|
11
|
+
capitalized_hash = {}
|
12
|
+
hsh.each_pair {|k,v| capitalized_hash[k.to_s.upcase] = v }
|
13
|
+
capitalized_hash
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|