auth-hmac 1.0.1 → 1.1.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/History.txt +4 -0
- data/config/hoe.rb +3 -2
- data/lib/auth-hmac.rb +141 -71
- data/lib/auth-hmac/version.rb +2 -2
- data/spec/auth-hmac_spec.rb +295 -115
- metadata +6 -6
data/History.txt
CHANGED
data/config/hoe.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
require 'auth-hmac/version'
|
2
2
|
|
3
|
-
AUTHOR = 'Sean Geoghegan' # can also be an array of Authors
|
3
|
+
AUTHOR = ['Sean Geoghegan', 'ascarter'] # can also be an array of Authors
|
4
4
|
EMAIL = "seangeo@gmail.com"
|
5
5
|
DESCRIPTION = "A gem providing HMAC based authentication for HTTP"
|
6
6
|
GEM_NAME = 'auth-hmac' # what ppl will type to install your gem
|
@@ -51,7 +51,8 @@ end
|
|
51
51
|
# Generate all the Rake tasks
|
52
52
|
# Run 'rake -T' to see list of generated tasks (from gem root directory)
|
53
53
|
$hoe = Hoe.new(GEM_NAME, VERS) do |p|
|
54
|
-
p.
|
54
|
+
p.author = AUTHOR
|
55
|
+
p.email = EMAIL
|
55
56
|
p.description = DESCRIPTION
|
56
57
|
p.summary = DESCRIPTION
|
57
58
|
p.url = HOMEPATH
|
data/lib/auth-hmac.rb
CHANGED
@@ -17,6 +17,12 @@ require 'base64'
|
|
17
17
|
# As a result of the generalization, it won't work with AWS because it doesn't support
|
18
18
|
# the Amazon extension headers.
|
19
19
|
#
|
20
|
+
# === References
|
21
|
+
# Cryptographic Hash functions:: http://en.wikipedia.org/wiki/Cryptographic_hash_function
|
22
|
+
# SHA-1 Hash function:: http://en.wikipedia.org/wiki/SHA-1
|
23
|
+
# HMAC algorithm:: http://en.wikipedia.org/wiki/HMAC
|
24
|
+
# RFC 2104:: http://tools.ietf.org/html/rfc2104
|
25
|
+
#
|
20
26
|
class AuthHMAC
|
21
27
|
module Headers # :nodoc:
|
22
28
|
# Gets the headers for a request.
|
@@ -42,69 +48,7 @@ class AuthHMAC
|
|
42
48
|
end
|
43
49
|
|
44
50
|
include Headers
|
45
|
-
|
46
|
-
# Signs a request using a given access key id and secret.
|
47
|
-
#
|
48
|
-
def AuthHMAC.sign!(request, access_key_id, secret)
|
49
|
-
self.new(access_key_id => secret).sign!(request, access_key_id)
|
50
|
-
end
|
51
|
-
|
52
|
-
def AuthHMAC.authenticated?(request, access_key_id, secret)
|
53
|
-
self.new(access_key_id => secret).authenticated?(request)
|
54
|
-
end
|
55
|
-
|
56
|
-
# Create an AuthHMAC instance using a given credential store.
|
57
|
-
#
|
58
|
-
# A credential store must respond to the [] method and return
|
59
|
-
# the secret for the access key id passed to [].
|
60
|
-
#
|
61
|
-
def initialize(credential_store)
|
62
|
-
@credential_store = credential_store
|
63
|
-
end
|
64
|
-
|
65
|
-
# Signs a request using the access_key_id and the secret associated with that id
|
66
|
-
# in the credential store.
|
67
|
-
#
|
68
|
-
# Signing a requests adds an Authorization header to the request in the format:
|
69
|
-
#
|
70
|
-
# AuthHMAC <access_key_id>:<signature>
|
71
|
-
#
|
72
|
-
# where <signature> is the Base64 encoded HMAC-SHA1 of the CanonicalString and the secret.
|
73
|
-
#
|
74
|
-
def sign!(request, access_key_id)
|
75
|
-
secret = @credential_store[access_key_id]
|
76
|
-
raise ArgumentError, "No secret found for key id '#{access_key_id}'" if secret.nil?
|
77
|
-
request['Authorization'] = build_authorization_header(request, access_key_id, secret)
|
78
|
-
end
|
79
|
-
|
80
|
-
# Authenticates a request using HMAC
|
81
|
-
#
|
82
|
-
# Returns true if the request has an AuthHMAC Authorization header and
|
83
|
-
# the access id and HMAC match an id and HMAC produced for the secret
|
84
|
-
# in the credential store. Otherwise returns false.
|
85
|
-
#
|
86
|
-
def authenticated?(request)
|
87
|
-
if md = /^AuthHMAC ([^:]+):(.+)$/.match(find_header(%w(Authorization HTTP_AUTHORIZATION), headers(request)))
|
88
|
-
access_key_id = md[1]
|
89
|
-
hmac = md[2]
|
90
|
-
secret = @credential_store[access_key_id]
|
91
|
-
!secret.nil? && hmac == build_signature(request, secret)
|
92
|
-
else
|
93
|
-
false
|
94
|
-
end
|
95
|
-
end
|
96
|
-
|
97
|
-
private
|
98
|
-
def build_authorization_header(request, access_key_id, secret)
|
99
|
-
"AuthHMAC #{access_key_id}:#{build_signature(request, secret)}"
|
100
|
-
end
|
101
|
-
|
102
|
-
def build_signature(request, secret)
|
103
|
-
canonical_string = CanonicalString.new(request)
|
104
|
-
digest = OpenSSL::Digest::Digest.new('sha1')
|
105
|
-
Base64.encode64(OpenSSL::HMAC.digest(digest, secret, canonical_string)).strip
|
106
|
-
end
|
107
|
-
|
51
|
+
|
108
52
|
# Build a Canonical String for a HTTP request.
|
109
53
|
#
|
110
54
|
# A Canonical String has the following format:
|
@@ -145,10 +89,10 @@ class AuthHMAC
|
|
145
89
|
def header_values(headers)
|
146
90
|
[ content_type(headers),
|
147
91
|
content_md5(headers),
|
148
|
-
(date(headers) or headers['Date'] = Time.now.
|
92
|
+
(date(headers) or headers['Date'] = Time.now.utc.httpdate)
|
149
93
|
].join("\n")
|
150
94
|
end
|
151
|
-
|
95
|
+
|
152
96
|
def content_type(headers)
|
153
97
|
find_header(%w(CONTENT-TYPE CONTENT_TYPE HTTP_CONTENT_TYPE), headers)
|
154
98
|
end
|
@@ -172,7 +116,129 @@ class AuthHMAC
|
|
172
116
|
path[/^[^?]*/]
|
173
117
|
end
|
174
118
|
end
|
119
|
+
|
120
|
+
@@default_signature_class = CanonicalString
|
121
|
+
|
122
|
+
# Create an AuthHMAC instance using the given credential store
|
123
|
+
#
|
124
|
+
# Credential Store:
|
125
|
+
# * Credential store must respond to the [] method and return a secret for access key id
|
126
|
+
#
|
127
|
+
# Options:
|
128
|
+
# Override default options
|
129
|
+
# * <tt>:service_id</tt> - Service ID used in the AUTHORIZATION header string. Default is AuthHMAC.
|
130
|
+
# * <tt>:signature_method</tt> - Proc object that takes request and produces the signature string
|
131
|
+
# used for authentication. Default is CanonicalString.
|
132
|
+
# Examples:
|
133
|
+
# my_hmac = AuthHMAC.new('access_id1' => 'secret1', 'access_id2' => 'secret2')
|
134
|
+
#
|
135
|
+
# cred_store = { 'access_id1' => 'secret1', 'access_id2' => 'secret2' }
|
136
|
+
# options = { :service_id => 'MyApp', :signature_method => lambda { |r| MyRequestString.new(r) } }
|
137
|
+
# my_hmac = AuthHMAC.new(cred_store, options)
|
138
|
+
#
|
139
|
+
def initialize(credential_store, options = nil)
|
140
|
+
@credential_store = credential_store
|
141
|
+
|
142
|
+
# Defaults
|
143
|
+
@service_id = self.class.name
|
144
|
+
@signature_class = @@default_signature_class
|
145
|
+
|
146
|
+
unless options.nil?
|
147
|
+
@service_id = options[:service_id] if options.key?(:service_id)
|
148
|
+
@signature_class = options[:signature] if options.key?(:signature) && options[:signature].is_a?(Class)
|
149
|
+
end
|
175
150
|
|
151
|
+
@signature_method = lambda { |r| @signature_class.send(:new, r) }
|
152
|
+
end
|
153
|
+
|
154
|
+
# Generates canonical signing string for given request
|
155
|
+
#
|
156
|
+
# Supports same options as AuthHMAC.initialize for overriding service_id and
|
157
|
+
# signature method.
|
158
|
+
#
|
159
|
+
def AuthHMAC.canonical_string(request, options = nil)
|
160
|
+
self.new(nil, options).canonical_string(request)
|
161
|
+
end
|
162
|
+
|
163
|
+
# Generates signature string for a given secret
|
164
|
+
#
|
165
|
+
# Supports same options as AuthHMAC.initialize for overriding service_id and
|
166
|
+
# signature method.
|
167
|
+
#
|
168
|
+
def AuthHMAC.signature(request, secret, options = nil)
|
169
|
+
self.new(nil, options).signature(request, secret)
|
170
|
+
end
|
171
|
+
|
172
|
+
# Signs a request using a given access key id and secret.
|
173
|
+
#
|
174
|
+
# Supports same options as AuthHMAC.initialize for overriding service_id and
|
175
|
+
# signature method.
|
176
|
+
#
|
177
|
+
def AuthHMAC.sign!(request, access_key_id, secret, options = nil)
|
178
|
+
credentials = { access_key_id => secret }
|
179
|
+
self.new(credentials, options).sign!(request, access_key_id)
|
180
|
+
end
|
181
|
+
|
182
|
+
# Authenticates a request using HMAC
|
183
|
+
#
|
184
|
+
# Supports same options as AuthHMAC.initialize for overriding service_id and
|
185
|
+
# signature method.
|
186
|
+
#
|
187
|
+
def AuthHMAC.authenticated?(request, access_key_id, secret, options)
|
188
|
+
credentials = { access_key_id => secret }
|
189
|
+
self.new(credentials, options).authenticated?(request)
|
190
|
+
end
|
191
|
+
|
192
|
+
# Signs a request using the access_key_id and the secret associated with that id
|
193
|
+
# in the credential store.
|
194
|
+
#
|
195
|
+
# Signing a requests adds an Authorization header to the request in the format:
|
196
|
+
#
|
197
|
+
# <service_id> <access_key_id>:<signature>
|
198
|
+
#
|
199
|
+
# where <signature> is the Base64 encoded HMAC-SHA1 of the CanonicalString and the secret.
|
200
|
+
#
|
201
|
+
def sign!(request, access_key_id)
|
202
|
+
secret = @credential_store[access_key_id]
|
203
|
+
raise ArgumentError, "No secret found for key id '#{access_key_id}'" if secret.nil?
|
204
|
+
request['Authorization'] = authorization(request, access_key_id, secret)
|
205
|
+
end
|
206
|
+
|
207
|
+
# Authenticates a request using HMAC
|
208
|
+
#
|
209
|
+
# Returns true if the request has an AuthHMAC Authorization header and
|
210
|
+
# the access id and HMAC match an id and HMAC produced for the secret
|
211
|
+
# in the credential store. Otherwise returns false.
|
212
|
+
#
|
213
|
+
def authenticated?(request)
|
214
|
+
rx = Regexp.new("#{@service_id} ([^:]+):(.+)$")
|
215
|
+
if md = rx.match(authorization_header(request))
|
216
|
+
access_key_id = md[1]
|
217
|
+
hmac = md[2]
|
218
|
+
secret = @credential_store[access_key_id]
|
219
|
+
!secret.nil? && hmac == signature(request, secret)
|
220
|
+
else
|
221
|
+
false
|
222
|
+
end
|
223
|
+
end
|
224
|
+
|
225
|
+
def signature(request, secret)
|
226
|
+
digest = OpenSSL::Digest::Digest.new('sha1')
|
227
|
+
Base64.encode64(OpenSSL::HMAC.digest(digest, secret, canonical_string(request))).strip
|
228
|
+
end
|
229
|
+
|
230
|
+
def canonical_string(request)
|
231
|
+
@signature_method.call(request)
|
232
|
+
end
|
233
|
+
|
234
|
+
def authorization_header(request)
|
235
|
+
find_header(%w(Authorization HTTP_AUTHORIZATION), headers(request))
|
236
|
+
end
|
237
|
+
|
238
|
+
def authorization(request, access_key_id, secret)
|
239
|
+
"#{@service_id} #{access_key_id}:#{signature(request, secret)}"
|
240
|
+
end
|
241
|
+
|
176
242
|
# Integration with Rails
|
177
243
|
#
|
178
244
|
class Rails # :nodoc:
|
@@ -185,21 +251,22 @@ class AuthHMAC
|
|
185
251
|
# * +failure_message+: The text to use when authentication fails.
|
186
252
|
# * +only+: A list off actions to protect.
|
187
253
|
# * +except+: A list of actions to not protect.
|
254
|
+
# * +hmac+: Options for HMAC creation. See AuthHMAC#initialize for options.
|
188
255
|
#
|
189
256
|
def with_auth_hmac(credentials, options = {})
|
190
257
|
unless credentials.nil?
|
191
258
|
self.credentials = credentials
|
192
|
-
self.authhmac = AuthHMAC.new(self.credentials)
|
193
259
|
self.authhmac_failure_message = (options.delete(:failure_message) or "HMAC Authentication failed")
|
260
|
+
self.authhmac = AuthHMAC.new(self.credentials, options.delete(:hmac))
|
194
261
|
before_filter(:hmac_login_required, options)
|
195
262
|
else
|
196
|
-
$stderr
|
263
|
+
$stderr.puts("with_auth_hmac called with nil credentials - authentication will be skipped")
|
197
264
|
end
|
198
265
|
end
|
199
266
|
end
|
200
267
|
|
201
268
|
module InstanceMethods # :nodoc:
|
202
|
-
def hmac_login_required
|
269
|
+
def hmac_login_required
|
203
270
|
unless hmac_authenticated?
|
204
271
|
response.headers['WWW-Authenticate'] = 'AuthHMAC'
|
205
272
|
render :text => self.class.authhmac_failure_message, :status => :unauthorized
|
@@ -243,6 +310,7 @@ class AuthHMAC
|
|
243
310
|
base.class_inheritable_accessor :hmac_access_id
|
244
311
|
base.class_inheritable_accessor :hmac_secret
|
245
312
|
base.class_inheritable_accessor :use_hmac
|
313
|
+
base.class_inheritable_accessor :hmac_options
|
246
314
|
end
|
247
315
|
|
248
316
|
module ClassMethods
|
@@ -268,7 +336,7 @@ class AuthHMAC
|
|
268
336
|
# patch of the internals of ActiveResource it might not work with past or
|
269
337
|
# future versions.
|
270
338
|
#
|
271
|
-
def with_auth_hmac(access_id, secret = nil)
|
339
|
+
def with_auth_hmac(access_id, secret = nil, options = nil)
|
272
340
|
if access_id.is_a?(Hash)
|
273
341
|
self.hmac_access_id = access_id.keys.first
|
274
342
|
self.hmac_secret = access_id[self.hmac_access_id]
|
@@ -277,6 +345,7 @@ class AuthHMAC
|
|
277
345
|
self.hmac_secret = secret
|
278
346
|
end
|
279
347
|
self.use_hmac = true
|
348
|
+
self.hmac_options = options
|
280
349
|
|
281
350
|
class << self
|
282
351
|
alias_method_chain :connection, :hmac
|
@@ -288,6 +357,7 @@ class AuthHMAC
|
|
288
357
|
c.hmac_access_id = self.hmac_access_id
|
289
358
|
c.hmac_secret = self.hmac_secret
|
290
359
|
c.use_hmac = self.use_hmac
|
360
|
+
c.hmac_options = self.hmac_options
|
291
361
|
c
|
292
362
|
end
|
293
363
|
end
|
@@ -300,7 +370,7 @@ class AuthHMAC
|
|
300
370
|
def self.included(base)
|
301
371
|
base.send :alias_method_chain, :request, :hmac
|
302
372
|
base.class_eval do
|
303
|
-
attr_accessor :hmac_secret, :hmac_access_id, :use_hmac
|
373
|
+
attr_accessor :hmac_secret, :hmac_access_id, :use_hmac, :hmac_options
|
304
374
|
end
|
305
375
|
end
|
306
376
|
|
@@ -308,7 +378,7 @@ class AuthHMAC
|
|
308
378
|
if use_hmac && hmac_access_id && hmac_secret
|
309
379
|
arguments.last['Date'] = Time.now.httpdate if arguments.last['Date'].nil?
|
310
380
|
temp = "Net::HTTP::#{method.to_s.capitalize}".constantize.new(path, arguments.last)
|
311
|
-
AuthHMAC.sign!(temp, hmac_access_id, hmac_secret)
|
381
|
+
AuthHMAC.sign!(temp, hmac_access_id, hmac_secret, hmac_options)
|
312
382
|
arguments.last['Authorization'] = temp['Authorization']
|
313
383
|
end
|
314
384
|
|
data/lib/auth-hmac/version.rb
CHANGED
data/spec/auth-hmac_spec.rb
CHANGED
@@ -10,73 +10,141 @@ require 'action_controller/test_process'
|
|
10
10
|
require 'active_resource'
|
11
11
|
require 'active_resource/http_mock'
|
12
12
|
|
13
|
+
# Class for doing a custom signature
|
14
|
+
class CustomSignature < String
|
15
|
+
def initialize(request)
|
16
|
+
self << "Custom signature string: #{request.method}"
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def signature(value, secret)
|
21
|
+
digest = OpenSSL::Digest::Digest.new('sha1')
|
22
|
+
Base64.encode64(OpenSSL::HMAC.digest(digest, secret, value)).strip
|
23
|
+
end
|
24
|
+
|
13
25
|
describe AuthHMAC do
|
26
|
+
before(:each) do
|
27
|
+
@request = Net::HTTP::Put.new("/path/to/put?foo=bar&bar=foo",
|
28
|
+
'content-type' => 'text/plain',
|
29
|
+
'content-md5' => 'blahblah',
|
30
|
+
'date' => "Thu, 10 Jul 2008 03:29:56 GMT")
|
31
|
+
end
|
32
|
+
|
33
|
+
describe ".canonical_string" do
|
34
|
+
it "should generate a canonical string using default method" do
|
35
|
+
AuthHMAC.canonical_string(@request).should == "PUT\ntext/plain\nblahblah\nThu, 10 Jul 2008 03:29:56 GMT\n/path/to/put"
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
describe ".signature" do
|
40
|
+
it "should generate a valid signature string for a secret" do
|
41
|
+
AuthHMAC.signature(@request, 'secret').should == "71wAJM4IIu/3o6lcqx/tw7XnAJs="
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
14
45
|
describe ".sign!" do
|
46
|
+
before(:each) do
|
47
|
+
@request = Net::HTTP::Put.new("/path/to/put?foo=bar&bar=foo",
|
48
|
+
'content-type' => 'text/plain',
|
49
|
+
'content-md5' => 'blahblah',
|
50
|
+
'date' => "Thu, 10 Jul 2008 03:29:56 GMT")
|
51
|
+
end
|
52
|
+
|
15
53
|
it "should sign using the key passed in as a parameter" do
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
request
|
54
|
+
AuthHMAC.sign!(@request, "my-key-id", "secret")
|
55
|
+
@request['Authorization'].should == "AuthHMAC my-key-id:71wAJM4IIu/3o6lcqx/tw7XnAJs="
|
56
|
+
end
|
57
|
+
|
58
|
+
it "should sign using custom service id" do
|
59
|
+
AuthHMAC.sign!(@request, "my-key-id", "secret", { :service_id => 'MyService' })
|
60
|
+
@request['Authorization'].should == "MyService my-key-id:71wAJM4IIu/3o6lcqx/tw7XnAJs="
|
61
|
+
end
|
62
|
+
|
63
|
+
it "should sign using custom signature method" do
|
64
|
+
options = {
|
65
|
+
:service_id => 'MyService',
|
66
|
+
:signature => CustomSignature
|
67
|
+
}
|
68
|
+
AuthHMAC.sign!(@request, "my-key-id", "secret", options)
|
69
|
+
@request['Authorization'].should == "MyService my-key-id:/L4N1v1BZSHfAYkQjsvZn696D9c="
|
22
70
|
end
|
23
71
|
end
|
24
72
|
|
25
73
|
describe "#sign!" do
|
26
74
|
before(:each) do
|
27
|
-
@
|
75
|
+
@get_request = Net::HTTP::Get.new("/")
|
76
|
+
@put_request = Net::HTTP::Put.new("/path/to/put?foo=bar&bar=foo",
|
77
|
+
'content-type' => 'text/plain',
|
78
|
+
'content-md5' => 'blahblah',
|
79
|
+
'date' => "Thu, 10 Jul 2008 03:29:56 GMT")
|
80
|
+
@store = mock('store')
|
28
81
|
@store.stub!(:[]).and_return("")
|
29
82
|
@authhmac = AuthHMAC.new(@store)
|
30
83
|
end
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
84
|
+
|
85
|
+
describe "default AuthHMAC with CanonicalString signature" do
|
86
|
+
it "should add an Authorization header" do
|
87
|
+
@authhmac.sign!(@get_request, 'key-id')
|
88
|
+
@get_request.key?("Authorization").should be_true
|
89
|
+
end
|
90
|
+
|
91
|
+
it "should fetch the secret from the store" do
|
92
|
+
@store.should_receive(:[]).with('key-id').and_return('secret')
|
93
|
+
@authhmac.sign!(@get_request, 'key-id')
|
94
|
+
end
|
95
|
+
|
96
|
+
it "should prefix the Authorization Header with AuthHMAC" do
|
97
|
+
@authhmac.sign!(@get_request, 'key-id')
|
98
|
+
@get_request['Authorization'].should match(/^AuthHMAC /)
|
99
|
+
end
|
100
|
+
|
101
|
+
it "should include the key id as the first part of the Authorization header value" do
|
102
|
+
@authhmac.sign!(@get_request, 'key-id')
|
103
|
+
@get_request['Authorization'].should match(/^AuthHMAC key-id:/)
|
104
|
+
end
|
105
|
+
|
106
|
+
it "should include the base64 encoded HMAC signature as the last part of the header value" do
|
107
|
+
@authhmac.sign!(@get_request, 'key-id')
|
108
|
+
@get_request['Authorization'].should match(/:[A-Za-z0-9+\/]{26,28}[=]{0,2}$/)
|
109
|
+
end
|
110
|
+
|
111
|
+
it "should create a complete signature" do
|
112
|
+
@store.should_receive(:[]).with('my-key-id').and_return('secret')
|
113
|
+
@authhmac.sign!(@put_request, "my-key-id")
|
114
|
+
@put_request['Authorization'].should == "AuthHMAC my-key-id:71wAJM4IIu/3o6lcqx/tw7XnAJs="
|
115
|
+
end
|
60
116
|
end
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
117
|
+
|
118
|
+
describe "custom signatures" do
|
119
|
+
before(:each) do
|
120
|
+
@options = {
|
121
|
+
:service_id => 'MyService',
|
122
|
+
:signature => CustomSignature
|
123
|
+
}
|
124
|
+
@authhmac = AuthHMAC.new(@store, @options)
|
125
|
+
end
|
126
|
+
|
127
|
+
it "should prefix the Authorization header with custom service id" do
|
128
|
+
@authhmac.sign!(@get_request, 'key-id')
|
129
|
+
@get_request['Authorization'].should match(/^MyService /)
|
130
|
+
end
|
131
|
+
|
132
|
+
it "should create a complete signature using options" do
|
133
|
+
@store.should_receive(:[]).with('my-key-id').and_return('secret')
|
134
|
+
@authhmac.sign!(@put_request, "my-key-id")
|
135
|
+
@put_request['Authorization'].should == "MyService my-key-id:/L4N1v1BZSHfAYkQjsvZn696D9c="
|
136
|
+
end
|
70
137
|
end
|
71
138
|
end
|
72
139
|
|
73
140
|
describe "authenticated?" do
|
74
141
|
before(:each) do
|
75
|
-
@
|
142
|
+
@credentials = YAML.load(File.read(File.join(File.dirname(__FILE__), 'fixtures', 'credentials.yml')))
|
143
|
+
@authhmac = AuthHMAC.new(@credentials)
|
76
144
|
@request = Net::HTTP::Get.new("/path/to/get?foo=bar&bar=foo", 'date' => "Thu, 10 Jul 2008 03:29:56 GMT")
|
77
145
|
end
|
78
146
|
|
79
|
-
it "should return false when there is no
|
147
|
+
it "should return false when there is no Authorization Header" do
|
80
148
|
@authhmac.authenticated?(@request).should be_false
|
81
149
|
end
|
82
150
|
|
@@ -110,6 +178,31 @@ describe AuthHMAC do
|
|
110
178
|
@authhmac.sign!(@request, 'access key 1')
|
111
179
|
@authhmac.authenticated?(@request).should be_true
|
112
180
|
end
|
181
|
+
|
182
|
+
describe "custom signatures" do
|
183
|
+
before(:each) do
|
184
|
+
@options = {
|
185
|
+
:service_id => 'MyService',
|
186
|
+
:signature => CustomSignature
|
187
|
+
}
|
188
|
+
end
|
189
|
+
|
190
|
+
it "should return false for invalid service id" do
|
191
|
+
@authhmac.sign!(@request, 'access key 1')
|
192
|
+
AuthHMAC.new(@credentials, @options.except(:signature)).authenticated?(@request).should be_false
|
193
|
+
end
|
194
|
+
|
195
|
+
it "should return false for request using default CanonicalString signature" do
|
196
|
+
@authhmac.sign!(@request, 'access key 1')
|
197
|
+
AuthHMAC.new(@credentials, @options.except(:service_id)).authenticated?(@request).should be_false
|
198
|
+
end
|
199
|
+
|
200
|
+
it "should return true when valid" do
|
201
|
+
@authhmac = AuthHMAC.new(@credentials, @options)
|
202
|
+
@authhmac.sign!(@request, 'access key 1')
|
203
|
+
@authhmac.authenticated?(@request).should be_true
|
204
|
+
end
|
205
|
+
end
|
113
206
|
end
|
114
207
|
|
115
208
|
describe "#sign! with YAML credentials" do
|
@@ -247,69 +340,129 @@ describe AuthHMAC do
|
|
247
340
|
hmac_authenticated?
|
248
341
|
end
|
249
342
|
end
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
TestController.new.process(request, ActionController::TestResponse.new).code.should == "200"
|
267
|
-
end
|
268
|
-
|
269
|
-
it "should reject a request with no hmac" do
|
270
|
-
request = ActionController::TestRequest.new
|
271
|
-
request.action = 'index'
|
272
|
-
TestController.new.process(request, ActionController::TestResponse.new).code.should == "401"
|
273
|
-
end
|
274
|
-
|
275
|
-
it "should reject a request with the wrong hmac" do
|
276
|
-
request = ActionController::TestRequest.new
|
277
|
-
request.action = 'index'
|
278
|
-
request.env['Authorization'] = "AuthHMAC bogus:bogus"
|
279
|
-
TestController.new.process(request, ActionController::TestResponse.new).code.should == "401"
|
280
|
-
end
|
281
|
-
|
282
|
-
it "should include a WWW-Authenticate header with the schema AuthHMAC" do
|
283
|
-
request = ActionController::TestRequest.new
|
284
|
-
request.action = 'index'
|
285
|
-
request.env['Authorization'] = "AuthHMAC bogus:bogus"
|
286
|
-
TestController.new.process(request, ActionController::TestResponse.new).headers['WWW-Authenticate'].should == "AuthHMAC"
|
343
|
+
|
344
|
+
class CustomTestController < ActionController::Base
|
345
|
+
with_auth_hmac YAML.load(File.read(File.join(File.dirname(__FILE__), 'fixtures', 'credentials.yml'))),
|
346
|
+
:failure_message => "Stay away!",
|
347
|
+
:except => :public,
|
348
|
+
:hmac => { :service_id => 'MyService', :signature => CustomSignature }
|
349
|
+
|
350
|
+
def index
|
351
|
+
render :nothing => true, :status => :ok
|
352
|
+
end
|
353
|
+
|
354
|
+
def public
|
355
|
+
render :nothing => true, :status => :ok
|
356
|
+
end
|
357
|
+
|
358
|
+
def rescue_action(e) raise(e) end
|
287
359
|
end
|
288
360
|
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
361
|
+
describe NilCredentialsController do
|
362
|
+
it "should not raise an error when credentials are nil" do
|
363
|
+
request = ActionController::TestRequest.new
|
364
|
+
request.action = 'index'
|
365
|
+
request.path = "/index"
|
366
|
+
lambda do
|
367
|
+
NilCredentialsController.new.process(request, ActionController::TestResponse.new).code.should == "200"
|
368
|
+
end.should_not raise_error
|
369
|
+
end
|
294
370
|
end
|
295
|
-
|
296
|
-
|
297
|
-
request
|
298
|
-
|
299
|
-
|
300
|
-
|
371
|
+
|
372
|
+
describe TestController do
|
373
|
+
it "should allow a request with the proper hmac" do
|
374
|
+
request = ActionController::TestRequest.new
|
375
|
+
request.env['Authorization'] = "AuthHMAC access key 1:6BVEVfAyIDoI3K+WallRMnDxROQ="
|
376
|
+
request.env['DATE'] = "Thu, 10 Jul 2008 03:29:56 GMT"
|
377
|
+
request.action = 'index'
|
378
|
+
request.path = "/index"
|
379
|
+
TestController.new.process(request, ActionController::TestResponse.new).code.should == "200"
|
380
|
+
end
|
381
|
+
|
382
|
+
it "should reject a request with no hmac" do
|
383
|
+
request = ActionController::TestRequest.new
|
384
|
+
request.action = 'index'
|
385
|
+
TestController.new.process(request, ActionController::TestResponse.new).code.should == "401"
|
386
|
+
end
|
387
|
+
|
388
|
+
it "should reject a request with the wrong hmac" do
|
389
|
+
request = ActionController::TestRequest.new
|
390
|
+
request.action = 'index'
|
391
|
+
request.env['Authorization'] = "AuthHMAC bogus:bogus"
|
392
|
+
TestController.new.process(request, ActionController::TestResponse.new).code.should == "401"
|
393
|
+
end
|
394
|
+
|
395
|
+
it "should include a WWW-Authenticate header with the schema AuthHMAC" do
|
396
|
+
request = ActionController::TestRequest.new
|
397
|
+
request.action = 'index'
|
398
|
+
request.env['Authorization'] = "AuthHMAC bogus:bogus"
|
399
|
+
TestController.new.process(request, ActionController::TestResponse.new).headers['WWW-Authenticate'].should == "AuthHMAC"
|
400
|
+
end
|
401
|
+
|
402
|
+
it "should include a default error message" do
|
403
|
+
request = ActionController::TestRequest.new
|
404
|
+
request.action = 'index'
|
405
|
+
request.env['Authorization'] = "AuthHMAC bogus:bogus"
|
406
|
+
TestController.new.process(request, ActionController::TestResponse.new).body.should == "HMAC Authentication failed"
|
407
|
+
end
|
408
|
+
|
409
|
+
it "should allow anything to access the public action (using only)" do
|
410
|
+
request = ActionController::TestRequest.new
|
411
|
+
request.action = 'public'
|
412
|
+
TestController.new.process(request, ActionController::TestResponse.new).code.should == "200"
|
413
|
+
end
|
301
414
|
end
|
302
|
-
|
303
|
-
|
304
|
-
request
|
305
|
-
|
306
|
-
|
415
|
+
|
416
|
+
describe MessageTestController do
|
417
|
+
it "should reject a request with a given message" do
|
418
|
+
request = ActionController::TestRequest.new
|
419
|
+
request.action = 'index'
|
420
|
+
request.env['Authorization'] = "AuthHMAC bogus:bogus"
|
421
|
+
MessageTestController.new.process(request, ActionController::TestResponse.new).body.should == "Stay away!"
|
422
|
+
end
|
423
|
+
|
424
|
+
it "should allow anything to access the public action (using except)" do
|
425
|
+
request = ActionController::TestRequest.new
|
426
|
+
request.action = 'public'
|
427
|
+
MessageTestController.new.process(request, ActionController::TestResponse.new).code.should == "200"
|
428
|
+
end
|
307
429
|
end
|
308
|
-
|
309
|
-
|
310
|
-
request
|
311
|
-
|
312
|
-
|
430
|
+
|
431
|
+
describe CustomTestController do
|
432
|
+
it "should allow a request with the proper hmac" do
|
433
|
+
request = ActionController::TestRequest.new
|
434
|
+
request.env['Authorization'] = "MyService access key 1:J2W4dOrv/sGsL0C5adnZYiQ3d70="
|
435
|
+
request.env['DATE'] = "Thu, 10 Jul 2008 03:29:56 GMT"
|
436
|
+
request.action = 'index'
|
437
|
+
request.path = "/index"
|
438
|
+
CustomTestController.new.process(request, ActionController::TestResponse.new).code.should == "200"
|
439
|
+
end
|
440
|
+
|
441
|
+
it "should reject a request with no hmac" do
|
442
|
+
request = ActionController::TestRequest.new
|
443
|
+
request.action = 'index'
|
444
|
+
CustomTestController.new.process(request, ActionController::TestResponse.new).code.should == "401"
|
445
|
+
end
|
446
|
+
|
447
|
+
it "should reject a request with the wrong hmac" do
|
448
|
+
request = ActionController::TestRequest.new
|
449
|
+
request.action = 'index'
|
450
|
+
request.env['Authorization'] = "AuthHMAC bogus:bogus"
|
451
|
+
CustomTestController.new.process(request, ActionController::TestResponse.new).code.should == "401"
|
452
|
+
end
|
453
|
+
|
454
|
+
it "should reject a request with a given message" do
|
455
|
+
request = ActionController::TestRequest.new
|
456
|
+
request.action = 'index'
|
457
|
+
request.env['Authorization'] = "AuthHMAC bogus:bogus"
|
458
|
+
CustomTestController.new.process(request, ActionController::TestResponse.new).body.should == "Stay away!"
|
459
|
+
end
|
460
|
+
|
461
|
+
it "should allow anything to access the public action (using except)" do
|
462
|
+
request = ActionController::TestRequest.new
|
463
|
+
request.action = 'public'
|
464
|
+
CustomTestController.new.process(request, ActionController::TestResponse.new).code.should == "200"
|
465
|
+
end
|
313
466
|
end
|
314
467
|
end
|
315
468
|
|
@@ -318,17 +471,44 @@ describe AuthHMAC do
|
|
318
471
|
with_auth_hmac("access_id", "secret")
|
319
472
|
self.site = "http://localhost/"
|
320
473
|
end
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
474
|
+
|
475
|
+
class CustomTestResource < ActiveResource::Base
|
476
|
+
with_auth_hmac("access_id", "secret", { :service_id => 'MyService', :signature => CustomSignature })
|
477
|
+
self.site = "http://localhost/"
|
478
|
+
end
|
479
|
+
|
480
|
+
describe TestResource do
|
481
|
+
it "should send requests using HMAC authentication" do
|
482
|
+
now = Time.parse("Thu, 10 Jul 2008 03:29:56 GMT")
|
483
|
+
Time.should_receive(:now).at_least(1).and_return(now)
|
484
|
+
ActiveResource::HttpMock.respond_to do |mock|
|
485
|
+
mock.get "/test_resources/1.xml",
|
486
|
+
{
|
487
|
+
'Authorization' => 'AuthHMAC access_id:44dvKATf4xanDtypqEA0EFYvOgI=',
|
488
|
+
'Accept' => 'application/xml',
|
489
|
+
'Date' => "Thu, 10 Jul 2008 03:29:56 GMT"
|
490
|
+
},
|
491
|
+
{ :id => "1" }.to_xml(:root => 'test_resource')
|
492
|
+
end
|
493
|
+
TestResource.find(1)
|
494
|
+
end
|
495
|
+
end
|
496
|
+
|
497
|
+
describe CustomTestResource do
|
498
|
+
it "should send requests using HMAC authentication" do
|
499
|
+
now = Time.parse("Thu, 10 Jul 2008 03:29:56 GMT")
|
500
|
+
Time.should_receive(:now).at_least(1).and_return(now)
|
501
|
+
ActiveResource::HttpMock.respond_to do |mock|
|
502
|
+
mock.get "/custom_test_resources/1.xml",
|
503
|
+
{
|
504
|
+
'Authorization' => 'MyService access_id:ZwCBL2rWLOMnwRrdF7wWEdJn7yA=',
|
505
|
+
'Accept' => 'application/xml',
|
506
|
+
'Date' => "Thu, 10 Jul 2008 03:29:56 GMT"
|
507
|
+
},
|
508
|
+
{ :id => "1" }.to_xml(:root => 'custom_test_resource')
|
509
|
+
end
|
510
|
+
CustomTestResource.find(1)
|
329
511
|
end
|
330
|
-
|
331
|
-
TestResource.find(1)
|
332
512
|
end
|
333
513
|
end
|
334
514
|
end
|
metadata
CHANGED
@@ -1,15 +1,16 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: auth-hmac
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0
|
4
|
+
version: 1.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Sean Geoghegan
|
8
|
+
- ascarter
|
8
9
|
autorequire:
|
9
10
|
bindir: bin
|
10
11
|
cert_chain: []
|
11
12
|
|
12
|
-
date:
|
13
|
+
date: 2009-02-26 00:00:00 +10:30
|
13
14
|
default_executable:
|
14
15
|
dependencies:
|
15
16
|
- !ruby/object:Gem::Dependency
|
@@ -20,11 +21,10 @@ dependencies:
|
|
20
21
|
requirements:
|
21
22
|
- - ">="
|
22
23
|
- !ruby/object:Gem::Version
|
23
|
-
version: 1.
|
24
|
+
version: 1.8.2
|
24
25
|
version:
|
25
26
|
description: A gem providing HMAC based authentication for HTTP
|
26
|
-
email:
|
27
|
-
- seangeo@gmail.com
|
27
|
+
email: seangeo@gmail.com
|
28
28
|
executables: []
|
29
29
|
|
30
30
|
extensions: []
|
@@ -87,7 +87,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
87
87
|
requirements: []
|
88
88
|
|
89
89
|
rubyforge_project: auth-hmac
|
90
|
-
rubygems_version: 1.
|
90
|
+
rubygems_version: 1.3.1
|
91
91
|
signing_key:
|
92
92
|
specification_version: 2
|
93
93
|
summary: A gem providing HMAC based authentication for HTTP
|