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
data/lib/htauth/digest.rb
DELETED
@@ -1,132 +0,0 @@
|
|
1
|
-
require 'htauth/version'
|
2
|
-
require 'htauth/errors'
|
3
|
-
require 'htauth/digest_file'
|
4
|
-
|
5
|
-
require 'ostruct'
|
6
|
-
require 'optparse'
|
7
|
-
|
8
|
-
require 'highline'
|
9
|
-
|
10
|
-
module HTAuth
|
11
|
-
class Digest
|
12
|
-
|
13
|
-
MAX_PASSWD_LENGTH = 255
|
14
|
-
|
15
|
-
attr_accessor :digest_file
|
16
|
-
|
17
|
-
def initialize
|
18
|
-
@digest_file = nil
|
19
|
-
@option_parser = nil
|
20
|
-
@options = nil
|
21
|
-
end
|
22
|
-
|
23
|
-
def options
|
24
|
-
if @options.nil? then
|
25
|
-
@options = ::OpenStruct.new
|
26
|
-
@options.show_version = false
|
27
|
-
@options.show_help = false
|
28
|
-
@options.file_mode = DigestFile::ALTER
|
29
|
-
@options.passwdfile = nil
|
30
|
-
@options.realm = nil
|
31
|
-
@options.username = nil
|
32
|
-
@options.delete_entry = false
|
33
|
-
end
|
34
|
-
@options
|
35
|
-
end
|
36
|
-
|
37
|
-
def option_parser
|
38
|
-
if not @option_parser then
|
39
|
-
@option_parser = OptionParser.new do |op|
|
40
|
-
op.banner = "Usage: #{op.program_name} [options] passwordfile realm username"
|
41
|
-
op.on("-c", "--create", "Create a new digest password file; this overwrites an existing file.") do |c|
|
42
|
-
options.file_mode = DigestFile::CREATE
|
43
|
-
end
|
44
|
-
|
45
|
-
op.on("-D", "--delete", "Delete the specified user.") do |d|
|
46
|
-
options.delete_entry = d
|
47
|
-
end
|
48
|
-
|
49
|
-
op.on("-h", "--help", "Display this help.") do |h|
|
50
|
-
options.show_help = h
|
51
|
-
end
|
52
|
-
|
53
|
-
op.on("-v", "--version", "Show version info.") do |v|
|
54
|
-
options.show_version = v
|
55
|
-
end
|
56
|
-
end
|
57
|
-
end
|
58
|
-
@option_parser
|
59
|
-
end
|
60
|
-
|
61
|
-
def show_help
|
62
|
-
$stdout.puts option_parser
|
63
|
-
exit 1
|
64
|
-
end
|
65
|
-
|
66
|
-
def show_version
|
67
|
-
$stdout.puts "#{option_parser.program_name}: version #{HTAuth::VERSION}"
|
68
|
-
exit 1
|
69
|
-
end
|
70
|
-
|
71
|
-
def parse_options(argv)
|
72
|
-
begin
|
73
|
-
option_parser.parse!(argv)
|
74
|
-
show_version if options.show_version
|
75
|
-
show_help if options.show_help or argv.size < 3
|
76
|
-
|
77
|
-
options.passwdfile = argv.shift
|
78
|
-
options.realm = argv.shift
|
79
|
-
options.username = argv.shift
|
80
|
-
rescue ::OptionParser::ParseError => pe
|
81
|
-
$stderr.puts "ERROR: #{option_parser.program_name} - #{pe}"
|
82
|
-
$stderr.puts "Try `#{option_parser.program_name} --help` for more information"
|
83
|
-
exit 1
|
84
|
-
end
|
85
|
-
end
|
86
|
-
|
87
|
-
def run(argv, env = ENV)
|
88
|
-
begin
|
89
|
-
parse_options(argv)
|
90
|
-
digest_file = DigestFile.new(options.passwdfile, options.file_mode)
|
91
|
-
|
92
|
-
if options.delete_entry then
|
93
|
-
digest_file.delete(options.username, options.realm)
|
94
|
-
else
|
95
|
-
# initialize here so that if $stdin is overwritten it gets picked up
|
96
|
-
hl = ::HighLine.new
|
97
|
-
|
98
|
-
action = digest_file.has_entry?(options.username, options.realm) ? "Changing" : "Adding"
|
99
|
-
|
100
|
-
$stdout.puts "#{action} password for #{options.username} in realm #{options.realm}."
|
101
|
-
|
102
|
-
pw_in = hl.ask(" New password: ") { |q| q.echo = '*' }
|
103
|
-
raise PasswordError, "password '#{pw_in}' too long" if pw_in.length >= MAX_PASSWD_LENGTH
|
104
|
-
|
105
|
-
pw_validate = hl.ask("Re-type new password: ") { |q| q.echo = '*' }
|
106
|
-
raise PasswordError, "They don't match, sorry." unless pw_in == pw_validate
|
107
|
-
|
108
|
-
digest_file.add_or_update(options.username, options.realm, pw_in)
|
109
|
-
end
|
110
|
-
|
111
|
-
digest_file.save!
|
112
|
-
|
113
|
-
rescue HTAuth::FileAccessError => fae
|
114
|
-
msg = "Could not open password file #{options.passwdfile} "
|
115
|
-
$stderr.puts "#{msg}: #{fae.message}"
|
116
|
-
$stderr.puts fae.backtrace.join("\n")
|
117
|
-
exit 1
|
118
|
-
rescue HTAuth::PasswordError => pe
|
119
|
-
$stderr.puts "#{pe.message}"
|
120
|
-
exit 1
|
121
|
-
rescue HTAuth::DigestFileError => fe
|
122
|
-
$stderr.puts "#{fe.message}"
|
123
|
-
exit 1
|
124
|
-
rescue SignalException => se
|
125
|
-
$stderr.puts
|
126
|
-
$stderr.puts "Interrupted #{se}"
|
127
|
-
exit 1
|
128
|
-
end
|
129
|
-
exit 0
|
130
|
-
end
|
131
|
-
end
|
132
|
-
end
|
data/lib/htauth/errors.rb
DELETED
@@ -1,10 +0,0 @@
|
|
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 FileAccessError < StandardError ; end
|
8
|
-
class TempFileError < StandardError ; end
|
9
|
-
class PasswordError < StandardError ; end
|
10
|
-
end
|
data/lib/htauth/passwd.rb
DELETED
@@ -1,181 +0,0 @@
|
|
1
|
-
require 'htauth/errors'
|
2
|
-
require 'htauth/passwd_file'
|
3
|
-
|
4
|
-
require 'ostruct'
|
5
|
-
require 'optparse'
|
6
|
-
|
7
|
-
require 'highline'
|
8
|
-
|
9
|
-
module HTAuth
|
10
|
-
class Passwd
|
11
|
-
|
12
|
-
MAX_PASSWD_LENGTH = 255
|
13
|
-
|
14
|
-
attr_accessor :passwd_file
|
15
|
-
|
16
|
-
def initialize
|
17
|
-
@passwd_file = nil
|
18
|
-
@option_parser = nil
|
19
|
-
@options = nil
|
20
|
-
end
|
21
|
-
|
22
|
-
def options
|
23
|
-
if @options.nil? then
|
24
|
-
@options = ::OpenStruct.new
|
25
|
-
@options.batch_mode = false
|
26
|
-
@options.file_mode = File::ALTER
|
27
|
-
@options.passwdfile = nil
|
28
|
-
@options.algorithm = Algorithm::EXISTING
|
29
|
-
@options.send_to_stdout = false
|
30
|
-
@options.show_version = false
|
31
|
-
@options.show_help = false
|
32
|
-
@options.username = nil
|
33
|
-
@options.delete_entry = false
|
34
|
-
@options.password = ""
|
35
|
-
end
|
36
|
-
@options
|
37
|
-
end
|
38
|
-
|
39
|
-
def option_parser
|
40
|
-
if not @option_parser then
|
41
|
-
@option_parser = OptionParser.new do |op|
|
42
|
-
op.banner = <<EOB
|
43
|
-
Usage:
|
44
|
-
#{op.program_name} [-cmdpsD] passwordfile username
|
45
|
-
#{op.program_name} -b[cmdpsD] passwordfile username password
|
46
|
-
|
47
|
-
#{op.program_name} -n[mdps] username
|
48
|
-
#{op.program_name} -nb[mdps] username password
|
49
|
-
EOB
|
50
|
-
|
51
|
-
op.separator ""
|
52
|
-
|
53
|
-
op.on("-b", "--batch", "Batch mode, get the password from the command line, rather than prompt") do |b|
|
54
|
-
options.batch_mode = b
|
55
|
-
end
|
56
|
-
|
57
|
-
op.on("-c", "--create", "Create a new file; this overwrites an existing file.") do |c|
|
58
|
-
options.file_mode = HTAuth::File::CREATE
|
59
|
-
end
|
60
|
-
|
61
|
-
op.on("-d", "--crypt", "Force CRYPT encryption of the password.") do |c|
|
62
|
-
options.algorithm = "crypt"
|
63
|
-
end
|
64
|
-
|
65
|
-
op.on("-D", "--delete", "Delete the specified user.") do |d|
|
66
|
-
options.delete_entry = d
|
67
|
-
end
|
68
|
-
|
69
|
-
op.on("-h", "--help", "Display this help.") do |h|
|
70
|
-
options.show_help = h
|
71
|
-
end
|
72
|
-
|
73
|
-
op.on("-m", "--md5", "Force MD5 encryption of the password (default).") do |m|
|
74
|
-
options.algorithm = "md5"
|
75
|
-
end
|
76
|
-
|
77
|
-
op.on("-n", "--stdout", "Do not update the file; Display the results on stdout instead.") do |n|
|
78
|
-
options.send_to_stdout = true
|
79
|
-
options.passwdfile = HTAuth::File::STDOUT_FLAG
|
80
|
-
end
|
81
|
-
|
82
|
-
op.on("-p", "--plaintext", "Do not encrypt the password (plaintext).") do |p|
|
83
|
-
options.algorithm = "plaintext"
|
84
|
-
end
|
85
|
-
|
86
|
-
op.on("-s", "--sha1", "Force SHA encryption of the password.") do |s|
|
87
|
-
options.algorithm = "sha1"
|
88
|
-
end
|
89
|
-
|
90
|
-
op.on("-v", "--version", "Show version info.") do |v|
|
91
|
-
options.show_version = v
|
92
|
-
end
|
93
|
-
|
94
|
-
op.separator ""
|
95
|
-
|
96
|
-
op.separator "The SHA algorihtm does not use a salt and is less secure than the MD5 algorithm"
|
97
|
-
end
|
98
|
-
end
|
99
|
-
@option_parser
|
100
|
-
end
|
101
|
-
|
102
|
-
def show_help
|
103
|
-
$stdout.puts option_parser
|
104
|
-
exit 1
|
105
|
-
end
|
106
|
-
|
107
|
-
def show_version
|
108
|
-
$stdout.puts "#{option_parser.program_name}: version #{HTAuth::VERSION}"
|
109
|
-
exit 1
|
110
|
-
end
|
111
|
-
|
112
|
-
def parse_options(argv)
|
113
|
-
begin
|
114
|
-
option_parser.parse!(argv)
|
115
|
-
show_version if options.show_version
|
116
|
-
show_help if options.show_help
|
117
|
-
|
118
|
-
raise ::OptionParser::ParseError, "Unable to send to stdout AND create a new file" if options.send_to_stdout and (options.file_mode == File::CREATE)
|
119
|
-
raise ::OptionParser::ParseError, "a username is needed" if options.send_to_stdout and argv.size < 1
|
120
|
-
raise ::OptionParser::ParseError, "a username and password are needed" if options.send_to_stdout and options.batch_mode and ( argv.size < 2 )
|
121
|
-
raise ::OptionParser::ParseError, "a passwordfile, username and password are needed " if not options.send_to_stdout and options.batch_mode and ( argv.size < 3 )
|
122
|
-
raise ::OptionParser::ParseError, "a passwordfile and username are needed" if argv.size < 2
|
123
|
-
|
124
|
-
options.passwdfile = argv.shift unless options.send_to_stdout
|
125
|
-
options.username = argv.shift
|
126
|
-
options.password = argv.shift if options.batch_mode
|
127
|
-
|
128
|
-
rescue ::OptionParser::ParseError => pe
|
129
|
-
$stderr.puts "ERROR: #{option_parser.program_name} - #{pe}"
|
130
|
-
show_help
|
131
|
-
exit 1
|
132
|
-
end
|
133
|
-
end
|
134
|
-
|
135
|
-
def run(argv, env = ENV)
|
136
|
-
begin
|
137
|
-
parse_options(argv)
|
138
|
-
passwd_file = PasswdFile.new(options.passwdfile, options.file_mode)
|
139
|
-
|
140
|
-
if options.delete_entry then
|
141
|
-
passwd_file.delete(options.username)
|
142
|
-
else
|
143
|
-
unless options.batch_mode
|
144
|
-
# initialize here so that if $stdin is overwritten it gets picked up
|
145
|
-
hl = ::HighLine.new
|
146
|
-
|
147
|
-
action = passwd_file.has_entry?(options.username) ? "Changing" : "Adding"
|
148
|
-
|
149
|
-
$stdout.puts "#{action} password for #{options.username}."
|
150
|
-
|
151
|
-
pw_in = hl.ask(" New password: ") { |q| q.echo = '*' }
|
152
|
-
raise PasswordError, "password '#{pw_in}' too long" if pw_in.length >= MAX_PASSWD_LENGTH
|
153
|
-
|
154
|
-
pw_validate = hl.ask("Re-type new password: ") { |q| q.echo = '*' }
|
155
|
-
raise PasswordError, "They don't match, sorry." unless pw_in == pw_validate
|
156
|
-
options.password = pw_in
|
157
|
-
end
|
158
|
-
passwd_file.add_or_update(options.username, options.password, options.algorithm)
|
159
|
-
end
|
160
|
-
|
161
|
-
passwd_file.save!
|
162
|
-
|
163
|
-
rescue HTAuth::FileAccessError => fae
|
164
|
-
msg = "Password file failure (#{options.passwdfile}) "
|
165
|
-
$stderr.puts "#{msg}: #{fae.message}"
|
166
|
-
exit 1
|
167
|
-
rescue HTAuth::PasswordError => pe
|
168
|
-
$stderr.puts "#{pe.message}"
|
169
|
-
exit 1
|
170
|
-
rescue HTAuth::PasswdFileError => fe
|
171
|
-
$stderr.puts "#{fe.message}"
|
172
|
-
exit 1
|
173
|
-
rescue SignalException => se
|
174
|
-
$stderr.puts
|
175
|
-
$stderr.puts "Interrupted #{se}"
|
176
|
-
exit 1
|
177
|
-
end
|
178
|
-
exit 0
|
179
|
-
end
|
180
|
-
end
|
181
|
-
end
|
data/spec/digest_spec.rb
DELETED
@@ -1,150 +0,0 @@
|
|
1
|
-
require 'spec_helper'
|
2
|
-
require 'htauth/digest'
|
3
|
-
require 'tempfile'
|
4
|
-
|
5
|
-
describe HTAuth::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::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 = StringIO.new
|
20
|
-
@old_stdout = $stdout
|
21
|
-
$stdout = @stdout
|
22
|
-
|
23
|
-
@stderr = StringIO.new
|
24
|
-
@old_stderr = $stderr
|
25
|
-
$stderr = @stderr
|
26
|
-
|
27
|
-
@stdin = StringIO.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
|