From 8c2a52937f3b71efa7bcb48e7b8b00bc6b616ab4 Mon Sep 17 00:00:00 2001 From: drbrain Date: Tue, 10 May 2011 23:37:43 +0000 Subject: [PATCH] * lib/webrick: Add documentation for WEBrick::HTTPAuth git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@31505 b2dd03c8-39d4-4d8f-98ff-823fe69b080e --- ChangeLog | 4 ++ lib/webrick.rb | 6 +++ lib/webrick/httpauth.rb | 56 +++++++++++++++++++++++++-- lib/webrick/httpauth/authenticator.rb | 37 +++++++++++++++++- lib/webrick/httpauth/basicauth.rb | 43 ++++++++++++++++++++ lib/webrick/httpauth/digestauth.rb | 18 +++++---- lib/webrick/httpauth/htdigest.rb | 39 ++++++++++++++++++- lib/webrick/httpauth/htgroup.rb | 32 +++++++++++++++ lib/webrick/httpauth/htpasswd.rb | 40 ++++++++++++++++++- lib/webrick/httpauth/userdb.rb | 27 ++++++++++++- 10 files changed, 285 insertions(+), 17 deletions(-) diff --git a/ChangeLog b/ChangeLog index 9f07e3ab89..6a13040734 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,7 @@ +Wed May 11 08:36:38 2011 Eric Hodel + + * lib/webrick: Add documentation for WEBrick::HTTPAuth + Wed May 11 03:06:35 2011 Eric Hodel * lib/rss.rb: Add documentation for RSS. Patch by Steve Klabnik. diff --git a/lib/webrick.rb b/lib/webrick.rb index e0395f4944..842bda21ef 100644 --- a/lib/webrick.rb +++ b/lib/webrick.rb @@ -133,6 +133,12 @@ # +:ProxyContentHandler+ callback which will be invoked with the request and # respone after the remote content has been fetched. # +# == Basic and Digest authentication +# +# WEBrick provides both Basic and Digest authentication for regular and proxy +# servers. See WEBrick::HTTPAuth, WEBrick::HTTPAuth::BasicAuth and +# WEBrick::HTTPAuth::DigestAuth. +# # == WEBrick as a Production Web Server # # WEBrick can be run as a production server for small loads. diff --git a/lib/webrick/httpauth.rb b/lib/webrick/httpauth.rb index 147c04021c..96d479b2d7 100644 --- a/lib/webrick/httpauth.rb +++ b/lib/webrick/httpauth.rb @@ -15,10 +15,46 @@ require 'webrick/httpauth/htdigest' require 'webrick/httpauth/htgroup' module WEBrick + + ## + # HTTPAuth provides both basic and digest authentication. + # + # To enable authentication for requests in WEBrick you will need a user + # database and an authenticator. To start, here's an Htpasswd database for + # use with a DigestAuth authenticator: + # + # config = { :Realm => 'DigestAuth example realm' } + # + # htpasswd = WEBrick::HTTPAuth::Htpasswd.new 'my_password_file' + # htpasswd.auth_type = WEBrick::HTTPAuth::DigestAuth + # htpasswd.set_passwd config[:Realm], 'username', 'password' + # htpasswd.flush + # + # The +:Realm+ is used to provide different access to different groups + # across several resources on a server. Typically you'll need only one + # realm for a server. + # + # This database can be used to create an authenticator: + # + # config[:UserDB] = htpasswd + # + # digest_auth = WEBrick::HTTPAuth::DigestAuth.new config + # + # To authenticate a request call #authenticate with a request and response + # object in a servlet: + # + # def do_GET req, res + # @authenticator.authenticate req, res + # end + # + # For digest authentication the authenticator must not be created every + # request, it must be passed in as an option via WEBrick::HTTPServer#mount. + module HTTPAuth module_function - def _basic_auth(req, res, realm, req_field, res_field, err_type, block) + def _basic_auth(req, res, realm, req_field, res_field, err_type, + block) # :nodoc: user = pass = nil if /^Basic\s+(.*)/o =~ req[req_field] userpass = $1 @@ -32,12 +68,26 @@ module WEBrick raise err_type end - def basic_auth(req, res, realm, &block) + ## + # Simple wrapper for providing basic authentication for a request. When + # called with a request +req+, response +res+, authentication +realm+ and + # +block+ the block will be called with a +username+ and +password+. If + # the block returns true the request is allowed to continue, otherwise an + # HTTPStatus::Unauthorized error is raised. + + def basic_auth(req, res, realm, &block) # :yield: username, password _basic_auth(req, res, realm, "Authorization", "WWW-Authenticate", HTTPStatus::Unauthorized, block) end - def proxy_basic_auth(req, res, realm, &block) + ## + # Simple wrapper for providing basic authentication for a proxied request. + # When called with a request +req+, response +res+, authentication +realm+ + # and +block+ the block will be called with a +username+ and +password+. + # If the block returns true the request is allowed to continue, otherwise + # an HTTPStatus::ProxyAuthenticationRequired error is raised. + + def proxy_basic_auth(req, res, realm, &block) # :yield: username, password _basic_auth(req, res, realm, "Proxy-Authorization", "Proxy-Authenticate", HTTPStatus::ProxyAuthenticationRequired, block) end diff --git a/lib/webrick/httpauth/authenticator.rb b/lib/webrick/httpauth/authenticator.rb index af34a19b88..0739d4371d 100644 --- a/lib/webrick/httpauth/authenticator.rb +++ b/lib/webrick/httpauth/authenticator.rb @@ -9,17 +9,43 @@ module WEBrick module HTTPAuth + + ## + # Module providing generic support for both Digest and Basic + # authentication schemes. + module Authenticator + RequestField = "Authorization" ResponseField = "WWW-Authenticate" ResponseInfoField = "Authentication-Info" AuthException = HTTPStatus::Unauthorized - AuthScheme = nil # must override by the derived class - attr_reader :realm, :userdb, :logger + ## + # Method of authentication, must be overriden 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 + ## + # Initializes the authenticator from +config+ + def check_init(config) [:UserDB, :Realm].each{|sym| unless config[sym] @@ -37,6 +63,9 @@ module WEBrick @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.") @@ -69,6 +98,10 @@ module WEBrick end end + ## + # Module providing generic support for both Digest and Basic + # authentication schemes for proxies. + module ProxyAuthenticator RequestField = "Proxy-Authorization" ResponseField = "Proxy-Authenticate" diff --git a/lib/webrick/httpauth/basicauth.rb b/lib/webrick/httpauth/basicauth.rb index 210fb00bbe..4c51e53199 100644 --- a/lib/webrick/httpauth/basicauth.rb +++ b/lib/webrick/httpauth/basicauth.rb @@ -13,11 +13,32 @@ 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" + ## + # Used by UserDB to create a basic password entry + def self.make_passwd(realm, user, pass) pass ||= "" pass.crypt(Utils::random_string(2)) @@ -25,11 +46,26 @@ module WEBrick 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) @@ -52,12 +88,19 @@ module WEBrick 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 diff --git a/lib/webrick/httpauth/digestauth.rb b/lib/webrick/httpauth/digestauth.rb index 4e62678b3f..8f7f32f82b 100644 --- a/lib/webrick/httpauth/digestauth.rb +++ b/lib/webrick/httpauth/digestauth.rb @@ -29,12 +29,11 @@ module WEBrick # # config = { :Realm => 'DigestAuth example realm' } # - # htpasswd = WEBrick::HTTPAuth::Htpasswd.new 'my_password_file' - # htpasswd.auth_type = WEBrick::HTTPAuth::DigestAuth - # htpasswd.set_passwd config[:Realm], 'username', 'password' - # htpasswd.flush + # htdigest = WEBrick::HTTPAuth::Htdigest.new 'my_password_file' + # htdigest.set_passwd config[:Realm], 'username', 'password' + # htdigest.flush # - # config[:UserDB] = htpasswd + # config[:UserDB] = htdigest # # digest_auth = WEBrick::HTTPAuth::DigestAuth.new config # @@ -51,7 +50,7 @@ module WEBrick attr_reader :algorithm, :qop ## - # Used by UserDB to create a password entry + # Used by UserDB to create a digest password entry def self.make_passwd(realm, user, pass) pass ||= "" @@ -68,8 +67,8 @@ module WEBrick # You must supply the following configuration entries: # # :Realm:: The name of the realm being protected. - # :UserDB:: A database of usernames and passwords. See Htpasswd, - # Htdigest, Htgroup + # :UserDB:: A database of usernames and passwords. + # A WEBrick::HTTPAuth::Htdigest instance should be used. def initialize(config, default=Config::DigestAuth) check_init(config) @@ -381,6 +380,9 @@ module WEBrick end + ## + # Digest authentication for proxy servers. See DigestAuth for details. + class ProxyDigestAuth < DigestAuth include ProxyAuthenticator diff --git a/lib/webrick/httpauth/htdigest.rb b/lib/webrick/httpauth/htdigest.rb index 3949756f2b..4b74588c77 100644 --- a/lib/webrick/httpauth/htdigest.rb +++ b/lib/webrick/httpauth/htdigest.rb @@ -13,9 +13,26 @@ 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) @@ -26,6 +43,9 @@ module WEBrick reload end + ## + # Reloads passwords from the database + def reload mtime = File::mtime(@path) if mtime > @mtime @@ -44,6 +64,10 @@ module WEBrick 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.new("htpasswd", File::dirname(output)) @@ -56,6 +80,10 @@ module WEBrick 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] @@ -63,6 +91,9 @@ module WEBrick 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] @@ -72,13 +103,19 @@ module WEBrick } 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 - def each + ## + # 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| diff --git a/lib/webrick/httpauth/htgroup.rb b/lib/webrick/httpauth/htgroup.rb index c9270c61cc..0ecabef820 100644 --- a/lib/webrick/httpauth/htgroup.rb +++ b/lib/webrick/httpauth/htgroup.rb @@ -11,7 +11,26 @@ 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) @@ -20,6 +39,9 @@ module WEBrick reload end + ## + # Reload groups from the database + def reload if (mtime = File::mtime(@path)) > @mtime @group.clear @@ -34,6 +56,10 @@ module WEBrick 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)) @@ -48,11 +74,17 @@ module WEBrick 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 diff --git a/lib/webrick/httpauth/htpasswd.rb b/lib/webrick/httpauth/htpasswd.rb index 8a058861d3..205a6db2f0 100644 --- a/lib/webrick/httpauth/htpasswd.rb +++ b/lib/webrick/httpauth/htpasswd.rb @@ -13,9 +13,27 @@ 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) @@ -25,6 +43,9 @@ module WEBrick reload end + ## + # Reload passwords from the database + def reload mtime = File::mtime(@path) if mtime > @mtime @@ -48,6 +69,10 @@ module WEBrick 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.new("htpasswd", File::dirname(output)) @@ -60,20 +85,33 @@ module WEBrick 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 - def each + ## + # Iterate passwords in the database. + + def each # :yields: [user, password] @passwd.keys.sort.each{|user| yield([user, @passwd[user]]) } diff --git a/lib/webrick/httpauth/userdb.rb b/lib/webrick/httpauth/userdb.rb index 95834f36e3..005c18dfd0 100644 --- a/lib/webrick/httpauth/userdb.rb +++ b/lib/webrick/httpauth/userdb.rb @@ -9,19 +9,42 @@ 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 - attr_accessor :auth_type # BasicAuth or DigestAuth + + ## + # 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) - # reload_db is dummy make_passwd(realm, user, self[user]) end end