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,206 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'htauth/cli/passwd'
|
3
|
+
require 'tempfile'
|
4
|
+
|
5
|
+
describe HTAuth::CLI::Passwd do
|
6
|
+
|
7
|
+
before(:each) do
|
8
|
+
|
9
|
+
# existing
|
10
|
+
@tf = Tempfile.new("rpasswrd-passwd-test")
|
11
|
+
@tf.write(IO.read(PASSWD_ORIGINAL_TEST_FILE))
|
12
|
+
@tf.close
|
13
|
+
@htauth = HTAuth::CLI::Passwd.new
|
14
|
+
|
15
|
+
# new file
|
16
|
+
@new_file = File.join(File.dirname(@tf.path), "new-testfile")
|
17
|
+
|
18
|
+
# rework stdout and stderr
|
19
|
+
@stdout = ConsoleIO.new
|
20
|
+
@old_stdout = $stdout
|
21
|
+
$stdout = @stdout
|
22
|
+
|
23
|
+
@stderr = ConsoleIO.new
|
24
|
+
@old_stderr = $stderr
|
25
|
+
$stderr = @stderr
|
26
|
+
|
27
|
+
@stdin = ConsoleIO.new
|
28
|
+
@old_stdin = $stdin
|
29
|
+
$stdin = @stdin
|
30
|
+
end
|
31
|
+
|
32
|
+
after(:each) do
|
33
|
+
@tf.close(true)
|
34
|
+
$stderr = @old_stderr
|
35
|
+
$stdout = @old_stdout
|
36
|
+
$stdin = @old_stdin
|
37
|
+
File.unlink(@new_file) if File.exist?(@new_file)
|
38
|
+
end
|
39
|
+
|
40
|
+
it "displays help appropriately" do
|
41
|
+
begin
|
42
|
+
@htauth.run([ "-h" ])
|
43
|
+
rescue SystemExit => se
|
44
|
+
se.status.must_equal 1
|
45
|
+
@stdout.string.must_match( /passwordfile username/m )
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
it "displays the version appropriately" do
|
50
|
+
begin
|
51
|
+
@htauth.run([ "--version" ])
|
52
|
+
rescue SystemExit => se
|
53
|
+
se.status.must_equal 1
|
54
|
+
@stdout.string.must_match( /version #{HTAuth::VERSION}/)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
it "creates a new file with one md5 entry" do
|
59
|
+
begin
|
60
|
+
@stdin.puts "a secret"
|
61
|
+
@stdin.puts "a secret"
|
62
|
+
@stdin.rewind
|
63
|
+
@htauth.run([ "-m", "-c", @new_file, "alice" ])
|
64
|
+
rescue SystemExit => se
|
65
|
+
se.status.must_equal 0
|
66
|
+
l = IO.readlines(@new_file)
|
67
|
+
fields = l.first.split(':')
|
68
|
+
fields.first.must_equal "alice"
|
69
|
+
fields.last.must_match( /^\$apr1\$/ )
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
it "truncates an exiting file if told to create a new file" do
|
74
|
+
before_lines = IO.readlines(@tf.path)
|
75
|
+
begin
|
76
|
+
@stdin.puts "b secret"
|
77
|
+
@stdin.puts "b secret"
|
78
|
+
@stdin.rewind
|
79
|
+
@htauth.run([ "-c", @tf.path, "bob"])
|
80
|
+
rescue SystemExit => se
|
81
|
+
se.status.must_equal 0
|
82
|
+
after_lines = IO.readlines(@tf.path)
|
83
|
+
after_lines.size.must_equal 1
|
84
|
+
before_lines.size.must_equal 2
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
it "adds an entry to an existing file and force SHA" do
|
89
|
+
begin
|
90
|
+
@stdin.puts "c secret"
|
91
|
+
@stdin.puts "c secret"
|
92
|
+
@stdin.rewind
|
93
|
+
@htauth.run([ "-s", @tf.path, "charlie" ])
|
94
|
+
rescue SystemExit => se
|
95
|
+
se.status.must_equal 0
|
96
|
+
after_lines = IO.readlines(@tf.path)
|
97
|
+
after_lines.size.must_equal 3
|
98
|
+
al = after_lines.last.split(':')
|
99
|
+
al.first.must_equal "charlie"
|
100
|
+
al.last.must_match( /\{SHA\}/ )
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
it "can create a plaintext encrypted file" do
|
105
|
+
begin
|
106
|
+
@stdin.puts "a bad password"
|
107
|
+
@stdin.puts "a bad password"
|
108
|
+
@stdin.rewind
|
109
|
+
@htauth.run(["-c", "-p", @tf.path, "bradley"])
|
110
|
+
rescue SystemExit => se
|
111
|
+
se.status.must_equal 0
|
112
|
+
IO.read(@tf.path).strip.must_equal "bradley:a bad password"
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
it "has a batch mode for command line passwords" do
|
117
|
+
begin
|
118
|
+
@htauth.run(["-c", "-p", "-b", @tf.path, "bradley", "a bad password"])
|
119
|
+
rescue SystemExit => se
|
120
|
+
se.status.must_equal 0
|
121
|
+
IO.read(@tf.path).strip.must_equal "bradley:a bad password"
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
it "updates an entry in an existing file and force crypt" do
|
126
|
+
before_lines = IO.readlines(@tf.path)
|
127
|
+
begin
|
128
|
+
@stdin.puts "a new secret"
|
129
|
+
@stdin.puts "a new secret"
|
130
|
+
@stdin.rewind
|
131
|
+
@htauth.run([ "-d", @tf.path, "alice" ])
|
132
|
+
rescue SystemExit => se
|
133
|
+
@stderr.string.must_equal ""
|
134
|
+
se.status.must_equal 0
|
135
|
+
after_lines = IO.readlines(@tf.path)
|
136
|
+
after_lines.size.must_equal before_lines.size
|
137
|
+
|
138
|
+
a_b = before_lines.first.split(":")
|
139
|
+
a_a = after_lines.first.split(":")
|
140
|
+
|
141
|
+
a_b.first.must_equal a_a.first
|
142
|
+
a_b.last.wont_equal a_a.last
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
it "deletes an entry in an existing file" do
|
147
|
+
begin
|
148
|
+
@htauth.run([ "-D", @tf.path, "bob" ])
|
149
|
+
rescue SystemExit => se
|
150
|
+
@stderr.string.must_equal ""
|
151
|
+
se.status.must_equal 0
|
152
|
+
IO.read(@tf.path).must_equal IO.read(PASSWD_DELETE_TEST_FILE)
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
it "sends to STDOUT when the -n option is used" do
|
157
|
+
begin
|
158
|
+
@htauth.run(["-n", "-p", "-b", "bradley", "a bad password"])
|
159
|
+
rescue SystemExit => se
|
160
|
+
se.status.must_equal 0
|
161
|
+
@stdout.string.strip.must_equal "bradley:a bad password"
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
it "has an error if it does not have permissions on the file" do
|
166
|
+
begin
|
167
|
+
@stdin.puts "a secret"
|
168
|
+
@stdin.puts "a secret"
|
169
|
+
@stdin.rewind
|
170
|
+
@htauth.run([ "-c", "/etc/you-cannot-create-me", "alice"])
|
171
|
+
rescue SystemExit => se
|
172
|
+
@stderr.string.must_match( %r{Password file failure \(/etc/you-cannot-create-me\)}m )
|
173
|
+
se.status.must_equal 1
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
it "has an error if the input passwords do not match" do
|
178
|
+
begin
|
179
|
+
@stdin.puts "a secret"
|
180
|
+
@stdin.puts "a bad secret"
|
181
|
+
@stdin.rewind
|
182
|
+
@htauth.run([ @tf.path, "alice"])
|
183
|
+
rescue SystemExit => se
|
184
|
+
@stderr.string.must_match( /They don't match, sorry./m )
|
185
|
+
se.status.must_equal 1
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
it "has an error if the options are incorrect" do
|
190
|
+
begin
|
191
|
+
@htauth.run(["--blah"])
|
192
|
+
rescue SystemExit => se
|
193
|
+
@stderr.string.must_match( /ERROR:/m )
|
194
|
+
se.status.must_equal 1
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
it "errors if send to stdout and create a new file options are both used" do
|
199
|
+
begin
|
200
|
+
@htauth.run(["-c", "-n"])
|
201
|
+
rescue SystemExit => se
|
202
|
+
@stderr.string.must_match( /ERROR:/m )
|
203
|
+
se.status.must_equal 1
|
204
|
+
end
|
205
|
+
end
|
206
|
+
end
|
data/spec/digest_entry_spec.rb
CHANGED
@@ -2,59 +2,59 @@ require 'spec_helper'
|
|
2
2
|
require 'htauth/digest_entry'
|
3
3
|
|
4
4
|
describe HTAuth::DigestEntry do
|
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
|
-
|
5
|
+
before(:each) do
|
6
|
+
@alice = HTAuth::DigestEntry.new("alice", "htauth")
|
7
|
+
@bob = HTAuth::DigestEntry.new("bob", "htauth", "b secret")
|
8
|
+
end
|
9
|
+
|
10
|
+
it "initializes with a user and realm" do
|
11
|
+
@alice.user.must_equal "alice"
|
12
|
+
@alice.realm.must_equal "htauth"
|
13
|
+
end
|
14
|
+
|
15
|
+
it "has the correct digest for a password" do
|
16
|
+
@alice.password = "digit"
|
17
|
+
@alice.digest.must_equal "4ed9e5744c6747af8f292d28afd6372e"
|
18
|
+
end
|
19
|
+
|
20
|
+
it "returns username:realm for a key" do
|
21
|
+
@alice.key.must_equal "alice:htauth"
|
22
|
+
end
|
23
|
+
|
24
|
+
it "checks the password correctly" do
|
25
|
+
@bob.authenticated?("b secret").must_equal true
|
26
|
+
end
|
27
|
+
|
28
|
+
it "formats correctly when put to a string" do
|
29
|
+
@bob.to_s.must_equal "bob:htauth:fcbeab6821d2ab3b00934c958db0fd1e"
|
30
|
+
end
|
31
|
+
|
32
|
+
it "parses an input line" do
|
33
|
+
@bob_new = HTAuth::DigestEntry.from_line("bob:htauth:fcbeab6821d2ab3b00934c958db0fd1e")
|
34
|
+
@bob.user.must_equal @bob_new.user
|
35
|
+
@bob.digest.must_equal @bob_new.digest
|
36
|
+
@bob.realm.must_equal @bob_new.realm
|
37
|
+
end
|
38
|
+
|
39
|
+
it "knows if an input line is a possible entry and raises an exception" do
|
40
|
+
lambda { HTAuth::DigestEntry.is_entry!("#stuff") }.must_raise(HTAuth::InvalidDigestEntry)
|
41
|
+
lambda { HTAuth::DigestEntry.is_entry!("this:that:other:stuff") }.must_raise(HTAuth::InvalidDigestEntry)
|
42
|
+
lambda { HTAuth::DigestEntry.is_entry!("this:that:other") }.must_raise(HTAuth::InvalidDigestEntry)
|
43
|
+
lambda { HTAuth::DigestEntry.is_entry!("this:that:0a90549e8ffb2dd62f98252a95d88xyz") }.must_raise(HTAuth::InvalidDigestEntry)
|
44
|
+
end
|
45
|
+
|
46
|
+
it "knows if an input line is a possible entry and returns false" do
|
47
|
+
HTAuth::DigestEntry.is_entry?("#stuff").must_equal false
|
48
|
+
HTAuth::DigestEntry.is_entry?("this:that:other:stuff").must_equal false
|
49
|
+
HTAuth::DigestEntry.is_entry?("this:that:other").must_equal false
|
50
|
+
HTAuth::DigestEntry.is_entry?("this:that:0a90549e8ffb2dd62f98252a95d88xyz").must_equal false
|
51
|
+
end
|
52
|
+
|
53
|
+
it "knows if an input line is a possible entry and returns true" do
|
54
|
+
HTAuth::DigestEntry.is_entry?("bob:htauth:0a90549e8ffb2dd62f98252a95d88697").must_equal true
|
55
|
+
end
|
56
|
+
|
57
|
+
it "duplicates itself" do
|
58
|
+
@alice.dup.to_s.must_equal @alice.to_s
|
59
|
+
end
|
60
60
|
end
|
data/spec/digest_file_spec.rb
CHANGED
@@ -4,62 +4,62 @@ require 'tempfile'
|
|
4
4
|
|
5
5
|
describe HTAuth::DigestFile do
|
6
6
|
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
@tf2 = Tempfile.new("rpasswrd-digest-empty")
|
14
|
-
@tf2.close
|
15
|
-
@empty_digest_file = HTAuth::DigestFile.new(@tf2.path)
|
16
|
-
end
|
7
|
+
before(:each) do
|
8
|
+
@tf = Tempfile.new("rpasswrd-digest")
|
9
|
+
@tf.write(IO.read(DIGEST_ORIGINAL_TEST_FILE))
|
10
|
+
@tf.close
|
11
|
+
@digest_file = HTAuth::DigestFile.new(@tf.path)
|
17
12
|
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
13
|
+
@tf2 = Tempfile.new("rpasswrd-digest-empty")
|
14
|
+
@tf2.close
|
15
|
+
@empty_digest_file = HTAuth::DigestFile.new(@tf2.path)
|
16
|
+
end
|
22
17
|
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
18
|
+
after(:each) do
|
19
|
+
@tf2.close(true)
|
20
|
+
@tf.close(true)
|
21
|
+
end
|
27
22
|
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
it "can update an entry in an already existing digest file" do
|
34
|
-
@digest_file.add_or_update("alice", "htauth", "a new secret")
|
35
|
-
@digest_file.contents.must_equal IO.read(DIGEST_UPDATE_TEST_FILE)
|
36
|
-
end
|
23
|
+
it "can add a new entry to an already existing digest file" do
|
24
|
+
@digest_file.add_or_update("charlie", "htauth-new", "c secret")
|
25
|
+
@digest_file.contents.must_equal IO.read(DIGEST_ADD_TEST_FILE)
|
26
|
+
end
|
37
27
|
|
38
|
-
|
39
|
-
|
40
|
-
|
28
|
+
it "can tell if an entry already exists in the digest file" do
|
29
|
+
@digest_file.has_entry?("alice", "htauth").must_equal true
|
30
|
+
@digest_file.has_entry?("alice", "some other realm").must_equal false
|
31
|
+
end
|
41
32
|
|
42
|
-
|
43
|
-
|
44
|
-
|
33
|
+
it "can update an entry in an already existing digest file" do
|
34
|
+
@digest_file.add_or_update("alice", "htauth", "a new secret")
|
35
|
+
@digest_file.contents.must_equal IO.read(DIGEST_UPDATE_TEST_FILE)
|
36
|
+
end
|
45
37
|
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
end
|
38
|
+
it "fetches a copy of an entry" do
|
39
|
+
@digest_file.fetch("alice", "htauth").to_s.must_equal "alice:htauth:2f361db93147d84831eb34f19d05bfbb"
|
40
|
+
end
|
50
41
|
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
42
|
+
it "raises an error if an attempt is made to alter a non-existenet file" do
|
43
|
+
lambda { HTAuth::DigestFile.new("some-file") }.must_raise(HTAuth::FileAccessError)
|
44
|
+
end
|
45
|
+
|
46
|
+
# this test will only work on systems that have /etc/ssh_host_rsa_key
|
47
|
+
it "raises an error if an attempt is made to open a file where no permissions are granted" do
|
48
|
+
lambda { HTAuth::DigestFile.new("/etc/ssh_host_rsa_key") }.must_raise(HTAuth::FileAccessError)
|
49
|
+
end
|
50
|
+
|
51
|
+
it "deletes an entry" do
|
52
|
+
@digest_file.delete("alice", "htauth")
|
53
|
+
@digest_file.contents.must_equal IO.read(DIGEST_DELETE_TEST_FILE)
|
54
|
+
end
|
55
|
+
|
56
|
+
it "is usable in a ruby manner and yeilds itself when opened" do
|
57
|
+
HTAuth::DigestFile.open(@tf.path) do |pf|
|
58
|
+
pf.add_or_update("alice", "htauth", "a secret")
|
59
|
+
pf.delete('bob', 'htauth')
|
64
60
|
end
|
61
|
+
lines = IO.readlines(@tf.path)
|
62
|
+
lines.size.must_equal 1
|
63
|
+
lines.first.strip.must_equal "alice:htauth:2f361db93147d84831eb34f19d05bfbb"
|
64
|
+
end
|
65
65
|
end
|
data/spec/md5_spec.rb
CHANGED
@@ -3,15 +3,15 @@ require File.join(File.dirname(__FILE__),"spec_helper.rb")
|
|
3
3
|
require 'htauth/md5'
|
4
4
|
|
5
5
|
describe HTAuth::Md5 do
|
6
|
-
|
7
|
-
|
8
|
-
|
6
|
+
it "has a prefix" do
|
7
|
+
HTAuth::Md5.new.prefix.must_equal "$apr1$"
|
8
|
+
end
|
9
9
|
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
10
|
+
it "encrypts the same way that apache does" do
|
11
|
+
apache_salt = "L0LDd/.."
|
12
|
+
apache_result = "$apr1$L0LDd/..$yhUzDjpxam5F1kWdtwMco1"
|
13
|
+
md5 = HTAuth::Md5.new({ 'salt' => apache_salt })
|
14
|
+
md5.encode("a secret").must_equal apache_result
|
15
|
+
end
|
16
16
|
end
|
17
17
|
|