lib/httpclient/auth.rb in httpclient-2.1.4 vs lib/httpclient/auth.rb in httpclient-2.1.5
- old
+ new
@@ -59,25 +59,28 @@
#
# Authentication filter is implemented using request filter of HTTPClient.
# It traps HTTP response header and maintains authentication state, and
# traps HTTP request header for inserting necessary authentication header.
#
- # WWWAuth has sub filters (BasicAuth, DigestAuth, and NegotiateAuth) and
- # delegates some operations to it.
+ # WWWAuth has sub filters (BasicAuth, DigestAuth, NegotiateAuth and
+ # SSPINegotiateAuth) and delegates some operations to it.
# NegotiateAuth requires 'ruby/ntlm' module.
+ # SSPINegotiateAuth requires 'win32/sspi' module.
class WWWAuth < AuthFilterBase
attr_reader :basic_auth
attr_reader :digest_auth
attr_reader :negotiate_auth
+ attr_reader :sspi_negotiate_auth
# Creates new WWWAuth.
def initialize
@basic_auth = BasicAuth.new
@digest_auth = DigestAuth.new
@negotiate_auth = NegotiateAuth.new
+ @sspi_negotiate_auth = SSPINegotiateAuth.new
# sort authenticators by priority
- @authenticator = [@negotiate_auth, @digest_auth, @basic_auth]
+ @authenticator = [@negotiate_auth, @sspi_negotiate_auth, @digest_auth, @basic_auth]
end
# Resets challenge state. See sub filters for more details.
def reset_challenge
@authenticator.each do |auth|
@@ -315,32 +318,41 @@
# Thanks!
# supported algorithm: MD5 only for now
def calc_cred(method, uri, user, passwd, param)
a_1 = "#{user}:#{param['realm']}:#{passwd}"
a_2 = "#{method}:#{uri.path}"
+ nonce = param['nonce']
+ cnonce = generate_cnonce()
@nonce_count += 1
message_digest = []
message_digest << Digest::MD5.hexdigest(a_1)
- message_digest << param['nonce']
+ message_digest << nonce
message_digest << ('%08x' % @nonce_count)
- message_digest << param['nonce']
+ message_digest << cnonce
message_digest << param['qop']
message_digest << Digest::MD5.hexdigest(a_2)
header = []
header << "username=\"#{user}\""
header << "realm=\"#{param['realm']}\""
- header << "nonce=\"#{param['nonce']}\""
+ header << "nonce=\"#{nonce}\""
header << "uri=\"#{uri.path}\""
- header << "cnonce=\"#{param['nonce']}\""
+ header << "cnonce=\"#{cnonce}\""
header << "nc=#{'%08x' % @nonce_count}"
header << "qop=\"#{param['qop']}\""
header << "response=\"#{Digest::MD5.hexdigest(message_digest.join(":"))}\""
header << "algorithm=\"MD5\""
header << "opaque=\"#{param['opaque']}\"" if param.key?('opaque')
header.join(", ")
end
+ # cf. WEBrick::HTTPAuth::DigestAuth#generate_next_nonce(aTime)
+ def generate_cnonce
+ now = "%012d" % Time.now.to_i
+ pk = Digest::MD5.hexdigest([now, self.__id__, Process.pid, rand(65535)].join)[0, 32]
+ [now + ':' + pk].pack('m*').chop
+ end
+
def parse_challenge_param(param_str)
param = {}
param_str.scan(/\s*([^\,]+(?:\\.[^\,]*)*)/).each do |str|
key, value = str[0].scan(/\A([^=]+)=(.*)\z/)[0]
if /\A"(.*)"\z/ =~ value
@@ -479,10 +491,10 @@
authenticator = param[:authenticator]
authphrase = param[:authphrase]
case state
when :init
authenticator = param[:authenticator] = Win32::SSPI::NegotiateAuth.new
- return authenticator.get_initial_token
+ return authenticator.get_initial_token(@scheme)
when :response
@challenge.delete(domain_uri)
return authenticator.complete_authentication(authphrase)
end
nil