summaryrefslogtreecommitdiff
path: root/lib/webrick/httpauth/htpasswd.rb
blob: 976eeeb13e1b3a49f5a43c63f9cd49aa5248dc95 (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
# frozen_string_literal: false
#
# httpauth/htpasswd -- Apache compatible htpasswd file
#
# Author: IPR -- Internet Programming with Ruby -- writers
# Copyright (c) 2003 Internet Programming with Ruby writers. All rights
# reserved.
#
# $IPR: htpasswd.rb,v 1.4 2003/07/22 19:20:45 gotoyuzo Exp $

require 'webrick/httpauth/userdb'
require 'webrick/httpauth/basicauth'
require 'tempfile'

module WEBrick
  module HTTPAuth

    ##
    # Htpasswd accesses apache-compatible password files.  Passwords are
    # matched to a realm where they are valid.  For security, the path for a
    # password database should be stored outside of the paths available to the
    # HTTP server.
    #
    # Htpasswd is intended for use with WEBrick::HTTPAuth::BasicAuth.
    #
    # To create an Htpasswd database with a single user:
    #
    #   htpasswd = WEBrick::HTTPAuth::Htpasswd.new 'my_password_file'
    #   htpasswd.set_passwd 'my realm', 'username', 'password'
    #   htpasswd.flush

    class Htpasswd
      include UserDB

      ##
      # Open a password database at +path+

      def initialize(path)
        @path = path
        @mtime = Time.at(0)
        @passwd = Hash.new
        @auth_type = BasicAuth
        File.open(@path,"a").close unless File.exist?(@path)
        reload
      end

      ##
      # Reload passwords from the database

      def reload
        mtime = File::mtime(@path)
        if mtime > @mtime
          @passwd.clear
          File.open(@path){|io|
            while line = io.gets
              line.chomp!
              case line
              when %r!\A[^:]+:[a-zA-Z0-9./]{13}\z!
                user, pass = line.split(":")
              when /:\$/, /:{SHA}/
                raise NotImplementedError,
                      'MD5, SHA1 .htpasswd file not supported'
              else
                raise StandardError, 'bad .htpasswd file'
              end
              @passwd[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.create("htpasswd", File::dirname(output))
        renamed = false
        begin
          each{|item| tmp.puts(item.join(":")) }
          tmp.close
          File::rename(tmp.path, output)
          renamed = true
        ensure
          tmp.close
          File.unlink(tmp.path) if !renamed
        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
        @passwd[user]
      end

      ##
      # 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)
      end

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

      def delete_passwd(realm, user)
        @passwd.delete(user)
      end

      ##
      # Iterate passwords in the database.

      def each # :yields: [user, password]
        @passwd.keys.sort.each{|user|
          yield([user, @passwd[user]])
        }
      end
    end
  end
end