File: //usr/lib/ruby/vendor_ruby/net/http/digest_auth.rb
require 'cgi'
require 'digest'
require 'monitor'
require 'net/http'
require 'securerandom'
##
# An implementation of RFC 2617 Digest Access Authentication.
#
# http://www.rfc-editor.org/rfc/rfc2617.txt
#
# Here is a sample usage of DigestAuth on Net::HTTP:
#
# require 'uri'
# require 'net/http'
# require 'net/http/digest_auth'
#
# digest_auth = Net::HTTP::DigestAuth.new
#
# uri = URI.parse 'http://localhost:8000/'
# uri.user = 'username'
# uri.password = 'password'
#
# h = Net::HTTP.new uri.host, uri.port
#
# req = Net::HTTP::Get.new uri.request_uri
#
# res = h.request req
# # res is a 401 response with a WWW-Authenticate header
#
# auth = digest_auth.auth_header uri, res['www-authenticate'], 'GET'
#
# # create a new request with the Authorization header
# req = Net::HTTP::Get.new uri.request_uri
# req.add_field 'Authorization', auth
#
# # re-issue request with Authorization
# res = h.request req
class Net::HTTP::DigestAuth
include MonitorMixin
##
# DigestAuth error class
class Error < RuntimeError; end
##
# Version of Net::HTTP::DigestAuth you are using
VERSION = '1.4'
##
# Creates a new DigestAuth header creator.
def initialize ignored = :ignored
mon_initialize
@nonce_count = -1
end
##
# Creates a digest auth header for +uri+ from the +www_authenticate+ header
# for HTTP method +method+.
#
# The result of this method should be sent along with the HTTP request as
# the "Authorization" header. In Net::HTTP this will look like:
#
# request.add_field 'Authorization', digest_auth.auth_header # ...
#
# See Net::HTTP::DigestAuth for a complete example.
#
# IIS servers handle the "qop" parameter of digest authentication
# differently so you may need to set +iis+ to true for such servers.
def auth_header uri, www_authenticate, method, iis = false
nonce_count = next_nonce
user = CGI.unescape uri.user
password = CGI.unescape uri.password
www_authenticate =~ /^(\w+) (.*)/
challenge = $2
params = {}
challenge.gsub(/(\w+)="(.*?)"/) { params[$1] = $2 }
challenge =~ /algorithm="?(.*?)"?([, ]|$)/
params['algorithm'] = $1 || 'MD5'
if params['algorithm'] =~ /(.*?)(-sess)?$/
algorithm = case $1
when 'MD5' then Digest::MD5
when 'SHA1' then Digest::SHA1
when 'SHA2' then Digest::SHA2
when 'SHA256' then Digest::SHA256
when 'SHA384' then Digest::SHA384
when 'SHA512' then Digest::SHA512
when 'RMD160' then Digest::RMD160
else raise Error, "unknown algorithm \"#{$1}\""
end
sess = $2
end
qop = params['qop']
cnonce = make_cnonce if qop or sess
a1 = if sess then
[ algorithm.hexdigest("#{user}:#{params['realm']}:#{password}"),
params['nonce'],
cnonce,
].join ':'
else
"#{user}:#{params['realm']}:#{password}"
end
ha1 = algorithm.hexdigest a1
ha2 = algorithm.hexdigest "#{method}:#{uri.request_uri}"
request_digest = [ha1, params['nonce']]
request_digest.push(('%08x' % nonce_count), cnonce, qop) if qop
request_digest << ha2
request_digest = request_digest.join ':'
header = [
"Digest username=\"#{user}\"",
"realm=\"#{params['realm']}\"",
"algorithm=#{params['algorithm']}",
if qop.nil? then
elsif iis then
"qop=\"#{qop}\""
else
"qop=#{qop}"
end,
"uri=\"#{uri.request_uri}\"",
"nonce=\"#{params['nonce']}\"",
if qop then
[
"nc=#{'%08x' % @nonce_count}",
"cnonce=\"#{cnonce}\"",
]
end,
"response=\"#{algorithm.hexdigest(request_digest)[0, 32]}\"",
if params.key? 'opaque' then
"opaque=\"#{params['opaque']}\""
end
].compact
header.join ', '
end
##
# Creates a client nonce value that is used across all requests based on the
# current time, process id and a random number
def make_cnonce
Digest::MD5.hexdigest [
Time.now.to_i,
$$,
SecureRandom.random_number(2**32),
].join ':'
end
def next_nonce
synchronize do
@nonce_count += 1
end
end
end