1# 2# httpauth/htpasswd -- Apache compatible htpasswd 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: htpasswd.rb,v 1.4 2003/07/22 19:20:45 gotoyuzo Exp $ 9 10require 'webrick/httpauth/userdb' 11require 'webrick/httpauth/basicauth' 12require 'tempfile' 13 14module WEBrick 15 module HTTPAuth 16 17 ## 18 # Htpasswd accesses apache-compatible password files. Passwords are 19 # matched to a realm where they are valid. For security, the path for a 20 # password database should be stored outside of the paths available to the 21 # HTTP server. 22 # 23 # Htpasswd is intended for use with WEBrick::HTTPAuth::BasicAuth. 24 # 25 # To create an Htpasswd database with a single user: 26 # 27 # htpasswd = WEBrick::HTTPAuth::Htpasswd.new 'my_password_file' 28 # htpasswd.set_passwd 'my realm', 'username', 'password' 29 # htpasswd.flush 30 31 class Htpasswd 32 include UserDB 33 34 ## 35 # Open a password database at +path+ 36 37 def initialize(path) 38 @path = path 39 @mtime = Time.at(0) 40 @passwd = Hash.new 41 @auth_type = BasicAuth 42 open(@path,"a").close unless File::exist?(@path) 43 reload 44 end 45 46 ## 47 # Reload passwords from the database 48 49 def reload 50 mtime = File::mtime(@path) 51 if mtime > @mtime 52 @passwd.clear 53 open(@path){|io| 54 while line = io.gets 55 line.chomp! 56 case line 57 when %r!\A[^:]+:[a-zA-Z0-9./]{13}\z! 58 user, pass = line.split(":") 59 when /:\$/, /:{SHA}/ 60 raise NotImplementedError, 61 'MD5, SHA1 .htpasswd file not supported' 62 else 63 raise StandardError, 'bad .htpasswd file' 64 end 65 @passwd[user] = pass 66 end 67 } 68 @mtime = mtime 69 end 70 end 71 72 ## 73 # Flush the password database. If +output+ is given the database will 74 # be written there instead of to the original path. 75 76 def flush(output=nil) 77 output ||= @path 78 tmp = Tempfile.new("htpasswd", File::dirname(output)) 79 begin 80 each{|item| tmp.puts(item.join(":")) } 81 tmp.close 82 File::rename(tmp.path, output) 83 rescue 84 tmp.close(true) 85 end 86 end 87 88 ## 89 # Retrieves a password from the database for +user+ in +realm+. If 90 # +reload_db+ is true the database will be reloaded first. 91 92 def get_passwd(realm, user, reload_db) 93 reload() if reload_db 94 @passwd[user] 95 end 96 97 ## 98 # Sets a password in the database for +user+ in +realm+ to +pass+. 99 100 def set_passwd(realm, user, pass) 101 @passwd[user] = make_passwd(realm, user, pass) 102 end 103 104 ## 105 # Removes a password from the database for +user+ in +realm+. 106 107 def delete_passwd(realm, user) 108 @passwd.delete(user) 109 end 110 111 ## 112 # Iterate passwords in the database. 113 114 def each # :yields: [user, password] 115 @passwd.keys.sort.each{|user| 116 yield([user, @passwd[user]]) 117 } 118 end 119 end 120 end 121end 122