summaryrefslogtreecommitdiff
path: root/ruby_2_2/lib/webrick/httpauth
diff options
context:
space:
mode:
authorusa <usa@b2dd03c8-39d4-4d8f-98ff-823fe69b080e>2017-12-14 15:09:35 +0000
committerusa <usa@b2dd03c8-39d4-4d8f-98ff-823fe69b080e>2017-12-14 15:09:35 +0000
commit1a74fa4b04da04bd2bb33103dd3cf431438df38e (patch)
treef4a1d6c2961339e0c1d653c0f8427a53315080f0 /ruby_2_2/lib/webrick/httpauth
parenta5b755e50e2d9aabf28ba24bf58644ca22b01a4f (diff)
add tag v2_2_9
git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/tags/v2_2_9@61257 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
Diffstat (limited to 'ruby_2_2/lib/webrick/httpauth')
-rw-r--r--ruby_2_2/lib/webrick/httpauth/authenticator.rb116
-rw-r--r--ruby_2_2/lib/webrick/httpauth/basicauth.rb108
-rw-r--r--ruby_2_2/lib/webrick/httpauth/digestauth.rb408
-rw-r--r--ruby_2_2/lib/webrick/httpauth/htdigest.rb131
-rw-r--r--ruby_2_2/lib/webrick/httpauth/htgroup.rb93
-rw-r--r--ruby_2_2/lib/webrick/httpauth/htpasswd.rb124
-rw-r--r--ruby_2_2/lib/webrick/httpauth/userdb.rb52
7 files changed, 1032 insertions, 0 deletions
diff --git a/ruby_2_2/lib/webrick/httpauth/authenticator.rb b/ruby_2_2/lib/webrick/httpauth/authenticator.rb
new file mode 100644
index 0000000000..f6d4ab844f
--- /dev/null
+++ b/ruby_2_2/lib/webrick/httpauth/authenticator.rb
@@ -0,0 +1,116 @@
+#--
+# httpauth/authenticator.rb -- Authenticator mix-in module.
+#
+# Author: IPR -- Internet Programming with Ruby -- writers
+# Copyright (c) 2003 Internet Programming with Ruby writers. All rights
+# reserved.
+#
+# $IPR: authenticator.rb,v 1.3 2003/02/20 07:15:47 gotoyuzo Exp $
+
+module WEBrick
+ module HTTPAuth
+
+ ##
+ # Module providing generic support for both Digest and Basic
+ # authentication schemes.
+
+ module Authenticator
+
+ RequestField = "Authorization" # :nodoc:
+ ResponseField = "WWW-Authenticate" # :nodoc:
+ ResponseInfoField = "Authentication-Info" # :nodoc:
+ AuthException = HTTPStatus::Unauthorized # :nodoc:
+
+ ##
+ # Method of authentication, must be overridden by the including class
+
+ AuthScheme = nil
+
+ ##
+ # The realm this authenticator covers
+
+ attr_reader :realm
+
+ ##
+ # The user database for this authenticator
+
+ attr_reader :userdb
+
+ ##
+ # The logger for this authenticator
+
+ attr_reader :logger
+
+ private
+
+ # :stopdoc:
+
+ ##
+ # Initializes the authenticator from +config+
+
+ def check_init(config)
+ [:UserDB, :Realm].each{|sym|
+ unless config[sym]
+ raise ArgumentError, "Argument #{sym.inspect} missing."
+ end
+ }
+ @realm = config[:Realm]
+ @userdb = config[:UserDB]
+ @logger = config[:Logger] || Log::new($stderr)
+ @reload_db = config[:AutoReloadUserDB]
+ @request_field = self::class::RequestField
+ @response_field = self::class::ResponseField
+ @resp_info_field = self::class::ResponseInfoField
+ @auth_exception = self::class::AuthException
+ @auth_scheme = self::class::AuthScheme
+ end
+
+ ##
+ # Ensures +req+ has credentials that can be authenticated.
+
+ def check_scheme(req)
+ unless credentials = req[@request_field]
+ error("no credentials in the request.")
+ return nil
+ end
+ unless match = /^#{@auth_scheme}\s+/i.match(credentials)
+ error("invalid scheme in %s.", credentials)
+ info("%s: %s", @request_field, credentials) if $DEBUG
+ return nil
+ end
+ return match.post_match
+ end
+
+ def log(meth, fmt, *args)
+ msg = format("%s %s: ", @auth_scheme, @realm)
+ msg << fmt % args
+ @logger.send(meth, msg)
+ end
+
+ def error(fmt, *args)
+ if @logger.error?
+ log(:error, fmt, *args)
+ end
+ end
+
+ def info(fmt, *args)
+ if @logger.info?
+ log(:info, fmt, *args)
+ end
+ end
+
+ # :startdoc:
+ end
+
+ ##
+ # Module providing generic support for both Digest and Basic
+ # authentication schemes for proxies.
+
+ module ProxyAuthenticator
+ RequestField = "Proxy-Authorization" # :nodoc:
+ ResponseField = "Proxy-Authenticate" # :nodoc:
+ InfoField = "Proxy-Authentication-Info" # :nodoc:
+ AuthException = HTTPStatus::ProxyAuthenticationRequired # :nodoc:
+ end
+ end
+end
diff --git a/ruby_2_2/lib/webrick/httpauth/basicauth.rb b/ruby_2_2/lib/webrick/httpauth/basicauth.rb
new file mode 100644
index 0000000000..3ff20b56d2
--- /dev/null
+++ b/ruby_2_2/lib/webrick/httpauth/basicauth.rb
@@ -0,0 +1,108 @@
+#
+# httpauth/basicauth.rb -- HTTP basic access authentication
+#
+# Author: IPR -- Internet Programming with Ruby -- writers
+# Copyright (c) 2003 Internet Programming with Ruby writers. All rights
+# reserved.
+#
+# $IPR: basicauth.rb,v 1.5 2003/02/20 07:15:47 gotoyuzo Exp $
+
+require 'webrick/config'
+require 'webrick/httpstatus'
+require 'webrick/httpauth/authenticator'
+
+module WEBrick
+ module HTTPAuth
+
+ ##
+ # Basic Authentication for WEBrick
+ #
+ # Use this class to add basic authentication to a WEBrick servlet.
+ #
+ # Here is an example of how to set up a BasicAuth:
+ #
+ # config = { :Realm => 'BasicAuth example realm' }
+ #
+ # htpasswd = WEBrick::HTTPAuth::Htpasswd.new 'my_password_file'
+ # htpasswd.set_passwd config[:Realm], 'username', 'password'
+ # htpasswd.flush
+ #
+ # config[:UserDB] = htpasswd
+ #
+ # basic_auth = WEBrick::HTTPAuth::BasicAuth.new config
+
+ class BasicAuth
+ include Authenticator
+
+ AuthScheme = "Basic" # :nodoc:
+
+ ##
+ # Used by UserDB to create a basic password entry
+
+ def self.make_passwd(realm, user, pass)
+ pass ||= ""
+ pass.crypt(Utils::random_string(2))
+ end
+
+ attr_reader :realm, :userdb, :logger
+
+ ##
+ # Creates a new BasicAuth instance.
+ #
+ # See WEBrick::Config::BasicAuth for default configuration entries
+ #
+ # You must supply the following configuration entries:
+ #
+ # :Realm:: The name of the realm being protected.
+ # :UserDB:: A database of usernames and passwords.
+ # A WEBrick::HTTPAuth::Htpasswd instance should be used.
+
+ def initialize(config, default=Config::BasicAuth)
+ check_init(config)
+ @config = default.dup.update(config)
+ end
+
+ ##
+ # Authenticates a +req+ and returns a 401 Unauthorized using +res+ if
+ # the authentication was not correct.
+
+ def authenticate(req, res)
+ unless basic_credentials = check_scheme(req)
+ challenge(req, res)
+ end
+ userid, password = basic_credentials.unpack("m*")[0].split(":", 2)
+ password ||= ""
+ if userid.empty?
+ error("user id was not given.")
+ challenge(req, res)
+ end
+ unless encpass = @userdb.get_passwd(@realm, userid, @reload_db)
+ error("%s: the user is not allowed.", userid)
+ challenge(req, res)
+ end
+ if password.crypt(encpass) != encpass
+ error("%s: password unmatch.", userid)
+ challenge(req, res)
+ end
+ info("%s: authentication succeeded.", userid)
+ req.user = userid
+ end
+
+ ##
+ # Returns a challenge response which asks for for authentication
+ # information
+
+ def challenge(req, res)
+ res[@response_field] = "#{@auth_scheme} realm=\"#{@realm}\""
+ raise @auth_exception
+ end
+ end
+
+ ##
+ # Basic authentication for proxy servers. See BasicAuth for details.
+
+ class ProxyBasicAuth < BasicAuth
+ include ProxyAuthenticator
+ end
+ end
+end
diff --git a/ruby_2_2/lib/webrick/httpauth/digestauth.rb b/ruby_2_2/lib/webrick/httpauth/digestauth.rb
new file mode 100644
index 0000000000..0eea94774f
--- /dev/null
+++ b/ruby_2_2/lib/webrick/httpauth/digestauth.rb
@@ -0,0 +1,408 @@
+#
+# 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
+
+ ##
+ # RFC 2617 Digest Access Authentication for WEBrick
+ #
+ # Use this class to add digest authentication to a WEBrick servlet.
+ #
+ # Here is an example of how to set up DigestAuth:
+ #
+ # config = { :Realm => 'DigestAuth example realm' }
+ #
+ # htdigest = WEBrick::HTTPAuth::Htdigest.new 'my_password_file'
+ # htdigest.set_passwd config[:Realm], 'username', 'password'
+ # htdigest.flush
+ #
+ # config[:UserDB] = htdigest
+ #
+ # digest_auth = WEBrick::HTTPAuth::DigestAuth.new config
+ #
+ # When using this as with a servlet be sure not to create a new DigestAuth
+ # object in the servlet's #initialize. By default WEBrick creates a new
+ # servlet instance for every request and the DigestAuth object must be
+ # used across requests.
+
+ class DigestAuth
+ include Authenticator
+
+ AuthScheme = "Digest" # :nodoc:
+
+ ##
+ # Struct containing the opaque portion of the digest authentication
+
+ OpaqueInfo = Struct.new(:time, :nonce, :nc) # :nodoc:
+
+ ##
+ # Digest authentication algorithm
+
+ attr_reader :algorithm
+
+ ##
+ # Quality of protection. RFC 2617 defines "auth" and "auth-int"
+
+ attr_reader :qop
+
+ ##
+ # Used by UserDB to create a digest password entry
+
+ def self.make_passwd(realm, user, pass)
+ pass ||= ""
+ Digest::MD5::hexdigest([user, realm, pass].join(":"))
+ end
+
+ ##
+ # Creates a new DigestAuth instance. Be sure to use the same DigestAuth
+ # instance for multiple requests as it saves state between requests in
+ # order to perform authentication.
+ #
+ # See WEBrick::Config::DigestAuth for default configuration entries
+ #
+ # You must supply the following configuration entries:
+ #
+ # :Realm:: The name of the realm being protected.
+ # :UserDB:: A database of usernames and passwords.
+ # A WEBrick::HTTPAuth::Htdigest instance should be used.
+
+ 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]
+
+ case @algorithm
+ when 'MD5','MD5-sess'
+ @h = Digest::MD5
+ when 'SHA1','SHA1-sess' # it is a bonus feature :-)
+ @h = Digest::SHA1
+ else
+ msg = format('Algorithm "%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
+
+ ##
+ # Authenticates a +req+ and returns a 401 Unauthorized using +res+ if
+ # the authentication was not correct.
+
+ 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
+
+ ##
+ # Returns a challenge response which asks for for authentication
+ # information
+
+ 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
+
+ # :stopdoc:
+
+ 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'].upcase != @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 allowed.', 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$/i =~ 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 succeeded.', auth_req['username'])
+ req.user = auth_req['username']
+ return true
+ end
+
+ def split_param_value(string)
+ ret = {}
+ while string.bytesize != 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
+
+ # :startdoc:
+ end
+
+ ##
+ # Digest authentication for proxy servers. See DigestAuth for details.
+
+ class ProxyDigestAuth < DigestAuth
+ include ProxyAuthenticator
+
+ private
+ def check_uri(req, auth_req) # :nodoc:
+ return true
+ end
+ end
+ end
+end
diff --git a/ruby_2_2/lib/webrick/httpauth/htdigest.rb b/ruby_2_2/lib/webrick/httpauth/htdigest.rb
new file mode 100644
index 0000000000..5fb0635e2a
--- /dev/null
+++ b/ruby_2_2/lib/webrick/httpauth/htdigest.rb
@@ -0,0 +1,131 @@
+#
+# httpauth/htdigest.rb -- Apache compatible htdigest file
+#
+# Author: IPR -- Internet Programming with Ruby -- writers
+# Copyright (c) 2003 Internet Programming with Ruby writers. All rights
+# reserved.
+#
+# $IPR: htdigest.rb,v 1.4 2003/07/22 19:20:45 gotoyuzo Exp $
+
+require 'webrick/httpauth/userdb'
+require 'webrick/httpauth/digestauth'
+require 'tempfile'
+
+module WEBrick
+ module HTTPAuth
+
+ ##
+ # Htdigest accesses apache-compatible digest password files. Passwords are
+ # matched to a realm where they are valid. For security, the path for a
+ # digest password database should be stored outside of the paths available
+ # to the HTTP server.
+ #
+ # Htdigest is intended for use with WEBrick::HTTPAuth::DigestAuth and
+ # stores passwords using cryptographic hashes.
+ #
+ # htpasswd = WEBrick::HTTPAuth::Htdigest.new 'my_password_file'
+ # htpasswd.set_passwd 'my realm', 'username', 'password'
+ # htpasswd.flush
+
+ class Htdigest
+ include UserDB
+
+ ##
+ # Open a digest password database at +path+
+
+ def initialize(path)
+ @path = path
+ @mtime = Time.at(0)
+ @digest = Hash.new
+ @mutex = Mutex::new
+ @auth_type = DigestAuth
+ open(@path,"a").close unless File::exist?(@path)
+ reload
+ end
+
+ ##
+ # Reloads passwords from the database
+
+ def reload
+ mtime = File::mtime(@path)
+ if mtime > @mtime
+ @digest.clear
+ open(@path){|io|
+ while line = io.gets
+ line.chomp!
+ user, realm, pass = line.split(/:/, 3)
+ unless @digest[realm]
+ @digest[realm] = Hash.new
+ end
+ @digest[realm][user] = pass
+ end
+ }
+ @mtime = mtime
+ end
+ end
+
+ ##
+ # Flush the password database. If +output+ is given the database will
+ # be written there instead of to the original path.
+
+ def flush(output=nil)
+ output ||= @path
+ tmp = Tempfile.create("htpasswd", File::dirname(output))
+ renamed = false
+ begin
+ each{|item| tmp.puts(item.join(":")) }
+ tmp.close
+ File::rename(tmp.path, output)
+ renamed = true
+ ensure
+ tmp.close if !tmp.closed?
+ File.unlink(tmp.path) if !renamed
+ end
+ end
+
+ ##
+ # Retrieves a password from the database for +user+ in +realm+. If
+ # +reload_db+ is true the database will be reloaded first.
+
+ def get_passwd(realm, user, reload_db)
+ reload() if reload_db
+ if hash = @digest[realm]
+ hash[user]
+ end
+ end
+
+ ##
+ # Sets a password in the database for +user+ in +realm+ to +pass+.
+
+ def set_passwd(realm, user, pass)
+ @mutex.synchronize{
+ unless @digest[realm]
+ @digest[realm] = Hash.new
+ end
+ @digest[realm][user] = make_passwd(realm, user, pass)
+ }
+ end
+
+ ##
+ # Removes a password from the database for +user+ in +realm+.
+
+ def delete_passwd(realm, user)
+ if hash = @digest[realm]
+ hash.delete(user)
+ end
+ end
+
+ ##
+ # Iterate passwords in the database.
+
+ def each # :yields: [user, realm, password_hash]
+ @digest.keys.sort.each{|realm|
+ hash = @digest[realm]
+ hash.keys.sort.each{|user|
+ yield([user, realm, hash[user]])
+ }
+ }
+ end
+ end
+ end
+end
diff --git a/ruby_2_2/lib/webrick/httpauth/htgroup.rb b/ruby_2_2/lib/webrick/httpauth/htgroup.rb
new file mode 100644
index 0000000000..0ecabef820
--- /dev/null
+++ b/ruby_2_2/lib/webrick/httpauth/htgroup.rb
@@ -0,0 +1,93 @@
+#
+# httpauth/htgroup.rb -- Apache compatible htgroup file
+#
+# Author: IPR -- Internet Programming with Ruby -- writers
+# Copyright (c) 2003 Internet Programming with Ruby writers. All rights
+# reserved.
+#
+# $IPR: htgroup.rb,v 1.1 2003/02/16 22:22:56 gotoyuzo Exp $
+
+require 'tempfile'
+
+module WEBrick
+ module HTTPAuth
+
+ ##
+ # Htgroup accesses apache-compatible group files. Htgroup can be used to
+ # provide group-based authentication for users. Currently Htgroup is not
+ # directly integrated with any authenticators in WEBrick. For security,
+ # the path for a digest password database should be stored outside of the
+ # paths available to the HTTP server.
+ #
+ # Example:
+ #
+ # htgroup = WEBrick::HTTPAuth::Htgroup.new 'my_group_file'
+ # htgroup.add 'superheroes', %w[spiderman batman]
+ #
+ # htgroup.members('superheroes').include? 'magneto' # => false
+
+ class Htgroup
+
+ ##
+ # Open a group database at +path+
+
+ def initialize(path)
+ @path = path
+ @mtime = Time.at(0)
+ @group = Hash.new
+ open(@path,"a").close unless File::exist?(@path)
+ reload
+ end
+
+ ##
+ # Reload groups from the database
+
+ def reload
+ if (mtime = File::mtime(@path)) > @mtime
+ @group.clear
+ open(@path){|io|
+ while line = io.gets
+ line.chomp!
+ group, members = line.split(/:\s*/)
+ @group[group] = members.split(/\s+/)
+ end
+ }
+ @mtime = mtime
+ end
+ end
+
+ ##
+ # Flush the group database. If +output+ is given the database will be
+ # written there instead of to the original path.
+
+ def flush(output=nil)
+ output ||= @path
+ tmp = Tempfile.new("htgroup", File::dirname(output))
+ begin
+ @group.keys.sort.each{|group|
+ tmp.puts(format("%s: %s", group, self.members(group).join(" ")))
+ }
+ tmp.close
+ File::rename(tmp.path, output)
+ rescue
+ tmp.close(true)
+ end
+ end
+
+ ##
+ # Retrieve the list of members from +group+
+
+ def members(group)
+ reload
+ @group[group] || []
+ end
+
+ ##
+ # Add an Array of +members+ to +group+
+
+ def add(group, members)
+ @group[group] = members(group) | members
+ end
+ end
+ end
+end
diff --git a/ruby_2_2/lib/webrick/httpauth/htpasswd.rb b/ruby_2_2/lib/webrick/httpauth/htpasswd.rb
new file mode 100644
index 0000000000..69b739fbfe
--- /dev/null
+++ b/ruby_2_2/lib/webrick/httpauth/htpasswd.rb
@@ -0,0 +1,124 @@
+#
+# httpauth/htpasswd -- Apache compatible htpasswd file
+#
+# Author: IPR -- Internet Programming with Ruby -- writers
+# Copyright (c) 2003 Internet Programming with Ruby writers. All rights
+# reserved.
+#
+# $IPR: htpasswd.rb,v 1.4 2003/07/22 19:20:45 gotoyuzo Exp $
+
+require 'webrick/httpauth/userdb'
+require 'webrick/httpauth/basicauth'
+require 'tempfile'
+
+module WEBrick
+ module HTTPAuth
+
+ ##
+ # Htpasswd accesses apache-compatible password files. Passwords are
+ # matched to a realm where they are valid. For security, the path for a
+ # password database should be stored outside of the paths available to the
+ # HTTP server.
+ #
+ # Htpasswd is intended for use with WEBrick::HTTPAuth::BasicAuth.
+ #
+ # To create an Htpasswd database with a single user:
+ #
+ # htpasswd = WEBrick::HTTPAuth::Htpasswd.new 'my_password_file'
+ # htpasswd.set_passwd 'my realm', 'username', 'password'
+ # htpasswd.flush
+
+ class Htpasswd
+ include UserDB
+
+ ##
+ # Open a password database at +path+
+
+ def initialize(path)
+ @path = path
+ @mtime = Time.at(0)
+ @passwd = Hash.new
+ @auth_type = BasicAuth
+ open(@path,"a").close unless File::exist?(@path)
+ reload
+ end
+
+ ##
+ # Reload passwords from the database
+
+ def reload
+ mtime = File::mtime(@path)
+ if mtime > @mtime
+ @passwd.clear
+ open(@path){|io|
+ while line = io.gets
+ line.chomp!
+ case line
+ when %r!\A[^:]+:[a-zA-Z0-9./]{13}\z!
+ user, pass = line.split(":")
+ when /:\$/, /:{SHA}/
+ raise NotImplementedError,
+ 'MD5, SHA1 .htpasswd file not supported'
+ else
+ raise StandardError, 'bad .htpasswd file'
+ end
+ @passwd[user] = pass
+ end
+ }
+ @mtime = mtime
+ end
+ end
+
+ ##
+ # Flush the password database. If +output+ is given the database will
+ # be written there instead of to the original path.
+
+ def flush(output=nil)
+ output ||= @path
+ tmp = Tempfile.create("htpasswd", File::dirname(output))
+ renamed = false
+ begin
+ each{|item| tmp.puts(item.join(":")) }
+ tmp.close
+ File::rename(tmp.path, output)
+ renamed = true
+ ensure
+ tmp.close if !tmp.closed?
+ File.unlink(tmp.path) if !renamed
+ end
+ end
+
+ ##
+ # Retrieves a password from the database for +user+ in +realm+. If
+ # +reload_db+ is true the database will be reloaded first.
+
+ def get_passwd(realm, user, reload_db)
+ reload() if reload_db
+ @passwd[user]
+ end
+
+ ##
+ # Sets a password in the database for +user+ in +realm+ to +pass+.
+
+ def set_passwd(realm, user, pass)
+ @passwd[user] = make_passwd(realm, user, pass)
+ end
+
+ ##
+ # Removes a password from the database for +user+ in +realm+.
+
+ def delete_passwd(realm, user)
+ @passwd.delete(user)
+ end
+
+ ##
+ # Iterate passwords in the database.
+
+ def each # :yields: [user, password]
+ @passwd.keys.sort.each{|user|
+ yield([user, @passwd[user]])
+ }
+ end
+ end
+ end
+end
diff --git a/ruby_2_2/lib/webrick/httpauth/userdb.rb b/ruby_2_2/lib/webrick/httpauth/userdb.rb
new file mode 100644
index 0000000000..005c18dfd0
--- /dev/null
+++ b/ruby_2_2/lib/webrick/httpauth/userdb.rb
@@ -0,0 +1,52 @@
+#--
+# httpauth/userdb.rb -- UserDB mix-in module.
+#
+# Author: IPR -- Internet Programming with Ruby -- writers
+# Copyright (c) 2003 Internet Programming with Ruby writers. All rights
+# reserved.
+#
+# $IPR: userdb.rb,v 1.2 2003/02/20 07:15:48 gotoyuzo Exp $
+
+module WEBrick
+ module HTTPAuth
+
+ ##
+ # User database mixin for HTTPAuth. This mixin dispatches user record
+ # access to the underlying auth_type for this database.
+
+ module UserDB
+
+ ##
+ # The authentication type.
+ #
+ # WEBrick::HTTPAuth::BasicAuth or WEBrick::HTTPAuth::DigestAuth are
+ # built-in.
+
+ attr_accessor :auth_type
+
+ ##
+ # Creates an obscured password in +realm+ with +user+ and +password+
+ # using the auth_type of this database.
+
+ def make_passwd(realm, user, pass)
+ @auth_type::make_passwd(realm, user, pass)
+ end
+
+ ##
+ # Sets a password in +realm+ with +user+ and +password+ for the
+ # auth_type of this database.
+
+ def set_passwd(realm, user, pass)
+ self[user] = pass
+ end
+
+ ##
+ # Retrieves a password in +realm+ for +user+ for the auth_type of this
+ # database. +reload_db+ is a dummy value.
+
+ def get_passwd(realm, user, reload_db=false)
+ make_passwd(realm, user, self[user])
+ end
+ end
+ end
+end