/test/mri/webrick/test_httpauth.rb
Ruby | 348 lines | 327 code | 20 blank | 1 comment | 2 complexity | 6377eaabd0709383c90071440efd6814 MD5 | raw file
Possible License(s): GPL-2.0
- # 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_auth2
- 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)
- tmp_pass.set_passwd(realm, "webrick", "supersecretpassword")
- tmp_pass.set_passwd(realm, "foo", "supersecretpassword")
- tmp_pass.flush
- htpasswd = WEBrick::HTTPAuth::Htpasswd.new(tmpfile.path)
- 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
- def test_basic_auth3
- Tempfile.create("test_webrick_auth") {|tmpfile|
- tmpfile.puts("webrick:{SHA}GJYFRpBbdchp595jlh3Bhfmgp8k=")
- tmpfile.flush
- assert_raise(NotImplementedError){
- WEBrick::HTTPAuth::Htpasswd.new(tmpfile.path)
- }
- }
- 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
- def test_bad_username_with_control_characters
- 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)
- tmp_pass.set_passwd(realm, "webrick", "supersecretpassword")
- tmp_pass.set_passwd(realm, "foo", "supersecretpassword")
- tmp_pass.flush
- htpasswd = WEBrick::HTTPAuth::Htpasswd.new(tmpfile.path)
- 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
- 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