summaryrefslogtreecommitdiff
path: root/trunk/lib/webrick/httpauth/digestauth.rb
diff options
context:
space:
mode:
Diffstat (limited to 'trunk/lib/webrick/httpauth/digestauth.rb')
-rw-r--r--trunk/lib/webrick/httpauth/digestauth.rb344
1 files changed, 0 insertions, 344 deletions
diff --git a/trunk/lib/webrick/httpauth/digestauth.rb b/trunk/lib/webrick/httpauth/digestauth.rb
deleted file mode 100644
index 2bc3e97817..0000000000
--- a/trunk/lib/webrick/httpauth/digestauth.rb
+++ /dev/null
@@ -1,344 +0,0 @@
-#
-# httpauth/digestauth.rb -- HTTP digest access authentication
-#
-# Author: IPR -- Internet Programming with Ruby -- writers
-# Copyright (c) 2003 Internet Programming with Ruby writers.
-# Copyright (c) 2003 H.M.
-#
-# The original implementation is provided by H.M.
-# URL: http://rwiki.jin.gr.jp/cgi-bin/rw-cgi.rb?cmd=view;name=
-# %C7%A7%BE%DA%B5%A1%C7%BD%A4%F2%B2%FE%C2%A4%A4%B7%A4%C6%A4%DF%A4%EB
-#
-# $IPR: digestauth.rb,v 1.5 2003/02/20 07:15:47 gotoyuzo Exp $
-
-require 'webrick/config'
-require 'webrick/httpstatus'
-require 'webrick/httpauth/authenticator'
-require 'digest/md5'
-require 'digest/sha1'
-
-module WEBrick
- module HTTPAuth
- class DigestAuth
- include Authenticator
-
- AuthScheme = "Digest"
- OpaqueInfo = Struct.new(:time, :nonce, :nc)
- attr_reader :algorithm, :qop
-
- def self.make_passwd(realm, user, pass)
- pass ||= ""
- Digest::MD5::hexdigest([user, realm, pass].join(":"))
- end
-
- def initialize(config, default=Config::DigestAuth)
- check_init(config)
- @config = default.dup.update(config)
- @algorithm = @config[:Algorithm]
- @domain = @config[:Domain]
- @qop = @config[:Qop]
- @use_opaque = @config[:UseOpaque]
- @use_next_nonce = @config[:UseNextNonce]
- @check_nc = @config[:CheckNc]
- @use_auth_info_header = @config[:UseAuthenticationInfoHeader]
- @nonce_expire_period = @config[:NonceExpirePeriod]
- @nonce_expire_delta = @config[:NonceExpireDelta]
- @internet_explorer_hack = @config[:InternetExplorerHack]
- @opera_hack = @config[:OperaHack]
-
- case @algorithm
- when 'MD5','MD5-sess'
- @h = Digest::MD5
- when 'SHA1','SHA1-sess' # it is a bonus feature :-)
- @h = Digest::SHA1
- else
- msg = format('Alogrithm "%s" is not supported.', @algorithm)
- raise ArgumentError.new(msg)
- end
-
- @instance_key = hexdigest(self.__id__, Time.now.to_i, Process.pid)
- @opaques = {}
- @last_nonce_expire = Time.now
- @mutex = Mutex.new
- end
-
- def authenticate(req, res)
- unless result = @mutex.synchronize{ _authenticate(req, res) }
- challenge(req, res)
- end
- if result == :nonce_is_stale
- challenge(req, res, true)
- end
- return true
- end
-
- def challenge(req, res, stale=false)
- nonce = generate_next_nonce(req)
- if @use_opaque
- opaque = generate_opaque(req)
- @opaques[opaque].nonce = nonce
- end
-
- param = Hash.new
- param["realm"] = HTTPUtils::quote(@realm)
- param["domain"] = HTTPUtils::quote(@domain.to_a.join(" ")) if @domain
- param["nonce"] = HTTPUtils::quote(nonce)
- param["opaque"] = HTTPUtils::quote(opaque) if opaque
- param["stale"] = stale.to_s
- param["algorithm"] = @algorithm
- param["qop"] = HTTPUtils::quote(@qop.to_a.join(",")) if @qop
-
- res[@response_field] =
- "#{@auth_scheme} " + param.map{|k,v| "#{k}=#{v}" }.join(", ")
- info("%s: %s", @response_field, res[@response_field]) if $DEBUG
- raise @auth_exception
- end
-
- private
-
- MustParams = ['username','realm','nonce','uri','response']
- MustParamsAuth = ['cnonce','nc']
-
- def _authenticate(req, res)
- unless digest_credentials = check_scheme(req)
- return false
- end
-
- auth_req = split_param_value(digest_credentials)
- if auth_req['qop'] == "auth" || auth_req['qop'] == "auth-int"
- req_params = MustParams + MustParamsAuth
- else
- req_params = MustParams
- end
- req_params.each{|key|
- unless auth_req.has_key?(key)
- error('%s: parameter missing. "%s"', auth_req['username'], key)
- raise HTTPStatus::BadRequest
- end
- }
-
- if !check_uri(req, auth_req)
- raise HTTPStatus::BadRequest
- end
-
- if auth_req['realm'] != @realm
- error('%s: realm unmatch. "%s" for "%s"',
- auth_req['username'], auth_req['realm'], @realm)
- return false
- end
-
- auth_req['algorithm'] ||= 'MD5'
- if auth_req['algorithm'] != @algorithm &&
- (@opera_hack && auth_req['algorithm'] != @algorithm.upcase)
- error('%s: algorithm unmatch. "%s" for "%s"',
- auth_req['username'], auth_req['algorithm'], @algorithm)
- return false
- end
-
- if (@qop.nil? && auth_req.has_key?('qop')) ||
- (@qop && (! @qop.member?(auth_req['qop'])))
- error('%s: the qop is not allowed. "%s"',
- auth_req['username'], auth_req['qop'])
- return false
- end
-
- password = @userdb.get_passwd(@realm, auth_req['username'], @reload_db)
- unless password
- error('%s: the user is not allowd.', auth_req['username'])
- return false
- end
-
- nonce_is_invalid = false
- if @use_opaque
- info("@opaque = %s", @opaque.inspect) if $DEBUG
- if !(opaque = auth_req['opaque'])
- error('%s: opaque is not given.', auth_req['username'])
- nonce_is_invalid = true
- elsif !(opaque_struct = @opaques[opaque])
- error('%s: invalid opaque is given.', auth_req['username'])
- nonce_is_invalid = true
- elsif !check_opaque(opaque_struct, req, auth_req)
- @opaques.delete(auth_req['opaque'])
- nonce_is_invalid = true
- end
- elsif !check_nonce(req, auth_req)
- nonce_is_invalid = true
- end
-
- if /-sess$/ =~ auth_req['algorithm'] ||
- (@opera_hack && /-SESS$/ =~ auth_req['algorithm'])
- ha1 = hexdigest(password, auth_req['nonce'], auth_req['cnonce'])
- else
- ha1 = password
- end
-
- if auth_req['qop'] == "auth" || auth_req['qop'] == nil
- ha2 = hexdigest(req.request_method, auth_req['uri'])
- ha2_res = hexdigest("", auth_req['uri'])
- elsif auth_req['qop'] == "auth-int"
- ha2 = hexdigest(req.request_method, auth_req['uri'],
- hexdigest(req.body))
- ha2_res = hexdigest("", auth_req['uri'], hexdigest(res.body))
- end
-
- if auth_req['qop'] == "auth" || auth_req['qop'] == "auth-int"
- param2 = ['nonce', 'nc', 'cnonce', 'qop'].map{|key|
- auth_req[key]
- }.join(':')
- digest = hexdigest(ha1, param2, ha2)
- digest_res = hexdigest(ha1, param2, ha2_res)
- else
- digest = hexdigest(ha1, auth_req['nonce'], ha2)
- digest_res = hexdigest(ha1, auth_req['nonce'], ha2_res)
- end
-
- if digest != auth_req['response']
- error("%s: digest unmatch.", auth_req['username'])
- return false
- elsif nonce_is_invalid
- error('%s: digest is valid, but nonce is not valid.',
- auth_req['username'])
- return :nonce_is_stale
- elsif @use_auth_info_header
- auth_info = {
- 'nextnonce' => generate_next_nonce(req),
- 'rspauth' => digest_res
- }
- if @use_opaque
- opaque_struct.time = req.request_time
- opaque_struct.nonce = auth_info['nextnonce']
- opaque_struct.nc = "%08x" % (auth_req['nc'].hex + 1)
- end
- if auth_req['qop'] == "auth" || auth_req['qop'] == "auth-int"
- ['qop','cnonce','nc'].each{|key|
- auth_info[key] = auth_req[key]
- }
- end
- res[@resp_info_field] = auth_info.keys.map{|key|
- if key == 'nc'
- key + '=' + auth_info[key]
- else
- key + "=" + HTTPUtils::quote(auth_info[key])
- end
- }.join(', ')
- end
- info('%s: authentication scceeded.', auth_req['username'])
- req.user = auth_req['username']
- return true
- end
-
- def split_param_value(string)
- ret = {}
- while string.size != 0
- case string
- when /^\s*([\w\-\.\*\%\!]+)=\s*\"((\\.|[^\"])*)\"\s*,?/
- key = $1
- matched = $2
- string = $'
- ret[key] = matched.gsub(/\\(.)/, "\\1")
- when /^\s*([\w\-\.\*\%\!]+)=\s*([^,\"]*),?/
- key = $1
- matched = $2
- string = $'
- ret[key] = matched.clone
- when /^s*^,/
- string = $'
- else
- break
- end
- end
- ret
- end
-
- def generate_next_nonce(req)
- now = "%012d" % req.request_time.to_i
- pk = hexdigest(now, @instance_key)[0,32]
- nonce = [now + ":" + pk].pack("m*").chop # it has 60 length of chars.
- nonce
- end
-
- def check_nonce(req, auth_req)
- username = auth_req['username']
- nonce = auth_req['nonce']
-
- pub_time, pk = nonce.unpack("m*")[0].split(":", 2)
- if (!pub_time || !pk)
- error("%s: empty nonce is given", username)
- return false
- elsif (hexdigest(pub_time, @instance_key)[0,32] != pk)
- error("%s: invalid private-key: %s for %s",
- username, hexdigest(pub_time, @instance_key)[0,32], pk)
- return false
- end
-
- diff_time = req.request_time.to_i - pub_time.to_i
- if (diff_time < 0)
- error("%s: difference of time-stamp is negative.", username)
- return false
- elsif diff_time > @nonce_expire_period
- error("%s: nonce is expired.", username)
- return false
- end
-
- return true
- end
-
- def generate_opaque(req)
- @mutex.synchronize{
- now = req.request_time
- if now - @last_nonce_expire > @nonce_expire_delta
- @opaques.delete_if{|key,val|
- (now - val.time) > @nonce_expire_period
- }
- @last_nonce_expire = now
- end
- begin
- opaque = Utils::random_string(16)
- end while @opaques[opaque]
- @opaques[opaque] = OpaqueInfo.new(now, nil, '00000001')
- opaque
- }
- end
-
- def check_opaque(opaque_struct, req, auth_req)
- if (@use_next_nonce && auth_req['nonce'] != opaque_struct.nonce)
- error('%s: nonce unmatched. "%s" for "%s"',
- auth_req['username'], auth_req['nonce'], opaque_struct.nonce)
- return false
- elsif !check_nonce(req, auth_req)
- return false
- end
- if (@check_nc && auth_req['nc'] != opaque_struct.nc)
- error('%s: nc unmatched."%s" for "%s"',
- auth_req['username'], auth_req['nc'], opaque_struct.nc)
- return false
- end
- true
- end
-
- def check_uri(req, auth_req)
- uri = auth_req['uri']
- if uri != req.request_uri.to_s && uri != req.unparsed_uri &&
- (@internet_explorer_hack && uri != req.path)
- error('%s: uri unmatch. "%s" for "%s"', auth_req['username'],
- auth_req['uri'], req.request_uri.to_s)
- return false
- end
- true
- end
-
- def hexdigest(*args)
- @h.hexdigest(args.join(":"))
- end
-
- end
-
- class ProxyDigestAuth < DigestAuth
- include ProxyAuthenticator
-
- def check_uri(req, auth_req)
- return true
- end
- end
- end
-end