mirror of
				https://github.com/ruby/ruby.git
				synced 2022-11-09 12:17:21 -05:00 
			
		
		
		
	Create the substrings necessary parts only, instead of cutting the rest of the buffer. Also removed a useless, probable typo, regexp.
		
			
				
	
	
		
			366 lines
		
	
	
	
		
			12 KiB
		
	
	
	
		
			Ruby
		
	
	
	
	
	
			
		
		
	
	
			366 lines
		
	
	
	
		
			12 KiB
		
	
	
	
		
			Ruby
		
	
	
	
	
	
# frozen_string_literal: false
 | 
						|
require "test/unit"
 | 
						|
require "net/http"
 | 
						|
require "tempfile"
 | 
						|
require "webrick"
 | 
						|
require "webrick/httpauth/basicauth"
 | 
						|
require "stringio"
 | 
						|
require_relative "utils"
 | 
						|
 | 
						|
class TestWEBrickHTTPAuth < Test::Unit::TestCase
 | 
						|
  def teardown
 | 
						|
    WEBrick::Utils::TimeoutHandler.terminate
 | 
						|
    super
 | 
						|
  end
 | 
						|
 | 
						|
  def test_basic_auth
 | 
						|
    log_tester = lambda {|log, access_log|
 | 
						|
      assert_equal(1, log.length)
 | 
						|
      assert_match(/ERROR WEBrick::HTTPStatus::Unauthorized/, log[0])
 | 
						|
    }
 | 
						|
    TestWEBrick.start_httpserver({}, log_tester) {|server, addr, port, log|
 | 
						|
      realm = "WEBrick's realm"
 | 
						|
      path = "/basic_auth"
 | 
						|
 | 
						|
      server.mount_proc(path){|req, res|
 | 
						|
        WEBrick::HTTPAuth.basic_auth(req, res, realm){|user, pass|
 | 
						|
          user == "webrick" && pass == "supersecretpassword"
 | 
						|
        }
 | 
						|
        res.body = "hoge"
 | 
						|
      }
 | 
						|
      http = Net::HTTP.new(addr, port)
 | 
						|
      g = Net::HTTP::Get.new(path)
 | 
						|
      g.basic_auth("webrick", "supersecretpassword")
 | 
						|
      http.request(g){|res| assert_equal("hoge", res.body, log.call)}
 | 
						|
      g.basic_auth("webrick", "not super")
 | 
						|
      http.request(g){|res| assert_not_equal("hoge", res.body, log.call)}
 | 
						|
    }
 | 
						|
  end
 | 
						|
 | 
						|
  def test_basic_auth_sha
 | 
						|
    Tempfile.create("test_webrick_auth") {|tmpfile|
 | 
						|
      tmpfile.puts("webrick:{SHA}GJYFRpBbdchp595jlh3Bhfmgp8k=")
 | 
						|
      tmpfile.flush
 | 
						|
      assert_raise(NotImplementedError){
 | 
						|
        WEBrick::HTTPAuth::Htpasswd.new(tmpfile.path)
 | 
						|
      }
 | 
						|
    }
 | 
						|
  end
 | 
						|
 | 
						|
  def test_basic_auth_md5
 | 
						|
    Tempfile.create("test_webrick_auth") {|tmpfile|
 | 
						|
      tmpfile.puts("webrick:$apr1$IOVMD/..$rmnOSPXr0.wwrLPZHBQZy0")
 | 
						|
      tmpfile.flush
 | 
						|
      assert_raise(NotImplementedError){
 | 
						|
        WEBrick::HTTPAuth::Htpasswd.new(tmpfile.path)
 | 
						|
      }
 | 
						|
    }
 | 
						|
  end
 | 
						|
 | 
						|
  [nil, :crypt, :bcrypt].each do |hash_algo|
 | 
						|
    # OpenBSD does not support insecure DES-crypt
 | 
						|
    next if /openbsd/ =~ RUBY_PLATFORM && hash_algo != :bcrypt
 | 
						|
 | 
						|
    begin
 | 
						|
      case hash_algo
 | 
						|
      when :crypt
 | 
						|
        # require 'string/crypt'
 | 
						|
      when :bcrypt
 | 
						|
        require 'bcrypt'
 | 
						|
      end
 | 
						|
    rescue LoadError
 | 
						|
      next
 | 
						|
    end
 | 
						|
 | 
						|
    define_method(:"test_basic_auth_htpasswd_#{hash_algo}") do
 | 
						|
      log_tester = lambda {|log, access_log|
 | 
						|
        log.reject! {|line| /\A\s*\z/ =~ line }
 | 
						|
        pats = [
 | 
						|
          /ERROR Basic WEBrick's realm: webrick: password unmatch\./,
 | 
						|
          /ERROR WEBrick::HTTPStatus::Unauthorized/
 | 
						|
        ]
 | 
						|
        pats.each {|pat|
 | 
						|
          assert(!log.grep(pat).empty?, "webrick log doesn't have expected error: #{pat.inspect}")
 | 
						|
          log.reject! {|line| pat =~ line }
 | 
						|
        }
 | 
						|
        assert_equal([], log)
 | 
						|
      }
 | 
						|
      TestWEBrick.start_httpserver({}, log_tester) {|server, addr, port, log|
 | 
						|
        realm = "WEBrick's realm"
 | 
						|
        path = "/basic_auth2"
 | 
						|
 | 
						|
        Tempfile.create("test_webrick_auth") {|tmpfile|
 | 
						|
          tmpfile.close
 | 
						|
          tmp_pass = WEBrick::HTTPAuth::Htpasswd.new(tmpfile.path, password_hash: hash_algo)
 | 
						|
          tmp_pass.set_passwd(realm, "webrick", "supersecretpassword")
 | 
						|
          tmp_pass.set_passwd(realm, "foo", "supersecretpassword")
 | 
						|
          tmp_pass.flush
 | 
						|
 | 
						|
          htpasswd = WEBrick::HTTPAuth::Htpasswd.new(tmpfile.path, password_hash: hash_algo)
 | 
						|
          users = []
 | 
						|
          htpasswd.each{|user, pass| users << user }
 | 
						|
          assert_equal(2, users.size, log.call)
 | 
						|
          assert(users.member?("webrick"), log.call)
 | 
						|
          assert(users.member?("foo"), log.call)
 | 
						|
 | 
						|
          server.mount_proc(path){|req, res|
 | 
						|
            auth = WEBrick::HTTPAuth::BasicAuth.new(
 | 
						|
              :Realm => realm, :UserDB => htpasswd,
 | 
						|
              :Logger => server.logger
 | 
						|
            )
 | 
						|
            auth.authenticate(req, res)
 | 
						|
            res.body = "hoge"
 | 
						|
          }
 | 
						|
          http = Net::HTTP.new(addr, port)
 | 
						|
          g = Net::HTTP::Get.new(path)
 | 
						|
          g.basic_auth("webrick", "supersecretpassword")
 | 
						|
          http.request(g){|res| assert_equal("hoge", res.body, log.call)}
 | 
						|
          g.basic_auth("webrick", "not super")
 | 
						|
          http.request(g){|res| assert_not_equal("hoge", res.body, log.call)}
 | 
						|
        }
 | 
						|
      }
 | 
						|
    end
 | 
						|
 | 
						|
    define_method(:"test_basic_auth_bad_username_htpasswd_#{hash_algo}") do
 | 
						|
      log_tester = lambda {|log, access_log|
 | 
						|
        assert_equal(2, log.length)
 | 
						|
        assert_match(/ERROR Basic WEBrick's realm: foo\\ebar: the user is not allowed\./, log[0])
 | 
						|
        assert_match(/ERROR WEBrick::HTTPStatus::Unauthorized/, log[1])
 | 
						|
      }
 | 
						|
      TestWEBrick.start_httpserver({}, log_tester) {|server, addr, port, log|
 | 
						|
        realm = "WEBrick's realm"
 | 
						|
        path = "/basic_auth"
 | 
						|
 | 
						|
        Tempfile.create("test_webrick_auth") {|tmpfile|
 | 
						|
          tmpfile.close
 | 
						|
          tmp_pass = WEBrick::HTTPAuth::Htpasswd.new(tmpfile.path, password_hash: hash_algo)
 | 
						|
          tmp_pass.set_passwd(realm, "webrick", "supersecretpassword")
 | 
						|
          tmp_pass.set_passwd(realm, "foo", "supersecretpassword")
 | 
						|
          tmp_pass.flush
 | 
						|
 | 
						|
          htpasswd = WEBrick::HTTPAuth::Htpasswd.new(tmpfile.path, password_hash: hash_algo)
 | 
						|
          users = []
 | 
						|
          htpasswd.each{|user, pass| users << user }
 | 
						|
          server.mount_proc(path){|req, res|
 | 
						|
            auth = WEBrick::HTTPAuth::BasicAuth.new(
 | 
						|
              :Realm => realm, :UserDB => htpasswd,
 | 
						|
              :Logger => server.logger
 | 
						|
            )
 | 
						|
            auth.authenticate(req, res)
 | 
						|
            res.body = "hoge"
 | 
						|
          }
 | 
						|
          http = Net::HTTP.new(addr, port)
 | 
						|
          g = Net::HTTP::Get.new(path)
 | 
						|
          g.basic_auth("foo\ebar", "passwd")
 | 
						|
          http.request(g){|res| assert_not_equal("hoge", res.body, log.call) }
 | 
						|
        }
 | 
						|
      }
 | 
						|
    end
 | 
						|
  end
 | 
						|
 | 
						|
  DIGESTRES_ = /
 | 
						|
    ([a-zA-Z\-]+)
 | 
						|
      [ \t]*(?:\r\n[ \t]*)*
 | 
						|
      =
 | 
						|
      [ \t]*(?:\r\n[ \t]*)*
 | 
						|
      (?:
 | 
						|
       "((?:[^"]+|\\[\x00-\x7F])*)" |
 | 
						|
       ([!\#$%&'*+\-.0-9A-Z^_`a-z|~]+)
 | 
						|
      )/x
 | 
						|
 | 
						|
  def test_digest_auth
 | 
						|
    log_tester = lambda {|log, access_log|
 | 
						|
      log.reject! {|line| /\A\s*\z/ =~ line }
 | 
						|
      pats = [
 | 
						|
        /ERROR Digest WEBrick's realm: no credentials in the request\./,
 | 
						|
        /ERROR WEBrick::HTTPStatus::Unauthorized/,
 | 
						|
        /ERROR Digest WEBrick's realm: webrick: digest unmatch\./
 | 
						|
      ]
 | 
						|
      pats.each {|pat|
 | 
						|
        assert(!log.grep(pat).empty?, "webrick log doesn't have expected error: #{pat.inspect}")
 | 
						|
        log.reject! {|line| pat =~ line }
 | 
						|
      }
 | 
						|
      assert_equal([], log)
 | 
						|
    }
 | 
						|
    TestWEBrick.start_httpserver({}, log_tester) {|server, addr, port, log|
 | 
						|
      realm = "WEBrick's realm"
 | 
						|
      path = "/digest_auth"
 | 
						|
 | 
						|
      Tempfile.create("test_webrick_auth") {|tmpfile|
 | 
						|
        tmpfile.close
 | 
						|
        tmp_pass = WEBrick::HTTPAuth::Htdigest.new(tmpfile.path)
 | 
						|
        tmp_pass.set_passwd(realm, "webrick", "supersecretpassword")
 | 
						|
        tmp_pass.set_passwd(realm, "foo", "supersecretpassword")
 | 
						|
        tmp_pass.flush
 | 
						|
 | 
						|
        htdigest = WEBrick::HTTPAuth::Htdigest.new(tmpfile.path)
 | 
						|
        users = []
 | 
						|
        htdigest.each{|user, pass| users << user }
 | 
						|
        assert_equal(2, users.size, log.call)
 | 
						|
        assert(users.member?("webrick"), log.call)
 | 
						|
        assert(users.member?("foo"), log.call)
 | 
						|
 | 
						|
        auth = WEBrick::HTTPAuth::DigestAuth.new(
 | 
						|
          :Realm => realm, :UserDB => htdigest,
 | 
						|
          :Algorithm => 'MD5',
 | 
						|
          :Logger => server.logger
 | 
						|
        )
 | 
						|
        server.mount_proc(path){|req, res|
 | 
						|
          auth.authenticate(req, res)
 | 
						|
          res.body = "hoge"
 | 
						|
        }
 | 
						|
 | 
						|
        Net::HTTP.start(addr, port) do |http|
 | 
						|
          g = Net::HTTP::Get.new(path)
 | 
						|
          params = {}
 | 
						|
          http.request(g) do |res|
 | 
						|
            assert_equal('401', res.code, log.call)
 | 
						|
            res["www-authenticate"].scan(DIGESTRES_) do |key, quoted, token|
 | 
						|
              params[key.downcase] = token || quoted.delete('\\')
 | 
						|
            end
 | 
						|
            params['uri'] = "http://#{addr}:#{port}#{path}"
 | 
						|
          end
 | 
						|
 | 
						|
          g['Authorization'] = credentials_for_request('webrick', "supersecretpassword", params)
 | 
						|
          http.request(g){|res| assert_equal("hoge", res.body, log.call)}
 | 
						|
 | 
						|
          params['algorithm'].downcase! #4936
 | 
						|
          g['Authorization'] = credentials_for_request('webrick', "supersecretpassword", params)
 | 
						|
          http.request(g){|res| assert_equal("hoge", res.body, log.call)}
 | 
						|
 | 
						|
          g['Authorization'] = credentials_for_request('webrick', "not super", params)
 | 
						|
          http.request(g){|res| assert_not_equal("hoge", res.body, log.call)}
 | 
						|
        end
 | 
						|
      }
 | 
						|
    }
 | 
						|
  end
 | 
						|
 | 
						|
  def test_digest_auth_int
 | 
						|
    log_tester = lambda {|log, access_log|
 | 
						|
      log.reject! {|line| /\A\s*\z/ =~ line }
 | 
						|
      pats = [
 | 
						|
        /ERROR Digest wb auth-int realm: no credentials in the request\./,
 | 
						|
        /ERROR WEBrick::HTTPStatus::Unauthorized/,
 | 
						|
        /ERROR Digest wb auth-int realm: foo: digest unmatch\./
 | 
						|
      ]
 | 
						|
      pats.each {|pat|
 | 
						|
        assert(!log.grep(pat).empty?, "webrick log doesn't have expected error: #{pat.inspect}")
 | 
						|
        log.reject! {|line| pat =~ line }
 | 
						|
      }
 | 
						|
      assert_equal([], log)
 | 
						|
    }
 | 
						|
    TestWEBrick.start_httpserver({}, log_tester) {|server, addr, port, log|
 | 
						|
      realm = "wb auth-int realm"
 | 
						|
      path = "/digest_auth_int"
 | 
						|
 | 
						|
      Tempfile.create("test_webrick_auth_int") {|tmpfile|
 | 
						|
        tmpfile.close
 | 
						|
        tmp_pass = WEBrick::HTTPAuth::Htdigest.new(tmpfile.path)
 | 
						|
        tmp_pass.set_passwd(realm, "foo", "Hunter2")
 | 
						|
        tmp_pass.flush
 | 
						|
 | 
						|
        htdigest = WEBrick::HTTPAuth::Htdigest.new(tmpfile.path)
 | 
						|
        users = []
 | 
						|
        htdigest.each{|user, pass| users << user }
 | 
						|
        assert_equal %w(foo), users
 | 
						|
 | 
						|
        auth = WEBrick::HTTPAuth::DigestAuth.new(
 | 
						|
          :Realm => realm, :UserDB => htdigest,
 | 
						|
          :Algorithm => 'MD5',
 | 
						|
          :Logger => server.logger,
 | 
						|
          :Qop => %w(auth-int),
 | 
						|
        )
 | 
						|
        server.mount_proc(path){|req, res|
 | 
						|
          auth.authenticate(req, res)
 | 
						|
          res.body = "bbb"
 | 
						|
        }
 | 
						|
        Net::HTTP.start(addr, port) do |http|
 | 
						|
          post = Net::HTTP::Post.new(path)
 | 
						|
          params = {}
 | 
						|
          data = 'hello=world'
 | 
						|
          body = StringIO.new(data)
 | 
						|
          post.content_length = data.bytesize
 | 
						|
          post['Content-Type'] = 'application/x-www-form-urlencoded'
 | 
						|
          post.body_stream = body
 | 
						|
 | 
						|
          http.request(post) do |res|
 | 
						|
            assert_equal('401', res.code, log.call)
 | 
						|
            res["www-authenticate"].scan(DIGESTRES_) do |key, quoted, token|
 | 
						|
              params[key.downcase] = token || quoted.delete('\\')
 | 
						|
            end
 | 
						|
             params['uri'] = "http://#{addr}:#{port}#{path}"
 | 
						|
          end
 | 
						|
 | 
						|
          body.rewind
 | 
						|
          cred = credentials_for_request('foo', 'Hunter3', params, body)
 | 
						|
          post['Authorization'] = cred
 | 
						|
          post.body_stream = body
 | 
						|
          http.request(post){|res|
 | 
						|
            assert_equal('401', res.code, log.call)
 | 
						|
            assert_not_equal("bbb", res.body, log.call)
 | 
						|
          }
 | 
						|
 | 
						|
          body.rewind
 | 
						|
          cred = credentials_for_request('foo', 'Hunter2', params, body)
 | 
						|
          post['Authorization'] = cred
 | 
						|
          post.body_stream = body
 | 
						|
          http.request(post){|res| assert_equal("bbb", res.body, log.call)}
 | 
						|
        end
 | 
						|
      }
 | 
						|
    }
 | 
						|
  end
 | 
						|
 | 
						|
  def test_digest_auth_invalid
 | 
						|
    digest_auth = WEBrick::HTTPAuth::DigestAuth.new(Realm: 'realm', UserDB: '')
 | 
						|
 | 
						|
    def digest_auth.error(fmt, *)
 | 
						|
    end
 | 
						|
 | 
						|
    def digest_auth.try_bad_request(len)
 | 
						|
      request = {"Authorization" => %[Digest a="#{'\b'*len}]}
 | 
						|
      authenticate request, nil
 | 
						|
    end
 | 
						|
 | 
						|
    bad_request = WEBrick::HTTPStatus::BadRequest
 | 
						|
    t0 = Process.clock_gettime(Process::CLOCK_MONOTONIC)
 | 
						|
    assert_raise(bad_request) {digest_auth.try_bad_request(10)}
 | 
						|
    limit = (Process.clock_gettime(Process::CLOCK_MONOTONIC) - t0)
 | 
						|
    [20, 50, 100, 200].each do |len|
 | 
						|
      assert_raise(bad_request) do
 | 
						|
        Timeout.timeout(len*limit) {digest_auth.try_bad_request(len)}
 | 
						|
      end
 | 
						|
    end
 | 
						|
  end
 | 
						|
 | 
						|
  private
 | 
						|
  def credentials_for_request(user, password, params, body = nil)
 | 
						|
    cnonce = "hoge"
 | 
						|
    nonce_count = 1
 | 
						|
    ha1 = "#{user}:#{params['realm']}:#{password}"
 | 
						|
    if body
 | 
						|
      dig = Digest::MD5.new
 | 
						|
      while buf = body.read(16384)
 | 
						|
        dig.update(buf)
 | 
						|
      end
 | 
						|
      body.rewind
 | 
						|
      ha2 = "POST:#{params['uri']}:#{dig.hexdigest}"
 | 
						|
    else
 | 
						|
      ha2 = "GET:#{params['uri']}"
 | 
						|
    end
 | 
						|
 | 
						|
    request_digest =
 | 
						|
      "#{Digest::MD5.hexdigest(ha1)}:" \
 | 
						|
      "#{params['nonce']}:#{'%08x' % nonce_count}:#{cnonce}:#{params['qop']}:" \
 | 
						|
      "#{Digest::MD5.hexdigest(ha2)}"
 | 
						|
    "Digest username=\"#{user}\"" \
 | 
						|
      ", realm=\"#{params['realm']}\"" \
 | 
						|
      ", nonce=\"#{params['nonce']}\"" \
 | 
						|
      ", uri=\"#{params['uri']}\"" \
 | 
						|
      ", qop=#{params['qop']}" \
 | 
						|
      ", nc=#{'%08x' % nonce_count}" \
 | 
						|
      ", cnonce=\"#{cnonce}\"" \
 | 
						|
      ", response=\"#{Digest::MD5.hexdigest(request_digest)}\"" \
 | 
						|
      ", opaque=\"#{params['opaque']}\"" \
 | 
						|
      ", algorithm=#{params['algorithm']}"
 | 
						|
  end
 | 
						|
end
 |