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.
- checksums.yaml +4 -4
- data/HISTORY.md +7 -0
- data/Manifest.txt +7 -5
- data/README.md +0 -1
- data/Rakefile +0 -2
- data/bin/htdigest-ruby +2 -9
- data/bin/htpasswd-ruby +2 -9
- data/lib/htauth.rb +2 -3
- data/lib/htauth/algorithm.rb +54 -14
- data/lib/htauth/cli.rb +8 -0
- data/lib/htauth/cli/digest.rb +130 -0
- data/lib/htauth/cli/passwd.rb +179 -0
- data/lib/htauth/console.rb +31 -0
- data/lib/htauth/crypt.rb +1 -2
- data/lib/htauth/digest_entry.rb +31 -11
- data/lib/htauth/digest_file.rb +95 -9
- data/lib/htauth/entry.rb +2 -2
- data/lib/htauth/error.rb +18 -0
- data/lib/htauth/file.rb +87 -16
- data/lib/htauth/md5.rb +1 -2
- data/lib/htauth/passwd_entry.rb +47 -15
- data/lib/htauth/passwd_file.rb +108 -11
- data/lib/htauth/plaintext.rb +1 -2
- data/lib/htauth/sha1.rb +13 -13
- data/lib/htauth/version.rb +2 -19
- data/spec/cli/digest_spec.rb +150 -0
- data/spec/cli/passwd_spec.rb +206 -0
- data/spec/digest_entry_spec.rb +55 -55
- data/spec/digest_file_spec.rb +50 -50
- data/spec/md5_spec.rb +9 -9
- data/spec/passwd_entry_spec.rb +133 -133
- data/spec/passwd_file_spec.rb +50 -50
- data/spec/plaintext_spec.rb +8 -8
- data/spec/sha1_spec.rb +8 -8
- data/spec/spec_helper.rb +7 -0
- metadata +11 -23
- data/lib/htauth/digest.rb +0 -132
- data/lib/htauth/errors.rb +0 -10
- data/lib/htauth/passwd.rb +0 -181
- data/spec/digest_spec.rb +0 -150
- data/spec/passwd_spec.rb +0 -206
@@ -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
|
data/lib/htauth/crypt.rb
CHANGED
data/lib/htauth/digest_entry.rb
CHANGED
@@ -1,18 +1,24 @@
|
|
1
|
-
require 'htauth/
|
1
|
+
require 'htauth/error'
|
2
2
|
require 'htauth/entry'
|
3
3
|
require 'digest/md5'
|
4
4
|
|
5
5
|
module HTAuth
|
6
|
-
|
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
|
24
|
-
#
|
25
|
-
#
|
26
|
-
# and the md5sum must be
|
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
|
-
#
|
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
|
-
|
63
|
-
return
|
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
|
data/lib/htauth/digest_file.rb
CHANGED
@@ -1,21 +1,52 @@
|
|
1
1
|
require 'stringio'
|
2
|
-
require 'htauth/
|
2
|
+
require 'htauth/error'
|
3
3
|
require 'htauth/file'
|
4
4
|
require 'htauth/digest_entry'
|
5
5
|
|
6
6
|
module HTAuth
|
7
|
-
|
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
|
-
#
|
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
|
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
|
-
#
|
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
|
-
#
|
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
|
-
#
|
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
|
-
#
|
62
|
-
#
|
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
|
data/lib/htauth/entry.rb
CHANGED
data/lib/htauth/error.rb
ADDED
@@ -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
|
+
|
data/lib/htauth/file.rb
CHANGED
@@ -1,19 +1,55 @@
|
|
1
1
|
require 'stringio'
|
2
|
-
require 'htauth/
|
2
|
+
require 'htauth/error'
|
3
3
|
|
4
4
|
module HTAuth
|
5
|
-
class
|
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
|
-
|
8
|
-
|
9
|
-
|
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
|
-
#
|
16
|
-
#
|
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
|
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
|
-
#
|
33
|
-
#
|
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
|
-
#
|
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
|
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
|
-
#
|
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
|
-
#
|
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
|
-
#
|
91
|
-
#
|
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
|