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.
Files changed (56) hide show
  1. checksums.yaml +4 -4
  2. data/HISTORY.md +14 -0
  3. data/Manifest.txt +4 -28
  4. data/exe/htdigest-ruby +14 -0
  5. data/exe/htpasswd-ruby +14 -0
  6. data/htauth.gemspec +33 -0
  7. data/lib/htauth/algorithm.rb +30 -29
  8. data/lib/htauth/argon2.rb +45 -36
  9. data/lib/htauth/bcrypt.rb +12 -11
  10. data/lib/htauth/cli/digest.rb +42 -46
  11. data/lib/htauth/cli/passwd.rb +126 -115
  12. data/lib/htauth/cli.rb +5 -3
  13. data/lib/htauth/console.rb +9 -6
  14. data/lib/htauth/crypt.rb +11 -9
  15. data/lib/htauth/descendant_tracker.rb +11 -9
  16. data/lib/htauth/digest_entry.rb +22 -20
  17. data/lib/htauth/digest_file.rb +25 -18
  18. data/lib/htauth/entry.rb +3 -1
  19. data/lib/htauth/error.rb +6 -5
  20. data/lib/htauth/file.rb +35 -39
  21. data/lib/htauth/md5.rb +35 -34
  22. data/lib/htauth/passwd_entry.rb +26 -24
  23. data/lib/htauth/passwd_file.rb +26 -21
  24. data/lib/htauth/plaintext.rb +7 -5
  25. data/lib/htauth/sha1.rb +9 -7
  26. data/lib/htauth/version.rb +3 -1
  27. data/lib/htauth.rb +29 -28
  28. metadata +15 -133
  29. data/Rakefile +0 -29
  30. data/bin/htdigest-ruby +0 -12
  31. data/bin/htpasswd-ruby +0 -12
  32. data/spec/algorithm_spec.rb +0 -7
  33. data/spec/argon2_spec.rb +0 -28
  34. data/spec/bcrypt_spec.rb +0 -32
  35. data/spec/cli/digest_spec.rb +0 -149
  36. data/spec/cli/passwd_spec.rb +0 -346
  37. data/spec/crypt_spec.rb +0 -11
  38. data/spec/digest_entry_spec.rb +0 -59
  39. data/spec/digest_file_spec.rb +0 -64
  40. data/spec/md5_spec.rb +0 -11
  41. data/spec/passwd_entry_spec.rb +0 -172
  42. data/spec/passwd_file_spec.rb +0 -84
  43. data/spec/plaintext_spec.rb +0 -11
  44. data/spec/sha1_spec.rb +0 -10
  45. data/spec/spec_helper.rb +0 -25
  46. data/spec/test.add.digest +0 -3
  47. data/spec/test.add.passwd +0 -3
  48. data/spec/test.delete.digest +0 -1
  49. data/spec/test.delete.passwd +0 -1
  50. data/spec/test.original.digest +0 -2
  51. data/spec/test.original.passwd +0 -2
  52. data/spec/test.update.digest +0 -2
  53. data/spec/test.update.passwd +0 -2
  54. data/tasks/default.rake +0 -250
  55. data/tasks/this.rb +0 -208
  56. /data/{LICENSE → LICENSE.txt} +0 -0
@@ -1,346 +0,0 @@
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 "creates a new file with one bcrypt entry" do
74
- begin
75
- @stdin.puts "b secret"
76
- @stdin.puts "b secret"
77
- @stdin.rewind
78
- @htauth.run([ "-B", "-c", @new_file, "brenda" ])
79
- rescue SystemExit => se
80
- _(se.status).must_equal 0
81
- l = IO.readlines(@new_file)
82
- fields = l.first.split(':')
83
- _(fields.first).must_equal "brenda"
84
- bcrypt_hash = fields.last
85
-
86
- _(::BCrypt::Password.valid_hash?(bcrypt_hash)).wont_be_nil
87
- end
88
- end
89
-
90
- it "allows the bcrypt cost to be set" do
91
- begin
92
- cost = 12
93
- @stdin.puts "b secret"
94
- @stdin.puts "b secret"
95
- @stdin.rewind
96
- @htauth.run([ "-C", "#{cost}", "-B", "-c", @new_file, "brenda" ])
97
- rescue SystemExit => se
98
- _(se.status).must_equal 0
99
- l = IO.readlines(@new_file)
100
- fields = l.first.split(':')
101
- _(fields.first).must_equal "brenda"
102
- bcrypt_hash = fields.last
103
- _(::BCrypt::Password.valid_hash?(bcrypt_hash)).wont_be_nil
104
-
105
- _, _version, count, _rest = bcrypt_hash.split("$")
106
- _(count).must_equal ("%02d" % cost)
107
- end
108
- end
109
-
110
- it "raises an error if the bcrypt cost is out of range" do
111
- begin
112
- @stdin.puts "b secret"
113
- @stdin.puts "b secret"
114
- @stdin.rewind
115
- @htauth.run([ "-C", "42", "-B", "-c", @new_file, "brenda" ])
116
- rescue SystemExit => se
117
- _(@stderr.string).must_match( /ERROR:/m )
118
- _(se.status).must_equal 1
119
- end
120
- end
121
-
122
- it "raises an error if the bcrypt cost is not an integer" do
123
- begin
124
- @stdin.puts "b secret"
125
- @stdin.puts "b secret"
126
- @stdin.rewind
127
- @htauth.run([ "-C", "forty-two", "-B", "-c", @new_file, "brenda" ])
128
- rescue SystemExit => se
129
- _(@stderr.string).must_match( /ERROR:/m )
130
- _(se.status).must_equal 1
131
- end
132
- end
133
-
134
- it "creates a new file with one argon2 entry" do
135
- begin
136
- @stdin.puts "a secret"
137
- @stdin.puts "a secret"
138
- @stdin.rewind
139
- @htauth.run([ "--argon", "-c", @new_file, "agatha" ])
140
- rescue SystemExit => se
141
- _(se.status).must_equal 0
142
- l = IO.readlines(@new_file)
143
- fields = l.first.split(':')
144
- _(fields.first).must_equal "agatha"
145
- argon2_hash = fields.last
146
-
147
- _(::Argon2::Password.valid_hash?(argon2_hash)).wont_be_nil
148
- end
149
- end
150
-
151
- it "does not verify the password from stdin on -i option" do
152
- begin
153
- @stdin.puts "b secret"
154
- @stdin.rewind
155
- @htauth.run([ "-i", "-B", "-c", @new_file, "brenda" ])
156
- rescue SystemExit => se
157
- _(se.status).must_equal 0
158
- l = IO.readlines(@new_file)
159
- fields = l.first.split(':')
160
- _(fields.first).must_equal "brenda"
161
- bcrypt_hash = fields.last
162
-
163
- _(::BCrypt::Password.valid_hash?(bcrypt_hash)).wont_be_nil
164
- end
165
- end
166
-
167
- it "does not allow options -i and -b to both be set" do
168
- begin
169
- @stdin.puts "b secret"
170
- @stdin.rewind
171
- @htauth.run([ "-i", "-b", "-B", "-c", @new_file, "brenda", "b-secret" ])
172
- rescue SystemExit => se
173
- _(@stderr.string).must_match( /ERROR:/m )
174
- _(se.status).must_equal 1
175
- end
176
- end
177
-
178
- it "truncates an exiting file if told to create a new file" do
179
- before_lines = IO.readlines(@tf.path)
180
- begin
181
- @stdin.puts "b secret"
182
- @stdin.puts "b secret"
183
- @stdin.rewind
184
- @htauth.run([ "-c", @tf.path, "bob"])
185
- rescue SystemExit => se
186
- _(se.status).must_equal 0
187
- after_lines = IO.readlines(@tf.path)
188
- _(after_lines.size).must_equal 1
189
- _(before_lines.size).must_equal 2
190
- end
191
- end
192
-
193
- it "adds an entry to an existing file and force SHA" do
194
- begin
195
- @stdin.puts "c secret"
196
- @stdin.puts "c secret"
197
- @stdin.rewind
198
- @htauth.run([ "-s", @tf.path, "charlie" ])
199
- rescue SystemExit => se
200
- _(se.status).must_equal 0
201
- after_lines = IO.readlines(@tf.path)
202
- _(after_lines.size).must_equal 3
203
- al = after_lines.last.split(':')
204
- _(al.first).must_equal "charlie"
205
- _(al.last).must_match( /\{SHA\}/ )
206
- end
207
- end
208
-
209
- it "can create a plaintext encrypted file" do
210
- begin
211
- @stdin.puts "a bad password"
212
- @stdin.puts "a bad password"
213
- @stdin.rewind
214
- @htauth.run(["-c", "-p", @tf.path, "bradley"])
215
- rescue SystemExit => se
216
- _(se.status).must_equal 0
217
- _(IO.read(@tf.path).strip).must_equal "bradley:a bad password"
218
- end
219
- end
220
-
221
- it "has a batch mode for command line passwords" do
222
- begin
223
- @htauth.run(["-c", "-p", "-b", @tf.path, "bradley", "a bad password"])
224
- rescue SystemExit => se
225
- _(se.status).must_equal 0
226
- _(IO.read(@tf.path).strip).must_equal "bradley:a bad password"
227
- end
228
- end
229
-
230
- it "updates an entry in an existing file and force crypt" do
231
- before_lines = IO.readlines(@tf.path)
232
- begin
233
- @stdin.puts "a new secret"
234
- @stdin.puts "a new secret"
235
- @stdin.rewind
236
- @htauth.run([ "-d", @tf.path, "alice" ])
237
- rescue SystemExit => se
238
- _(@stderr.string).must_equal ""
239
- _(se.status).must_equal 0
240
- after_lines = IO.readlines(@tf.path)
241
- _(after_lines.size).must_equal before_lines.size
242
-
243
- a_b = before_lines.first.split(":")
244
- a_a = after_lines.first.split(":")
245
-
246
- _(a_b.first).must_equal a_a.first
247
- _(a_b.last).wont_equal a_a.last
248
- end
249
- end
250
-
251
- it "deletes an entry in an existing file" do
252
- begin
253
- @htauth.run([ "-D", @tf.path, "bob" ])
254
- rescue SystemExit => se
255
- _(@stderr.string).must_equal ""
256
- _(se.status).must_equal 0
257
- _(IO.read(@tf.path)).must_equal IO.read(PASSWD_DELETE_TEST_FILE)
258
- end
259
- end
260
-
261
- it "sends to STDOUT when the -n option is used" do
262
- begin
263
- @htauth.run(["-n", "-p", "-b", "bradley", "a bad password"])
264
- rescue SystemExit => se
265
- _(se.status).must_equal 0
266
- _(@stdout.string.strip).must_equal "bradley:a bad password"
267
- end
268
- end
269
-
270
- it "verifies a password when --verify is used - valid" do
271
- begin
272
- @htauth.run(["--verify", "-b", @tf.path, "alice", "a secret"])
273
- rescue SystemExit => se
274
- _(@stderr.string.strip).must_equal "Password for user alice correct."
275
- _(se.status).must_equal 0
276
- end
277
- end
278
-
279
- it "verifies a password when --verify is used - invalid" do
280
- begin
281
- @htauth.run(["--verify", "-b", @tf.path, "alice", "the wrong secret"])
282
- rescue SystemExit => se
283
- _(@stderr.string.strip).must_equal "Password verification for user alice failed."
284
- _(se.status).must_equal 1
285
- end
286
- end
287
-
288
- it "has an error if it does not have permissions on the file" do
289
- begin
290
- @stdin.puts "a secret"
291
- @stdin.puts "a secret"
292
- @stdin.rewind
293
- @htauth.run([ "-c", "/etc/you-cannot-create-me", "alice"])
294
- rescue SystemExit => se
295
- _(@stderr.string).must_match( %r{Password file failure \(/etc/you-cannot-create-me\)}m )
296
- _(se.status).must_equal 1
297
- end
298
- end
299
-
300
- it "has an error if the input passwords do not match" do
301
- begin
302
- @stdin.puts "a secret"
303
- @stdin.puts "a bad secret"
304
- @stdin.rewind
305
- @htauth.run([ @tf.path, "alice"])
306
- rescue SystemExit => se
307
- _(@stderr.string).must_match( /They don't match, sorry./m )
308
- _(se.status).must_equal 1
309
- end
310
- end
311
-
312
- it "has an error if the options are incorrect" do
313
- begin
314
- @htauth.run(["--blah"])
315
- rescue SystemExit => se
316
- _(@stderr.string).must_match( /ERROR:/m )
317
- _(se.status).must_equal 1
318
- end
319
- end
320
-
321
- it "errors if send to stdout and create a new file options are both used" do
322
- begin
323
- @htauth.run(["-c", "-n"])
324
- rescue SystemExit => se
325
- _(@stderr.string).must_match( /ERROR:/m )
326
- _(se.status).must_equal 1
327
- end
328
- end
329
-
330
- it "errors if multiple types of operations are attmpted to be used at once" do
331
- begin
332
- @htauth.run(["-n", "-D"])
333
- rescue SystemExit => se
334
- _(@stderr.string).must_match( /ERROR:/m )
335
- _(se.status).must_equal 1
336
- end
337
-
338
- begin
339
- @htauth.run(["--verify", "-D"])
340
- rescue SystemExit => se
341
- _(@stderr.string).must_match( /ERROR:/m )
342
- _(se.status).must_equal 1
343
- end
344
-
345
- end
346
- end
data/spec/crypt_spec.rb DELETED
@@ -1,11 +0,0 @@
1
- require 'spec_helper'
2
-
3
- describe HTAuth::Crypt do
4
- it "encrypts the same way that apache does" do
5
- apache_salt = "L0LDd/.."
6
- apache_result = "L0ekWYm59LT1M"
7
- crypt = HTAuth::Crypt.new({ :salt => apache_salt} )
8
- _(crypt.encode("a secret")).must_equal apache_result
9
- end
10
- end
11
-
@@ -1,59 +0,0 @@
1
- require 'spec_helper'
2
-
3
- describe HTAuth::DigestEntry do
4
- before(:each) do
5
- @alice = HTAuth::DigestEntry.new("alice", "htauth")
6
- @bob = HTAuth::DigestEntry.new("bob", "htauth", "b secret")
7
- end
8
-
9
- it "initializes with a user and realm" do
10
- _(@alice.user).must_equal "alice"
11
- _(@alice.realm).must_equal "htauth"
12
- end
13
-
14
- it "has the correct digest for a password" do
15
- @alice.password = "digit"
16
- _(@alice.digest).must_equal "4ed9e5744c6747af8f292d28afd6372e"
17
- end
18
-
19
- it "returns username:realm for a key" do
20
- _(@alice.key).must_equal "alice:htauth"
21
- end
22
-
23
- it "checks the password correctly" do
24
- _(@bob.authenticated?("b secret")).must_equal true
25
- end
26
-
27
- it "formats correctly when put to a string" do
28
- _(@bob.to_s).must_equal "bob:htauth:fcbeab6821d2ab3b00934c958db0fd1e"
29
- end
30
-
31
- it "parses an input line" do
32
- @bob_new = HTAuth::DigestEntry.from_line("bob:htauth:fcbeab6821d2ab3b00934c958db0fd1e")
33
- _(@bob.user).must_equal @bob_new.user
34
- _(@bob.digest).must_equal @bob_new.digest
35
- _(@bob.realm).must_equal @bob_new.realm
36
- end
37
-
38
- it "knows if an input line is a possible entry and raises an exception" do
39
- _ { HTAuth::DigestEntry.is_entry!("#stuff") }.must_raise(HTAuth::InvalidDigestEntry)
40
- _ { HTAuth::DigestEntry.is_entry!("this:that:other:stuff") }.must_raise(HTAuth::InvalidDigestEntry)
41
- _ { HTAuth::DigestEntry.is_entry!("this:that:other") }.must_raise(HTAuth::InvalidDigestEntry)
42
- _ { HTAuth::DigestEntry.is_entry!("this:that:0a90549e8ffb2dd62f98252a95d88xyz") }.must_raise(HTAuth::InvalidDigestEntry)
43
- end
44
-
45
- it "knows if an input line is a possible entry and returns false" do
46
- _(HTAuth::DigestEntry.is_entry?("#stuff")).must_equal false
47
- _(HTAuth::DigestEntry.is_entry?("this:that:other:stuff")).must_equal false
48
- _(HTAuth::DigestEntry.is_entry?("this:that:other")).must_equal false
49
- _(HTAuth::DigestEntry.is_entry?("this:that:0a90549e8ffb2dd62f98252a95d88xyz")).must_equal false
50
- end
51
-
52
- it "knows if an input line is a possible entry and returns true" do
53
- _(HTAuth::DigestEntry.is_entry?("bob:htauth:0a90549e8ffb2dd62f98252a95d88697")).must_equal true
54
- end
55
-
56
- it "duplicates itself" do
57
- _(@alice.dup.to_s).must_equal @alice.to_s
58
- end
59
- end
@@ -1,64 +0,0 @@
1
- require 'spec_helper'
2
- require 'tempfile'
3
-
4
- describe HTAuth::DigestFile do
5
-
6
- before(:each) do
7
- @tf = Tempfile.new("rpasswrd-digest")
8
- @tf.write(IO.read(DIGEST_ORIGINAL_TEST_FILE))
9
- @tf.close
10
- @digest_file = HTAuth::DigestFile.new(@tf.path)
11
-
12
- @tf2 = Tempfile.new("rpasswrd-digest-empty")
13
- @tf2.close
14
- @empty_digest_file = HTAuth::DigestFile.new(@tf2.path)
15
- end
16
-
17
- after(:each) do
18
- @tf2.close(true)
19
- @tf.close(true)
20
- end
21
-
22
- it "can add a new entry to an already existing digest file" do
23
- @digest_file.add_or_update("charlie", "htauth-new", "c secret")
24
- _(@digest_file.contents).must_equal IO.read(DIGEST_ADD_TEST_FILE)
25
- end
26
-
27
- it "can tell if an entry already exists in the digest file" do
28
- _(@digest_file.has_entry?("alice", "htauth")).must_equal true
29
- _(@digest_file.has_entry?("alice", "some other realm")).must_equal false
30
- end
31
-
32
- it "can update an entry in an already existing digest file" do
33
- @digest_file.add_or_update("alice", "htauth", "a new secret")
34
- _(@digest_file.contents).must_equal IO.read(DIGEST_UPDATE_TEST_FILE)
35
- end
36
-
37
- it "fetches a copy of an entry" do
38
- _(@digest_file.fetch("alice", "htauth").to_s).must_equal "alice:htauth:2f361db93147d84831eb34f19d05bfbb"
39
- end
40
-
41
- it "raises an error if an attempt is made to alter a non-existenet file" do
42
- _ { HTAuth::DigestFile.new("some-file") }.must_raise(HTAuth::FileAccessError)
43
- end
44
-
45
- # this test will only work on systems that have /etc/ssh_host_rsa_key
46
- it "raises an error if an attempt is made to open a file where no permissions are granted" do
47
- _ { HTAuth::DigestFile.new("/etc/ssh_host_rsa_key") }.must_raise(HTAuth::FileAccessError)
48
- end
49
-
50
- it "deletes an entry" do
51
- @digest_file.delete("alice", "htauth")
52
- _(@digest_file.contents).must_equal IO.read(DIGEST_DELETE_TEST_FILE)
53
- end
54
-
55
- it "is usable in a ruby manner and yeilds itself when opened" do
56
- HTAuth::DigestFile.open(@tf.path) do |pf|
57
- pf.add_or_update("alice", "htauth", "a secret")
58
- pf.delete('bob', 'htauth')
59
- end
60
- lines = IO.readlines(@tf.path)
61
- _(lines.size).must_equal 1
62
- _(lines.first.strip).must_equal "alice:htauth:2f361db93147d84831eb34f19d05bfbb"
63
- end
64
- end
data/spec/md5_spec.rb DELETED
@@ -1,11 +0,0 @@
1
- require 'spec_helper'
2
-
3
- describe HTAuth::Md5 do
4
- it "encrypts the same way that apache does" do
5
- apache_salt = "L0LDd/.."
6
- apache_result = "$apr1$L0LDd/..$yhUzDjpxam5F1kWdtwMco1"
7
- md5 = HTAuth::Md5.new({ 'salt' => apache_salt })
8
- _(md5.encode("a secret")).must_equal apache_result
9
- end
10
- end
11
-
@@ -1,172 +0,0 @@
1
- require 'spec_helper'
2
-
3
- describe HTAuth::PasswdEntry do
4
- before(:each) do
5
- @alice = HTAuth::PasswdEntry.new("alice", "a secret", "crypt", { :salt => "mD" })
6
- @bob = HTAuth::PasswdEntry.new("bob", "b secret", "crypt", { :salt => "b8"})
7
- @salt = "lo1tk/.."
8
- end
9
-
10
- it "initializes with a user and realm" do
11
- _(@alice.user).must_equal "alice"
12
- end
13
-
14
- it "has the correct crypt password" do
15
- @alice.password = "a secret"
16
- _(@alice.digest).must_equal "mDwdZuXalQ5zk"
17
- end
18
-
19
- it "encrypts correctly for md5" do
20
- bob = HTAuth::PasswdEntry.new("bob", "b secret", "md5", { :salt => @salt })
21
- _(bob.digest).must_equal "$apr1$lo1tk/..$CarApvZPee0F6Wj1U0GxZ1"
22
- end
23
-
24
- it "encrypts correctly for sha1" do
25
- bob = HTAuth::PasswdEntry.new("bob", "b secret", "sha1", { :salt => @salt })
26
- _(bob.digest).must_equal "{SHA}b/tjGXbX80MEKVnF200S43ca4hY="
27
- end
28
-
29
- it "encrypts correctly for plaintext" do
30
- bob = HTAuth::PasswdEntry.new("bob", "b secret", "plaintext", { :salt => @salt })
31
- _(bob.digest).must_equal "b secret"
32
- end
33
-
34
- it "encypts correctly for argon2" do
35
- # Don't do this in real life, do not pass in a salt, let the algorithm generate it internally
36
- salt = ";L\xCDMRO\v\x13;\x012\x9B'\xEE\\i"
37
- agatha = HTAuth::PasswdEntry.new("agatha","ag secret", "argon2", {salt_do_not_supply: salt} )
38
- expected = "$argon2id$v=19$m=65536,t=3,p=4$O0zNTVJPCxM7ATKbJ+5caQ$e7wIsl7AY+uIbN+1StYOKkVCJhOrvX7BxAlQ+sPC+Nc"
39
- _(agatha.digest).must_equal expected
40
- end
41
-
42
- it "encrypts with crypt as a default, when parsed from crypt()'d line" do
43
- bob2 = HTAuth::PasswdEntry.from_line(@bob.to_s)
44
- _(bob2.algorithm).must_be_instance_of(HTAuth::Crypt)
45
- bob2.password = "another secret"
46
- _(bob2.algorithm).must_be_instance_of(HTAuth::Crypt)
47
- end
48
-
49
- it "encrypts with crypt as a default, when parsed from plaintext line" do
50
- p = HTAuth::PasswdEntry.new('paul', 'p secret', 'plaintext')
51
- p2 = HTAuth::PasswdEntry.from_line(p.to_s)
52
- _(p2.algorithm).must_be_instance_of(HTAuth::Plaintext)
53
- p2.password = "another secret"
54
- _(p2.algorithm).must_be_instance_of(HTAuth::Crypt)
55
- end
56
-
57
- it "encrypts with md5 as default, when parsed from an md5 line" do
58
- m = HTAuth::PasswdEntry.new("mary", "m secret", "md5")
59
- m2 = HTAuth::PasswdEntry.from_line(m.to_s)
60
- _(m2.algorithm).must_be_instance_of(HTAuth::Md5)
61
- end
62
-
63
- it "encrypts with sha1 as default, when parsed from an sha1 line" do
64
- s = HTAuth::PasswdEntry.new("steve", "s secret", "sha1")
65
- s2 = HTAuth::PasswdEntry.from_line(s.to_s)
66
- _(s2.algorithm).must_be_instance_of(HTAuth::Sha1)
67
- end
68
-
69
- it "encrypts with bcrypt as default when parsed from a bcrypt line" do
70
- b = HTAuth::PasswdEntry.new("brenda", "b secret", "bcrypt")
71
- b2 = HTAuth::PasswdEntry.from_line(b.to_s)
72
- _(b2.algorithm).must_be_instance_of(HTAuth::Bcrypt)
73
- end
74
-
75
- it "determins the algorithm to be crypt when checking a password" do
76
- bob2 = HTAuth::PasswdEntry.from_line(@bob.to_s)
77
- _(bob2.algorithm).must_be_instance_of(HTAuth::Crypt)
78
- _(bob2.authenticated?("b secret")).must_equal true
79
- _(bob2.algorithm).must_be_instance_of(HTAuth::Crypt)
80
- end
81
-
82
- it "determins the algorithm to be plain when checking a password" do
83
- bob2 = HTAuth::PasswdEntry.from_line("bob:b secret")
84
- _(bob2.algorithm).must_be_instance_of(HTAuth::Plaintext)
85
- _(bob2.authenticated?("b secret")).must_equal true
86
- _(bob2.algorithm).must_be_instance_of(HTAuth::Plaintext)
87
- end
88
-
89
- it "authenticates correctly against md5" do
90
- m = HTAuth::PasswdEntry.new("mary", "m secret", "md5")
91
- m2 = HTAuth::PasswdEntry.from_line(m.to_s)
92
- _(m2.authenticated?("m secret")).must_equal true
93
- end
94
-
95
- it "authenticates correctly against sha1" do
96
- s = HTAuth::PasswdEntry.new("steve", "s secret", "sha1")
97
- s2 = HTAuth::PasswdEntry.from_line(s.to_s)
98
- _(s2.authenticated?("s secret")).must_equal true
99
- end
100
-
101
- it "authenticates correctly against bcrypt" do
102
- s = HTAuth::PasswdEntry.new("brenda", "b secret", "bcrypt")
103
- s2 = HTAuth::PasswdEntry.from_line(s.to_s)
104
- _(s2.authenticated?("b secret")).must_equal true
105
- end
106
-
107
- it "authenticates correctly against argon2" do
108
- s = HTAuth::PasswdEntry.new("agatha", "ag secret", "argon2")
109
- s2 = HTAuth::PasswdEntry.from_line(s.to_s)
110
- _(s2.authenticated?("ag secret")).must_equal true
111
- end
112
-
113
- it "can update the cost of an entry after initialization before encoding password" do
114
- s = HTAuth::PasswdEntry.new("brenda", "b secret", "bcrypt")
115
- _(s.algorithm.cost).must_equal(::HTAuth::Bcrypt::DEFAULT_APACHE_COST)
116
-
117
- s2 = HTAuth::PasswdEntry.from_line(s.to_s)
118
- s2.algorithm_args = { :cost => 12 }
119
- s2.password = "b secret" # forces recalculation
120
-
121
- _(s2.algorithm.cost).must_equal(12)
122
- end
123
-
124
- it "raises an error if assigning an invalid algorithm" do
125
- b = HTAuth::PasswdEntry.new("brenda", "b secret", "bcrypt")
126
- _ { b.algorithm = 42 }.must_raise(HTAuth::InvalidAlgorithmError)
127
- end
128
-
129
- it "returns username for a key" do
130
- _(@alice.key).must_equal "alice"
131
- end
132
-
133
- it "checks the password correctly" do
134
- _(@bob.authenticated?("b secret")).must_equal true
135
- end
136
-
137
- it "formats correctly when put to a string" do
138
- _(@bob.to_s).must_equal "bob:b8Ml4Jp9I0N8E"
139
- end
140
-
141
- it "parses an input line" do
142
- @bob_new = HTAuth::PasswdEntry.from_line("bob:b8Ml4Jp9I0N8E")
143
- _(@bob.user).must_equal @bob_new.user
144
- _(@bob.digest).must_equal @bob_new.digest
145
- end
146
-
147
- it "knows if an input line is a possible entry and raises an exception" do
148
- _ { HTAuth::PasswdEntry.is_entry!("#stuff") }.must_raise(HTAuth::InvalidPasswdEntry)
149
- _ { HTAuth::PasswdEntry.is_entry!("this:that:other:stuff") }.must_raise(HTAuth::InvalidPasswdEntry)
150
- _ { HTAuth::PasswdEntry.is_entry!("this:that:other") }.must_raise(HTAuth::InvalidPasswdEntry)
151
- _ { HTAuth::PasswdEntry.is_entry!("this:that:0a90549e8ffb2dd62f98252a95d88xyz") }.must_raise(HTAuth::InvalidPasswdEntry)
152
- end
153
-
154
- it "knows if an input line is a possible entry and returns false" do
155
- _(HTAuth::PasswdEntry.is_entry?("#stuff")).must_equal false
156
- _(HTAuth::PasswdEntry.is_entry?("this:that:other:stuff")).must_equal false
157
- _(HTAuth::PasswdEntry.is_entry?("this:that:other")).must_equal false
158
- _(HTAuth::PasswdEntry.is_entry?("this:that:0a90549e8ffb2dd62f98252a95d88xyz")).must_equal false
159
- end
160
-
161
- it "knows if an input line is a possible entry and returns true" do
162
- _(HTAuth::PasswdEntry.is_entry?("bob:irRm0g.SDfCyI")).must_equal true
163
- _(HTAuth::PasswdEntry.is_entry?("bob:b secreat")).must_equal true
164
- _(HTAuth::PasswdEntry.is_entry?("bob:{SHA}b/tjGXbX80MEKVnF200S43ca4hY=")).must_equal true
165
- _(HTAuth::PasswdEntry.is_entry?("bob:$apr1$lo1tk/..$CarApvZPee0F6Wj1U0GxZ1")).must_equal true
166
- _(HTAuth::PasswdEntry.is_entry?("bob:$2y$05$ts3k1r.t0Cne6j6DLt0/SepT5X4qthDFEdfqHBBMO5MhqzyMz34j2")).must_equal true
167
- end
168
-
169
- it "duplicates itself" do
170
- _(@alice.dup.to_s).must_equal @alice.to_s
171
- end
172
- end