1# 2# httpauth/htdigest.rb -- Apache compatible htdigest file 3# 4# Author: IPR -- Internet Programming with Ruby -- writers 5# Copyright (c) 2003 Internet Programming with Ruby writers. All rights 6# reserved. 7# 8# $IPR: htdigest.rb,v 1.4 2003/07/22 19:20:45 gotoyuzo Exp $ 9 10require 'webrick/httpauth/userdb' 11require 'webrick/httpauth/digestauth' 12require 'tempfile' 13 14module WEBrick 15 module HTTPAuth 16 17 ## 18 # Htdigest accesses apache-compatible digest password files. Passwords are 19 # matched to a realm where they are valid. For security, the path for a 20 # digest password database should be stored outside of the paths available 21 # to the HTTP server. 22 # 23 # Htdigest is intended for use with WEBrick::HTTPAuth::DigestAuth and 24 # stores passwords using cryptographic hashes. 25 # 26 # htpasswd = WEBrick::HTTPAuth::Htdigest.new 'my_password_file' 27 # htpasswd.set_passwd 'my realm', 'username', 'password' 28 # htpasswd.flush 29 30 class Htdigest 31 include UserDB 32 33 ## 34 # Open a digest password database at +path+ 35 36 def initialize(path) 37 @path = path 38 @mtime = Time.at(0) 39 @digest = Hash.new 40 @mutex = Mutex::new 41 @auth_type = DigestAuth 42 open(@path,"a").close unless File::exist?(@path) 43 reload 44 end 45 46 ## 47 # Reloads passwords from the database 48 49 def reload 50 mtime = File::mtime(@path) 51 if mtime > @mtime 52 @digest.clear 53 open(@path){|io| 54 while line = io.gets 55 line.chomp! 56 user, realm, pass = line.split(/:/, 3) 57 unless @digest[realm] 58 @digest[realm] = Hash.new 59 end 60 @digest[realm][user] = pass 61 end 62 } 63 @mtime = mtime 64 end 65 end 66 67 ## 68 # Flush the password database. If +output+ is given the database will 69 # be written there instead of to the original path. 70 71 def flush(output=nil) 72 output ||= @path 73 tmp = Tempfile.new("htpasswd", File::dirname(output)) 74 begin 75 each{|item| tmp.puts(item.join(":")) } 76 tmp.close 77 File::rename(tmp.path, output) 78 rescue 79 tmp.close(true) 80 end 81 end 82 83 ## 84 # Retrieves a password from the database for +user+ in +realm+. If 85 # +reload_db+ is true the database will be reloaded first. 86 87 def get_passwd(realm, user, reload_db) 88 reload() if reload_db 89 if hash = @digest[realm] 90 hash[user] 91 end 92 end 93 94 ## 95 # Sets a password in the database for +user+ in +realm+ to +pass+. 96 97 def set_passwd(realm, user, pass) 98 @mutex.synchronize{ 99 unless @digest[realm] 100 @digest[realm] = Hash.new 101 end 102 @digest[realm][user] = make_passwd(realm, user, pass) 103 } 104 end 105 106 ## 107 # Removes a password from the database for +user+ in +realm+. 108 109 def delete_passwd(realm, user) 110 if hash = @digest[realm] 111 hash.delete(user) 112 end 113 end 114 115 ## 116 # Iterate passwords in the database. 117 118 def each # :yields: [user, realm, password_hash] 119 @digest.keys.sort.each{|realm| 120 hash = @digest[realm] 121 hash.keys.sort.each{|user| 122 yield([user, realm, hash[user]]) 123 } 124 } 125 end 126 end 127 end 128end 129