class BasicAuth::Htpasswd

Attributes

path[RW]
realm[RW]

Public Class Methods

_to64(v, n) click to toggle source
# File lib/basic_auth/htpasswd.rb, line 114
def Htpasswd._to64(v, n)
  chars = "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
  output = ""
  n.times do
    output << chars[v & 0x3f]
    v >>= 6
  end
  output
end
crypt_md5(pass, salt) click to toggle source
# File lib/basic_auth/htpasswd.rb, line 77
def Htpasswd.crypt_md5(pass, salt)
  ctx = Digest::MD5.new.update("#{pass}$apr1$#{salt}")
  final = Digest::MD5.new.update("#{pass}#{salt}#{pass}").digest!.bytes

  l = pass.length
  while l > 0
    ctx.update(final[0 .. (l > 16 ? 16 : l) - 1].pack("C*"))
    l -= 16
  end

  l = pass.length
  while l > 0
    ctx.update(l % 2 != 0 ? "\0" : pass[0])
    l >>= 1
  end

  final = ctx.digest!

  1000.times do |i|
    ctx = Digest::MD5.new
    ctx.update(i % 2 != 0 ? pass : final)
    ctx.update(salt) if i % 3 != 0
    ctx.update(pass) if i % 7 != 0
    ctx.update(i % 2 != 0 ? final : pass)
    final = ctx.digest!
  end

  final = final.bytes
  hash = ""
  for a, b, c in [[0, 6, 12], [1, 7, 13], [2, 8, 14], [3, 9, 15], [4, 10, 5]]
    hash << _to64(final[a] << 16 | final[b] << 8 | final[c], 4)
  end
  hash << _to64(final[11], 2)

  "$apr1$#{salt}$#{hash}"
end
crypt_sha1(pass) click to toggle source
# File lib/basic_auth/htpasswd.rb, line 124
def Htpasswd.crypt_sha1(pass)
  "{SHA}" + [Digest::SHA1.new.update(pass).digest!].pack("m").chomp
end
new(path, realm=nil) click to toggle source
# File lib/basic_auth/htpasswd.rb, line 33
def initialize(path, realm=nil)
  @path = path
  @realm = realm
end
validate(pass, hash) click to toggle source
# File lib/basic_auth/htpasswd.rb, line 128
def Htpasswd.validate(pass, hash)
  if /^\$apr1\$(.*)\$/.match(hash)
    encoded = crypt_md5(pass, $1)
  elsif /^{SHA}/.match(hash)
    encoded = crypt_sha1(pass)
  else
    raise "crypt-style password hash is not supported"
  end
  return encoded == hash
end

Public Instance Methods

call(env) click to toggle source
# File lib/basic_auth/htpasswd.rb, line 38
def call(env)
  if /\/\.ht/.match(env['PATH_INFO'])
    return [ 404, { "Content-Type" => "text/plain" }, [ "not found" ] ]
  end
  auth = env['HTTP_AUTHORIZATION'] # Example: Basic dXNlcjpwYXNz
  # dXNlcjpwYXNz is base64 encoded
  if auth
    method, cred = *auth.split(' ')
    if method.casecmp("basic") == 0
      user, pass = cred.unpack("m")[0].split(':', 2)
      begin
        if validate(user, pass)
          return true
        end
      rescue => e
        $stderr.puts "failed to validate password using file:#{@path}:#{e.message}"
        return false
      end
    end
  end
  false
end
data() click to toggle source
# File lib/basic_auth/htpasswd.rb, line 72
def data
  return @@data if @@data && !ENV['BASIC_AUTH_NO_CACHE']
  @@data = File.readlines(@path)
end
validate(user, pass) click to toggle source
# File lib/basic_auth/htpasswd.rb, line 61
def validate(user, pass)
  data.each do |line|
    line_user, hash = line.chomp.split(':', 2)
    if user == line_user && self.class.validate(pass, hash)
      return true
    end
  end
  return false
end