httpauth 0.2.0 → 0.2.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
-
|