httpauth 0.2.0 → 0.2.1
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.
- checksums.yaml +7 -0
- data/README.md +1 -1
- data/lib/httpauth.rb +1 -1
- data/lib/httpauth/basic.rb +15 -16
- data/lib/httpauth/constants.rb +5 -5
- data/lib/httpauth/digest.rb +112 -113
- data/lib/httpauth/exceptions.rb +1 -1
- metadata +42 -46
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 2404f2197812b252663439a4537d6a068d4e09c4
|
4
|
+
data.tar.gz: 71ece448d2d0370456216b90059d181f246c2131
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: a2e2f64693bd6e3c0ad4fa32dcd2bd01483cc8626dade2d3dde72a6de047fa29b9b9b8e05481fef81a212d317017e9990b4b4d0714f39f16134bfeef3976f8e2
|
7
|
+
data.tar.gz: 1d8d22a05e34731303255144c97bb29e6e7243bec8f36bec7ca6a2a651eec36b57815a413753f70f8c278469c199b468b85d6ad09dc7a09ddb051f84fe02cf28
|
data/README.md
CHANGED
@@ -37,4 +37,4 @@ IE doesn't use the full URI for digest calculation, it chops off the query param
|
|
37
37
|
|
38
38
|
## Known server implementation issues
|
39
39
|
|
40
|
-
Apache 2.0 sends Authorization-Info headers without a nextnonce directive.
|
40
|
+
Apache 2.0 sends Authorization-Info headers without a nextnonce directive.
|
data/lib/httpauth.rb
CHANGED
data/lib/httpauth/basic.rb
CHANGED
@@ -7,7 +7,7 @@ module HTTPAuth
|
|
7
7
|
# the server sends a challenge and the client has to respond to that with the correct credentials. These
|
8
8
|
# credentials will have to be sent with every request from that point on.
|
9
9
|
#
|
10
|
-
# == On the server
|
10
|
+
# == On the server
|
11
11
|
#
|
12
12
|
# On the server you will have to check the headers for the 'Authorization' header. When you find one unpack
|
13
13
|
# it and check it against your database of credentials. If the credentials are wrong you have to return a
|
@@ -53,47 +53,46 @@ module HTTPAuth
|
|
53
53
|
# end
|
54
54
|
class Basic
|
55
55
|
class << self
|
56
|
-
|
57
56
|
# Unpacks the HTTP Basic 'Authorization' credential header
|
58
57
|
#
|
59
58
|
# * <tt>authorization</tt>: The contents of the Authorization header
|
60
59
|
# * Returns a list with two items: the username and password
|
61
|
-
def unpack_authorization(authorization)
|
60
|
+
def unpack_authorization(authorization)
|
62
61
|
d = authorization.split ' '
|
63
|
-
|
62
|
+
fail(ArgumentError, 'HTTPAuth::Basic can only unpack Basic Authentication headers') unless d[0] == 'Basic'
|
64
63
|
Base64.decode64(d[1]).split(':')[0..1]
|
65
64
|
end
|
66
|
-
|
65
|
+
|
67
66
|
# Packs HTTP Basic credentials to an 'Authorization' header
|
68
67
|
#
|
69
68
|
# * <tt>username</tt>: A string with the username
|
70
69
|
# * <tt>password</tt>: A string with the password
|
71
70
|
def pack_authorization(username, password)
|
72
|
-
|
71
|
+
format('Basic %s', Base64.encode64("#{username}:#{password}").gsub("\n", ''))
|
73
72
|
end
|
74
|
-
|
73
|
+
|
75
74
|
# Returns contents for the WWW-authenticate header
|
76
75
|
#
|
77
76
|
# * <tt>realm</tt>: A string with a recognizable title for the restricted resource
|
78
77
|
def pack_challenge(realm)
|
79
|
-
"Basic realm=\"%s\""
|
78
|
+
format("Basic realm=\"%s\"", realm.gsub('"', ''))
|
80
79
|
end
|
81
|
-
|
80
|
+
|
82
81
|
# Returns the name of the realm in a WWW-Authenticate header
|
83
82
|
#
|
84
83
|
# * <tt>authenticate</tt>: The contents of the WWW-Authenticate header
|
85
84
|
def unpack_challenge(authenticate)
|
86
85
|
if authenticate =~ /Basic\srealm=\"([^\"]*)\"/
|
87
|
-
return
|
86
|
+
return Regexp.last_match[1]
|
88
87
|
else
|
89
88
|
if authenticate =~ /^Basic/
|
90
|
-
|
89
|
+
fail(UnwellformedHeader, "Can't parse the WWW-Authenticate header, it's probably not well formed")
|
91
90
|
else
|
92
|
-
|
91
|
+
fail(ArgumentError, 'HTTPAuth::Basic can only unpack Basic Authentication headers')
|
93
92
|
end
|
94
93
|
end
|
95
94
|
end
|
96
|
-
|
95
|
+
|
97
96
|
# Finds and unpacks the authorization credentials in a hash with the CGI enviroment. Returns [nil,nil] if no
|
98
97
|
# credentials were found. See HTTPAuth::CREDENTIAL_HEADERS for supported variable names.
|
99
98
|
#
|
@@ -105,10 +104,10 @@ module HTTPAuth
|
|
105
104
|
# RewriteEngine on
|
106
105
|
# RewriteRule ^admin/ - [E=X-HTTP-AUTHORIZATION:%{HTTP:Authorization}]
|
107
106
|
def get_credentials(env)
|
108
|
-
d = HTTPAuth::CREDENTIAL_HEADERS.inject(false) { |
|
109
|
-
return unpack_authorization(d) unless !d
|
107
|
+
d = HTTPAuth::CREDENTIAL_HEADERS.inject(false) { |a, e| env[e] || a }
|
108
|
+
return unpack_authorization(d) unless !d || d.nil? || d.empty?
|
110
109
|
[nil, nil]
|
111
110
|
end
|
112
111
|
end
|
113
112
|
end
|
114
|
-
end
|
113
|
+
end
|
data/lib/httpauth/constants.rb
CHANGED
@@ -4,11 +4,11 @@
|
|
4
4
|
# For more information see RFC 2617 (http://www.ietf.org/rfc/rfc2617.txt)
|
5
5
|
module HTTPAuth
|
6
6
|
VERSION = '0.2'
|
7
|
-
|
7
|
+
|
8
8
|
CREDENTIAL_HEADERS = %w{REDIRECT_X_HTTP_AUTHORIZATION X-HTTP-AUTHORIZATION X-HTTP_AUTHORIZATION HTTP_AUTHORIZATION}
|
9
|
-
SUPPORTED_SCHEMES = {
|
10
|
-
SUPPORTED_QOPS = [
|
11
|
-
SUPPORTED_ALGORITHMS = [
|
9
|
+
SUPPORTED_SCHEMES = {:basic => 'Basic', :digest => 'Digest'}
|
10
|
+
SUPPORTED_QOPS = %w[auth auth-int]
|
11
|
+
SUPPORTED_ALGORITHMS = %w[MD5 MD5-sess]
|
12
12
|
PREFERRED_QOP = 'auth'
|
13
13
|
PREFERRED_ALGORITHM = 'MD5'
|
14
|
-
end
|
14
|
+
end
|
data/lib/httpauth/digest.rb
CHANGED
@@ -60,66 +60,66 @@ module HTTPAuth
|
|
60
60
|
elsif variant == :challenge
|
61
61
|
encode.merge! :qop => :list_to_comma_quoted_string
|
62
62
|
else
|
63
|
-
|
63
|
+
fail(ArgumentError, "#{variant} is not a valid value for `variant' use :auth, :credentials or :challenge")
|
64
64
|
end
|
65
65
|
(variant == :auth ? '' : 'Digest ') + h.collect do |directive, value|
|
66
66
|
'' << directive.to_s << '=' << if encode[directive]
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
end
|
72
|
-
elsif encode[directive].nil?
|
73
|
-
begin
|
74
|
-
Conversions.quote_string value
|
75
|
-
rescue NoMethodError, ArgumentError
|
76
|
-
raise ArgumentError.new("Can't encode #{directive}(#{value.inspect}) with quote_string")
|
77
|
-
end
|
78
|
-
else
|
79
|
-
value
|
67
|
+
begin
|
68
|
+
Conversions.send encode[directive], value
|
69
|
+
rescue NoMethodError, ArgumentError
|
70
|
+
raise(ArgumentError, "Can't encode #{directive}(#{value.inspect}) with #{encode[directive]}")
|
80
71
|
end
|
81
|
-
|
72
|
+
elsif encode[directive].nil?
|
73
|
+
begin
|
74
|
+
Conversions.quote_string value
|
75
|
+
rescue NoMethodError, ArgumentError
|
76
|
+
raise(ArgumentError, "Can't encode #{directive}(#{value.inspect}) with quote_string")
|
77
|
+
end
|
78
|
+
else
|
79
|
+
value
|
80
|
+
end
|
81
|
+
end.join(', ')
|
82
82
|
end
|
83
83
|
|
84
84
|
# Decodes digest directives from a header. Returns a hash with directives.
|
85
85
|
#
|
86
86
|
# * <tt>directives</tt>: The directives
|
87
87
|
# * <tt>variant</tt>: Specifies whether the directives are for an Authorize header (:credentials),
|
88
|
-
# for a WWW-Authenticate header (:challenge) or for a Authentication-Info header (:auth_info).
|
88
|
+
# for a WWW-Authenticate header (:challenge) or for a Authentication-Info header (:auth_info).
|
89
89
|
def decode_directives(directives, variant)
|
90
|
-
|
90
|
+
fail(HTTPAuth::UnwellformedHeader, "Can't decode directives which are nil") if directives.nil?
|
91
91
|
decode = {:domain => :space_quoted_string_to_list, :algorithm => false, :stale => :str_to_bool, :nc => :hex_to_int}
|
92
92
|
if [:credentials, :auth].include? variant
|
93
93
|
decode.merge! :qop => false
|
94
94
|
elsif variant == :challenge
|
95
95
|
decode.merge! :qop => :comma_quoted_string_to_list
|
96
96
|
else
|
97
|
-
|
97
|
+
fail(ArgumentError, "#{variant} is not a valid value for `variant' use :auth, :credentials or :challenge")
|
98
98
|
end
|
99
|
-
|
100
|
-
start = 0
|
101
|
-
unless variant == :auth
|
99
|
+
|
100
|
+
start = 0
|
101
|
+
unless variant == :auth
|
102
102
|
# The first six characters are 'Digest '
|
103
103
|
start = 6
|
104
104
|
scheme = directives[0..6].strip
|
105
|
-
|
105
|
+
fail(HTTPAuth::UnwellformedHeader, "Scheme should be Digest, server responded with `#{directives}'") unless scheme == 'Digest'
|
106
106
|
end
|
107
|
-
|
107
|
+
|
108
108
|
# The rest are the directives
|
109
109
|
# TODO: split is ugly, I want a real parser (:
|
110
|
-
directives[start..-1].split(',').inject({}) do |h,part|
|
110
|
+
directives[start..-1].split(',').inject({}) do |h, part|
|
111
111
|
parts = part.split('=')
|
112
112
|
name = parts[0].strip.intern
|
113
113
|
value = parts[1..-1].join('=').strip
|
114
|
-
|
114
|
+
|
115
115
|
# --- HACK
|
116
116
|
# IE and Safari qoute qop values
|
117
117
|
# IE also quotes algorithm values
|
118
|
-
if variant != :challenge
|
118
|
+
if variant != :challenge && [:qop, :algorithm].include?(name) && value =~ /^\"[^\"]+\"$/
|
119
119
|
value = Conversions.unquote_string(value)
|
120
120
|
end
|
121
121
|
# --- END HACK
|
122
|
-
|
122
|
+
|
123
123
|
if decode[name]
|
124
124
|
h[name] = Conversions.send decode[name], value
|
125
125
|
elsif decode[name].nil?
|
@@ -130,19 +130,25 @@ module HTTPAuth
|
|
130
130
|
h
|
131
131
|
end
|
132
132
|
end
|
133
|
-
|
133
|
+
|
134
134
|
# Concat arguments the way it's done frequently in the Digest spec.
|
135
135
|
#
|
136
136
|
# digest_concat('a', 'b') #=> "a:b"
|
137
137
|
# digest_concat('a', 'b', c') #=> "a:b:c"
|
138
|
-
def digest_concat(*args)
|
139
|
-
|
138
|
+
def digest_concat(*args)
|
139
|
+
args.join ':'
|
140
|
+
end
|
141
|
+
|
140
142
|
# Calculate the MD5 hexdigest for the string data
|
141
|
-
def digest_h(data)
|
142
|
-
|
143
|
+
def digest_h(data)
|
144
|
+
::Digest::MD5.hexdigest data
|
145
|
+
end
|
146
|
+
|
143
147
|
# Calculate the KD value of a secret and data as explained in the RFC.
|
144
|
-
def digest_kd(secret, data)
|
145
|
-
|
148
|
+
def digest_kd(secret, data)
|
149
|
+
digest_h digest_concat(secret, data)
|
150
|
+
end
|
151
|
+
|
146
152
|
# Calculate the Digest for the credentials
|
147
153
|
def htdigest(username, realm, password)
|
148
154
|
digest_h digest_concat(username, realm, password)
|
@@ -162,7 +168,7 @@ module HTTPAuth
|
|
162
168
|
h[:digest] || htdigest(h[:username], h[:realm], h[:password])
|
163
169
|
end
|
164
170
|
end
|
165
|
-
|
171
|
+
|
166
172
|
# Calculate the H(A2) for the Authorize header as explained in the RFC.
|
167
173
|
def request_digest_a2(h)
|
168
174
|
# TODO: check for known qop values (look out for the safari qop quote bug)
|
@@ -186,7 +192,7 @@ module HTTPAuth
|
|
186
192
|
#
|
187
193
|
# * <tt>variant</tt>: Either <tt>:request</tt> or <tt>:response</tt>, as seen from the server.
|
188
194
|
def calculate_digest(h, s, variant)
|
189
|
-
|
195
|
+
fail(ArgumentError, "Variant should be either :request or :response, not #{variant}") unless [:request, :response].include?(variant)
|
190
196
|
# Compatability with RFC 2069
|
191
197
|
if h[:qop].nil?
|
192
198
|
digest_kd digest_a1(h, s), digest_concat(
|
@@ -203,7 +209,7 @@ module HTTPAuth
|
|
203
209
|
)
|
204
210
|
end
|
205
211
|
end
|
206
|
-
|
212
|
+
|
207
213
|
# Return a hash with the keys in <tt>keys</tt> found in <tt>h</tt>.
|
208
214
|
#
|
209
215
|
# Example
|
@@ -211,16 +217,16 @@ module HTTPAuth
|
|
211
217
|
# filter_h_on({1=>1,2=>2}, [1]) #=> {1=>1}
|
212
218
|
# filter_h_on({1=>1,2=>2}, [1, 2]) #=> {1=>1,2=>2}
|
213
219
|
def filter_h_on(h, keys)
|
214
|
-
h.inject({}) { |
|
220
|
+
h.inject({}) { |a, e| keys.include?(e[0]) ? a.merge(e[0] => e[1]) : a }
|
215
221
|
end
|
216
|
-
|
222
|
+
|
217
223
|
# Create a nonce value of the time and a salt. The nonce is created in such a
|
218
224
|
# way that the issuer can check the age of the nonce.
|
219
225
|
#
|
220
226
|
# * <tt>salt</tt>: A reasonably long passphrase known only to the issuer.
|
221
227
|
def create_nonce(salt)
|
222
228
|
now = Time.now
|
223
|
-
time = now.strftime(
|
229
|
+
time = now.strftime('%Y-%m-%d %H:%M:%S').to_s + ':' + now.usec.to_s
|
224
230
|
Base64.encode64(
|
225
231
|
digest_concat(
|
226
232
|
time,
|
@@ -228,20 +234,21 @@ module HTTPAuth
|
|
228
234
|
)
|
229
235
|
).gsub("\n", '')[0..-3]
|
230
236
|
end
|
231
|
-
|
237
|
+
|
232
238
|
# Create a 32 character long opaque string with a 'random' value
|
233
239
|
def create_opaque
|
234
|
-
s = []
|
240
|
+
s = []
|
241
|
+
16.times { s << rand(127).chr }
|
235
242
|
digest_h s.join
|
236
243
|
end
|
237
244
|
end
|
238
245
|
end
|
239
|
-
|
246
|
+
|
240
247
|
# Superclass for all the header container classes
|
241
248
|
class AbstractHeader
|
242
249
|
# holds directives and values for digest calculation
|
243
250
|
attr_reader :h
|
244
|
-
|
251
|
+
|
245
252
|
# Redirects attribute messages to the internal directives
|
246
253
|
#
|
247
254
|
# Example:
|
@@ -257,17 +264,16 @@ module HTTPAuth
|
|
257
264
|
# c.username = 'Mary'
|
258
265
|
# c.username #=> 'Mary'
|
259
266
|
def method_missing(m, *a)
|
260
|
-
if ((m.to_s =~ /^(.*)=$/) == 0)
|
261
|
-
@h[
|
267
|
+
if ((m.to_s =~ /^(.*)=$/) == 0) && @h.keys.include?(Regexp.last_match[1].intern)
|
268
|
+
@h[Regexp.last_match[1].intern] = a[0]
|
262
269
|
elsif @h.keys.include? m
|
263
270
|
@h[m]
|
264
271
|
else
|
265
|
-
|
272
|
+
fail(NameError, "undefined method `#{m}' for #{self}")
|
266
273
|
end
|
267
274
|
end
|
268
275
|
end
|
269
|
-
|
270
|
-
|
276
|
+
|
271
277
|
# The Credentials class handlers the Authorize header. The Authorize header is sent by a client who wants to
|
272
278
|
# let the server know he has the credentials needed to access a resource.
|
273
279
|
#
|
@@ -275,34 +281,34 @@ module HTTPAuth
|
|
275
281
|
class Credentials < AbstractHeader
|
276
282
|
# Holds an explanation why <tt>validate</tt> returned false.
|
277
283
|
attr_reader :reason
|
278
|
-
|
284
|
+
|
279
285
|
# Parses the information from an Authorize header and creates a new Credentials instance with the information.
|
280
286
|
# The options hash allows you to specify additional information.
|
281
287
|
#
|
282
288
|
# * <tt>authorization</tt>: The contents of the Authorize header
|
283
289
|
# See <tt>initialize</tt> for valid options.
|
284
|
-
def self.from_header(authorization, options={})
|
290
|
+
def self.from_header(authorization, options = {})
|
285
291
|
new Utils.decode_directives(authorization, :credentials), options
|
286
292
|
end
|
287
|
-
|
293
|
+
|
288
294
|
# Creates a new Credential instance based on a Challenge instance.
|
289
295
|
#
|
290
296
|
# * <tt>challenge</tt>: A Challenge instance
|
291
297
|
# See <tt>initialize</tt> for valid options.
|
292
|
-
def self.from_challenge(challenge, options={})
|
298
|
+
def self.from_challenge(challenge, options = {})
|
293
299
|
credentials = new challenge.h
|
294
300
|
credentials.update_from_challenge! options
|
295
301
|
credentials
|
296
302
|
end
|
297
303
|
|
298
|
-
def self.load(filename, options={})
|
304
|
+
def self.load(filename, options = {})
|
299
305
|
h = nil
|
300
306
|
File.open(filename, 'r') do |f|
|
301
307
|
h = Marshal.load f
|
302
308
|
end
|
303
309
|
new h, options
|
304
310
|
end
|
305
|
-
|
311
|
+
|
306
312
|
# Create a new instance.
|
307
313
|
#
|
308
314
|
# * <tt>h</tt>: A Hash with directives, normally this is filled with the directives coming from a Challenge instance.
|
@@ -314,26 +320,26 @@ module HTTPAuth
|
|
314
320
|
# * <tt>:uri</tt>: Mostly set by the client to send the uri
|
315
321
|
# * <tt>:method</tt>: The HTTP Method used by the client to send the request, this should be an uppercase string
|
316
322
|
# with the name of the verb.
|
317
|
-
def initialize(h, options={})
|
323
|
+
def initialize(h, options = {})
|
318
324
|
@h = h
|
319
325
|
@h.merge! options
|
320
326
|
session = Session.new h[:opaque], :tmpdir => options[:tmpdir]
|
321
327
|
@s = session.load
|
322
328
|
@reason = 'There has been no validation yet'
|
323
329
|
end
|
324
|
-
|
330
|
+
|
325
331
|
# Convenience method, basically an alias for <code>validate(options.merge(:password => password))</code>
|
326
|
-
def validate_password(password, options={})
|
332
|
+
def validate_password(password, options = {})
|
327
333
|
options[:password] = password
|
328
334
|
validate(options)
|
329
335
|
end
|
330
|
-
|
336
|
+
|
331
337
|
# Convenience method, basically an alias for <code>validate(options.merge(:digest => digest))</code>
|
332
|
-
def validate_digest(digest, options={})
|
338
|
+
def validate_digest(digest, options = {})
|
333
339
|
options[:digest] = digest
|
334
340
|
validate(options)
|
335
341
|
end
|
336
|
-
|
342
|
+
|
337
343
|
# Validates the credential information stored in the Credentials instance. Returns <tt>true</tt> or
|
338
344
|
# <tt>false</tt>. You can read the ue
|
339
345
|
#
|
@@ -346,9 +352,9 @@ module HTTPAuth
|
|
346
352
|
# provided.
|
347
353
|
def validate(options)
|
348
354
|
ho = @h.merge(options)
|
349
|
-
|
350
|
-
|
351
|
-
|
355
|
+
fail(ArgumentError, "You have to set the :request_body value if you want to use :qop => 'auth-int'") if @h[:qop] == 'auth-int' && ho[:request_body].nil?
|
356
|
+
fail(ArgumentError, 'Please specify the request method :method (ie. GET)') if ho[:method].nil?
|
357
|
+
|
352
358
|
calculated_response = Utils.calculate_digest(ho, @s, :request)
|
353
359
|
if ho[:response] == calculated_response
|
354
360
|
@reason = ''
|
@@ -358,13 +364,13 @@ module HTTPAuth
|
|
358
364
|
end
|
359
365
|
false
|
360
366
|
end
|
361
|
-
|
367
|
+
|
362
368
|
# Encodeds directives and returns a string that can be used in the Authorize header
|
363
369
|
def to_header
|
364
370
|
Utils.encode_directives Utils.filter_h_on(@h,
|
365
|
-
|
371
|
+
[:username, :realm, :nonce, :uri, :response, :algorithm, :cnonce, :opaque, :qop, :nc]), :credentials
|
366
372
|
end
|
367
|
-
|
373
|
+
|
368
374
|
# Updates @h from options, generally called after an instance was created with <tt>from_challenge</tt>.
|
369
375
|
def update_from_challenge!(options)
|
370
376
|
# TODO: integrity checks
|
@@ -375,17 +381,17 @@ module HTTPAuth
|
|
375
381
|
@h[:method] = options[:method]
|
376
382
|
@h[:request_body] = options[:request_body]
|
377
383
|
unless @h[:qop].nil?
|
378
|
-
# Determine the QOP
|
379
|
-
if !options[:qop].nil?
|
384
|
+
# Determine the QOP
|
385
|
+
if !options[:qop].nil? && @h[:qop].include?(options[:qop])
|
380
386
|
@h[:qop] = options[:qop]
|
381
387
|
elsif @h[:qop].include?(HTTPAuth::PREFERRED_QOP)
|
382
388
|
@h[:qop] = HTTPAuth::PREFERRED_QOP
|
383
389
|
else
|
384
|
-
qop = @h[:qop].detect { |
|
385
|
-
|
386
|
-
@h[:qop]
|
390
|
+
qop = @h[:qop].detect { |qop_field| HTTPAuth::SUPPORTED_QOPS.include? qop_field }
|
391
|
+
if qop.nil?
|
392
|
+
fail(UnsupportedError, "HTTPAuth doesn't support any of the proposed qop values: #{@h[:qop].inspect}")
|
387
393
|
else
|
388
|
-
|
394
|
+
@h[:qop] = qop
|
389
395
|
end
|
390
396
|
end
|
391
397
|
@h[:cnonce] ||= Utils.create_nonce options[:salt]
|
@@ -399,25 +405,23 @@ module HTTPAuth
|
|
399
405
|
Marshal.dump(Utils.filter_h_on(@h, [:username, :realm, :nonce, :algorithm, :cnonce, :opaque, :qop, :nc]), f)
|
400
406
|
end
|
401
407
|
end
|
402
|
-
|
403
408
|
end
|
404
|
-
|
409
|
+
|
405
410
|
# The Challenge class handlers the WWW-Authenticate header. The WWW-Authenticate header is sent by a server when
|
406
411
|
# accessing a resource without credentials is prohibided. The header should always be sent together with a 401
|
407
412
|
# status.
|
408
413
|
#
|
409
414
|
# See the Digest module for examples
|
410
415
|
class Challenge < AbstractHeader
|
411
|
-
|
412
416
|
# Parses the information from a WWW-Authenticate header and creates a new WWW-Authenticate instance with this
|
413
417
|
# data.
|
414
418
|
#
|
415
419
|
# * <tt>challenge</tt>: The contents of a WWW-Authenticate header
|
416
420
|
# See <tt>initialize</tt> for valid options.
|
417
|
-
def self.from_header(challenge, options={})
|
421
|
+
def self.from_header(challenge, options = {})
|
418
422
|
new Utils.decode_directives(challenge, :challenge), options
|
419
423
|
end
|
420
|
-
|
424
|
+
|
421
425
|
# Create a new instance.
|
422
426
|
#
|
423
427
|
# * <tt>h</tt>: A Hash with directives, normally this is filled with directives coming from a Challenge instance.
|
@@ -425,18 +429,18 @@ module HTTPAuth
|
|
425
429
|
# * <tt>:realm</tt>: The name of the realm the client should authenticate for. The RFC suggests to use a string
|
426
430
|
# like 'admin@yourhost.domain.com'. Be sure to use a reasonably long string to avoid brute force attacks.
|
427
431
|
# * <tt>:qop</tt>: A list with supported qop values. For example: <code>['auth-int']</code>. This will default
|
428
|
-
# to <code>['auth']</code>. Although this implementation supports both auth and auth-int, most
|
432
|
+
# to <code>['auth']</code>. Although this implementation supports both auth and auth-int, most
|
429
433
|
# implementations don't. Some implementations get confused when they receive anything but 'auth'. For
|
430
434
|
# maximum compatibility you should leave this setting alone.
|
431
435
|
# * <tt>:algorithm</tt>: The preferred algorithm for calculating the digest. For
|
432
436
|
# example: <code>'MD5-sess'</code>. This will default to <code>'MD5'</code>. For
|
433
437
|
# maximum compatibility you should leave this setting alone.
|
434
438
|
#
|
435
|
-
def initialize(h, options={})
|
439
|
+
def initialize(h, options = {})
|
436
440
|
@h = h
|
437
441
|
@h.merge! options
|
438
442
|
end
|
439
|
-
|
443
|
+
|
440
444
|
# Encodes directives and returns a string that can be used as the WWW-Authenticate header
|
441
445
|
def to_header
|
442
446
|
@h[:nonce] ||= Utils.create_nonce @h[:salt]
|
@@ -444,36 +448,35 @@ module HTTPAuth
|
|
444
448
|
@h[:algorithm] ||= HTTPAuth::PREFERRED_ALGORITHM
|
445
449
|
@h[:qop] ||= [HTTPAuth::PREFERRED_QOP]
|
446
450
|
Utils.encode_directives Utils.filter_h_on(@h,
|
447
|
-
|
451
|
+
[:realm, :domain, :nonce, :opaque, :stale, :algorithm, :qop]), :challenge
|
448
452
|
end
|
449
453
|
end
|
450
|
-
|
454
|
+
|
451
455
|
# The AuthenticationInfo class handles the Authentication-Info header. Sending Authentication-Info headers will
|
452
|
-
# allow the client to check the integrity of the response, but it isn't compulsory and will get in the way of
|
456
|
+
# allow the client to check the integrity of the response, but it isn't compulsory and will get in the way of
|
453
457
|
# pipelined retrieval of resources.
|
454
458
|
#
|
455
459
|
# See the Digest module for examples
|
456
460
|
class AuthenticationInfo < AbstractHeader
|
457
|
-
|
458
461
|
# Parses the information from a Authentication-Info header and creates a new AuthenticationInfo instance with
|
459
462
|
# this data.
|
460
463
|
#
|
461
464
|
# * <tt>auth_info</tt>: The contents of the Authentication-Info header
|
462
465
|
# See <tt>initialize</tt> for valid options.
|
463
|
-
def self.from_header(auth_info, options={})
|
466
|
+
def self.from_header(auth_info, options = {})
|
464
467
|
new Utils.decode_directives(auth_info, :auth), options
|
465
468
|
end
|
466
|
-
|
469
|
+
|
467
470
|
# Creates a new AuthenticationInfo instance based on the information from Credentials instance.
|
468
471
|
#
|
469
472
|
# * <tt>credentials</tt>: A Credentials instance
|
470
473
|
# See <tt>initialize</tt> for valid options.
|
471
|
-
def self.from_credentials(credentials, options={})
|
474
|
+
def self.from_credentials(credentials, options = {})
|
472
475
|
auth_info = new credentials.h
|
473
476
|
auth_info.update_from_credentials! options
|
474
477
|
auth_info
|
475
478
|
end
|
476
|
-
|
479
|
+
|
477
480
|
# Create a new instance.
|
478
481
|
#
|
479
482
|
# * <tt>h</tt>: A Hash with directives, normally this is filled with the directives coming from a
|
@@ -482,17 +485,17 @@ module HTTPAuth
|
|
482
485
|
# * <tt>:digest</tt>: The digest for the specified username and realm.
|
483
486
|
# * <tt>:response_body</tt> The body of the response that's going to be sent to the client. This is a
|
484
487
|
# compulsory option if the qop directive is 'auth-int'.
|
485
|
-
def initialize(h, options={})
|
488
|
+
def initialize(h, options = {})
|
486
489
|
@h = h
|
487
490
|
@h.merge! options
|
488
491
|
end
|
489
|
-
|
492
|
+
|
490
493
|
# Encodes directives and returns a string that can be used as the AuthorizationInfo header
|
491
494
|
def to_header
|
492
495
|
Utils.encode_directives Utils.filter_h_on(@h,
|
493
|
-
|
496
|
+
[:nextnonce, :qop, :rspauth, :cnonce, :nc]), :auth
|
494
497
|
end
|
495
|
-
|
498
|
+
|
496
499
|
# Updates @h from options, generally called after an instance was created with <tt>from_credentials</tt>.
|
497
500
|
def update_from_credentials!(options)
|
498
501
|
# TODO: update @h after nonce invalidation
|
@@ -512,16 +515,14 @@ module HTTPAuth
|
|
512
515
|
# * <tt>:nonce</tt>:nonce
|
513
516
|
def validate(options)
|
514
517
|
ho = @h.merge(options)
|
515
|
-
|
518
|
+
@h[:rspauth] == Utils.calculate_digest(ho, @s, :response)
|
516
519
|
end
|
517
|
-
|
518
520
|
end
|
519
|
-
|
521
|
+
|
520
522
|
# Conversion for a number of internal data structures to and from directives in the headers. Implementations
|
521
523
|
# shouldn't have to call any methods on Conversions.
|
522
524
|
class Conversions
|
523
525
|
class << self
|
524
|
-
|
525
526
|
# Adds quotes around the string
|
526
527
|
def quote_string(str)
|
527
528
|
"\"#{str.gsub(/\"/, '')}\""
|
@@ -529,7 +530,7 @@ module HTTPAuth
|
|
529
530
|
|
530
531
|
# Removes quotes from around a string
|
531
532
|
def unquote_string(str)
|
532
|
-
str =~ /^\"([^\"]*)\"$/ ?
|
533
|
+
str =~ /^\"([^\"]*)\"$/ ? Regexp.last_match[1] : str
|
533
534
|
end
|
534
535
|
|
535
536
|
# Creates an int value from hex values
|
@@ -546,7 +547,7 @@ module HTTPAuth
|
|
546
547
|
def str_to_bool(str)
|
547
548
|
str == 'true'
|
548
549
|
end
|
549
|
-
|
550
|
+
|
550
551
|
# Creates a string value from a boolean => 'true' or 'false'
|
551
552
|
def bool_to_str(bool)
|
552
553
|
bool ? 'true' : 'false'
|
@@ -578,41 +579,39 @@ module HTTPAuth
|
|
578
579
|
class Session
|
579
580
|
attr_accessor :opaque
|
580
581
|
attr_accessor :options
|
581
|
-
|
582
|
+
|
582
583
|
# Initializes the new Session object.
|
583
584
|
#
|
584
585
|
# * <tt>opaque</tt> - A string to identify the session. This would normally be the <tt>opaque</tt> sent by the
|
585
586
|
# client, but it could also be an identifier sent through a different mechanism.
|
586
587
|
# * <tt>options</tt> - Additional options
|
587
588
|
# * <tt>:tmpdir</tt> A tempory directory for storing the session data. Dir::tmpdir is the default.
|
588
|
-
def initialize(opaque, options={})
|
589
|
+
def initialize(opaque, options = {})
|
589
590
|
self.opaque = opaque
|
590
591
|
self.options = options
|
591
592
|
end
|
592
|
-
|
593
|
+
|
593
594
|
# Associates the new data to the session and removes the old
|
594
595
|
def save(data)
|
595
596
|
File.open(filename, 'w') do |f|
|
596
597
|
f.write Marshal.dump(data)
|
597
598
|
end
|
598
599
|
end
|
599
|
-
|
600
|
+
|
600
601
|
# Returns the data from this session
|
601
602
|
def load
|
602
|
-
|
603
|
-
|
604
|
-
Marshal.load f.read
|
605
|
-
end
|
606
|
-
rescue Errno::ENOENT
|
607
|
-
{}
|
603
|
+
File.open(filename, 'r') do |f|
|
604
|
+
Marshal.load f.read
|
608
605
|
end
|
606
|
+
rescue Errno::ENOENT
|
607
|
+
{}
|
609
608
|
end
|
610
|
-
|
611
|
-
|
612
|
-
|
609
|
+
|
610
|
+
protected
|
611
|
+
|
613
612
|
# The filename from which the session will be saved and read from
|
614
613
|
def filename
|
615
|
-
"#{options[:tmpdir] || Dir
|
614
|
+
"#{options[:tmpdir] || Dir.tmpdir}/ruby_digest_cache.#{opaque}"
|
616
615
|
end
|
617
616
|
end
|
618
617
|
end
|
data/lib/httpauth/exceptions.rb
CHANGED
metadata
CHANGED
@@ -1,72 +1,68 @@
|
|
1
|
-
--- !ruby/object:Gem::Specification
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
2
|
name: httpauth
|
3
|
-
version: !ruby/object:Gem::Version
|
4
|
-
|
5
|
-
prerelease:
|
6
|
-
segments:
|
7
|
-
- 0
|
8
|
-
- 2
|
9
|
-
- 0
|
10
|
-
version: 0.2.0
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.2.1
|
11
5
|
platform: ruby
|
12
|
-
authors:
|
6
|
+
authors:
|
13
7
|
- Manfred Stienstra
|
14
8
|
autorequire:
|
15
9
|
bindir: bin
|
16
10
|
cert_chain: []
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
11
|
+
date: 2014-01-20 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.0'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.0'
|
21
27
|
description: Library for the HTTP Authentication protocol (RFC 2617)
|
22
28
|
email: manfred@fngtpspec.com
|
23
29
|
executables: []
|
24
|
-
|
25
30
|
extensions: []
|
26
|
-
|
27
|
-
extra_rdoc_files:
|
31
|
+
extra_rdoc_files:
|
28
32
|
- README.md
|
29
33
|
- LICENSE
|
30
|
-
files:
|
31
|
-
- README.md
|
34
|
+
files:
|
32
35
|
- LICENSE
|
36
|
+
- README.md
|
37
|
+
- lib/httpauth.rb
|
33
38
|
- lib/httpauth/basic.rb
|
34
39
|
- lib/httpauth/constants.rb
|
35
40
|
- lib/httpauth/digest.rb
|
36
41
|
- lib/httpauth/exceptions.rb
|
37
|
-
- lib/httpauth.rb
|
38
42
|
homepage: https://github.com/Manfred/HTTPauth
|
39
|
-
licenses:
|
40
|
-
|
43
|
+
licenses:
|
44
|
+
- MIT
|
45
|
+
metadata: {}
|
41
46
|
post_install_message:
|
42
|
-
rdoc_options:
|
43
|
-
- --charset=utf-8
|
44
|
-
require_paths:
|
47
|
+
rdoc_options:
|
48
|
+
- "--charset=utf-8"
|
49
|
+
require_paths:
|
45
50
|
- lib
|
46
|
-
required_ruby_version: !ruby/object:Gem::Requirement
|
47
|
-
|
48
|
-
requirements:
|
51
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
52
|
+
requirements:
|
49
53
|
- - ">="
|
50
|
-
- !ruby/object:Gem::Version
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
version: "0"
|
55
|
-
required_rubygems_version: !ruby/object:Gem::Requirement
|
56
|
-
none: false
|
57
|
-
requirements:
|
54
|
+
- !ruby/object:Gem::Version
|
55
|
+
version: '0'
|
56
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
57
|
+
requirements:
|
58
58
|
- - ">="
|
59
|
-
- !ruby/object:Gem::Version
|
60
|
-
|
61
|
-
segments:
|
62
|
-
- 0
|
63
|
-
version: "0"
|
59
|
+
- !ruby/object:Gem::Version
|
60
|
+
version: '0'
|
64
61
|
requirements: []
|
65
|
-
|
66
62
|
rubyforge_project:
|
67
|
-
rubygems_version:
|
63
|
+
rubygems_version: 2.2.0
|
68
64
|
signing_key:
|
69
|
-
specification_version:
|
70
|
-
summary: HTTPauth is a library supporting the full HTTP Authentication protocol as
|
65
|
+
specification_version: 4
|
66
|
+
summary: HTTPauth is a library supporting the full HTTP Authentication protocol as
|
67
|
+
specified in RFC 2617; both Digest Authentication and Basic Authentication.
|
71
68
|
test_files: []
|
72
|
-
|