warden-hmac-authentication 0.4.0 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -117,6 +117,13 @@ name is also used to construct the default values for various header names. The
117
117
 
118
118
  No authentication attempt is made if the scheme name in the `Authorization` header does not match the configured scheme name.
119
119
 
120
+ ## Authentication Header Format
121
+
122
+ The format of the Authentication Header can be controlled using the `:auth_header_format` directive. The given format string will be interpolated
123
+ with all given options and the signature. The default value is `%{auth_scheme} %{signature}` which will result in an auth header with a format such as `HMAC 539263f4f83878a4917d2f9c1521320c28b926a9`.
124
+
125
+ The `:auth_header_format` directive has a companion directive, `:auth_header_parse` which can be a proc or a regular expression. Any given regular expression will be evaluated against the string and any named capture pattern will be added to the parameters for the request. The regular expression must at least contain a pattern named `scheme` and pattern named `signature`. The default value is `/(?<scheme>\w+) (?<signature>\w+)/`
126
+
120
127
  ## Optional nonce
121
128
 
122
129
  An optional nonce can be passed in the request to increase security. The nonce is not limited to digits and can be any string. It's
@@ -263,16 +270,89 @@ The canonical representation is:
263
270
  nonce:foLiequei7oosaiWun5aoy8oo\n
264
271
  /example/resource.html?order=id,asc&page=3
265
272
 
266
-
267
273
  ## HMACSigner usage
268
274
 
269
275
  The HMACSigner class can be used to validate and generate signatures for a given request. Most methods accept a hash as an intermediate
270
276
  representation of the request but some methods accept and operate on full urls.
271
277
 
272
- h = HMACSigner.new
278
+ h = HMAC::Signer.new
273
279
  h.sign_url('http://example.org/example.html', 'secret')
274
280
  h.validate_url_signature('http://example.org/example.html?auth[signature]=foo', 'secret')
275
-
281
+
282
+ ## Using multiple authentication secrets
283
+
284
+ Most applications will need to authenticate users using a combination of a user-identifier and and associated secret.
285
+
286
+
287
+ ### Header-Based Authentication
288
+
289
+ The format of the Autorization header can be controlled using the `:auth_header_format` option, the regular expression used to parse can be
290
+ set using `:auth_header_parse`. Combining these two options with a proc that retrieves the signing key from a storage authentication with multiple
291
+ secrets allows us to implement multiple signing keys:
292
+
293
+
294
+ use Warden::Manager do |manager|
295
+ manager.failure_app = -> env { [401, {"Content-Length" => "0"}, [""]] }
296
+ # other scopes
297
+ manager.scope_defaults :hmac, :strategies => [:hmac_query, :hmac_header],
298
+ :store => false,
299
+ :hmac => {
300
+ :secret => Proc.new {|strategy|
301
+ keys = {
302
+ "KEY1" => 'secrit',
303
+ "KEY2" => "foo"
304
+ }
305
+
306
+ access_key_id = strategy.parsed_auth_header["access_key_id"]
307
+ keys[access_key_id]
308
+ },
309
+ :auth_header_format => '%{scheme} %{access_key_id} %{signature}',
310
+ :auth_header_parse => /(?<scheme>\w+) (?<access_key_id>\w+) (?<signature>\w+)/
311
+ }
312
+ end
313
+
314
+ This combination of settings uses a slightly different Format for the authorization header and transports the secret keys ID in the header of the form `HMAC KEY2 a59456da1f61f86e96622e283780f58b7428c892`
315
+
316
+ Another option would be transporting the access key id in a separate header.
317
+
318
+ ### Query-Based Authentication
319
+
320
+ The same result can be achieved using query-based auth by injecting extra authentication parameters and retrieving the access key in the proc. Given a url such as `http://example.org/example.html?auth[signature]=foo&auth[access_key_id]=KEY2` the following configuration will validate the signature with the secret `foo`:
321
+
322
+ use Warden::Manager do |manager|
323
+ manager.failure_app = -> env { [401, {"Content-Length" => "0"}, [""]] }
324
+ # other scopes
325
+ manager.scope_defaults :hmac, :strategies => [:hmac_query, :hmac_header],
326
+ :store => false,
327
+ :hmac => {
328
+ :secret => Proc.new {|strategy|
329
+ keys = {
330
+ "KEY1" => 'secrit',
331
+ "KEY2" => "foo"
332
+ }
333
+
334
+ access_key_id = strategy.params["auth"]["access_key_id"]
335
+ keys[access_key_id]
336
+ }
337
+ }
338
+ end
339
+
340
+ To simplify the generation of such urls, the `HMAC::Signer` accepts an `:extra_auth_params` option for query based authentication. Parameters passed via this option will be injected in the auth hash. Parameters injected in the auth hash via this option will not be part of the signature, so only parameters that control the generation of the signature should be placed here.
341
+
342
+
343
+ h.sign_url('http://example.org/example.html', 'foo', {:extra_auth_params => {"access_key_id" => "KEY2"}})
344
+
345
+
346
+ ## Faraday Middleware
347
+
348
+ The library includes a faraday middleware that can be used to sign requests made with the faraday http lib. The middleware accepts the same list of options as the HMAC::Signer class.
349
+
350
+ Faraday.new(:url => "http://example.com") do |builder|
351
+ builder.use Faraday::Request::Hmac, secret, {:extra_auth_params => {"access_key_id" => "KEY2"}}
352
+ builder.response :raise_error
353
+ builder.adapter :net_http
354
+ end
355
+
276
356
  ## Licence
277
357
 
278
358
  Copyright (c) 2011 Florian Gilcher <florian.gilcher@asquera.de>, Felix Gilcher <felix.gilcher@asquera.de>
@@ -1,54 +1,16 @@
1
1
  #!/usr/bin/env ruby
2
+ #
3
+ # This file was generated by Bundler.
4
+ #
5
+ # The application 'warden-hmac-authentication' is installed as part of a gem, and
6
+ # this file is here to facilitate running it.
7
+ #
2
8
 
3
- require 'trollop'
4
- require 'hmac/signer'
9
+ require 'pathname'
10
+ ENV['BUNDLE_GEMFILE'] ||= File.expand_path("../../Gemfile",
11
+ Pathname.new(__FILE__).realpath)
5
12
 
6
- opts = Trollop::options do
7
-
8
- version "warden-hmac-sign 0.3.0 (c) 2011 Felix Gilcher, Florian Gilcher"
9
- banner <<-EOS
10
- warden-hmac-authentication is used to create and validate signed urls for
11
- usage with the HMAC authentication scheme used by
12
- https://github.com/Asquera/warden-hmac-authentication
13
+ require 'rubygems'
14
+ require 'bundler/setup'
13
15
 
14
- Usage:
15
- warden-hmac-authentication [options] <command> url
16
-
17
- where command is one of
18
-
19
- sign: signs the given url
20
- validate: validates the given url
21
-
22
- and where [options] are:
23
-
24
- EOS
25
-
26
- opt :algorithm, "The hashing algorithm to use for the HMAC", :type => :string, :default => "sha1"
27
- opt :secret, "The shared secret for the HMAC", :type => :string, :required => true
28
- opt :"auth-param", "The name for the auth param in the url", :default => "auth"
29
- opt :"date", "The date to use for the signature (defaults to now)"
30
- end
31
-
32
- cmd = ARGV.shift
33
- Trollop::die "You must give a command" if cmd.nil?
34
- Trollop::die "You command must be one of [sign, validate]" unless ["sign", "validate"].include? cmd
35
- Trollop::die "You must provide a URL" if ARGV.empty?
36
- url = ARGV.shift
37
-
38
- secret = opts.delete(:secret)
39
- algorithm = opts.delete(:algorithm)
40
-
41
- signer = HMAC::Signer.new(algorithm)
42
-
43
- if "sign" == cmd
44
- puts signer.sign_url(url, secret, opts)
45
- else
46
- success = signer.validate_url_signature(url, secret, opts)
47
- if success
48
- puts "URL #{url} is valid"
49
- exit 0
50
- else
51
- puts "URL #{url} does not contain a valid signature"
52
- exit 1
53
- end
54
- end
16
+ load Gem.bin_path('warden-hmac-authentication', 'warden-hmac-authentication')
@@ -0,0 +1,47 @@
1
+ require 'faraday'
2
+ require 'hmac/signer'
3
+
4
+ module Faraday
5
+ class Request::Hmac < Faraday::Middleware
6
+
7
+ # create a new Hmac middleware instance
8
+ #
9
+ # @param [Object] app The url of the request
10
+ # @param [String] secret The shared secret for the signature
11
+ # @param [Hash] options Options for the signature generation
12
+ #
13
+ # @option options [String] :nonce ('') The nonce to use in the signature
14
+ # @option options [String, #strftime] :date (Time.now) The date to use in the signature
15
+ # @option options [Hash] :headers ({}) A list of optional headers to include in the signature
16
+ #
17
+ # @option options [String] :auth_scheme ('HMAC') The name of the authorization scheme used in the Authorization header and to construct various header-names
18
+ # @option options [String] :auth_param ('auth') The name of the authentication param to use for query based authentication
19
+ # @option options [Hash] :extra_auth_params ({}) Additional parameters to inject in the auth parameter
20
+ # @option options [String] :auth_header ('Authorization') The name of the authorization header to use
21
+ # @option options [String] :auth_header_format ('%{auth_scheme} %{signature}') The format of the authorization header. Will be interpolated with the given options and the signature.
22
+ # @option options [String] :nonce_header ('X-#{auth_scheme}-Nonce') The header name for the request nonce
23
+ # @option options [String] :alternate_date_header ('X-#{auth_scheme}-Date') The header name for the alternate date header
24
+ # @option options [Bool] :query_based (false) Whether to use query based authentication
25
+ # @option options [Bool] :use_alternate_date_header (false) Use the alternate date header instead of `Date`
26
+ #
27
+ def initialize(app, secret, options = {})
28
+ @app, @secret, @options, @query_values = app, secret, options
29
+ end
30
+
31
+ def call(env)
32
+ sign(env)
33
+ @app.call(env)
34
+ end
35
+
36
+ def sign(env)
37
+ signer = HMAC::Signer.new
38
+ url = env[:url]
39
+ headers, url = *signer.sign_request(url, @secret, @options)
40
+
41
+ env[:request_headers] = (env[:request_headers] || {}).merge(headers)
42
+ env[:url] = Addressable::URI.parse(url)
43
+ env
44
+ end
45
+
46
+ end
47
+ end
data/lib/hmac/signer.rb CHANGED
@@ -36,7 +36,8 @@ module HMAC
36
36
  :nonce_header => "X-%{scheme}-Nonce" % {:scheme => (default_opts[:auth_scheme] || "HMAC")},
37
37
  :alternate_date_header => "X-%{scheme}-Date" % {:scheme => (default_opts[:auth_scheme] || "HMAC")},
38
38
  :query_based => false,
39
- :use_alternate_date_header => false
39
+ :use_alternate_date_header => false,
40
+ :extra_auth_params => {}
40
41
  }.merge(default_opts)
41
42
 
42
43
  end
@@ -55,6 +56,7 @@ module HMAC
55
56
  # @option params [Hash] :headers ({}) All headers given in the request (optional and required)
56
57
  # @option params [String] :auth_scheme ('HMAC') The name of the authorization scheme used in the Authorization header and to construct various header-names
57
58
  # @option params [String] :auth_param ('auth') The name of the authentication param to use for query based authentication
59
+ # @option params [Hash] :extra_auth_params ({}) Additional parameters to inject in the auth parameter
58
60
  # @option params [String] :auth_header ('Authorization') The name of the authorization header to use
59
61
  # @option params [String] :auth_header_format ('%{auth_scheme} %{signature}') The format of the authorization header. Will be interpolated with the given options and the signature.
60
62
  # @option params [String] :nonce_header ('X-#{auth_scheme}-Nonce') The header name for the request nonce
@@ -87,6 +89,7 @@ module HMAC
87
89
  # @option params [Hash] :headers ({}) All headers given in the request (optional and required)
88
90
  # @option params [String] :auth_scheme ('HMAC') The name of the authorization scheme used in the Authorization header and to construct various header-names
89
91
  # @option params [String] :auth_param ('auth') The name of the authentication param to use for query based authentication
92
+ # @option params [Hash] :extra_auth_params ({}) Additional parameters to inject in the auth parameter
90
93
  # @option params [String] :auth_header ('Authorization') The name of the authorization header to use
91
94
  # @option params [String] :auth_header_format ('%{auth_scheme} %{signature}') The format of the authorization header. Will be interpolated with the given options and the signature.
92
95
  # @option params [String] :nonce_header ('X-#{auth_scheme}-Nonce') The header name for the request nonce
@@ -132,6 +135,7 @@ module HMAC
132
135
  # @option params [Hash] :headers ({}) All headers given in the request (optional and required)
133
136
  # @option params [String] :auth_scheme ('HMAC') The name of the authorization scheme used in the Authorization header and to construct various header-names
134
137
  # @option params [String] :auth_param ('auth') The name of the authentication param to use for query based authentication
138
+ # @option params [Hash] :extra_auth_params ({}) Additional parameters to inject in the auth parameter
135
139
  # @option params [String] :auth_header ('Authorization') The name of the authorization header to use
136
140
  # @option params [String] :auth_header_format ('%{auth_scheme} %{signature}') The format of the authorization header. Will be interpolated with the given options and the signature.
137
141
  # @option params [String] :nonce_header ('X-#{auth_scheme}-Nonce') The header name for the request nonce
@@ -181,6 +185,7 @@ module HMAC
181
185
  #
182
186
  # @option opts [String] :auth_scheme ('HMAC') The name of the authorization scheme used in the Authorization header and to construct various header-names
183
187
  # @option opts [String] :auth_param ('auth') The name of the authentication param to use for query based authentication
188
+ # @option opts [Hash] :extra_auth_params ({}) Additional parameters to inject in the auth parameter
184
189
  # @option opts [String] :auth_header ('Authorization') The name of the authorization header to use
185
190
  # @option opts [String] :auth_header_format ('%{auth_scheme} %{signature}') The format of the authorization header. Will be interpolated with the given options and the signature.
186
191
  # @option opts [String] :nonce_header ('X-#{auth_scheme}-Nonce') The header name for the request nonce
@@ -200,12 +205,12 @@ module HMAC
200
205
  signature = generate_signature(:secret => secret, :method => "GET", :path => uri.path, :date => date, :nonce => opts[:nonce], :query => uri.query_values, :headers => opts[:headers])
201
206
 
202
207
  if opts[:query_based]
203
- auth_params = {
208
+ auth_params = opts[:extra_auth_params].merge({
204
209
  "date" => date,
205
210
  "signature" => signature
206
- }
211
+ })
207
212
  auth_params[:nonce] = opts[:nonce] unless opts[:nonce].nil?
208
-
213
+
209
214
  query_values = uri.query_values || {}
210
215
  query_values[opts[:auth_param]] = auth_params
211
216
  uri.query_values = query_values
@@ -229,7 +234,8 @@ module HMAC
229
234
  # @param [String] secret the secret used to sign the url
230
235
  # @param [Hash] opts Options controlling the singature generation
231
236
  #
232
- # @option opts [String] :auth_param ('auth') The name of the authentication param to use for query based authentication
237
+ # @option opts [String] :auth_param ('auth') The name of the authentication param to use for query based authentication
238
+ # @option opts [Hash] :extra_auth_params ({}) Additional parameters to inject in the auth parameter
233
239
  #
234
240
  # @return [String] The signed url
235
241
  def sign_url(url, secret, opts = {})
@@ -118,6 +118,10 @@ module Warden
118
118
  def optional_headers
119
119
  (config[:optional_headers] || []) + ["Content-MD5", "Content-Type"]
120
120
  end
121
+
122
+ def auth_header_parse
123
+ config[:auth_header_parse] || /(?<scheme>\w+) (?<signature>\w+)/
124
+ end
121
125
 
122
126
  def lowercase_headers
123
127
 
@@ -53,7 +53,20 @@ module Warden
53
53
  #
54
54
  # @return [String] The signature from the request
55
55
  def given_signature
56
- headers[auth_header].split(" ")[1]
56
+ parsed_auth_header['signature']
57
+ end
58
+
59
+ # parses the authentication header from the request using the
60
+ # regexp or proc given in the :auth_header_parse option. The result
61
+ # is memoized
62
+ #
63
+ # @return [Hash] The parsed header
64
+ def parsed_auth_header
65
+ if @parsed_auth_header.nil?
66
+ @parsed_auth_header = auth_header_parse.match(headers[auth_header]) || {}
67
+ end
68
+
69
+ @parsed_auth_header
57
70
  end
58
71
 
59
72
  # retrieve the nonce from the request
@@ -79,7 +92,7 @@ module Warden
79
92
  end
80
93
 
81
94
  def scheme_valid?
82
- headers[auth_header].to_s.split(" ").first == auth_scheme_name
95
+ parsed_auth_header['scheme'] == auth_scheme_name
83
96
  end
84
97
 
85
98
  def date_header
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: warden-hmac-authentication
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.0
4
+ version: 0.5.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -14,7 +14,7 @@ date: 2011-12-13 00:00:00.000000000Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: addressable
17
- requirement: &2152266240 !ruby/object:Gem::Requirement
17
+ requirement: &2156860860 !ruby/object:Gem::Requirement
18
18
  none: false
19
19
  requirements:
20
20
  - - ! '>='
@@ -22,10 +22,10 @@ dependencies:
22
22
  version: '0'
23
23
  type: :runtime
24
24
  prerelease: false
25
- version_requirements: *2152266240
25
+ version_requirements: *2156860860
26
26
  - !ruby/object:Gem::Dependency
27
27
  name: rack
28
- requirement: &2152265520 !ruby/object:Gem::Requirement
28
+ requirement: &2156860040 !ruby/object:Gem::Requirement
29
29
  none: false
30
30
  requirements:
31
31
  - - ! '>='
@@ -33,10 +33,10 @@ dependencies:
33
33
  version: '0'
34
34
  type: :runtime
35
35
  prerelease: false
36
- version_requirements: *2152265520
36
+ version_requirements: *2156860040
37
37
  - !ruby/object:Gem::Dependency
38
38
  name: trollop
39
- requirement: &2152265000 !ruby/object:Gem::Requirement
39
+ requirement: &2156858860 !ruby/object:Gem::Requirement
40
40
  none: false
41
41
  requirements:
42
42
  - - ! '>='
@@ -44,10 +44,10 @@ dependencies:
44
44
  version: '0'
45
45
  type: :runtime
46
46
  prerelease: false
47
- version_requirements: *2152265000
47
+ version_requirements: *2156858860
48
48
  - !ruby/object:Gem::Dependency
49
49
  name: warden
50
- requirement: &2152264180 !ruby/object:Gem::Requirement
50
+ requirement: &2156858380 !ruby/object:Gem::Requirement
51
51
  none: false
52
52
  requirements:
53
53
  - - ! '>='
@@ -55,10 +55,10 @@ dependencies:
55
55
  version: '0'
56
56
  type: :runtime
57
57
  prerelease: false
58
- version_requirements: *2152264180
58
+ version_requirements: *2156858380
59
59
  - !ruby/object:Gem::Dependency
60
60
  name: rake
61
- requirement: &2152263320 !ruby/object:Gem::Requirement
61
+ requirement: &2156857780 !ruby/object:Gem::Requirement
62
62
  none: false
63
63
  requirements:
64
64
  - - ! '>='
@@ -66,10 +66,10 @@ dependencies:
66
66
  version: '0'
67
67
  type: :development
68
68
  prerelease: false
69
- version_requirements: *2152263320
69
+ version_requirements: *2156857780
70
70
  - !ruby/object:Gem::Dependency
71
71
  name: rack-test
72
- requirement: &2152262180 !ruby/object:Gem::Requirement
72
+ requirement: &2156857300 !ruby/object:Gem::Requirement
73
73
  none: false
74
74
  requirements:
75
75
  - - ! '>='
@@ -77,10 +77,10 @@ dependencies:
77
77
  version: '0'
78
78
  type: :development
79
79
  prerelease: false
80
- version_requirements: *2152262180
80
+ version_requirements: *2156857300
81
81
  - !ruby/object:Gem::Dependency
82
82
  name: riot
83
- requirement: &2152261120 !ruby/object:Gem::Requirement
83
+ requirement: &2156856800 !ruby/object:Gem::Requirement
84
84
  none: false
85
85
  requirements:
86
86
  - - ! '>='
@@ -88,10 +88,10 @@ dependencies:
88
88
  version: '0'
89
89
  type: :development
90
90
  prerelease: false
91
- version_requirements: *2152261120
91
+ version_requirements: *2156856800
92
92
  - !ruby/object:Gem::Dependency
93
93
  name: timecop
94
- requirement: &2152228000 !ruby/object:Gem::Requirement
94
+ requirement: &2156856220 !ruby/object:Gem::Requirement
95
95
  none: false
96
96
  requirements:
97
97
  - - ! '>='
@@ -99,10 +99,10 @@ dependencies:
99
99
  version: '0'
100
100
  type: :development
101
101
  prerelease: false
102
- version_requirements: *2152228000
102
+ version_requirements: *2156856220
103
103
  - !ruby/object:Gem::Dependency
104
104
  name: simplecov
105
- requirement: &2152227480 !ruby/object:Gem::Requirement
105
+ requirement: &2156843320 !ruby/object:Gem::Requirement
106
106
  none: false
107
107
  requirements:
108
108
  - - ! '>='
@@ -110,10 +110,10 @@ dependencies:
110
110
  version: '0'
111
111
  type: :development
112
112
  prerelease: false
113
- version_requirements: *2152227480
113
+ version_requirements: *2156843320
114
114
  - !ruby/object:Gem::Dependency
115
115
  name: simplecov-html
116
- requirement: &2152226940 !ruby/object:Gem::Requirement
116
+ requirement: &2156842780 !ruby/object:Gem::Requirement
117
117
  none: false
118
118
  requirements:
119
119
  - - ! '>='
@@ -121,7 +121,7 @@ dependencies:
121
121
  version: '0'
122
122
  type: :development
123
123
  prerelease: false
124
- version_requirements: *2152226940
124
+ version_requirements: *2156842780
125
125
  description: ! "This gem provides request authentication via [HMAC](http://en.wikipedia.org/wiki/Hmac).
126
126
  The main usage is request based, noninteractive\n authentication for API implementations.
127
127
  Two strategies are supported that differ mainly in how the authentication information
@@ -141,6 +141,7 @@ files:
141
141
  - README.md
142
142
  - Rakefile
143
143
  - LICENSE
144
+ - lib/faraday/request/hmac.rb
144
145
  - lib/hmac/signer.rb
145
146
  - lib/hmac/strategies/base.rb
146
147
  - lib/hmac/strategies/header.rb