warden-hmac-authentication 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
|