htauth 1.2.0 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
data/lib/htauth/md5.rb
CHANGED
@@ -2,8 +2,7 @@ require 'htauth/algorithm'
|
|
2
2
|
require 'digest/md5'
|
3
3
|
|
4
4
|
module HTAuth
|
5
|
-
|
6
|
-
# an implementation of the MD5 based encoding algorithm
|
5
|
+
# Internal: an implementation of the MD5 based encoding algorithm
|
7
6
|
# as used in the apache htpasswd -m option
|
8
7
|
class Md5 < Algorithm
|
9
8
|
|
data/lib/htauth/passwd_entry.rb
CHANGED
@@ -1,18 +1,24 @@
|
|
1
|
-
require 'htauth/
|
1
|
+
require 'htauth/error'
|
2
2
|
require 'htauth/entry'
|
3
3
|
require 'htauth/algorithm'
|
4
4
|
|
5
5
|
module HTAuth
|
6
|
-
|
7
|
-
|
8
|
-
# A single record in an htdigest file.
|
6
|
+
# Internal: Object version of a single entry from a htpasswd file
|
9
7
|
class PasswdEntry < Entry
|
10
8
|
|
9
|
+
# Internal: the user of this entry
|
11
10
|
attr_accessor :user
|
11
|
+
# Internal: the password digest of this entry
|
12
12
|
attr_accessor :digest
|
13
|
+
# Internal: the algorithm used to create the digest of this entry
|
13
14
|
attr_reader :algorithm
|
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 htpasswd file
|
20
|
+
#
|
21
|
+
# Returns an instance of PasswdEntry
|
16
22
|
def from_line(line)
|
17
23
|
parts = is_entry!(line)
|
18
24
|
d = PasswdEntry.new(parts[0])
|
@@ -21,17 +27,26 @@ module HTAuth
|
|
21
27
|
return d
|
22
28
|
end
|
23
29
|
|
24
|
-
# test if
|
25
|
-
#
|
26
|
-
#
|
30
|
+
# Internal: test if the given line is valid for this Entry class
|
31
|
+
#
|
32
|
+
# A valid entry is a single line composed of two parts; a username and a
|
33
|
+
# password separated by a ':' character. Neither the username nor the
|
34
|
+
# password may contain a ':' character
|
35
|
+
#
|
36
|
+
# line - a line of text from a file
|
37
|
+
#
|
38
|
+
# Returns the individual parts of the line
|
39
|
+
# Raises InvalidPasswdEntry if it is not an valid entry
|
27
40
|
def is_entry!(line)
|
28
41
|
raise InvalidPasswdEntry, "line commented out" if line =~ /\A#/
|
29
42
|
parts = line.strip.split(":")
|
30
|
-
raise InvalidPasswdEntry, "line must be of the format username:
|
43
|
+
raise InvalidPasswdEntry, "line must be of the format username:password" if parts.size != 2
|
31
44
|
return parts
|
32
45
|
end
|
33
46
|
|
34
|
-
#
|
47
|
+
# Internal: Returns whether or not the line is a valid entry
|
48
|
+
#
|
49
|
+
# Returns true or false
|
35
50
|
def is_entry?(line)
|
36
51
|
begin
|
37
52
|
is_entry!(line)
|
@@ -42,13 +57,15 @@ module HTAuth
|
|
42
57
|
end
|
43
58
|
end
|
44
59
|
|
60
|
+
# Internal: Create a new Entry with the given user, password, and algorithm
|
45
61
|
def initialize(user, password = nil, alg = Algorithm::DEFAULT, alg_params = {} )
|
46
62
|
@user = user
|
47
63
|
alg = Algorithm::DEFAULT if alg == Algorithm::EXISTING
|
48
64
|
@algorithm = Algorithm.algorithm_from_name(alg, alg_params)
|
49
|
-
@digest =
|
65
|
+
@digest = calc_digest(password)
|
50
66
|
end
|
51
67
|
|
68
|
+
# Internal: set the algorithm for the entry
|
52
69
|
def algorithm=(alg)
|
53
70
|
if alg.kind_of?(Array) then
|
54
71
|
if alg.size == 1 then
|
@@ -62,35 +79,50 @@ module HTAuth
|
|
62
79
|
return @algorithm
|
63
80
|
end
|
64
81
|
|
82
|
+
# Internal: Update the password of the entry with its new value
|
83
|
+
#
|
84
|
+
# If we have an array of algorithms, then we set it to CRYPT
|
65
85
|
def password=(new_password)
|
66
86
|
if algorithm.kind_of?(Array) then
|
67
|
-
@algorithm = Algorithm.algorithm_from_name(
|
87
|
+
@algorithm = Algorithm.algorithm_from_name(Algorithm::CRYPT)
|
68
88
|
end
|
69
|
-
@digest =
|
89
|
+
@digest = calc_digest(new_password)
|
90
|
+
end
|
91
|
+
|
92
|
+
# Internal: calculate the new digest of the given password
|
93
|
+
def calc_digest(password)
|
94
|
+
return nil unless password
|
95
|
+
algorithm.encode(password)
|
70
96
|
end
|
71
97
|
|
98
|
+
# Public: Check if the given password is the password of this entry
|
72
99
|
# check the password and make sure it works, in the case that the algorithm is unknown it
|
73
100
|
# tries all of the ones that it thinks it could be, and marks the algorithm if it matches
|
101
|
+
# when looking for a matche, we always compare all of them, no short
|
102
|
+
# circuiting
|
74
103
|
def authenticated?(check_password)
|
75
104
|
authed = false
|
76
105
|
if algorithm.kind_of?(Array) then
|
77
106
|
algorithm.each do |alg|
|
78
|
-
|
107
|
+
encoded = alg.encode(check_password)
|
108
|
+
if Algorithm.secure_compare(encoded, digest) then
|
79
109
|
@algorithm = alg
|
80
110
|
authed = true
|
81
|
-
break
|
82
111
|
end
|
83
112
|
end
|
84
113
|
else
|
85
|
-
|
114
|
+
encoded = algorithm.encode(check_password)
|
115
|
+
authed = Algorithm.secure_compare(encoded, digest)
|
86
116
|
end
|
87
117
|
return authed
|
88
118
|
end
|
89
119
|
|
120
|
+
# Internal: Returns the key of this entry
|
90
121
|
def key
|
91
122
|
return "#{user}"
|
92
123
|
end
|
93
124
|
|
125
|
+
# Internal: Returns the file line for this entry
|
94
126
|
def to_s
|
95
127
|
"#{user}:#{digest}"
|
96
128
|
end
|
data/lib/htauth/passwd_file.rb
CHANGED
@@ -1,25 +1,52 @@
|
|
1
1
|
require 'stringio'
|
2
2
|
require 'tempfile'
|
3
3
|
|
4
|
-
require 'htauth/
|
4
|
+
require 'htauth/error'
|
5
5
|
require 'htauth/file'
|
6
6
|
require 'htauth/passwd_entry'
|
7
7
|
|
8
8
|
module HTAuth
|
9
|
-
|
10
|
-
|
11
|
-
#
|
9
|
+
# Public: An API for managing an 'htpasswd' file
|
10
|
+
#
|
11
|
+
# Examples
|
12
|
+
#
|
13
|
+
# ::HTAuth::PasswdFile.open("my.passwd") do |pf|
|
14
|
+
# pf.has_entry?('myuser', 'myrealm')
|
15
|
+
# pf.add_or_update('someuser', 'myrealm', 'a password')
|
16
|
+
# pf.delete('someolduser', 'myotherrealm')
|
17
|
+
# end
|
18
|
+
#
|
12
19
|
class PasswdFile < HTAuth::File
|
13
20
|
|
21
|
+
# Private: The class implementing a single entry in the PasswdFile
|
14
22
|
ENTRY_KLASS = HTAuth::PasswdEntry
|
15
23
|
|
16
|
-
#
|
24
|
+
# Public: Checks if the given username exists in the file
|
25
|
+
#
|
26
|
+
# username - the username to check
|
27
|
+
#
|
28
|
+
# Examples
|
29
|
+
#
|
30
|
+
# passwd_file.has_entry?("myuser")
|
31
|
+
# # => true
|
32
|
+
#
|
33
|
+
# Returns true or false if the username
|
17
34
|
def has_entry?(username)
|
18
35
|
test_entry = PasswdEntry.new(username)
|
19
36
|
@entries.has_key?(test_entry.key)
|
20
37
|
end
|
21
38
|
|
22
|
-
# remove
|
39
|
+
# Public: remove the given username from the file
|
40
|
+
# The file is not written to disk until #save! is called.
|
41
|
+
#
|
42
|
+
# username - the username to remove
|
43
|
+
#
|
44
|
+
# Examples
|
45
|
+
#
|
46
|
+
# passwd_file.delete("myuser")
|
47
|
+
# passwd_file.save!
|
48
|
+
#
|
49
|
+
# Returns nothing
|
23
50
|
def delete(username)
|
24
51
|
if has_entry?(username) then
|
25
52
|
ir = internal_record(username)
|
@@ -31,7 +58,27 @@ module HTAuth
|
|
31
58
|
nil
|
32
59
|
end
|
33
60
|
|
34
|
-
#
|
61
|
+
# 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
|
+
# the file. If the entry does exist in the file, then the password
|
64
|
+
# of the entry is updated to the new password / algorithm
|
65
|
+
#
|
66
|
+
# The file is not written to disk until #save! is called.
|
67
|
+
#
|
68
|
+
# username - the username of the entry
|
69
|
+
# password - the username of the entry
|
70
|
+
# algorithm - the algorithm to use (default: "md5"). Valid options are:
|
71
|
+
# "md5", "sha1", "plaintext", or "crypt"
|
72
|
+
#
|
73
|
+
# Examples
|
74
|
+
#
|
75
|
+
# passwd_file.add_or_update("newuser", "password", Algorithm::SHA1)
|
76
|
+
# passwd_file.save!
|
77
|
+
#
|
78
|
+
# passwd_file.add_or_update("newuser", "password")
|
79
|
+
# passwd_file.save!
|
80
|
+
#
|
81
|
+
# Returns nothing.
|
35
82
|
def add_or_update(username, password, algorithm = Algorithm::DEFAULT)
|
36
83
|
if has_entry?(username) then
|
37
84
|
update(username, password, algorithm)
|
@@ -40,7 +87,23 @@ module HTAuth
|
|
40
87
|
end
|
41
88
|
end
|
42
89
|
|
43
|
-
#
|
90
|
+
# Public: Add a new record to the file.
|
91
|
+
#
|
92
|
+
# username - the username of the entry
|
93
|
+
# password - the username of the entry
|
94
|
+
# algorithm - the algorithm to use (default: "md5"). Valid options are:
|
95
|
+
# "md5", "sha1", "plaintext", or "crypt"
|
96
|
+
#
|
97
|
+
# Examples
|
98
|
+
#
|
99
|
+
# passwd_file.add("newuser", "password")
|
100
|
+
# passwd_file.save!
|
101
|
+
#
|
102
|
+
# passwd_file.add("newuser", "password", "sha1")
|
103
|
+
# passwd_file.save!
|
104
|
+
#
|
105
|
+
# Returns nothing.
|
106
|
+
# Raises PasswdFileError if the give username already exists.
|
44
107
|
def add(username, password, algorithm = Algorithm::DEFAULT)
|
45
108
|
raise PasswdFileError, "Unable to add already existing user #{username}" if has_entry?(username)
|
46
109
|
new_entry = PasswdEntry.new(username, password, algorithm)
|
@@ -51,7 +114,27 @@ module HTAuth
|
|
51
114
|
return nil
|
52
115
|
end
|
53
116
|
|
54
|
-
#
|
117
|
+
# Public: Update an existing record in the file.
|
118
|
+
#
|
119
|
+
# By default, the same algorithm that already exists for the entry will be
|
120
|
+
# used with the new password. You may change the algorithm for an entry by
|
121
|
+
# setting the `algorithm` parameter.
|
122
|
+
#
|
123
|
+
# username - the username of the entry
|
124
|
+
# password - the username of the entry
|
125
|
+
# algorithm - the algorithm to use (default: "existing"). Valid options are:
|
126
|
+
# "existing", "md5", "sha1", "plaintext", or "crypt"
|
127
|
+
#
|
128
|
+
# Examples
|
129
|
+
#
|
130
|
+
# passwd_file.update("newuser", "password")
|
131
|
+
# passwd_file.save!
|
132
|
+
#
|
133
|
+
# passwd_file.update("newuser", "password", "sha1")
|
134
|
+
# passwd_file.save!
|
135
|
+
#
|
136
|
+
# Returns nothing.
|
137
|
+
# Raises PasswdFileError if the give username does not exist.
|
55
138
|
def update(username, password, algorithm = Algorithm::EXISTING)
|
56
139
|
raise PasswdFileError, "Unable to update non-existent user #{username}" unless has_entry?(username)
|
57
140
|
ir = internal_record(username)
|
@@ -62,14 +145,28 @@ module HTAuth
|
|
62
145
|
return nil
|
63
146
|
end
|
64
147
|
|
65
|
-
#
|
66
|
-
#
|
148
|
+
# Public: Returns a copy of then given PasswdEntry from the file.
|
149
|
+
#
|
150
|
+
# Updating the PasswdEntry instance returned by this method will NOT update
|
151
|
+
# the file. To update the file, use #update and #save!
|
152
|
+
#
|
153
|
+
# username - the username of the entry
|
154
|
+
#
|
155
|
+
# Examples
|
156
|
+
#
|
157
|
+
# entry = password_file.fetch("myuser")
|
158
|
+
#
|
159
|
+
# Returns a PasswdEntry if the entry is found
|
160
|
+
# Returns nil if the entry is not found
|
67
161
|
def fetch(username)
|
68
162
|
return nil unless has_entry?(username)
|
69
163
|
ir = internal_record(username)
|
70
164
|
return ir['entry'].dup
|
71
165
|
end
|
72
166
|
|
167
|
+
# Internal: returns the class used for each entry
|
168
|
+
#
|
169
|
+
# Returns a Class
|
73
170
|
def entry_klass
|
74
171
|
ENTRY_KLASS
|
75
172
|
end
|
data/lib/htauth/plaintext.rb
CHANGED
data/lib/htauth/sha1.rb
CHANGED
@@ -3,21 +3,21 @@ require 'digest/sha1'
|
|
3
3
|
require 'base64'
|
4
4
|
|
5
5
|
module HTAuth
|
6
|
+
# Internal: an implementation of the SHA based encoding algorithm
|
7
|
+
# as used in the apache htpasswd -s option
|
8
|
+
#
|
9
|
+
class Sha1 < Algorithm
|
6
10
|
|
7
|
-
#
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
# ignore the params
|
12
|
-
def initialize(params = {})
|
13
|
-
end
|
11
|
+
# ignore the params
|
12
|
+
def initialize(params = {})
|
13
|
+
end
|
14
14
|
|
15
|
-
|
16
|
-
|
17
|
-
|
15
|
+
def prefix
|
16
|
+
"{SHA}"
|
17
|
+
end
|
18
18
|
|
19
|
-
|
20
|
-
|
21
|
-
end
|
19
|
+
def encode(password)
|
20
|
+
"#{prefix}#{Base64.encode64(::Digest::SHA1.digest(password)).strip}"
|
22
21
|
end
|
22
|
+
end
|
23
23
|
end
|
data/lib/htauth/version.rb
CHANGED
@@ -1,21 +1,4 @@
|
|
1
1
|
module HTAuth
|
2
|
-
|
3
|
-
|
4
|
-
STRING = HTAuth::VERSION
|
5
|
-
def to_a
|
6
|
-
STRING.split(".")
|
7
|
-
end
|
8
|
-
|
9
|
-
def to_s
|
10
|
-
STRING
|
11
|
-
end
|
12
|
-
|
13
|
-
module_function :to_a
|
14
|
-
module_function :to_s
|
15
|
-
|
16
|
-
MAJOR = Version.to_a[0]
|
17
|
-
MINOR = Version.to_a[1]
|
18
|
-
BUILD = Version.to_a[2]
|
19
|
-
|
20
|
-
end
|
2
|
+
# Public: The version of the htauth library
|
3
|
+
VERSION = "2.0.0"
|
21
4
|
end
|
@@ -0,0 +1,150 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'htauth/cli/digest'
|
3
|
+
require 'tempfile'
|
4
|
+
|
5
|
+
describe HTAuth::CLI::Digest do
|
6
|
+
|
7
|
+
before(:each) do
|
8
|
+
|
9
|
+
# existing
|
10
|
+
@tf = Tempfile.new("rpasswrd-digest-test")
|
11
|
+
@tf.write(IO.read(DIGEST_ORIGINAL_TEST_FILE))
|
12
|
+
@tf.close
|
13
|
+
@rdigest = HTAuth::CLI::Digest.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
|
+
@rdigest.run([ "-h" ])
|
43
|
+
rescue SystemExit => se
|
44
|
+
se.status.must_equal 1
|
45
|
+
@stdout.string.must_match( /passwordfile realm username/m )
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
it "displays the version appropriately" do
|
50
|
+
begin
|
51
|
+
@rdigest.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 entries" do
|
59
|
+
begin
|
60
|
+
@stdin.puts "b secret"
|
61
|
+
@stdin.puts "b secret"
|
62
|
+
@stdin.rewind
|
63
|
+
@rdigest.run([ "-c", @new_file, "htauth", "bob" ])
|
64
|
+
rescue SystemExit => se
|
65
|
+
se.status.must_equal 0
|
66
|
+
IO.read(@new_file).must_equal IO.readlines(DIGEST_ORIGINAL_TEST_FILE).first
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
it "truncates an exiting file if told to create a new file" do
|
71
|
+
begin
|
72
|
+
@stdin.puts "b secret"
|
73
|
+
@stdin.puts "b secret"
|
74
|
+
@stdin.rewind
|
75
|
+
@rdigest.run([ "-c", @tf.path, "htauth", "bob"])
|
76
|
+
rescue SystemExit => se
|
77
|
+
se.status.must_equal 0
|
78
|
+
IO.read(@tf.path).must_equal IO.read(DIGEST_DELETE_TEST_FILE)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
it "adds an entry to an existing file" do
|
83
|
+
begin
|
84
|
+
@stdin.puts "c secret"
|
85
|
+
@stdin.puts "c secret"
|
86
|
+
@stdin.rewind
|
87
|
+
@rdigest.run([ @tf.path, "htauth-new", "charlie" ])
|
88
|
+
rescue SystemExit => se
|
89
|
+
se.status.must_equal 0
|
90
|
+
IO.read(@tf.path).must_equal IO.read(DIGEST_ADD_TEST_FILE)
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
it "updates an entry in an existing file" do
|
95
|
+
begin
|
96
|
+
@stdin.puts "a new secret"
|
97
|
+
@stdin.puts "a new secret"
|
98
|
+
@stdin.rewind
|
99
|
+
@rdigest.run([ @tf.path, "htauth", "alice" ])
|
100
|
+
rescue SystemExit => se
|
101
|
+
@stderr.string.must_equal ""
|
102
|
+
se.status.must_equal 0
|
103
|
+
IO.read(@tf.path).must_equal IO.read(DIGEST_UPDATE_TEST_FILE)
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
it "deletes an entry in an existing file" do
|
108
|
+
begin
|
109
|
+
@rdigest.run([ "-d", @tf.path, "htauth", "alice" ])
|
110
|
+
rescue SystemExit => se
|
111
|
+
@stderr.string.must_equal ""
|
112
|
+
se.status.must_equal 0
|
113
|
+
IO.read(@tf.path).must_equal IO.read(DIGEST_DELETE_TEST_FILE)
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
it "has an error if it does not have permissions on the file" do
|
118
|
+
begin
|
119
|
+
@stdin.puts "a secret"
|
120
|
+
@stdin.puts "a secret"
|
121
|
+
@stdin.rewind
|
122
|
+
@rdigest.run([ "-c", "/etc/you-cannot-create-me", "htauth", "alice"])
|
123
|
+
rescue SystemExit => se
|
124
|
+
@stderr.string.must_match( %r{Could not open password file /etc/you-cannot-create-me}m )
|
125
|
+
se.status.must_equal 1
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
it "has an error if the input passwords do not match" do
|
130
|
+
begin
|
131
|
+
@stdin.puts "a secret"
|
132
|
+
@stdin.puts "a bad secret"
|
133
|
+
@stdin.rewind
|
134
|
+
@rdigest.run([ @tf.path, "htauth", "alice"])
|
135
|
+
rescue SystemExit => se
|
136
|
+
@stderr.string.must_match( /They don't match, sorry./m )
|
137
|
+
se.status.must_equal 1
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
it "has an error if the options are incorrect" do
|
142
|
+
begin
|
143
|
+
@rdigest.run(["--blah"])
|
144
|
+
rescue SystemExit => se
|
145
|
+
@stderr.string.must_match( /ERROR:/m )
|
146
|
+
se.status.must_equal 1
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
end
|