diff options
Diffstat (limited to 'jni/ruby/lib/webrick/httpauth')
| -rw-r--r-- | jni/ruby/lib/webrick/httpauth/authenticator.rb | 116 | ||||
| -rw-r--r-- | jni/ruby/lib/webrick/httpauth/basicauth.rb | 108 | ||||
| -rw-r--r-- | jni/ruby/lib/webrick/httpauth/digestauth.rb | 408 | ||||
| -rw-r--r-- | jni/ruby/lib/webrick/httpauth/htdigest.rb | 131 | ||||
| -rw-r--r-- | jni/ruby/lib/webrick/httpauth/htgroup.rb | 93 | ||||
| -rw-r--r-- | jni/ruby/lib/webrick/httpauth/htpasswd.rb | 124 | ||||
| -rw-r--r-- | jni/ruby/lib/webrick/httpauth/userdb.rb | 52 | 
7 files changed, 1032 insertions, 0 deletions
| diff --git a/jni/ruby/lib/webrick/httpauth/authenticator.rb b/jni/ruby/lib/webrick/httpauth/authenticator.rb new file mode 100644 index 0000000..f6d4ab8 --- /dev/null +++ b/jni/ruby/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/jni/ruby/lib/webrick/httpauth/basicauth.rb b/jni/ruby/lib/webrick/httpauth/basicauth.rb new file mode 100644 index 0000000..3ff20b5 --- /dev/null +++ b/jni/ruby/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/jni/ruby/lib/webrick/httpauth/digestauth.rb b/jni/ruby/lib/webrick/httpauth/digestauth.rb new file mode 100644 index 0000000..0eea947 --- /dev/null +++ b/jni/ruby/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/jni/ruby/lib/webrick/httpauth/htdigest.rb b/jni/ruby/lib/webrick/httpauth/htdigest.rb new file mode 100644 index 0000000..5fb0635 --- /dev/null +++ b/jni/ruby/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/jni/ruby/lib/webrick/httpauth/htgroup.rb b/jni/ruby/lib/webrick/httpauth/htgroup.rb new file mode 100644 index 0000000..0ecabef --- /dev/null +++ b/jni/ruby/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/jni/ruby/lib/webrick/httpauth/htpasswd.rb b/jni/ruby/lib/webrick/httpauth/htpasswd.rb new file mode 100644 index 0000000..69b739f --- /dev/null +++ b/jni/ruby/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/jni/ruby/lib/webrick/httpauth/userdb.rb b/jni/ruby/lib/webrick/httpauth/userdb.rb new file mode 100644 index 0000000..005c18d --- /dev/null +++ b/jni/ruby/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 | 
