auth-hmac 1.0.1 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/History.txt CHANGED
@@ -1,3 +1,7 @@
1
+ == 1.1.0 2009-02-26
2
+
3
+ * Added support for custom request signatures and service ids. (ascarter)
4
+
1
5
  == 1.0.1 2008-08-22
2
6
 
3
7
  * Fixed nil pointer exception when with_auth called with nil creds.
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.developer(AUTHOR, EMAIL)
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.getutc.httpdate)
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 << "with_auth_hmac called with nil credentials - authentication will be skipped\n"
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
 
@@ -1,8 +1,8 @@
1
1
  class AuthHMAC
2
2
  module VERSION #:nodoc:
3
3
  MAJOR = 1
4
- MINOR = 0
5
- TINY = 1
4
+ MINOR = 1
5
+ TINY = 0
6
6
 
7
7
  STRING = [MAJOR, MINOR, TINY].join('.')
8
8
  end
@@ -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
- request = Net::HTTP::Put.new("/path/to/put?foo=bar&bar=foo",
17
- 'content-type' => 'text/plain',
18
- 'content-md5' => 'blahblah',
19
- 'date' => "Thu, 10 Jul 2008 03:29:56 GMT")
20
- AuthHMAC.sign!(request, "my-key-id", "secret")
21
- request['Authorization'].should == "AuthHMAC my-key-id:71wAJM4IIu/3o6lcqx/tw7XnAJs="
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
- @store = mock('store')
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
- it "should add an Authorization header" do
33
- request = Net::HTTP::Get.new("/")
34
- @authhmac.sign!(request, 'key-id')
35
- request.key?("Authorization").should be_true
36
- end
37
-
38
- it "should fetch the secret from the store" do
39
- request = Net::HTTP::Get.new("/")
40
- @store.should_receive(:[]).with('key-id').and_return('secret')
41
- @authhmac.sign!(request, 'key-id')
42
- end
43
-
44
- it "should prefix the Authorization Header with AuthHMAC" do
45
- request = Net::HTTP::Get.new("/")
46
- @authhmac.sign!(request, 'key-id')
47
- request['Authorization'].should match(/^AuthHMAC /)
48
- end
49
-
50
- it "should include the key id as the first part of the Authorization header value" do
51
- request = Net::HTTP::Get.new("/")
52
- @authhmac.sign!(request, 'key-id')
53
- request['Authorization'].should match(/^AuthHMAC key-id:/)
54
- end
55
-
56
- it "should include the base64 encoded HMAC signature as the last part of the header value" do
57
- request = Net::HTTP::Get.new("/path")
58
- @authhmac.sign!(request, 'key-id')
59
- request['Authorization'].should match(/:[A-Za-z0-9+\/]{26,28}[=]{0,2}$/)
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
- it "should create a complete signature" do
63
- @store.should_receive(:[]).with('my-key-id').and_return('secret')
64
- request = Net::HTTP::Put.new("/path/to/put?foo=bar&bar=foo",
65
- 'content-type' => 'text/plain',
66
- 'content-md5' => 'blahblah',
67
- 'date' => "Thu, 10 Jul 2008 03:29:56 GMT")
68
- @authhmac.sign!(request, "my-key-id")
69
- request['Authorization'].should == "AuthHMAC my-key-id:71wAJM4IIu/3o6lcqx/tw7XnAJs="
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
- @authhmac = AuthHMAC.new(YAML.load(File.read(File.join(File.dirname(__FILE__), 'fixtures', 'credentials.yml'))))
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 Authoization Header" do
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
- it "should not raise an error when credentials are nil" do
252
- request = ActionController::TestRequest.new
253
- request.action = 'index'
254
- request.path = "/index"
255
- lambda do
256
- NilCredentialsController.new.process(request, ActionController::TestResponse.new).code.should == "200"
257
- end.should_not raise_error
258
- end
259
-
260
- it "should allow a request with the proper hmac" do
261
- request = ActionController::TestRequest.new
262
- request.env['Authorization'] = "AuthHMAC access key 1:6BVEVfAyIDoI3K+WallRMnDxROQ="
263
- request.env['DATE'] = "Thu, 10 Jul 2008 03:29:56 GMT"
264
- request.action = 'index'
265
- request.path = "/index"
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
- it "should include a default error message" do
290
- request = ActionController::TestRequest.new
291
- request.action = 'index'
292
- request.env['Authorization'] = "AuthHMAC bogus:bogus"
293
- TestController.new.process(request, ActionController::TestResponse.new).body.should == "HMAC Authentication failed"
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
- it "should reject a request with a given message" do
297
- request = ActionController::TestRequest.new
298
- request.action = 'index'
299
- request.env['Authorization'] = "AuthHMAC bogus:bogus"
300
- MessageTestController.new.process(request, ActionController::TestResponse.new).body.should == "Stay away!"
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
- it "should allow anything to access the public action (using only)" do
304
- request = ActionController::TestRequest.new
305
- request.action = 'public'
306
- TestController.new.process(request, ActionController::TestResponse.new).code.should == "200"
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
- it "should allow anything to access the public action (using except)" do
310
- request = ActionController::TestRequest.new
311
- request.action = 'public'
312
- MessageTestController.new.process(request, ActionController::TestResponse.new).code.should == "200"
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
- it "should send requests using HMAC authentication" do
323
- now = Time.parse("Thu, 10 Jul 2008 03:29:56 GMT")
324
- Time.should_receive(:now).at_least(1).and_return(now)
325
- ActiveResource::HttpMock.respond_to do |mock|
326
- mock.get "/test_resources/1.xml",
327
- {'Authorization' => 'AuthHMAC access_id:n8UlshMa8ve66U36XD3ZCbAIctg=', 'Content-Type' => 'application/xml', 'Date' => "Thu, 10 Jul 2008 03:29:56 GMT" },
328
- {:id => "1"}.to_xml(:root => 'test_resource')
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.1
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: 2008-08-22 00:00:00 +09:30
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.7.0
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.2.0
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