htauth 2.3.0 → 3.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 +14 -0
- data/Manifest.txt +4 -28
- data/exe/htdigest-ruby +14 -0
- data/exe/htpasswd-ruby +14 -0
- data/htauth.gemspec +33 -0
- data/lib/htauth/algorithm.rb +30 -29
- data/lib/htauth/argon2.rb +45 -36
- data/lib/htauth/bcrypt.rb +12 -11
- data/lib/htauth/cli/digest.rb +42 -46
- data/lib/htauth/cli/passwd.rb +126 -115
- data/lib/htauth/cli.rb +5 -3
- data/lib/htauth/console.rb +9 -6
- data/lib/htauth/crypt.rb +11 -9
- data/lib/htauth/descendant_tracker.rb +11 -9
- data/lib/htauth/digest_entry.rb +22 -20
- data/lib/htauth/digest_file.rb +25 -18
- data/lib/htauth/entry.rb +3 -1
- data/lib/htauth/error.rb +6 -5
- data/lib/htauth/file.rb +35 -39
- data/lib/htauth/md5.rb +35 -34
- data/lib/htauth/passwd_entry.rb +26 -24
- data/lib/htauth/passwd_file.rb +26 -21
- data/lib/htauth/plaintext.rb +7 -5
- data/lib/htauth/sha1.rb +9 -7
- data/lib/htauth/version.rb +3 -1
- data/lib/htauth.rb +29 -28
- metadata +15 -133
- data/Rakefile +0 -29
- data/bin/htdigest-ruby +0 -12
- data/bin/htpasswd-ruby +0 -12
- data/spec/algorithm_spec.rb +0 -7
- data/spec/argon2_spec.rb +0 -28
- data/spec/bcrypt_spec.rb +0 -32
- data/spec/cli/digest_spec.rb +0 -149
- data/spec/cli/passwd_spec.rb +0 -346
- data/spec/crypt_spec.rb +0 -11
- data/spec/digest_entry_spec.rb +0 -59
- data/spec/digest_file_spec.rb +0 -64
- data/spec/md5_spec.rb +0 -11
- data/spec/passwd_entry_spec.rb +0 -172
- data/spec/passwd_file_spec.rb +0 -84
- data/spec/plaintext_spec.rb +0 -11
- data/spec/sha1_spec.rb +0 -10
- data/spec/spec_helper.rb +0 -25
- data/spec/test.add.digest +0 -3
- data/spec/test.add.passwd +0 -3
- data/spec/test.delete.digest +0 -1
- data/spec/test.delete.passwd +0 -1
- data/spec/test.original.digest +0 -2
- data/spec/test.original.passwd +0 -2
- data/spec/test.update.digest +0 -2
- data/spec/test.update.passwd +0 -2
- data/tasks/default.rake +0 -250
- data/tasks/this.rb +0 -208
- /data/{LICENSE → LICENSE.txt} +0 -0
data/lib/htauth/error.rb
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
#--
|
|
2
4
|
# Copyrigth (c) 2008 Jeremy Hinegardner
|
|
3
5
|
# All rights reserved. See LICENSE and/or COPYING for details
|
|
4
6
|
#++
|
|
@@ -7,12 +9,11 @@ module HTAuth
|
|
|
7
9
|
class Error < StandardError; end
|
|
8
10
|
|
|
9
11
|
class ConsoleError < Error; end
|
|
10
|
-
class DigestFileError < Error
|
|
12
|
+
class DigestFileError < Error; end
|
|
11
13
|
class FileAccessError < Error; end
|
|
12
14
|
class InvalidDigestEntry < Error; end
|
|
13
|
-
class InvalidPasswdEntry < Error
|
|
15
|
+
class InvalidPasswdEntry < Error; end
|
|
14
16
|
class PasswordError < Error; end
|
|
15
|
-
class PasswdFileError < Error
|
|
17
|
+
class PasswdFileError < Error; end
|
|
16
18
|
class TempFileError < Error; end
|
|
17
19
|
end
|
|
18
|
-
|
data/lib/htauth/file.rb
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "stringio"
|
|
4
|
+
require "htauth/error"
|
|
3
5
|
|
|
4
6
|
module HTAuth
|
|
5
7
|
# Internal: A base class for DigestFile and PasswordFile to inherit from.
|
|
@@ -7,22 +9,20 @@ module HTAuth
|
|
|
7
9
|
# This class should not be instantiated directly. You must use DigestFile or
|
|
8
10
|
# PasswordFile.
|
|
9
11
|
class File
|
|
10
|
-
|
|
11
12
|
# Public: The mode to pass to #open for updating a file
|
|
12
|
-
ALTER = "alter"
|
|
13
|
+
ALTER = "alter"
|
|
13
14
|
|
|
14
15
|
# Public: The mode to pass to #open for creating a new file
|
|
15
|
-
CREATE = "create"
|
|
16
|
+
CREATE = "create"
|
|
16
17
|
|
|
17
18
|
# Public: A special 'filename' that may be passed to #open for 'saving' to $stdout
|
|
18
|
-
STDOUT_FLAG = "-"
|
|
19
|
+
STDOUT_FLAG = "-"
|
|
19
20
|
|
|
20
|
-
attr_reader :filename
|
|
21
|
-
attr_reader :file
|
|
21
|
+
attr_reader :filename, :file
|
|
22
22
|
|
|
23
23
|
class << self
|
|
24
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
|
|
25
|
+
# Altering a non-existent file is an error. Creating an existing file
|
|
26
26
|
# results in a truncation and overwrite of the existing file.
|
|
27
27
|
#
|
|
28
28
|
# filename - The name of the file to open
|
|
@@ -50,23 +50,23 @@ module HTAuth
|
|
|
50
50
|
# Returns the DigestFile or PasswordFile as appropriate.
|
|
51
51
|
# Raises FileAccessError if an invalid mode is used.
|
|
52
52
|
# Raises FileAccessError if ALTERing a non-existent file.
|
|
53
|
-
def open(filename, mode = ALTER)
|
|
54
|
-
f =
|
|
53
|
+
def open(filename, mode = ALTER)
|
|
54
|
+
f = new(filename, mode)
|
|
55
55
|
if block_given?
|
|
56
56
|
begin
|
|
57
57
|
yield f
|
|
58
58
|
ensure
|
|
59
|
-
f.save! if f
|
|
59
|
+
f.save! if f&.dirty?
|
|
60
60
|
end
|
|
61
61
|
end
|
|
62
|
-
|
|
62
|
+
f
|
|
63
63
|
end
|
|
64
64
|
end
|
|
65
65
|
|
|
66
66
|
# Public: Create a new DigestFile or PasswordFile.
|
|
67
67
|
# Generally you do not need to use this method. Use #open instead.
|
|
68
68
|
#
|
|
69
|
-
# Altering a non-existent file is an error. Creating an existing file
|
|
69
|
+
# Altering a non-existent file is an error. Creating an existing file
|
|
70
70
|
# results in a truncation and overwrite of the existing file.
|
|
71
71
|
#
|
|
72
72
|
# filename - The name of the file to open
|
|
@@ -87,17 +87,17 @@ module HTAuth
|
|
|
87
87
|
@mode = mode
|
|
88
88
|
@dirty = false
|
|
89
89
|
|
|
90
|
-
raise FileAccessError, "Invalid mode #{mode}" unless [
|
|
90
|
+
raise FileAccessError, "Invalid mode #{mode}" unless [ALTER, CREATE].include?(mode)
|
|
91
91
|
|
|
92
|
-
if (filename != STDOUT_FLAG)
|
|
93
|
-
raise FileAccessError, "Could not open passwd file #{filename} for reading."
|
|
92
|
+
if (filename != STDOUT_FLAG) && (mode == ALTER) && !::File.exist?(filename)
|
|
93
|
+
raise FileAccessError, "Could not open passwd file #{filename} for reading."
|
|
94
94
|
end
|
|
95
95
|
|
|
96
96
|
begin
|
|
97
97
|
@entries = {}
|
|
98
98
|
@lines = []
|
|
99
|
-
load_entries if (@mode == ALTER)
|
|
100
|
-
rescue => e
|
|
99
|
+
load_entries if (@mode == ALTER) && (filename != STDOUT_FLAG)
|
|
100
|
+
rescue StandardError => e
|
|
101
101
|
raise FileAccessError, e.message
|
|
102
102
|
end
|
|
103
103
|
end
|
|
@@ -127,19 +127,15 @@ module HTAuth
|
|
|
127
127
|
# Returns nothing
|
|
128
128
|
# Raises FileAccessError if there was a problem writing the file
|
|
129
129
|
def save!
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
::File.open(@filename,"w") do |f|
|
|
136
|
-
f.write(contents)
|
|
137
|
-
end
|
|
138
|
-
end
|
|
139
|
-
@dirty = false
|
|
140
|
-
rescue => e
|
|
141
|
-
raise FileAccessError, "Error saving file #{@filename} : #{e.message}"
|
|
130
|
+
case filename
|
|
131
|
+
when STDOUT_FLAG
|
|
132
|
+
$stdout.write(contents)
|
|
133
|
+
else
|
|
134
|
+
::File.write(@filename, contents)
|
|
142
135
|
end
|
|
136
|
+
@dirty = false
|
|
137
|
+
rescue StandardError => e
|
|
138
|
+
raise FileAccessError, "Error saving file #{@filename} : #{e.message}"
|
|
143
139
|
end
|
|
144
140
|
|
|
145
141
|
# Internal: Return the String of the entire file contents
|
|
@@ -147,7 +143,7 @@ module HTAuth
|
|
|
147
143
|
# Returns String
|
|
148
144
|
def contents
|
|
149
145
|
c = StringIO.new
|
|
150
|
-
@lines.each do |l|
|
|
146
|
+
@lines.each do |l|
|
|
151
147
|
c.puts l if l
|
|
152
148
|
end
|
|
153
149
|
c.string
|
|
@@ -160,13 +156,13 @@ module HTAuth
|
|
|
160
156
|
# out in the same order it was read with the appropriate entries updated or
|
|
161
157
|
# deleted.
|
|
162
158
|
def load_entries
|
|
163
|
-
@lines
|
|
164
|
-
@lines.each_with_index do |line,idx|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
159
|
+
@lines = IO.readlines(@filename)
|
|
160
|
+
@lines.each_with_index do |line, idx|
|
|
161
|
+
next unless entry_klass.entry?(line)
|
|
162
|
+
|
|
163
|
+
entry = entry_klass.from_line(line)
|
|
164
|
+
v = { "entry" => entry, "line_index" => idx }
|
|
165
|
+
@entries[entry.key] = v
|
|
170
166
|
end
|
|
171
167
|
nil
|
|
172
168
|
end
|
data/lib/htauth/md5.rb
CHANGED
|
@@ -1,23 +1,24 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "htauth/algorithm"
|
|
4
|
+
require "digest/md5"
|
|
3
5
|
|
|
4
6
|
module HTAuth
|
|
5
7
|
# Internal: an implementation of the MD5 based encoding algorithm
|
|
6
8
|
# as used in the apache htpasswd -m option
|
|
7
9
|
class Md5 < Algorithm
|
|
8
|
-
|
|
9
10
|
DIGEST_LENGTH = 16
|
|
10
11
|
PAD_LENGTH = 6
|
|
11
|
-
PREFIX = "$apr1$"
|
|
12
|
-
SALT_CHARS_STR = SALT_CHARS.join
|
|
13
|
-
ENTRY_REGEX =
|
|
12
|
+
PREFIX = "$apr1$"
|
|
13
|
+
SALT_CHARS_STR = SALT_CHARS.join
|
|
14
|
+
ENTRY_REGEX = /
|
|
14
15
|
\A
|
|
15
16
|
#{Regexp.escape(PREFIX)}
|
|
16
17
|
[#{SALT_CHARS_STR}]{#{SALT_LENGTH}}
|
|
17
|
-
#{Regexp.escape(
|
|
18
|
+
#{Regexp.escape('$')}
|
|
18
19
|
[#{SALT_CHARS_STR}]{#{DIGEST_LENGTH + PAD_LENGTH}}
|
|
19
20
|
\z
|
|
20
|
-
|
|
21
|
+
/x
|
|
21
22
|
|
|
22
23
|
def self.handles?(password_entry)
|
|
23
24
|
ENTRY_REGEX.match?(password_entry)
|
|
@@ -25,15 +26,16 @@ module HTAuth
|
|
|
25
26
|
|
|
26
27
|
def self.extract_salt_from_existing_password_field(existing)
|
|
27
28
|
p = existing.split("$")
|
|
28
|
-
|
|
29
|
+
p[2]
|
|
29
30
|
end
|
|
30
31
|
|
|
31
32
|
def initialize(params = {})
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
33
|
+
super()
|
|
34
|
+
@salt = if (existing = params["existing"] || params[:existing])
|
|
35
|
+
self.class.extract_salt_from_existing_password_field(existing)
|
|
36
|
+
else
|
|
37
|
+
params[:salt] || params["salt"] || gen_salt
|
|
38
|
+
end
|
|
37
39
|
end
|
|
38
40
|
|
|
39
41
|
# this algorigthm pulled straight from apr_md5_encode() and converted to ruby syntax
|
|
@@ -46,8 +48,8 @@ module HTAuth
|
|
|
46
48
|
md5_t = ::Digest::MD5.digest("#{password}#{@salt}#{password}")
|
|
47
49
|
|
|
48
50
|
l = password.length
|
|
49
|
-
while l
|
|
50
|
-
slice_size =
|
|
51
|
+
while l.positive?
|
|
52
|
+
slice_size = [l, DIGEST_LENGTH].min
|
|
51
53
|
primary << md5_t[0, slice_size]
|
|
52
54
|
l -= DIGEST_LENGTH
|
|
53
55
|
end
|
|
@@ -59,7 +61,7 @@ module HTAuth
|
|
|
59
61
|
when 1
|
|
60
62
|
primary << 0.chr
|
|
61
63
|
when 0
|
|
62
|
-
primary << password[0,1]
|
|
64
|
+
primary << password[0, 1]
|
|
63
65
|
end
|
|
64
66
|
l >>= 1
|
|
65
67
|
end
|
|
@@ -71,33 +73,32 @@ module HTAuth
|
|
|
71
73
|
# apr_md5_encode has this comment about a 60Mhz Pentium above this loop.
|
|
72
74
|
1000.times do |x|
|
|
73
75
|
ctx = ::Digest::MD5.new
|
|
74
|
-
ctx << ((
|
|
75
|
-
(ctx << @salt) unless (
|
|
76
|
-
(ctx << password) unless (
|
|
77
|
-
ctx << ((
|
|
76
|
+
ctx << (x.allbits?(1) ? password : pd[0, DIGEST_LENGTH])
|
|
77
|
+
(ctx << @salt) unless (x % 3).zero?
|
|
78
|
+
(ctx << password) unless (x % 7).zero?
|
|
79
|
+
ctx << (x.nobits?(1) ? password : pd[0, DIGEST_LENGTH])
|
|
78
80
|
pd = ctx.digest
|
|
79
81
|
end
|
|
80
82
|
|
|
81
|
-
|
|
82
83
|
pd = pd.bytes.to_a
|
|
83
84
|
|
|
84
|
-
l = (pd[
|
|
85
|
-
encoded_password <<
|
|
85
|
+
l = (pd[0] << 16) | (pd[6] << 8) | pd[12]
|
|
86
|
+
encoded_password << to64(l, 4)
|
|
86
87
|
|
|
87
|
-
l = (pd[
|
|
88
|
-
encoded_password <<
|
|
88
|
+
l = (pd[1] << 16) | (pd[7] << 8) | pd[13]
|
|
89
|
+
encoded_password << to64(l, 4)
|
|
89
90
|
|
|
90
|
-
l = (pd[
|
|
91
|
-
encoded_password <<
|
|
91
|
+
l = (pd[2] << 16) | (pd[8] << 8) | pd[14]
|
|
92
|
+
encoded_password << to64(l, 4)
|
|
92
93
|
|
|
93
|
-
l = (pd[
|
|
94
|
-
encoded_password <<
|
|
94
|
+
l = (pd[3] << 16) | (pd[9] << 8) | pd[15]
|
|
95
|
+
encoded_password << to64(l, 4)
|
|
95
96
|
|
|
96
|
-
l = (pd[
|
|
97
|
-
encoded_password <<
|
|
98
|
-
encoded_password <<
|
|
97
|
+
l = (pd[4] << 16) | (pd[10] << 8) | pd[5]
|
|
98
|
+
encoded_password << to64(l, 4)
|
|
99
|
+
encoded_password << to64(pd[11], 2)
|
|
99
100
|
|
|
100
|
-
|
|
101
|
+
encoded_password
|
|
101
102
|
end
|
|
102
103
|
end
|
|
103
104
|
end
|
data/lib/htauth/passwd_entry.rb
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
require
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "htauth/error"
|
|
4
|
+
require "htauth/entry"
|
|
5
|
+
require "htauth/algorithm"
|
|
4
6
|
|
|
5
7
|
module HTAuth
|
|
6
8
|
# Internal: Object version of a single entry from a htpasswd file
|
|
7
9
|
class PasswdEntry < Entry
|
|
8
|
-
|
|
9
10
|
# Internal: the user of this entry
|
|
10
11
|
attr_accessor :user
|
|
11
12
|
# Internal: the password digest of this entry
|
|
@@ -22,11 +23,11 @@ module HTAuth
|
|
|
22
23
|
#
|
|
23
24
|
# Returns an instance of PasswdEntry
|
|
24
25
|
def from_line(line)
|
|
25
|
-
parts =
|
|
26
|
+
parts = entry!(line)
|
|
26
27
|
d = PasswdEntry.new(parts[0])
|
|
27
28
|
d.digest = parts[1]
|
|
28
29
|
d.algorithm = Algorithm.algorithm_from_field(parts[1])
|
|
29
|
-
|
|
30
|
+
d
|
|
30
31
|
end
|
|
31
32
|
|
|
32
33
|
# Internal: test if the given line is valid for this Entry class
|
|
@@ -39,29 +40,30 @@ module HTAuth
|
|
|
39
40
|
#
|
|
40
41
|
# Returns the individual parts of the line
|
|
41
42
|
# Raises InvalidPasswdEntry if it is not an valid entry
|
|
42
|
-
def
|
|
43
|
-
raise InvalidPasswdEntry, "line commented out" if line
|
|
43
|
+
def entry!(line)
|
|
44
|
+
raise InvalidPasswdEntry, "line commented out" if line.start_with?("#")
|
|
45
|
+
|
|
44
46
|
parts = line.strip.split(":")
|
|
45
47
|
raise InvalidPasswdEntry, "line must be of the format username:password" if parts.size != 2
|
|
46
|
-
|
|
48
|
+
|
|
49
|
+
parts
|
|
47
50
|
end
|
|
48
51
|
|
|
49
52
|
# Internal: Returns whether or not the line is a valid entry
|
|
50
53
|
#
|
|
51
54
|
# Returns true or false
|
|
52
|
-
def
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
return false
|
|
58
|
-
end
|
|
55
|
+
def entry?(line)
|
|
56
|
+
entry!(line)
|
|
57
|
+
true
|
|
58
|
+
rescue InvalidPasswdEntry
|
|
59
|
+
false
|
|
59
60
|
end
|
|
60
61
|
end
|
|
61
62
|
|
|
62
63
|
# Internal: Create a new Entry with the given user, password, and algorithm
|
|
63
|
-
def initialize(user, password = nil, alg = Algorithm::DEFAULT, alg_params = {}
|
|
64
|
-
|
|
64
|
+
def initialize(user, password = nil, alg = Algorithm::DEFAULT, alg_params = {})
|
|
65
|
+
super()
|
|
66
|
+
@user = user
|
|
65
67
|
alg = Algorithm::DEFAULT if alg == Algorithm::EXISTING
|
|
66
68
|
@algorithm = Algorithm.algorithm_from_name(alg, alg_params)
|
|
67
69
|
@digest = calc_digest(password)
|
|
@@ -69,7 +71,8 @@ module HTAuth
|
|
|
69
71
|
|
|
70
72
|
# Internal: set the algorithm for the entry
|
|
71
73
|
def algorithm=(alg)
|
|
72
|
-
return @algorithm if Algorithm::EXISTING
|
|
74
|
+
return @algorithm if alg == Algorithm::EXISTING
|
|
75
|
+
|
|
73
76
|
case alg
|
|
74
77
|
when String
|
|
75
78
|
@algorithm = Algorithm.algorithm_from_name(alg)
|
|
@@ -78,7 +81,7 @@ module HTAuth
|
|
|
78
81
|
else
|
|
79
82
|
raise InvalidAlgorithmError, "Unable to assign #{alg} to algorithm"
|
|
80
83
|
end
|
|
81
|
-
|
|
84
|
+
@algorithm
|
|
82
85
|
end
|
|
83
86
|
|
|
84
87
|
# Internal: set fields on the algorithm
|
|
@@ -93,15 +96,14 @@ module HTAuth
|
|
|
93
96
|
#
|
|
94
97
|
# If we have an array of algorithms, then we set it to CRYPT
|
|
95
98
|
def password=(new_password)
|
|
96
|
-
if algorithm.
|
|
97
|
-
@algorithm = Algorithm.algorithm_from_name(Algorithm::CRYPT)
|
|
98
|
-
end
|
|
99
|
+
@algorithm = Algorithm.algorithm_from_name(Algorithm::CRYPT) if algorithm.is_a?(HTAuth::Plaintext)
|
|
99
100
|
@digest = calc_digest(new_password)
|
|
100
101
|
end
|
|
101
102
|
|
|
102
103
|
# Internal: calculate the new digest of the given password
|
|
103
104
|
def calc_digest(password)
|
|
104
105
|
return nil unless password
|
|
106
|
+
|
|
105
107
|
algorithm.encode(password)
|
|
106
108
|
end
|
|
107
109
|
|
|
@@ -113,7 +115,7 @@ module HTAuth
|
|
|
113
115
|
|
|
114
116
|
# Internal: Returns the key of this entry
|
|
115
117
|
def key
|
|
116
|
-
|
|
118
|
+
user.to_s
|
|
117
119
|
end
|
|
118
120
|
|
|
119
121
|
# Internal: Returns the file line for this entry
|
data/lib/htauth/passwd_file.rb
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
|
-
|
|
2
|
-
require 'tempfile'
|
|
1
|
+
# frozen_string_literal: true
|
|
3
2
|
|
|
4
|
-
require
|
|
5
|
-
require
|
|
6
|
-
|
|
3
|
+
require "stringio"
|
|
4
|
+
require "tempfile"
|
|
5
|
+
|
|
6
|
+
require "htauth/error"
|
|
7
|
+
require "htauth/file"
|
|
8
|
+
require "htauth/passwd_entry"
|
|
7
9
|
|
|
8
10
|
module HTAuth
|
|
9
11
|
# Public: An API for managing an 'htpasswd' file
|
|
@@ -17,7 +19,6 @@ module HTAuth
|
|
|
17
19
|
# end
|
|
18
20
|
#
|
|
19
21
|
class PasswdFile < HTAuth::File
|
|
20
|
-
|
|
21
22
|
# Private: The class implementing a single entry in the PasswdFile
|
|
22
23
|
ENTRY_KLASS = HTAuth::PasswdEntry
|
|
23
24
|
|
|
@@ -33,7 +34,7 @@ module HTAuth
|
|
|
33
34
|
# Returns true or false if the username
|
|
34
35
|
def has_entry?(username)
|
|
35
36
|
test_entry = PasswdEntry.new(username)
|
|
36
|
-
@entries.
|
|
37
|
+
@entries.key?(test_entry.key)
|
|
37
38
|
end
|
|
38
39
|
|
|
39
40
|
# Public: remove the given username from the file
|
|
@@ -48,10 +49,10 @@ module HTAuth
|
|
|
48
49
|
#
|
|
49
50
|
# Returns nothing
|
|
50
51
|
def delete(username)
|
|
51
|
-
if has_entry?(username)
|
|
52
|
+
if has_entry?(username)
|
|
52
53
|
ir = internal_record(username)
|
|
53
|
-
line_index = ir[
|
|
54
|
-
@entries.delete(ir[
|
|
54
|
+
line_index = ir["line_index"]
|
|
55
|
+
@entries.delete(ir["entry"].key)
|
|
55
56
|
@lines[line_index] = nil
|
|
56
57
|
dirty!
|
|
57
58
|
end
|
|
@@ -59,7 +60,7 @@ module HTAuth
|
|
|
59
60
|
end
|
|
60
61
|
|
|
61
62
|
# Public: Add or update the username entry with the new password and
|
|
62
|
-
# algorithm. This will add a new entry if the username does not exist in
|
|
63
|
+
# algorithm. This will add a new entry if the username does not exist in
|
|
63
64
|
# the file. If the entry does exist in the file, then the password
|
|
64
65
|
# of the entry is updated to the new password / algorithm
|
|
65
66
|
#
|
|
@@ -84,7 +85,7 @@ module HTAuth
|
|
|
84
85
|
#
|
|
85
86
|
# Returns nothing.
|
|
86
87
|
def add_or_update(username, password, algorithm = Algorithm::DEFAULT, algorithm_args = {})
|
|
87
|
-
if has_entry?(username)
|
|
88
|
+
if has_entry?(username)
|
|
88
89
|
update(username, password, algorithm, algorithm_args)
|
|
89
90
|
else
|
|
90
91
|
add(username, password, algorithm, algorithm_args)
|
|
@@ -116,12 +117,13 @@ module HTAuth
|
|
|
116
117
|
# Raises PasswdFileError if the give username already exists.
|
|
117
118
|
def add(username, password, algorithm = Algorithm::DEFAULT, algorithm_args = {})
|
|
118
119
|
raise PasswdFileError, "Unable to add already existing user #{username}" if has_entry?(username)
|
|
120
|
+
|
|
119
121
|
new_entry = PasswdEntry.new(username, password, algorithm, algorithm_args)
|
|
120
122
|
new_index = @lines.size
|
|
121
123
|
@lines << new_entry.to_s
|
|
122
|
-
@entries[new_entry.key] = {
|
|
124
|
+
@entries[new_entry.key] = { "entry" => new_entry, "line_index" => new_index }
|
|
123
125
|
dirty!
|
|
124
|
-
|
|
126
|
+
nil
|
|
125
127
|
end
|
|
126
128
|
|
|
127
129
|
# Public: Update an existing record in the file.
|
|
@@ -153,13 +155,14 @@ module HTAuth
|
|
|
153
155
|
# Raises PasswdFileError if the give username does not exist.
|
|
154
156
|
def update(username, password, algorithm = Algorithm::EXISTING, algorithm_args = {})
|
|
155
157
|
raise PasswdFileError, "Unable to update non-existent user #{username}" unless has_entry?(username)
|
|
158
|
+
|
|
156
159
|
ir = internal_record(username)
|
|
157
|
-
ir[
|
|
158
|
-
ir[
|
|
159
|
-
ir[
|
|
160
|
-
@lines[ir[
|
|
160
|
+
ir["entry"].algorithm = algorithm
|
|
161
|
+
ir["entry"].algorithm_args = algorithm_args.dup
|
|
162
|
+
ir["entry"].password = password
|
|
163
|
+
@lines[ir["line_index"]] = ir["entry"].to_s
|
|
161
164
|
dirty!
|
|
162
|
-
|
|
165
|
+
nil
|
|
163
166
|
end
|
|
164
167
|
|
|
165
168
|
# Public: Returns a copy of then given PasswdEntry from the file.
|
|
@@ -177,8 +180,9 @@ module HTAuth
|
|
|
177
180
|
# Returns nil if the entry is not found
|
|
178
181
|
def fetch(username)
|
|
179
182
|
return nil unless has_entry?(username)
|
|
183
|
+
|
|
180
184
|
ir = internal_record(username)
|
|
181
|
-
|
|
185
|
+
ir["entry"].dup
|
|
182
186
|
end
|
|
183
187
|
|
|
184
188
|
# Public: authenticates the password of a given username
|
|
@@ -194,8 +198,9 @@ module HTAuth
|
|
|
194
198
|
# Raises PasswordFileErrorif the given username does not exist
|
|
195
199
|
def authenticated?(username, password)
|
|
196
200
|
raise PasswdFileError, "Unable to authenticate a non-existent user #{username}" unless has_entry?(username)
|
|
201
|
+
|
|
197
202
|
ir = internal_record(username)
|
|
198
|
-
|
|
203
|
+
ir["entry"].authenticated?(password)
|
|
199
204
|
end
|
|
200
205
|
|
|
201
206
|
# Internal: returns the class used for each entry
|
data/lib/htauth/plaintext.rb
CHANGED
|
@@ -1,25 +1,27 @@
|
|
|
1
|
-
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "htauth/algorithm"
|
|
2
4
|
|
|
3
5
|
module HTAuth
|
|
4
6
|
# Internal: the plaintext algorithm, which does absolutly nothing
|
|
5
7
|
class Plaintext < Algorithm
|
|
6
|
-
|
|
7
8
|
ENTRY_REGEX = /\A[^$:]*\Z/
|
|
8
9
|
|
|
9
10
|
def self.entry_matches?(entry)
|
|
10
11
|
ENTRY_REGEX.match?(entry)
|
|
11
12
|
end
|
|
12
13
|
|
|
13
|
-
def self.handles?(
|
|
14
|
+
def self.handles?(_password_entry)
|
|
14
15
|
false
|
|
15
16
|
end
|
|
16
17
|
|
|
17
18
|
# ignore parameters
|
|
18
|
-
def initialize(
|
|
19
|
+
def initialize(_params = {})
|
|
20
|
+
super()
|
|
19
21
|
end
|
|
20
22
|
|
|
21
23
|
def encode(password)
|
|
22
|
-
|
|
24
|
+
password.to_s
|
|
23
25
|
end
|
|
24
26
|
end
|
|
25
27
|
end
|
data/lib/htauth/sha1.rb
CHANGED
|
@@ -1,22 +1,24 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
require
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "htauth/algorithm"
|
|
4
|
+
require "digest/sha1"
|
|
5
|
+
require "base64"
|
|
4
6
|
|
|
5
7
|
module HTAuth
|
|
6
8
|
# Internal: an implementation of the SHA based encoding algorithm
|
|
7
9
|
# as used in the apache htpasswd -s option
|
|
8
10
|
#
|
|
9
11
|
class Sha1 < Algorithm
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
ENTRY_REGEX = %r[\A#{Regexp.escape(PREFIX)}[A-Za-z0-9+\/=]{28}\z].freeze
|
|
12
|
+
PREFIX = "{SHA}"
|
|
13
|
+
ENTRY_REGEX = %r[\A#{Regexp.escape(PREFIX)}[A-Za-z0-9+/=]{28}\z]
|
|
13
14
|
|
|
14
15
|
def self.handles?(password_entry)
|
|
15
16
|
ENTRY_REGEX.match?(password_entry)
|
|
16
17
|
end
|
|
17
18
|
|
|
18
19
|
# ignore the params
|
|
19
|
-
def initialize(
|
|
20
|
+
def initialize(_params = {})
|
|
21
|
+
super()
|
|
20
22
|
end
|
|
21
23
|
|
|
22
24
|
def encode(password)
|