warden-hmac-authentication 0.2.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/LICENSE +21 -0
- data/README.md +298 -0
- data/Rakefile +10 -0
- data/lib/hmac_signer.rb +234 -0
- data/lib/strategies/base.rb +173 -0
- data/lib/strategies/hmac_header_strategy.rb +94 -0
- data/lib/strategies/hmac_query_strategy.rb +52 -0
- metadata +137 -0
data/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
Copyright (c) 2011 Florian Gilcher <florian.gilcher@asquera.de>, Felix Gilcher <felix.gilcher@asquera.de>
|
|
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.
|
|
21
|
+
|
data/README.md
ADDED
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
# HMAC
|
|
2
|
+
|
|
3
|
+
This gem provides request authentication via [HMAC](http://en.wikipedia.org/wiki/Hmac). The main usage is request based, noninteractive
|
|
4
|
+
authentication for API implementations. Two strategies are supported that differ mainly in how the authentication information is
|
|
5
|
+
transferred to the server: One header-based authentication method and one query-based. The authentication scheme is in some parts based
|
|
6
|
+
on ideas laid out in this article and the following discussion:
|
|
7
|
+
http://broadcast.oreilly.com/2009/12/principles-for-standardized-rest-authentication.html
|
|
8
|
+
|
|
9
|
+
The gem also provides a small helper class that can be used to generate request signatures.
|
|
10
|
+
|
|
11
|
+
## Header-Based authentication
|
|
12
|
+
|
|
13
|
+
The header-based authentication transports the authentication information in the (misnamed) `Authorization` HTTP-Header. The primary
|
|
14
|
+
advantage of header-based authentication is that request urls are stable even if authentication information changes. The improves
|
|
15
|
+
cacheability of the resource.
|
|
16
|
+
|
|
17
|
+
Header-based authentication is supported by the `:hmac_header` strategy.
|
|
18
|
+
|
|
19
|
+
## Query-Based authentication
|
|
20
|
+
|
|
21
|
+
Query-Based authentication encodes all authentication in the query string. Query-based authentication has unique advantages in
|
|
22
|
+
scenarios with little or no control over the request headers such as pre-generating and embedding a signed URL in a web-page or
|
|
23
|
+
similar cases. However, resources requested using query-based authentication cannot be cached since the request URL changes for
|
|
24
|
+
every request.
|
|
25
|
+
All information related to authentication is passed as a single hash in one single query parameter to minimize collisions with other
|
|
26
|
+
query parameters. The name of the query parameter defaults to `auth` and can be controlled using the `:auth_parameter` config option.
|
|
27
|
+
Query-based authentication takes optional headers into account if they are present in the request.
|
|
28
|
+
|
|
29
|
+
Query-based authentication is supported by the `:hmac_query` strategy.
|
|
30
|
+
|
|
31
|
+
## Shared secret
|
|
32
|
+
|
|
33
|
+
Both strategies use a secret that is shared between the server and the client to calculate the signature. The secret must be
|
|
34
|
+
configured when registering the strategy. For simple cases a single secret may be sufficient but most real-world scenarios
|
|
35
|
+
will require a different secret for each possible client. Such cases can be managed by passing a Proc as secret. An empty
|
|
36
|
+
secret (empty string or nil) will trigger authentication failure.
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
## Warden strategy usage
|
|
40
|
+
|
|
41
|
+
Both strategies can be used at the same time and will not interfere with each other. It is advisable to attempt query-based
|
|
42
|
+
authentication first to reduce the chance that a stray Authorization header triggers header-based authentication. Both strategies
|
|
43
|
+
read additional configuration from a hash named :hmac in the warden scope.
|
|
44
|
+
|
|
45
|
+
Configure the HMAC warden strategy:
|
|
46
|
+
|
|
47
|
+
use Warden::Manager do |manager|
|
|
48
|
+
manager.failure_app = -> env { [401, {"Content-Length" => "0"}, [""]] }
|
|
49
|
+
# other scopes
|
|
50
|
+
manager.scope_defaults :hmac, :strategies => [:hmac_query, :hmac_header],
|
|
51
|
+
:hmac => {
|
|
52
|
+
:secret => "secrit"
|
|
53
|
+
}
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
### Retrieving the secret from a database or other storage
|
|
59
|
+
|
|
60
|
+
If you want to retrieve the secret and token using a different strategy, either extend the HMAC strategy:
|
|
61
|
+
|
|
62
|
+
class Warden::Strategies::HMACQuery < Warden::Strategies::HMACBase
|
|
63
|
+
def retrieve_user
|
|
64
|
+
User.get(request[:user_id])
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def secret
|
|
68
|
+
retrieve_user.secret
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
or use a Proc that retrieves the secret.
|
|
73
|
+
|
|
74
|
+
use Warden::Manager do |manager|
|
|
75
|
+
manager.failure_app = -> env { [401, {"Content-Length" => "0"}, [""]] }
|
|
76
|
+
# other scopes
|
|
77
|
+
manager.scope_defaults :hmac, :strategies => [:hmac_query, :hmac_header],
|
|
78
|
+
:store => false,
|
|
79
|
+
:hmac => {
|
|
80
|
+
:secret => Proc.new {|strategy|
|
|
81
|
+
"secret"
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
### Controlling the HMAC algorithm
|
|
87
|
+
|
|
88
|
+
The algorithm can be controlled using the `:algorithm` option:
|
|
89
|
+
|
|
90
|
+
use Warden::Manager do |manager|
|
|
91
|
+
manager.failure_app = -> env { [401, {"Content-Length" => "0"}, [""]] }
|
|
92
|
+
# other scopes
|
|
93
|
+
manager.scope_defaults :hmac, :strategies => [:hmac_query, :hmac_header],
|
|
94
|
+
:hmac => {
|
|
95
|
+
:secret => "secrit",
|
|
96
|
+
:algorithm => "md5"
|
|
97
|
+
}
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
The algorithm defaults to SHA1.
|
|
101
|
+
|
|
102
|
+
## Auth Scheme Name
|
|
103
|
+
|
|
104
|
+
The name of the authentication scheme is primarily used for header authentication. It is used to construct the `Authorization` header and
|
|
105
|
+
must thus avoid names that are reserved for existing standardized authentication schemes such as `Basic` and `Digest`. The scheme
|
|
106
|
+
name is also used to construct the default values for various header names. The authentication scheme name defaults to `HMAC`
|
|
107
|
+
|
|
108
|
+
use Warden::Manager do |manager|
|
|
109
|
+
manager.failure_app = -> env { [401, {"Content-Length" => "0"}, [""]] }
|
|
110
|
+
# other scopes
|
|
111
|
+
manager.scope_defaults :hmac, :strategies => [:hmac_query, :hmac_header],
|
|
112
|
+
:hmac => {
|
|
113
|
+
:secret => "secrit",
|
|
114
|
+
:auth_scheme_name => "MyScheme"
|
|
115
|
+
}
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
No authentication attempt is made if the scheme name in the `Authorization` header does not match the configured scheme name.
|
|
119
|
+
|
|
120
|
+
## Optional nonce
|
|
121
|
+
|
|
122
|
+
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
|
|
123
|
+
advisable to limit the length of the nonce to a reasonable value. If a nonce is used it should be changed with every request. The
|
|
124
|
+
default header for the nonce is `X-#{auth-scheme-name}-Nonce` (`X-HMAC-Nonce`). The header name can be controlled using the `:nonce_header`
|
|
125
|
+
configuration option.
|
|
126
|
+
|
|
127
|
+
The `:require_nonce` configuration can be set to `true` to enforce a nonce. If a nonce is required no authentication attempt will be
|
|
128
|
+
made for requests not providing a nonce.
|
|
129
|
+
|
|
130
|
+
use Warden::Manager do |manager|
|
|
131
|
+
manager.failure_app = -> env { [401, {"Content-Length" => "0"}, [""]] }
|
|
132
|
+
# other scopes
|
|
133
|
+
manager.scope_defaults :hmac, :strategies => [:hmac_query, :hmac_header],
|
|
134
|
+
:hmac => {
|
|
135
|
+
:secret => "secrit",
|
|
136
|
+
:require_nonce => true
|
|
137
|
+
}
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
## Required headers and parameters
|
|
142
|
+
|
|
143
|
+
Required headers and parameters must be present for a successful authentication attempt. The list of required headers defaults to
|
|
144
|
+
the `Authorization` header for header-based authentication and is empty for query-based authentication. The list of required
|
|
145
|
+
parameters defaults to the chosen authentication parameter for query-based authentication and is empty for header-based authentication.
|
|
146
|
+
If a required parameter or header is not included in the request, no authentication attempt will be made for the strategy.
|
|
147
|
+
|
|
148
|
+
## Other optional headers
|
|
149
|
+
|
|
150
|
+
Some headers are optional but should be included in the signature of the request if present. The default list of optional headers
|
|
151
|
+
includes `Content-MD5` and `Content-Type`. The list of optional headers can be configured using the `:optional_headers` config option.
|
|
152
|
+
Optional headers are always included in the canonical representation if they are found in the request and not blank. Optional headers
|
|
153
|
+
will be included in the canonical representation for query-based authentication if they are present in the request so be careful
|
|
154
|
+
not to include any header that is out of your clients control.
|
|
155
|
+
|
|
156
|
+
## Date and TTL
|
|
157
|
+
|
|
158
|
+
It is good practice to enforce a max-age for tokens. The hmac strategy allows this via the `ttl` parameter. It controls the max age
|
|
159
|
+
of tokens in seconds and defaults to 900 seconds. Pass `nil` as ttl value to disable TTL checking.
|
|
160
|
+
|
|
161
|
+
The timestamp of the request is usually passed in the `Date` HTTP-Header. However, since some HTTP-Client libraries do not allow
|
|
162
|
+
setting the Date header another header may be used to override the `Date` header. The name of this header can be controlled via the
|
|
163
|
+
`:alternate_date_header` option and defaults to `X-#{auth-scheme-name}-Date` (`X-HMAC-Date`).
|
|
164
|
+
|
|
165
|
+
The date must be formatted as HTTP-Date according to RFC 1123, section 5.2.14 and should be provided in GMT time.
|
|
166
|
+
|
|
167
|
+
Example: Setting the ttl to 300 seconds:
|
|
168
|
+
|
|
169
|
+
use Warden::Manager do |manager|
|
|
170
|
+
manager.failure_app = -> env { [401, {"Content-Length" => "0"}, [""]] }
|
|
171
|
+
# other scopes
|
|
172
|
+
manager.scope_defaults :token, :strategies => [:hmac_query, :hmac_header],
|
|
173
|
+
:hmac => {
|
|
174
|
+
:secret => "secrit",
|
|
175
|
+
:ttl => 300 # make tokens valid for 5 minutes
|
|
176
|
+
}
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
### Clock Skew
|
|
180
|
+
|
|
181
|
+
The TTL allows for a little clock skew to accommodate servers that are slightly running off time. The allowed clock skew can be
|
|
182
|
+
controlled with the `:clockskew` option and defaults to 5 seconds.
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
## Canonical representation
|
|
186
|
+
|
|
187
|
+
Both request methods use a canonical representation of the request together with the shared secret to calculate a signature
|
|
188
|
+
that authenticates the request. The canonical representation is calculated using the following algorithm:
|
|
189
|
+
|
|
190
|
+
* Start with the empty string ("")
|
|
191
|
+
* Add the HTTP-Verb for the request ("GET", "POST", ...) in capital letters, followed by a single newline (U+000A).
|
|
192
|
+
* Add the date for the request using the form "date:#{date-of-request}" followed by a single newline. The date for the signature must be
|
|
193
|
+
formatted exactly as in the request.
|
|
194
|
+
* Add the nonce for the request in the form "nonce:#{nonce-in-request}" followed by a single newline. If no nonce is passed use the
|
|
195
|
+
empty string as nonce value.
|
|
196
|
+
* Convert all remaining header names to lowercase.
|
|
197
|
+
* Sort the remaining headers lexicographically by header name.
|
|
198
|
+
* Trim header values by removing any whitespace before the first non-whitespace character and after the last non-whitespace character.
|
|
199
|
+
* Combine lowercase header names and header values using a single colon (“:”) as separator. Do not include whitespace characters
|
|
200
|
+
around the separator.
|
|
201
|
+
* Combine all headers using a single newline (U+000A) character and append them to the canonical representation,
|
|
202
|
+
followed by a single newline (U+000A) character.
|
|
203
|
+
* Append the url-decoded query path to the canonical representation
|
|
204
|
+
* URL-decode query parameters if required
|
|
205
|
+
* If using query-based authentication: Remove all authentication-related parameters from the query parameters.
|
|
206
|
+
* Sort all query parameters lexicographically by parameter name and join them, using a single ampersand (“&”) as separator
|
|
207
|
+
* Append the query string using a single question mark (“?”) as separator unless the query string is empty
|
|
208
|
+
|
|
209
|
+
### Examples
|
|
210
|
+
|
|
211
|
+
Given the following request:
|
|
212
|
+
|
|
213
|
+
GET /example/resource.html?sort=header%20footer&order=ASC HTTP/1.1
|
|
214
|
+
Host: www.example.org
|
|
215
|
+
Date: Mon, 20 Jun 2011 12:06:11 GMT
|
|
216
|
+
User-Agent: curl/7.20.0 (x86_64-pc-linux-gnu) libcurl/7.20.0 OpenSSL/1.0.0a zlib/1.2.3
|
|
217
|
+
X-MAC-Nonce: Thohn2Mohd2zugoo
|
|
218
|
+
|
|
219
|
+
The canonical representation is:
|
|
220
|
+
|
|
221
|
+
GET\n
|
|
222
|
+
date:Mon, 20 Jun 2011 12:06:11 GMT\n
|
|
223
|
+
nonce:Thohn2Mohd2zugo\n
|
|
224
|
+
/example/resource.html?order=ASC&sort=header footer
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
Given the following request:
|
|
228
|
+
|
|
229
|
+
GET /example/resource.html?sort=header%20footer&order=ASC HTTP/1.1
|
|
230
|
+
Host: www.example.org
|
|
231
|
+
Date: Mon, 20 Jun 2011 12:06:11 GMT
|
|
232
|
+
User-Agent: curl/7.20.0 (x86_64-pc-linux-gnu) libcurl/7.20.0 OpenSSL/1.0.0a zlib/1.2.3
|
|
233
|
+
X-MAC-Nonce: Thohn2Mohd2zugoo
|
|
234
|
+
X-MAC-Date: Mon, 20 Jun 2011 14:06:57 GMT
|
|
235
|
+
|
|
236
|
+
The canonical representation is:
|
|
237
|
+
|
|
238
|
+
GET\n
|
|
239
|
+
date:Mon, 20 Jun 2011 14:06:57 GMT\n
|
|
240
|
+
nonce:Thohn2Mohd2zugo\n
|
|
241
|
+
/example/resource.html?order=ASC&sort=header footer
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
### Generating the canonical representation for query-based authentication
|
|
245
|
+
|
|
246
|
+
The canonical representation for query-based authentication is generated using the same algorithm as for header-based authentication, but some
|
|
247
|
+
of the values are retrieved from the query string instead of the respective headers. All query parameters related to authentication
|
|
248
|
+
must be removed from the query string before generating the canonical representation.
|
|
249
|
+
|
|
250
|
+
#### Example
|
|
251
|
+
|
|
252
|
+
Given the following request:
|
|
253
|
+
|
|
254
|
+
GET /example/resource.html?page=3&order=id%2casc&auth%5Bnonce%5D=foLiequei7oosaiWun5aoy8oo&auth%5Bdate%5D=Mon%2C+20+Jun+2011+14%3A06%3A57+GMT HTTP/1.1
|
|
255
|
+
Host: www.example.org
|
|
256
|
+
Date: Mon, 20 Jun 2011 12:06:11 GMT
|
|
257
|
+
User-Agent: curl/7.20.0 (x86_64-pc-linux-gnu) libcurl/7.20.0 OpenSSL/1.0.0a zlib/1.2.3
|
|
258
|
+
|
|
259
|
+
The canonical representation is:
|
|
260
|
+
|
|
261
|
+
GET\n
|
|
262
|
+
date:Mon, 20 Jun 2011 14:06:57 GMT\n
|
|
263
|
+
nonce:foLiequei7oosaiWun5aoy8oo\n
|
|
264
|
+
/example/resource.html?order=id,asc&page=3
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
## HMACSigner usage
|
|
268
|
+
|
|
269
|
+
The HMACSigner class can be used to validate and generate signatures for a given request. Most methods accept a hash as an intermediate
|
|
270
|
+
representation of the request but some methods accept and operate on full urls.
|
|
271
|
+
|
|
272
|
+
h = HMACSigner.new
|
|
273
|
+
h.sign_url('http://example.org/example.html', 'secret')
|
|
274
|
+
h.validate_url_signature('http://example.org/example.html?auth[signature]=foo', 'secret')
|
|
275
|
+
|
|
276
|
+
## Licence
|
|
277
|
+
|
|
278
|
+
Copyright (c) 2011 Florian Gilcher <florian.gilcher@asquera.de>, Felix Gilcher <felix.gilcher@asquera.de>
|
|
279
|
+
|
|
280
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
|
281
|
+
a copy of this software and associated documentation files (the
|
|
282
|
+
"Software"), to deal in the Software without restriction, including
|
|
283
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
|
284
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
|
285
|
+
permit persons to whom the Software is furnished to do so, subject to
|
|
286
|
+
the following conditions:
|
|
287
|
+
|
|
288
|
+
The above copyright notice and this permission notice shall be
|
|
289
|
+
included in all copies or substantial portions of the Software.
|
|
290
|
+
|
|
291
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
292
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
293
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
294
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
|
295
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
296
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
297
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
298
|
+
|
data/Rakefile
ADDED
data/lib/hmac_signer.rb
ADDED
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
require 'addressable/uri'
|
|
2
|
+
require 'openssl'
|
|
3
|
+
require 'rack/utils'
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
# Helper class that provides signing capabilites for the hmac strategies.
|
|
7
|
+
#
|
|
8
|
+
# @author Felix Gilcher <felix.gilcher@asquera.de>
|
|
9
|
+
class HMACSigner
|
|
10
|
+
attr_accessor :secret, :algorithm, :default_opts
|
|
11
|
+
|
|
12
|
+
# create a new HMAC instance
|
|
13
|
+
#
|
|
14
|
+
# @param [String] algorithm The hashing-algorithm to use. See the openssl documentation for valid values.
|
|
15
|
+
# @param [Hash] default_opts The default options for all calls that take opts
|
|
16
|
+
#
|
|
17
|
+
# @option default_opts [String] :auth_scheme ('HMAC') The name of the authorization scheme used in the Authorization header and to construct various header-names
|
|
18
|
+
# @option default_opts [String] :auth_param ('auth') The name of the authentication param to use for query based authentication
|
|
19
|
+
# @option default_opts [String] :auth_header ('Authorization') The name of the authorization header to use
|
|
20
|
+
# @option default_opts [String] :auth_header_format ('%{auth_scheme} %{signature}') The format of the authorization header. Will be interpolated with the given options and the signature.
|
|
21
|
+
# @option default_opts [String] :nonce_header ('X-#{auth_scheme}-Nonce') The header name for the request nonce
|
|
22
|
+
# @option default_opts [String] :alternate_date_header ('X-#{auth_scheme}-Date') The header name for the alternate date header
|
|
23
|
+
# @option default_opts [Bool] :query_based (false) Whether to use query based authentication
|
|
24
|
+
# @option default_opts [Bool] :use_alternate_date_header (false) Use the alternate date header instead of `Date`
|
|
25
|
+
#
|
|
26
|
+
def initialize(algorithm = "sha1", default_opts = {})
|
|
27
|
+
self.algorithm = algorithm
|
|
28
|
+
self.default_opts = {
|
|
29
|
+
:auth_scheme => "HMAC",
|
|
30
|
+
:auth_param => "auth",
|
|
31
|
+
:auth_header => "Authorization",
|
|
32
|
+
:auth_header_format => "%{auth_scheme} %{signature}",
|
|
33
|
+
:nonce_header => "X-%{scheme}-Nonce" % {:scheme => (default_opts[:auth_scheme] || "HMAC")},
|
|
34
|
+
:alternate_date_header => "X-%{scheme}-Date" % {:scheme => (default_opts[:auth_scheme] || "HMAC")},
|
|
35
|
+
:query_based => false,
|
|
36
|
+
:use_alternate_date_header => false
|
|
37
|
+
}.merge(default_opts)
|
|
38
|
+
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Generate the signature from a hash representation
|
|
42
|
+
#
|
|
43
|
+
# @param [Hash] params the parameters to create the representation with
|
|
44
|
+
# @option params [String] :secret The secret to generate the signature with
|
|
45
|
+
# @option params [String] :method The HTTP Verb of the request
|
|
46
|
+
# @option params [String] :date The date of the request as it was formatted in the request
|
|
47
|
+
# @option params [String] :nonce ('') The nonce given in the request
|
|
48
|
+
# @option params [String] :path The path portion of the request
|
|
49
|
+
# @option params [Hash] :query ({}) The query parameters given in the request. Must not contain the auth param.
|
|
50
|
+
# @option params [Hash] :headers ({}) All headers given in the request (optional and required)
|
|
51
|
+
# @option params [String] :auth_scheme ('HMAC') The name of the authorization scheme used in the Authorization header and to construct various header-names
|
|
52
|
+
# @option params [String] :auth_param ('auth') The name of the authentication param to use for query based authentication
|
|
53
|
+
# @option params [String] :auth_header ('Authorization') The name of the authorization header to use
|
|
54
|
+
# @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.
|
|
55
|
+
# @option params [String] :nonce_header ('X-#{auth_scheme}-Nonce') The header name for the request nonce
|
|
56
|
+
# @option params [String] :alternate_date_header ('X-#{auth_scheme}-Date') The header name for the alternate date header
|
|
57
|
+
# @option params [Bool] :query_based (false) Whether to use query based authentication
|
|
58
|
+
# @option params [Bool] :use_alternate_date_header (false) Use the alternate date header instead of `Date`
|
|
59
|
+
#
|
|
60
|
+
# @return [String] the signature
|
|
61
|
+
def generate_signature(params)
|
|
62
|
+
secret = params.delete(:secret)
|
|
63
|
+
OpenSSL::HMAC.hexdigest(algorithm, secret, canonical_representation(params))
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# compares the given signature with the signature created from a hash representation
|
|
67
|
+
#
|
|
68
|
+
# @param [String] signature the signature to compare with
|
|
69
|
+
# @param [Hash] params the parameters to create the representation with
|
|
70
|
+
# @option params [String] :secret The secret to generate the signature with
|
|
71
|
+
# @option params [String] :method The HTTP Verb of the request
|
|
72
|
+
# @option params [String] :date The date of the request as it was formatted in the request
|
|
73
|
+
# @option params [String] :nonce ('') The nonce given in the request
|
|
74
|
+
# @option params [String] :path The path portion of the request
|
|
75
|
+
# @option params [Hash] :query ({}) The query parameters given in the request. Must not contain the auth param.
|
|
76
|
+
# @option params [Hash] :headers ({}) All headers given in the request (optional and required)
|
|
77
|
+
# @option params [String] :auth_scheme ('HMAC') The name of the authorization scheme used in the Authorization header and to construct various header-names
|
|
78
|
+
# @option params [String] :auth_param ('auth') The name of the authentication param to use for query based authentication
|
|
79
|
+
# @option params [String] :auth_header ('Authorization') The name of the authorization header to use
|
|
80
|
+
# @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.
|
|
81
|
+
# @option params [String] :nonce_header ('X-#{auth_scheme}-Nonce') The header name for the request nonce
|
|
82
|
+
# @option params [String] :alternate_date_header ('X-#{auth_scheme}-Date') The header name for the alternate date header
|
|
83
|
+
# @option params [Bool] :query_based (false) Whether to use query based authentication
|
|
84
|
+
# @option params [Bool] :use_alternate_date_header (false) Use the alternate date header instead of `Date`
|
|
85
|
+
#
|
|
86
|
+
# @return [Bool] true if the signature matches
|
|
87
|
+
def validate_signature(signature, params)
|
|
88
|
+
signature == generate_signature(params)
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# convienience method to check the signature of a url with query-based authentication
|
|
92
|
+
#
|
|
93
|
+
# @param [String] url the url to test
|
|
94
|
+
# @param [String] secret the secret used to sign the url
|
|
95
|
+
# @param [Hash] opts Options controlling the singature generation
|
|
96
|
+
#
|
|
97
|
+
# @option opts [String] :auth_param ('auth') The name of the authentication param to use for query based authentication
|
|
98
|
+
#
|
|
99
|
+
# @return [Bool] true if the signature is valid
|
|
100
|
+
def validate_url_signature(url, secret, opts = {})
|
|
101
|
+
opts = default_opts.merge(opts)
|
|
102
|
+
opts[:query_based] = true
|
|
103
|
+
|
|
104
|
+
uri = Addressable::URI.parse(url)
|
|
105
|
+
query_values = uri.query_values
|
|
106
|
+
auth_params = query_values.delete(opts[:auth_param])
|
|
107
|
+
|
|
108
|
+
date = auth_params["date"]
|
|
109
|
+
nonce = auth_params["nonce"]
|
|
110
|
+
validate_signature(auth_params["signature"], :secret => secret, :method => "GET", :path => uri.path, :date => date, :nonce => nonce, :query => query_values, :headers => {})
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
# generates the canonical representation for a given request
|
|
114
|
+
#
|
|
115
|
+
# @param [Hash] params the parameters to create the representation with
|
|
116
|
+
# @option params [String] :method The HTTP Verb of the request
|
|
117
|
+
# @option params [String] :date The date of the request as it was formatted in the request
|
|
118
|
+
# @option params [String] :nonce ('') The nonce given in the request
|
|
119
|
+
# @option params [String] :path The path portion of the request
|
|
120
|
+
# @option params [Hash] :query ({}) The query parameters given in the request. Must not contain the auth param.
|
|
121
|
+
# @option params [Hash] :headers ({}) All headers given in the request (optional and required)
|
|
122
|
+
# @option params [String] :auth_scheme ('HMAC') The name of the authorization scheme used in the Authorization header and to construct various header-names
|
|
123
|
+
# @option params [String] :auth_param ('auth') The name of the authentication param to use for query based authentication
|
|
124
|
+
# @option params [String] :auth_header ('Authorization') The name of the authorization header to use
|
|
125
|
+
# @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.
|
|
126
|
+
# @option params [String] :nonce_header ('X-#{auth_scheme}-Nonce') The header name for the request nonce
|
|
127
|
+
# @option params [String] :alternate_date_header ('X-#{auth_scheme}-Date') The header name for the alternate date header
|
|
128
|
+
# @option params [Bool] :query_based (false) Whether to use query based authentication
|
|
129
|
+
# @option params [Bool] :use_alternate_date_header (false) Use the alternate date header instead of `Date`
|
|
130
|
+
#
|
|
131
|
+
# @return [String] the canonical representation
|
|
132
|
+
def canonical_representation(params)
|
|
133
|
+
rep = ""
|
|
134
|
+
|
|
135
|
+
rep << "#{params[:method].upcase}\n"
|
|
136
|
+
rep << "date:#{params[:date]}\n"
|
|
137
|
+
rep << "nonce:#{params[:nonce]}\n"
|
|
138
|
+
|
|
139
|
+
(params[:headers] || {}).sort.each do |pair|
|
|
140
|
+
name,value = *pair
|
|
141
|
+
rep << "#{name.downcase}:#{value}\n"
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
rep << params[:path]
|
|
145
|
+
|
|
146
|
+
p = (params[:query] || {}).dup
|
|
147
|
+
|
|
148
|
+
if !p.empty?
|
|
149
|
+
query = p.sort.map do |key, value|
|
|
150
|
+
"%{key}=%{value}" % {
|
|
151
|
+
:key => Rack::Utils.unescape(key.to_s),
|
|
152
|
+
:value => Rack::Utils.unescape(value.to_s)
|
|
153
|
+
}
|
|
154
|
+
end.join("&")
|
|
155
|
+
rep << "?#{query}"
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
rep
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
# sign the given request
|
|
162
|
+
#
|
|
163
|
+
# @param [String] url The url of the request
|
|
164
|
+
# @param [String] secret The shared secret for the signature
|
|
165
|
+
# @param [Hash] opts Options for the signature generation
|
|
166
|
+
#
|
|
167
|
+
# @option opts [String] :nonce ('') The nonce to use in the signature
|
|
168
|
+
# @option opts [String, #strftime] :date (Time.now) The date to use in the signature
|
|
169
|
+
# @option opts [Hash] :headers ({}) A list of optional headers to include in the signature
|
|
170
|
+
#
|
|
171
|
+
# @option opts [String] :auth_scheme ('HMAC') The name of the authorization scheme used in the Authorization header and to construct various header-names
|
|
172
|
+
# @option opts [String] :auth_param ('auth') The name of the authentication param to use for query based authentication
|
|
173
|
+
# @option opts [String] :auth_header ('Authorization') The name of the authorization header to use
|
|
174
|
+
# @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.
|
|
175
|
+
# @option opts [String] :nonce_header ('X-#{auth_scheme}-Nonce') The header name for the request nonce
|
|
176
|
+
# @option opts [String] :alternate_date_header ('X-#{auth_scheme}-Date') The header name for the alternate date header
|
|
177
|
+
# @option opts [Bool] :query_based (false) Whether to use query based authentication
|
|
178
|
+
# @option opts [Bool] :use_alternate_date_header (false) Use the alternate date header instead of `Date`
|
|
179
|
+
#
|
|
180
|
+
def sign_request(url, secret, opts = {})
|
|
181
|
+
opts = default_opts.merge(opts)
|
|
182
|
+
|
|
183
|
+
uri = Addressable::URI.parse(url)
|
|
184
|
+
headers = opts[:headers] || {}
|
|
185
|
+
|
|
186
|
+
date = opts[:date] || Time.now.gmtime
|
|
187
|
+
date = date.gmtime.strftime('%a, %e %b %Y %T GMT') if date.respond_to? :strftime
|
|
188
|
+
|
|
189
|
+
signature = generate_signature(:secret => secret, :method => "GET", :path => uri.path, :date => date, :nonce => opts[:nonce], :query => uri.query_values, :headers => opts[:headers])
|
|
190
|
+
|
|
191
|
+
if opts[:query_based]
|
|
192
|
+
auth_params = {
|
|
193
|
+
"date" => date,
|
|
194
|
+
"signature" => signature
|
|
195
|
+
}
|
|
196
|
+
auth_params[:nonce] = opts[:nonce] unless opts[:nonce].nil?
|
|
197
|
+
|
|
198
|
+
query_values = uri.query_values
|
|
199
|
+
query_values[opts[:auth_param]] = auth_params
|
|
200
|
+
uri.query_values = query_values
|
|
201
|
+
else
|
|
202
|
+
headers[opts[:auth_header]] = opts[:auth_header_format] % opts.merge({:signature => signature})
|
|
203
|
+
headers[opts[:nonce_header]] = opts[:nonce] unless opts[:nonce].nil?
|
|
204
|
+
|
|
205
|
+
if opts[:use_alternate_date_header]
|
|
206
|
+
headers[opts[:alternate_date_header]] = date
|
|
207
|
+
else
|
|
208
|
+
headers["Date"] = date
|
|
209
|
+
end
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
[headers, uri.to_s]
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
# convienience method to sign a url for use with query-based authentication
|
|
216
|
+
#
|
|
217
|
+
# @param [String] url the url to sign
|
|
218
|
+
# @param [String] secret the secret used to sign the url
|
|
219
|
+
# @param [Hash] opts Options controlling the singature generation
|
|
220
|
+
#
|
|
221
|
+
# @option opts [String] :auth_param ('auth') The name of the authentication param to use for query based authentication
|
|
222
|
+
#
|
|
223
|
+
# @return [String] The signed url
|
|
224
|
+
def sign_url(url, secret, opts = {})
|
|
225
|
+
opts = default_opts.merge(opts)
|
|
226
|
+
opts[:query_based] = true
|
|
227
|
+
|
|
228
|
+
headers, url = *sign_request(url, secret, opts)
|
|
229
|
+
url
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
end
|
|
234
|
+
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
require 'hmac_signer'
|
|
2
|
+
require 'warden'
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
# Base class for hmac authentication in warden. Provides shared methods such as config access
|
|
6
|
+
# and various helpers.
|
|
7
|
+
#
|
|
8
|
+
# @author Felix Gilcher <felix.gilcher@asquera.de>
|
|
9
|
+
class Warden::Strategies::HMACBase < Warden::Strategies::Base
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
# Performs authentication. Calls success! if authentication was performed successfully and halt!
|
|
13
|
+
# if the authentication information is invalid.
|
|
14
|
+
#
|
|
15
|
+
# Delegates parts of the work to signature_valid? which must be implemented in child-strategies.
|
|
16
|
+
#
|
|
17
|
+
# @see https://github.com/hassox/warden/wiki/Strategies
|
|
18
|
+
def authenticate!
|
|
19
|
+
if "" == secret.to_s
|
|
20
|
+
debug("authentication attempt with an empty secret")
|
|
21
|
+
return fail!("Cannot authenticate with an empty secret")
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
if check_ttl? && !timestamp_valid?
|
|
25
|
+
debug("authentication attempt with an invalid timestamp. Given was #{timestamp}, expected was #{Time.now.gmtime}")
|
|
26
|
+
return fail!("Invalid timestamp")
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
if signature_valid?
|
|
30
|
+
success!(retrieve_user)
|
|
31
|
+
else
|
|
32
|
+
debug("authentication attempt with an invalid signature.")
|
|
33
|
+
fail!("Invalid token passed")
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Retrieve the current request method
|
|
38
|
+
#
|
|
39
|
+
# @return [String] The request method in capital letters
|
|
40
|
+
def request_method
|
|
41
|
+
env['REQUEST_METHOD'].upcase
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Retrieve the request query parameters
|
|
45
|
+
#
|
|
46
|
+
# @return [Hash] The query parameters
|
|
47
|
+
def params
|
|
48
|
+
request.GET
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Retrieve the request headers. Header names are normalized by this method by stripping
|
|
52
|
+
# the `HTTP_`-prefix and replacing underscores with dashes. `HTTP_X_Foo` is normalized to
|
|
53
|
+
# `X-Foo`.
|
|
54
|
+
#
|
|
55
|
+
# @return [Hash] The request headers
|
|
56
|
+
def headers
|
|
57
|
+
pairs = env.select {|k,v| k.start_with? 'HTTP_'}
|
|
58
|
+
.collect {|pair| [pair[0].sub(/^HTTP_/, '').gsub(/_/, '-'), pair[1]]}
|
|
59
|
+
.sort
|
|
60
|
+
headers = Hash[*pairs.flatten]
|
|
61
|
+
headers
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# Retrieve a user from the database. Stub implementation that just returns true, needed for compat.
|
|
65
|
+
#
|
|
66
|
+
# @return [Bool] true
|
|
67
|
+
def retrieve_user
|
|
68
|
+
true
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# Log a debug message if a logger is available.
|
|
72
|
+
#
|
|
73
|
+
# @param [String] msg The message to log
|
|
74
|
+
def debug(msg)
|
|
75
|
+
if logger
|
|
76
|
+
logger.debug(msg)
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# Retrieve a logger. Current implementation can
|
|
81
|
+
# only handle Padrino loggers
|
|
82
|
+
#
|
|
83
|
+
# @return [Logger] the logger, nil if none is available
|
|
84
|
+
def logger
|
|
85
|
+
if defined? Padrino
|
|
86
|
+
Padrino.logger
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
private
|
|
91
|
+
def config
|
|
92
|
+
env["warden"].config[:scope_defaults][scope][:hmac]
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def auth_param
|
|
96
|
+
config[:auth_param] || "auth"
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def auth_header
|
|
100
|
+
config[:auth_header] || "Authorization"
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def auth_scheme_name
|
|
104
|
+
config[:auth_scheme] || "HMAC"
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def nonce_header_name
|
|
108
|
+
config[:nonce_header] || "X-#{auth_scheme_name}-Nonce"
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def alternate_date_header_name
|
|
112
|
+
config[:alternate_date_header] || "X-#{auth_scheme_name}-Date"
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def optional_headers
|
|
116
|
+
(config[:optional_headers] || []) + ["Content-MD5", "Content-Type"]
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def lowercase_headers
|
|
120
|
+
|
|
121
|
+
if @lowercase_headers.nil?
|
|
122
|
+
tmp = headers.map do |name,value|
|
|
123
|
+
[name.downcase, value]
|
|
124
|
+
end
|
|
125
|
+
@lowercase_headers = Hash[*tmp.flatten]
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
@lowercase_headers
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def hmac
|
|
132
|
+
HMACSigner.new(algorithm)
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def algorithm
|
|
136
|
+
config[:algorithm] || "sha1"
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def ttl
|
|
140
|
+
config[:ttl].to_i
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def check_ttl?
|
|
144
|
+
!config[:ttl].nil?
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
def timestamp
|
|
148
|
+
Time.strptime(request_timestamp, '%a, %e %b %Y %T %z') unless request_timestamp.nil?
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
def has_timestamp?
|
|
152
|
+
!timestamp.nil?
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
def timestamp_valid?
|
|
156
|
+
now = Time.now.gmtime.to_i
|
|
157
|
+
timestamp.to_i <= (now + clockskew) && timestamp.to_i >= (now - ttl)
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
def nonce_required?
|
|
161
|
+
!!config[:require_nonce]
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
def secret
|
|
165
|
+
@secret ||= config[:secret].respond_to?(:call) ? config[:secret].call(self) : config[:secret]
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
def clockskew
|
|
169
|
+
(config[:clockskew] || 5)
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
end
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
require_relative 'base'
|
|
2
|
+
|
|
3
|
+
# Implements header-based hmac authentication for warden. The strategy is registered as
|
|
4
|
+
# `:hmac_header` in the warden strategy list.
|
|
5
|
+
#
|
|
6
|
+
# @author Felix Gilcher <felix.gilcher@asquera.de>
|
|
7
|
+
class Warden::Strategies::HMACHeader < Warden::Strategies::HMACBase
|
|
8
|
+
|
|
9
|
+
# Checks that this strategy applies. Tests that the required
|
|
10
|
+
# authentication information was given.
|
|
11
|
+
#
|
|
12
|
+
# @return [Bool] true if all required authentication information is available in the request
|
|
13
|
+
# @see https://github.com/hassox/warden/wiki/Strategies
|
|
14
|
+
def valid?
|
|
15
|
+
valid = required_headers.all? { |h| headers.include?(h) } && headers.include?("Authorization") && has_timestamp?
|
|
16
|
+
valid = valid && scheme_valid?
|
|
17
|
+
valid
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# Check that the signature given in the request is valid.
|
|
21
|
+
#
|
|
22
|
+
# @return [Bool] true if the request is valid
|
|
23
|
+
def signature_valid?
|
|
24
|
+
|
|
25
|
+
#:method => "GET",
|
|
26
|
+
#:date => "Mon, 20 Jun 2011 12:06:11 GMT",
|
|
27
|
+
#:nonce => "TESTNONCE",
|
|
28
|
+
#:path => "/example",
|
|
29
|
+
#:query => {
|
|
30
|
+
# "foo" => "bar",
|
|
31
|
+
# "baz" => "foobared"
|
|
32
|
+
#},
|
|
33
|
+
#:headers => {
|
|
34
|
+
# "Content-Type" => "application/json;charset=utf8",
|
|
35
|
+
# "Content-MD5" => "d41d8cd98f00b204e9800998ecf8427e"
|
|
36
|
+
#}
|
|
37
|
+
|
|
38
|
+
hmac.validate_signature(given_signature, {
|
|
39
|
+
:secret => secret,
|
|
40
|
+
:method => request_method,
|
|
41
|
+
:date => request_timestamp,
|
|
42
|
+
:nonce => nonce,
|
|
43
|
+
:path => request.path,
|
|
44
|
+
:query => params,
|
|
45
|
+
:headers => headers.select {|name, value| optional_headers.include? name}
|
|
46
|
+
})
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# retrieve the signature from the request
|
|
50
|
+
#
|
|
51
|
+
# @return [String] The signature from the request
|
|
52
|
+
def given_signature
|
|
53
|
+
headers[auth_header].split(" ")[1]
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# retrieve the nonce from the request
|
|
57
|
+
#
|
|
58
|
+
# @return [String] The nonce or an empty string if no nonce was given in the request
|
|
59
|
+
def nonce
|
|
60
|
+
headers[nonce_header_name]
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# retrieve the request timestamp as string
|
|
64
|
+
#
|
|
65
|
+
# @return [String] The request timestamp or an empty string if no timestamp was given in the request
|
|
66
|
+
def request_timestamp
|
|
67
|
+
headers[date_header]
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
private
|
|
71
|
+
|
|
72
|
+
def required_headers
|
|
73
|
+
headers = [auth_header]
|
|
74
|
+
headers += [nonce_header_name] if nonce_required?
|
|
75
|
+
headers
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def scheme_valid?
|
|
79
|
+
headers[auth_header].to_s.split(" ").first == auth_scheme_name
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def date_header
|
|
83
|
+
if headers.include? alternate_date_header_name
|
|
84
|
+
alternate_date_header_name
|
|
85
|
+
else
|
|
86
|
+
"Date"
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
Warden::Strategies.add(:hmac_header, Warden::Strategies::HMACHeader)
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
require_relative 'base'
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
# Implements query-based hmac authentication for warden. The strategy is registered as
|
|
5
|
+
# `:hmac_query` in the warden strategy list.
|
|
6
|
+
#
|
|
7
|
+
# @author Felix Gilcher <felix.gilcher@asquera.de>
|
|
8
|
+
class Warden::Strategies::HMACQuery < Warden::Strategies::HMACBase
|
|
9
|
+
|
|
10
|
+
# Checks that this strategy applies. Tests that the required
|
|
11
|
+
# authentication information was given.
|
|
12
|
+
#
|
|
13
|
+
# @return [Bool] true if all required authentication information is available in the request
|
|
14
|
+
# @see https://github.com/hassox/warden/wiki/Strategies
|
|
15
|
+
def valid?
|
|
16
|
+
valid = auth_info.include? "signature"
|
|
17
|
+
valid = valid && has_timestamp? if check_ttl?
|
|
18
|
+
valid = valid && has_nonce? if nonce_required?
|
|
19
|
+
valid
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# Check that the signature given in the request is valid.
|
|
23
|
+
#
|
|
24
|
+
# @return [Bool] true if the request is valid
|
|
25
|
+
def signature_valid?
|
|
26
|
+
hmac.validate_url_signature(request.url, secret)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# retrieve the authentication information from the request
|
|
30
|
+
#
|
|
31
|
+
# @return [Hash] the authentication info in the request
|
|
32
|
+
def auth_info
|
|
33
|
+
params[auth_param] || {}
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# retrieve the nonce from the request
|
|
37
|
+
#
|
|
38
|
+
# @return [String] The nonce or an empty string if no nonce was given in the request
|
|
39
|
+
def nonce
|
|
40
|
+
auth_info["nonce"] || ""
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# retrieve the request timestamp as string
|
|
44
|
+
#
|
|
45
|
+
# @return [String] The request timestamp or an empty string if no timestamp was given in the request
|
|
46
|
+
def request_timestamp
|
|
47
|
+
auth_info["date"] || ""
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
Warden::Strategies.add(:hmac_query, Warden::Strategies::HMACQuery)
|
metadata
ADDED
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: warden-hmac-authentication
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
prerelease:
|
|
5
|
+
version: 0.2.0
|
|
6
|
+
platform: ruby
|
|
7
|
+
authors:
|
|
8
|
+
- Felix Gilcher
|
|
9
|
+
- Florian Gilcher
|
|
10
|
+
autorequire:
|
|
11
|
+
bindir: bin
|
|
12
|
+
cert_chain: []
|
|
13
|
+
|
|
14
|
+
date: 2011-07-16 00:00:00 +02:00
|
|
15
|
+
default_executable:
|
|
16
|
+
dependencies:
|
|
17
|
+
- !ruby/object:Gem::Dependency
|
|
18
|
+
name: addressable
|
|
19
|
+
prerelease: false
|
|
20
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
|
21
|
+
none: false
|
|
22
|
+
requirements:
|
|
23
|
+
- - ">="
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: "0"
|
|
26
|
+
type: :runtime
|
|
27
|
+
version_requirements: *id001
|
|
28
|
+
- !ruby/object:Gem::Dependency
|
|
29
|
+
name: rack
|
|
30
|
+
prerelease: false
|
|
31
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
|
32
|
+
none: false
|
|
33
|
+
requirements:
|
|
34
|
+
- - ">="
|
|
35
|
+
- !ruby/object:Gem::Version
|
|
36
|
+
version: "0"
|
|
37
|
+
type: :runtime
|
|
38
|
+
version_requirements: *id002
|
|
39
|
+
- !ruby/object:Gem::Dependency
|
|
40
|
+
name: yard
|
|
41
|
+
prerelease: false
|
|
42
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
|
43
|
+
none: false
|
|
44
|
+
requirements:
|
|
45
|
+
- - ">="
|
|
46
|
+
- !ruby/object:Gem::Version
|
|
47
|
+
version: "0"
|
|
48
|
+
type: :development
|
|
49
|
+
version_requirements: *id003
|
|
50
|
+
- !ruby/object:Gem::Dependency
|
|
51
|
+
name: rdiscount
|
|
52
|
+
prerelease: false
|
|
53
|
+
requirement: &id004 !ruby/object:Gem::Requirement
|
|
54
|
+
none: false
|
|
55
|
+
requirements:
|
|
56
|
+
- - ">="
|
|
57
|
+
- !ruby/object:Gem::Version
|
|
58
|
+
version: "0"
|
|
59
|
+
type: :development
|
|
60
|
+
version_requirements: *id004
|
|
61
|
+
- !ruby/object:Gem::Dependency
|
|
62
|
+
name: simplecov
|
|
63
|
+
prerelease: false
|
|
64
|
+
requirement: &id005 !ruby/object:Gem::Requirement
|
|
65
|
+
none: false
|
|
66
|
+
requirements:
|
|
67
|
+
- - ">="
|
|
68
|
+
- !ruby/object:Gem::Version
|
|
69
|
+
version: "0"
|
|
70
|
+
type: :development
|
|
71
|
+
version_requirements: *id005
|
|
72
|
+
- !ruby/object:Gem::Dependency
|
|
73
|
+
name: simplecov-html
|
|
74
|
+
prerelease: false
|
|
75
|
+
requirement: &id006 !ruby/object:Gem::Requirement
|
|
76
|
+
none: false
|
|
77
|
+
requirements:
|
|
78
|
+
- - ">="
|
|
79
|
+
- !ruby/object:Gem::Version
|
|
80
|
+
version: "0"
|
|
81
|
+
type: :development
|
|
82
|
+
version_requirements: *id006
|
|
83
|
+
description: |-
|
|
84
|
+
This gem provides request authentication via [HMAC](http://en.wikipedia.org/wiki/Hmac). The main usage is request based, noninteractive
|
|
85
|
+
authentication for API implementations. Two strategies are supported that differ mainly in how the authentication information is
|
|
86
|
+
transferred to the server: One header-based authentication method and one query-based. The authentication scheme is in some parts based
|
|
87
|
+
on ideas laid out in this article and the following discussion:
|
|
88
|
+
http://broadcast.oreilly.com/2009/12/principles-for-standardized-rest-authentication.html
|
|
89
|
+
|
|
90
|
+
The gem also provides a small helper class that can be used to generate request signatures.
|
|
91
|
+
email:
|
|
92
|
+
- felix.gilcher@asquera.de
|
|
93
|
+
- florian.gilcher@asquera.de
|
|
94
|
+
executables: []
|
|
95
|
+
|
|
96
|
+
extensions: []
|
|
97
|
+
|
|
98
|
+
extra_rdoc_files: []
|
|
99
|
+
|
|
100
|
+
files:
|
|
101
|
+
- README.md
|
|
102
|
+
- Rakefile
|
|
103
|
+
- LICENSE
|
|
104
|
+
- lib/strategies/hmac_query_strategy.rb
|
|
105
|
+
- lib/strategies/hmac_header_strategy.rb
|
|
106
|
+
- lib/strategies/base.rb
|
|
107
|
+
- lib/hmac_signer.rb
|
|
108
|
+
has_rdoc: true
|
|
109
|
+
homepage: https://github.com/Asquera/warden-hmac-authentication
|
|
110
|
+
licenses: []
|
|
111
|
+
|
|
112
|
+
post_install_message:
|
|
113
|
+
rdoc_options: []
|
|
114
|
+
|
|
115
|
+
require_paths:
|
|
116
|
+
- lib
|
|
117
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
118
|
+
none: false
|
|
119
|
+
requirements:
|
|
120
|
+
- - ">="
|
|
121
|
+
- !ruby/object:Gem::Version
|
|
122
|
+
version: "0"
|
|
123
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
124
|
+
none: false
|
|
125
|
+
requirements:
|
|
126
|
+
- - ">="
|
|
127
|
+
- !ruby/object:Gem::Version
|
|
128
|
+
version: "0"
|
|
129
|
+
requirements: []
|
|
130
|
+
|
|
131
|
+
rubyforge_project:
|
|
132
|
+
rubygems_version: 1.6.2
|
|
133
|
+
signing_key:
|
|
134
|
+
specification_version: 3
|
|
135
|
+
summary: Provides request based, non-interactive authentication for APIs
|
|
136
|
+
test_files: []
|
|
137
|
+
|