Feature #14940 ยป 0001-Support-bcrypt-password-hashing-in-webrick.patch
lib/webrick/httpauth/basicauth.rb | ||
---|---|---|
#
|
||
# config = { :Realm => 'BasicAuth example realm' }
|
||
#
|
||
# htpasswd = WEBrick::HTTPAuth::Htpasswd.new 'my_password_file'
|
||
# htpasswd = WEBrick::HTTPAuth::Htpasswd.new 'my_password_file', password_hash: :bcrypt
|
||
# htpasswd.set_passwd config[:Realm], 'username', 'password'
|
||
# htpasswd.flush
|
||
#
|
||
... | ... | |
error("%s: the user is not allowed.", userid)
|
||
challenge(req, res)
|
||
end
|
||
if password.crypt(encpass) != encpass
|
||
case encpass
|
||
when /\A\$2[aby]\$/
|
||
password_matches = BCrypt::Password.new(encpass.sub(/\A\$2[aby]\$/, '$2a$')) == password
|
||
else
|
||
password_matches = password.crypt(encpass) == encpass
|
||
end
|
||
unless password_matches
|
||
error("%s: password unmatch.", userid)
|
||
challenge(req, res)
|
||
end
|
lib/webrick/httpauth/htpasswd.rb | ||
---|---|---|
##
|
||
# Open a password database at +path+
|
||
def initialize(path)
|
||
def initialize(path, password_hash: nil)
|
||
@path = path
|
||
@mtime = Time.at(0)
|
||
@passwd = Hash.new
|
||
@auth_type = BasicAuth
|
||
@password_hash = password_hash
|
||
case @password_hash
|
||
when nil
|
||
# begin
|
||
# require "string/crypt"
|
||
# rescue LoadError
|
||
# warn("Unable to load string/crypt, proceeding with deprecated use of String#crypt, consider using password_hash: :bcrypt")
|
||
# end
|
||
@password_hash = :crypt
|
||
when :crypt
|
||
# require "string/crypt"
|
||
when :bcrypt
|
||
require "bcrypt"
|
||
else
|
||
raise ArgumentError, "only :crypt and :bcrypt are supported for password_hash keyword argument"
|
||
end
|
||
File.open(@path,"a").close unless File.exist?(@path)
|
||
reload
|
||
end
|
||
... | ... | |
while line = io.gets
|
||
line.chomp!
|
||
case line
|
||
when %r!\A[^:]+:[a-zA-Z0-9./]{13}\z!
|
||
when %r!\A[^:]+:[a-zA-Z0-9./]{13}\z!
|
||
if @password_hash == :bcrypt
|
||
raise StandardError, ".htpasswd file contains crypt password, only bcrypt passwords supported"
|
||
end
|
||
user, pass = line.split(":")
|
||
when %r!\A[^:]+:\$2[aby]\$\d{2}\$.{53}\z!
|
||
if @password_hash == :crypt
|
||
raise StandardError, ".htpasswd file contains bcrypt password, only crypt passwords supported"
|
||
end
|
||
user, pass = line.split(":")
|
||
when /:\$/, /:{SHA}/
|
||
raise NotImplementedError,
|
||
... | ... | |
# 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)
|
||
if @password_hash == :bcrypt
|
||
# Cost of 5 to match Apache default, and because the
|
||
# bcrypt default of 10 will introduce significant delays
|
||
# for every request.
|
||
@passwd[user] = BCrypt::Password.create(pass, :cost=>5)
|
||
else
|
||
@passwd[user] = make_passwd(realm, user, pass)
|
||
end
|
||
end
|
||
##
|
test/webrick/test_httpauth.rb | ||
---|---|---|
}
|
||
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
|
||
def test_basic_auth_sha
|
||
Tempfile.create("test_webrick_auth") {|tmpfile|
|
||
tmpfile.puts("webrick:{SHA}GJYFRpBbdchp595jlh3Bhfmgp8k=")
|
||
tmpfile.flush
|
||
... | ... | |
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
|
||
... | ... | |
}
|
||
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"
|
||
[nil, :crypt, :bcrypt].each do |hash_algo|
|
||
begin
|
||
case hash_algo
|
||
when :crypt
|
||
# require 'string/crypt'
|
||
when :bcrypt
|
||
require 'bcrypt'
|
||
end
|
||
rescue LoadError
|
||
next
|
||
end
|
||
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
|
||
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"
|
||
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"
|
||
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)}
|
||
}
|
||
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
|
||
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_ = /
|