htauth 1.2.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,31 @@
1
+ require 'io/console'
2
+ require 'htauth/error'
3
+
4
+ # With many thanks to JEG2 - http://graysoftinc.com/terminal-tricks/random-access-terminal
5
+ #
6
+ module HTAuth
7
+ # Internal: Utility class for managing console input/output
8
+ class Console
9
+ attr_reader :input
10
+ attr_reader :output
11
+
12
+ def initialize(input = $stdin, output = $stdout)
13
+ @input = input
14
+ @output = output
15
+ end
16
+
17
+ def say(msg)
18
+ @output.puts msg
19
+ end
20
+
21
+ def ask(prompt)
22
+ output.print prompt
23
+ answer = input.noecho(&:gets)
24
+ output.puts
25
+ raise ConsoleError, "No input given" if answer.nil?
26
+ answer.strip!
27
+ raise ConsoleError, "No input given" if answer.length == 0
28
+ return answer
29
+ end
30
+ end
31
+ end
@@ -1,8 +1,7 @@
1
1
  require 'htauth/algorithm'
2
2
 
3
3
  module HTAuth
4
-
5
- # The basic crypt algorithm
4
+ # Internal: The basic crypt algorithm
6
5
  class Crypt < Algorithm
7
6
 
8
7
  def initialize(params = {})
@@ -1,18 +1,24 @@
1
- require 'htauth/errors'
1
+ require 'htauth/error'
2
2
  require 'htauth/entry'
3
3
  require 'digest/md5'
4
4
 
5
5
  module HTAuth
6
- class InvalidDigestEntry < StandardError ; end
7
-
8
- # A single record in an htdigest file.
6
+ # Internal: Object version of a single record from an htdigest file
9
7
  class DigestEntry
10
8
 
9
+ # Internal: The user of this entry
11
10
  attr_accessor :user
11
+ # Internal: The realm of this entry
12
12
  attr_accessor :realm
13
+ # Internal: The passwod digest of this entry
13
14
  attr_accessor :digest
14
15
 
15
16
  class << self
17
+ # Internal: Create an instance of this class from a line of text
18
+ #
19
+ # line - a line of text from a htdigest file
20
+ #
21
+ # Returns an instance of DigestEntry
16
22
  def from_line(line)
17
23
  parts = is_entry!(line)
18
24
  d = DigestEntry.new(parts[0], parts[1])
@@ -20,10 +26,16 @@ module HTAuth
20
26
  return d
21
27
  end
22
28
 
23
- # test if a line is an entry, raise InvalidDigestEntry if it is not.
24
- # an entry must be composed of 3 parts, username:realm:md5sum
25
- # where username, and realm do not contain the ':' character
26
- # and the md5sum must be 32 characters long.
29
+ # Internal: test if the given line is valid for this Entry class
30
+ #
31
+ # A valid entry must be composed of 3 parts, username:realm:md5sum where
32
+ # username, and realm do not contain the ':' character; and md5sum must be
33
+ # 32 characters long
34
+ #
35
+ # line - a line of text from a file
36
+ #
37
+ # Returns the individual parts of the line
38
+ # Raises InvalidDigestEntry if it is not a a valid entry
27
39
  def is_entry!(line)
28
40
  raise InvalidDigestEntry, "line commented out" if line =~ /\A#/
29
41
  parts = line.strip.split(":")
@@ -33,7 +45,9 @@ module HTAuth
33
45
  return parts
34
46
  end
35
47
 
36
- # test if a line is an entry and return true or false
48
+ # Internal: Returns whether or not the line is a valid entry
49
+ #
50
+ # Returns true or false
37
51
  def is_entry?(line)
38
52
  begin
39
53
  is_entry!(line)
@@ -44,29 +58,35 @@ module HTAuth
44
58
  end
45
59
  end
46
60
 
61
+ # Internal: Create a new Entry with the given user, realm and password
47
62
  def initialize(user, realm, password = "")
48
63
  @user = user
49
64
  @realm = realm
50
65
  @digest = calc_digest(password)
51
66
  end
52
67
 
68
+ # Internal: Update the password of the entry with its new value
53
69
  def password=(new_password)
54
70
  @digest = calc_digest(new_password)
55
71
  end
56
72
 
73
+ # Internal: calculate the new digest of the given password
57
74
  def calc_digest(password)
58
75
  ::Digest::MD5.hexdigest("#{user}:#{realm}:#{password}")
59
76
  end
60
77
 
78
+ # Public: Check if the given password is the password of this entry.
61
79
  def authenticated?(check_password)
62
- hd = ::Digest::MD5.hexdigest("#{user}:#{realm}:#{check_password}")
63
- return hd == digest
80
+ check = calc_digest(check_password)
81
+ return Algorithm.secure_compare(check, digest)
64
82
  end
65
83
 
84
+ # Internal: Returns the key of this entry
66
85
  def key
67
86
  "#{user}:#{realm}"
68
87
  end
69
88
 
89
+ # Internal: Returns the file line for this entry
70
90
  def to_s
71
91
  "#{user}:#{realm}:#{digest}"
72
92
  end
@@ -1,21 +1,52 @@
1
1
  require 'stringio'
2
- require 'htauth/errors'
2
+ require 'htauth/error'
3
3
  require 'htauth/file'
4
4
  require 'htauth/digest_entry'
5
5
 
6
6
  module HTAuth
7
- class DigestFileError < StandardError ; end
7
+ # Public: An API for managing an 'htdigest' file
8
+ #
9
+ # Examples
10
+ #
11
+ # ::HTAuth::DigestFile.open("my.digest") do |df|
12
+ # df.has_entry?('myuser', 'myrealm')
13
+ # df.add_or_update('someuser', 'myrealm', 'a password')
14
+ # df.delete('someolduser', 'myotherrealm')
15
+ # end
16
+ #
8
17
  class DigestFile < HTAuth::File
9
18
 
19
+ # Private: The class implementing a single entry in the DigestFile
10
20
  ENTRY_KLASS = HTAuth::DigestEntry
11
21
 
12
- # does the entry the the specified username and realm exist in the file
22
+ # Public: Checks if the given username / realm combination exists
23
+ #
24
+ # username - the username to check
25
+ # realm - the realm to check
26
+ #
27
+ # Examples
28
+ #
29
+ # digest_file.has_entry?("myuser", "myrealm")
30
+ # # => true
31
+ #
32
+ # Returns true or false if the username/realm combination is found.
13
33
  def has_entry?(username, realm)
14
34
  test_entry = DigestEntry.new(username, realm)
15
35
  @entries.has_key?(test_entry.key)
16
36
  end
17
37
 
18
- # remove an entry from the file
38
+ # Public: remove the given username / realm from the file.
39
+ # The file is not written to disk until #save! is called.
40
+ #
41
+ # username - the username to remove
42
+ # realm - the realm to remove
43
+ #
44
+ # Examples
45
+ #
46
+ # digest_file.delete("myuser", "myrealm")
47
+ # digest_file.save!
48
+ #
49
+ # Returns nothing
19
50
  def delete(username, realm)
20
51
  if has_entry?(username, realm) then
21
52
  ir = internal_record(username, realm)
@@ -27,7 +58,23 @@ module HTAuth
27
58
  nil
28
59
  end
29
60
 
30
- # add or update an entry as appropriate
61
+ # Public: Add or update username / realm entry with the new password.
62
+ # This will add a new entry if the username / realm combination does not
63
+ # exist in the file. If the entry does exist in the file, then the password
64
+ # of the entry is updated to the new password.
65
+ #
66
+ # The file is not written to disk until #save! is called.
67
+ #
68
+ # username - the username of the entry
69
+ # realm - the realm of the entry
70
+ # password - the password of the entry
71
+ #
72
+ # Examples
73
+ #
74
+ # digest_file.add_or_update("newuser", "realm", "password")
75
+ # digest_file.save!
76
+ #
77
+ # Returns nothing.
31
78
  def add_or_update(username, realm, password)
32
79
  if has_entry?(username, realm) then
33
80
  update(username, realm, password)
@@ -36,7 +83,19 @@ module HTAuth
36
83
  end
37
84
  end
38
85
 
39
- # add an new record. raises an error if the entry exists.
86
+ # Public: Add a new record to the file.
87
+ #
88
+ # username - the username of the entry
89
+ # realm - the realm of the entry
90
+ # password - the password of the entry
91
+ #
92
+ # Examples
93
+ #
94
+ # digest_file.add("newuser", "realm", "password")
95
+ # digest_file.save!
96
+ #
97
+ # Returns nothing.
98
+ # Raises DigestFileError if the give username / realm already exists.
40
99
  def add(username, realm, password)
41
100
  raise DigestFileError, "Unable to add already existing user #{username} in realm #{realm}" if has_entry?(username, realm)
42
101
 
@@ -48,7 +107,19 @@ module HTAuth
48
107
  return nil
49
108
  end
50
109
 
51
- # update an already existing entry with a new password. raises an error if the entry does not exist
110
+ # Public: Updates an existing username / relam entry with a new password
111
+ #
112
+ # username - the username of the entry
113
+ # realm - the realm of the entry
114
+ # password - the password of the entry
115
+ #
116
+ # Examples
117
+ #
118
+ # digest_file.update("existinguser", "realm", "newpassword")
119
+ # digest_file.save!
120
+ #
121
+ # Returns nothing
122
+ # Raises DigestfileError if the username / realm is not found in the file
52
123
  def update(username, realm, password)
53
124
  raise DigestFileError, "Unable to update non-existent user #{username} in realm #{realm}" unless has_entry?(username, realm)
54
125
  ir = internal_record(username, realm)
@@ -58,14 +129,29 @@ module HTAuth
58
129
  return nil
59
130
  end
60
131
 
61
- # fetches a copy of an entry from the file. Updateing the entry returned from fetch will NOT
62
- # propogate back to the file.
132
+ # Public: Returns the given DigestEntry from the file.
133
+ #
134
+ # Updating the DigestEntry instance returned by this method will NOT update
135
+ # the file. To update the file, use #update and #save!
136
+ #
137
+ # username - the username of the entry
138
+ # realm - the realm of the entry
139
+ #
140
+ # Examples
141
+ #
142
+ # entry = digest_file.fetch("myuser", "myrealm")
143
+ #
144
+ # Returns a DigestEntry if the entry is found
145
+ # Returns nil if the entry is not found
63
146
  def fetch(username, realm)
64
147
  return nil unless has_entry?(username, realm)
65
148
  ir = internal_record(username, realm)
66
149
  return ir['entry'].dup
67
150
  end
68
151
 
152
+ # Internal: returns the class used for each entry
153
+ #
154
+ # Returns a Class
69
155
  def entry_klass
70
156
  ENTRY_KLASS
71
157
  end
@@ -1,7 +1,7 @@
1
1
  module HTAuth
2
-
3
- # base class from which all entries are derived
2
+ # Internal: base class from which all entries are derived
4
3
  class Entry
4
+ # Internal: return a new instance of this entry
5
5
  def dup
6
6
  self.class.from_line(self.to_s)
7
7
  end
@@ -0,0 +1,18 @@
1
+ #--
2
+ # Copyrigth (c) 2008 Jeremy Hinegardner
3
+ # All rights reserved. See LICENSE and/or COPYING for details
4
+ #++
5
+
6
+ module HTAuth
7
+ class Error < StandardError; end
8
+
9
+ class ConsoleError < Error; end
10
+ class DigestFileError < Error ; end
11
+ class FileAccessError < Error; end
12
+ class InvalidDigestEntry < Error; end
13
+ class InvalidPasswdEntry < Error ; end
14
+ class PasswordError < Error; end
15
+ class PasswdFileError < Error ; end
16
+ class TempFileError < Error; end
17
+ end
18
+
@@ -1,19 +1,55 @@
1
1
  require 'stringio'
2
- require 'htauth/errors'
2
+ require 'htauth/error'
3
3
 
4
4
  module HTAuth
5
- class FileAccessError < StandardError ; end
5
+ # Internal: A base class for DigestFile and PasswordFile to inherit from.
6
+ #
7
+ # This class should not be instantiated directly. You must use DigestFile or
8
+ # PasswordFile.
6
9
  class File
7
- ALTER = "alter"
8
- CREATE = "create"
9
- STDOUT_FLAG = "-"
10
+
11
+ # Public: The mode to pass to #open for updating a file
12
+ ALTER = "alter".freeze
13
+
14
+ # Public: The mode to pass to #open for creating a new file
15
+ CREATE = "create".freeze
16
+
17
+ # Public: A special 'filename' that may be passed to #open for 'saving' to $stdout
18
+ STDOUT_FLAG = "-".freeze
10
19
 
11
20
  attr_reader :filename
12
21
  attr_reader :file
13
22
 
14
23
  class << self
15
- # open a file yielding the the file object for use. The file is saved when
16
- # the block exists, if the file has had alterations made.
24
+ # Public: The method to use to open a DigestFile or PasswordFile.
25
+ # Altering a non-existent file is an error. Creating an existing file
26
+ # results in a truncation and overwrite of the existing file.
27
+ #
28
+ # filename - The name of the file to open
29
+ # mode - The mode to open the file this must be either CREATE or
30
+ # ALTER. (default: ALTER)
31
+ #
32
+ # Yields the instance of DigestFile or PasswordFile that was opened.
33
+ # The File will be saved at the end of the block if any entries have been
34
+ # added, updated, or deleted.
35
+ #
36
+ # Examples
37
+ #
38
+ # df = ::HTAuth::DigestFile.open("my.digest")
39
+ #
40
+ # ::HTAuth::Digestfile.open("my.digest") do |df|
41
+ # # ...
42
+ # end
43
+ #
44
+ # pf = ::HTAuth::PasswordFile.open("my.passwd")
45
+ #
46
+ # ::HTAuth::PasswordFile.open("my.passwd") do |pf|
47
+ # # ...
48
+ # end
49
+ #
50
+ # Returns the DigestFile or PasswordFile as appropriate.
51
+ # Raises FileAccessError if an invalid mode is used.
52
+ # Raises FileAccessError if ALTERing a non-existent file.
17
53
  def open(filename, mode = ALTER)
18
54
  f = self.new(filename, mode)
19
55
  if block_given?
@@ -27,10 +63,25 @@ module HTAuth
27
63
  end
28
64
  end
29
65
 
30
- # Create or Alter a password file.
66
+ # Public: Create a new DigestFile or PasswordFile.
67
+ # Generally you do not need to use this method. Use #open instead.
68
+ #
69
+ # Altering a non-existent file is an error. Creating an existing file
70
+ # results in a truncation and overwrite of the existing file.
71
+ #
72
+ # filename - The name of the file to open
73
+ # mode - The mode to open the file this must be either CREATE or
74
+ # ALTER. (default: ALTER)
75
+ #
76
+ # Examples
77
+ #
78
+ # df = ::HTAuth::DigestFile.open("my.digest")
79
+ #
80
+ # pf = ::HTAuth::PasswordFile.open("my.passwd")
31
81
  #
32
- # Altering a non-existent file is an error. Creating an existing file results in
33
- # a truncation and overwrite of the existing file.
82
+ # Returns the DigestFile or PasswordFile as appropriate.
83
+ # Raises FileAccessError if an invalid mode is used.
84
+ # Raises FileAccessError if ALTERing a non-existent file.
34
85
  def initialize(filename, mode = ALTER)
35
86
  @filename = filename
36
87
  @mode = mode
@@ -51,17 +102,30 @@ module HTAuth
51
102
  end
52
103
  end
53
104
 
54
- # return whether or not an alteration to the file has happened
105
+ # Public: Returns if the file has had any alterations.
106
+ #
107
+ # Returns true or false
55
108
  def dirty?
56
109
  @dirty
57
110
  end
58
111
 
59
- # mark the file as dirty
112
+ # Public: Explicitly mark the file as having had alterations
113
+ #
114
+ # Returns true
60
115
  def dirty!
61
116
  @dirty = true
62
117
  end
63
118
 
64
- # update the original file with the new contents
119
+ # Public: Write out the file to filename from #open.
120
+ # This will write out the whole file at once. If writing to a filesystem
121
+ # file this overwrites the whole file.
122
+ #
123
+ # Example
124
+ #
125
+ # df.save!
126
+ #
127
+ # Returns nothing
128
+ # Raises FileAccessError if there was a problem writing the file
65
129
  def save!
66
130
  begin
67
131
  case filename
@@ -78,7 +142,9 @@ module HTAuth
78
142
  end
79
143
  end
80
144
 
81
- # return what should be the contents of the file
145
+ # Internal: Return the String of the entire file contents
146
+ #
147
+ # Returns String
82
148
  def contents
83
149
  c = StringIO.new
84
150
  @lines.each do |l|
@@ -87,8 +153,12 @@ module HTAuth
87
153
  c.string
88
154
  end
89
155
 
90
- # load up entries, keep items in the same order and do not trim out any
91
- # items in the file, like commented out lines or empty space
156
+ # Internal: Loads all the entries from the file into an internal array.
157
+ #
158
+ # This keeps the original lines in one array and all the entries in a
159
+ # separate structure indexed by key. This allows the file to be written back
160
+ # out in the same order it was read with the appropriate entries updated or
161
+ # deleted.
92
162
  def load_entries
93
163
  @lines = IO.readlines(@filename)
94
164
  @lines.each_with_index do |line,idx|
@@ -98,6 +168,7 @@ module HTAuth
98
168
  @entries[entry.key] = v
99
169
  end
100
170
  end
171
+ nil
101
172
  end
102
173
  end
103
174
  end