summaryrefslogtreecommitdiff
path: root/lib/webrick/httpauth/htdigest.rb
blob: 4b74588c77675c989451a509e4aae717ed6dda72 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
#
# httpauth/htdigest.rb -- Apache compatible htdigest file
#
# Author: IPR -- Internet Programming with Ruby -- writers
# Copyright (c) 2003 Internet Programming with Ruby writers. All rights
# reserved.
#
# $IPR: htdigest.rb,v 1.4 2003/07/22 19:20:45 gotoyuzo Exp $

require 'webrick/httpauth/userdb'
require 'webrick/httpauth/digestauth'
require 'tempfile'

module WEBrick
  module HTTPAuth

    ##
    # Htdigest accesses apache-compatible digest password files.  Passwords are
    # matched to a realm where they are valid.  For security, the path for a
    # digest password database should be stored outside of the paths available
    # to the HTTP server.
    #
    # Htdigest is intended for use with WEBrick::HTTPAuth::DigestAuth and
    # stores passwords using cryptographic hashes.
    #
    #   htpasswd = WEBrick::HTTPAuth::Htdigest.new 'my_password_file'
    #   htpasswd.set_passwd 'my realm', 'username', 'password'
    #   htpasswd.flush

    class Htdigest
      include UserDB

      ##
      # Open a digest password database at +path+

      def initialize(path)
        @path = path
        @mtime = Time.at(0)
        @digest = Hash.new
        @mutex = Mutex::new
        @auth_type = DigestAuth
        open(@path,"a").close unless File::exist?(@path)
        reload
      end

      ##
      # Reloads passwords from the database

      def reload
        mtime = File::mtime(@path)
        if mtime > @mtime
          @digest.clear
          open(@path){|io|
            while line = io.gets
              line.chomp!
              user, realm, pass = line.split(/:/, 3)
              unless @digest[realm]
                @digest[realm] = Hash.new
              end
              @digest[realm][user] = pass
            end
          }
          @mtime = mtime
        end
      end

      ##
      # Flush the password database.  If +output+ is given the database will
      # be written there instead of to the original path.

      def flush(output=nil)
        output ||= @path
        tmp = Tempfile.new("htpasswd", File::dirname(output))
        begin
          each{|item| tmp.puts(item.join(":")) }
          tmp.close
          File::rename(tmp.path, output)
        rescue
          tmp.close(true)
        end
      end

      ##
      # Retrieves a password from the database for +user+ in +realm+.  If
      # +reload_db+ is true the database will be reloaded first.

      def get_passwd(realm, user, reload_db)
        reload() if reload_db
        if hash = @digest[realm]
          hash[user]
        end
      end

      ##
      # Sets a password in the database for +user+ in +realm+ to +pass+.

      def set_passwd(realm, user, pass)
        @mutex.synchronize{
          unless @digest[realm]
            @digest[realm] = Hash.new
          end
          @digest[realm][user] = make_passwd(realm, user, pass)
        }
      end

      ##
      # Removes a password from the database for +user+ in +realm+.

      def delete_passwd(realm, user)
        if hash = @digest[realm]
          hash.delete(user)
        end
      end

      ##
      # Iterate passwords in the database.

      def each # :yields: [user, realm, password_hash]
        @digest.keys.sort.each{|realm|
          hash = @digest[realm]
          hash.keys.sort.each{|user|
            yield([user, realm, hash[user]])
          }
        }
      end
    end
  end
end