htauth 1.0.1 → 1.0.2
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.
- data/{CHANGES → HISTORY} +4 -0
- data/README +5 -4
- data/bin/htdigest-ruby +13 -6
- data/bin/htpasswd-ruby +8 -1
- data/gemspec.rb +43 -0
- data/lib/htauth.rb +29 -7
- data/lib/htauth/algorithm.rb +51 -50
- data/lib/htauth/crypt.rb +11 -11
- data/lib/htauth/digest.rb +107 -108
- data/lib/htauth/digest_entry.rb +53 -53
- data/lib/htauth/digest_file.rb +61 -66
- data/lib/htauth/entry.rb +5 -5
- data/lib/htauth/file.rb +84 -84
- data/lib/htauth/md5.rb +72 -72
- data/lib/htauth/passwd.rb +149 -150
- data/lib/htauth/passwd_entry.rb +77 -77
- data/lib/htauth/passwd_file.rb +0 -4
- data/lib/htauth/version.rb +16 -13
- data/tasks/announce.rake +38 -0
- data/tasks/config.rb +98 -0
- data/tasks/distribution.rake +46 -0
- data/tasks/documentation.rake +31 -0
- data/tasks/rspec.rake +29 -0
- data/tasks/rubyforge.rake +59 -0
- data/tasks/utils.rb +80 -0
- metadata +79 -89
- data/lib/htauth/gemspec.rb +0 -53
- data/lib/htauth/specification.rb +0 -129
- 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/{CHANGES → HISTORY}
RENAMED
data/README
CHANGED
@@ -2,17 +2,18 @@
|
|
2
2
|
|
3
3
|
* Homepage[http://copiousfreetime.rubyforge.org/htauth]
|
4
4
|
* {Rubyforge Project}[http://rubyforge.org/projects/copiousfreetime/]
|
5
|
-
*
|
5
|
+
* Github[http://github.com/copiousfreetime/htauth/tree/master]
|
6
|
+
* email jeremy at copiousfreetime dot org
|
6
7
|
|
7
8
|
== DESCRIPTION
|
8
9
|
|
9
|
-
HTAuth is a pure ruby replacement for the Apache support programs
|
10
|
-
|
10
|
+
HTAuth is a pure ruby replacement for the Apache support programs htdigest and
|
11
|
+
htpasswd. Command line and API access are provided for access to htdigest and
|
11
12
|
htpasswd files.
|
12
13
|
|
13
14
|
== FEATURES
|
14
15
|
|
15
|
-
|
16
|
+
HTAuth provides to drop in commands *htdigest-ruby* and *htpasswd-ruby* that
|
16
17
|
can manipulate the digest and passwd files in the same manner as Apache's
|
17
18
|
original commands.
|
18
19
|
|
data/bin/htdigest-ruby
CHANGED
@@ -1,12 +1,19 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
|
3
|
+
begin
|
4
|
+
require 'highline'
|
5
|
+
rescue LoadError
|
6
|
+
require 'rubygems'
|
7
|
+
require 'highline'
|
8
|
+
end
|
9
|
+
|
3
10
|
begin
|
4
|
-
|
11
|
+
require 'htauth'
|
5
12
|
rescue LoadError
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
13
|
+
path = File.expand_path(File.join(File.dirname(__FILE__),"..","lib"))
|
14
|
+
raise if $:.include?(path)
|
15
|
+
$: << path
|
16
|
+
retry
|
10
17
|
end
|
11
18
|
|
12
|
-
HTAuth::Digest.new.run(ARGV)
|
19
|
+
HTAuth::Digest.new.run(ARGV, ENV)
|
data/bin/htpasswd-ruby
CHANGED
@@ -1,5 +1,12 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
|
3
|
+
begin
|
4
|
+
require 'highline'
|
5
|
+
rescue LoadError
|
6
|
+
require 'rubygems'
|
7
|
+
require 'highline'
|
8
|
+
end
|
9
|
+
|
3
10
|
begin
|
4
11
|
require 'htauth'
|
5
12
|
rescue LoadError
|
@@ -9,4 +16,4 @@ rescue LoadError
|
|
9
16
|
retry
|
10
17
|
end
|
11
18
|
|
12
|
-
HTAuth::Passwd.new.run(ARGV)
|
19
|
+
HTAuth::Passwd.new.run(ARGV, ENV)
|
data/gemspec.rb
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'htauth/version'
|
3
|
+
require 'tasks/config'
|
4
|
+
|
5
|
+
HTAuth::GEM_SPEC = Gem::Specification.new do |spec|
|
6
|
+
proj = Configuration.for('project')
|
7
|
+
spec.name = proj.name
|
8
|
+
spec.version = HTAuth::VERSION
|
9
|
+
|
10
|
+
spec.author = proj.author
|
11
|
+
spec.email = proj.email
|
12
|
+
spec.homepage = proj.homepage
|
13
|
+
spec.summary = proj.summary
|
14
|
+
spec.description = proj.description
|
15
|
+
spec.platform = Gem::Platform::RUBY
|
16
|
+
|
17
|
+
|
18
|
+
pkg = Configuration.for('packaging')
|
19
|
+
spec.files = pkg.files.all
|
20
|
+
spec.executables = pkg.files.bin.collect { |b| File.basename(b) }
|
21
|
+
|
22
|
+
# add dependencies here
|
23
|
+
# spec.add_dependency("rake", ">= 0.8.1")
|
24
|
+
spec.add_dependency("highline", "~> 1.4.0")
|
25
|
+
|
26
|
+
|
27
|
+
if rdoc = Configuration.for_if_exist?('rdoc') then
|
28
|
+
spec.has_rdoc = true
|
29
|
+
spec.extra_rdoc_files = pkg.files.rdoc
|
30
|
+
spec.rdoc_options = rdoc.options + [ "--main" , rdoc.main_page ]
|
31
|
+
else
|
32
|
+
spec.has_rdoc = false
|
33
|
+
end
|
34
|
+
|
35
|
+
if test = Configuration.for_if_exist?('testing') then
|
36
|
+
spec.test_files = test.files
|
37
|
+
end
|
38
|
+
|
39
|
+
if rf = Configuration.for_if_exist?('rubyforge') then
|
40
|
+
spec.rubyforge_project = rf.project
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
data/lib/htauth.rb
CHANGED
@@ -1,14 +1,36 @@
|
|
1
|
+
#--
|
2
|
+
# Copyrigth (c) 2008 Jeremy Hinegardner
|
3
|
+
# All rights reserved. See LICENSE and/or COPYING for details
|
4
|
+
#++
|
5
|
+
|
1
6
|
module HTAuth
|
2
|
-
|
3
|
-
ROOT_DIR = ::File.expand_path(::File.join(::File.dirname(__FILE__),".."))
|
4
|
-
LIB_DIR = ::File.join(ROOT_DIR,"lib").freeze
|
5
7
|
|
6
|
-
|
7
|
-
|
8
|
-
|
8
|
+
# The root directory of the project is considered to be the parent directory
|
9
|
+
# of the 'lib' directory.
|
10
|
+
#
|
11
|
+
def self.root_dir
|
12
|
+
unless @root_dir
|
13
|
+
path_parts = ::File.expand_path( __FILE__ ).split( ::File::SEPARATOR )
|
14
|
+
lib_index = path_parts.rindex( 'lib' )
|
15
|
+
@root_dir = path_parts[ 0...lib_index].join( ::File::SEPARATOR ) + ::File::SEPARATOR
|
16
|
+
end
|
17
|
+
return @root_dir
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.lib_path( *args )
|
21
|
+
self.sub_path( "lib", *args )
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.sub_path( sub, *args )
|
25
|
+
sp = ::File.join( root_dir, sub ) + ::File::SEPARATOR
|
26
|
+
sp = ::File.join( sp, *args ) if args
|
27
|
+
end
|
28
|
+
|
29
|
+
class FileAccessError < StandardError ; end
|
30
|
+
class TempFileError < StandardError ; end
|
31
|
+
class PasswordError < StandardError ; end
|
9
32
|
end
|
10
33
|
|
11
34
|
require 'htauth/version'
|
12
|
-
require 'htauth/gemspec'
|
13
35
|
require 'htauth/passwd'
|
14
36
|
require 'htauth/digest'
|
data/lib/htauth/algorithm.rb
CHANGED
@@ -1,67 +1,68 @@
|
|
1
1
|
require 'htauth'
|
2
2
|
|
3
3
|
module HTAuth
|
4
|
-
|
5
|
-
|
6
|
-
|
4
|
+
class InvalidAlgorithmError < StandardError ; end
|
5
|
+
# base class all the Passwd algorithms derive from
|
6
|
+
class Algorithm
|
7
7
|
|
8
|
-
|
9
|
-
|
10
|
-
|
8
|
+
SALT_CHARS = (%w[ . / ] + ("0".."9").to_a + ('A'..'Z').to_a + ('a'..'z').to_a).freeze
|
9
|
+
DEFAULT = ( RUBY_PLATFORM !~ /mswin32/ ) ? "crypt" : "md5"
|
10
|
+
EXISTING = "existing"
|
11
11
|
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
12
|
+
class << self
|
13
|
+
def algorithm_from_name(a_name, params = {})
|
14
|
+
raise InvalidAlgorithmError, "`#{a_name}' is an invalid encryption algorithm, use one of #{sub_klasses.keys.join(', ')}" unless sub_klasses[a_name.downcase]
|
15
|
+
sub_klasses[a_name.downcase].new(params)
|
16
|
+
end
|
17
17
|
|
18
|
-
|
19
|
-
|
18
|
+
def algorithms_from_field(password_field)
|
19
|
+
matches = []
|
20
20
|
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
21
|
+
if password_field.index(sub_klasses['sha1'].new.prefix) then
|
22
|
+
matches << sub_klasses['sha1'].new
|
23
|
+
elsif password_field.index(sub_klasses['md5'].new.prefix) then
|
24
|
+
p = password_field.split("$")
|
25
|
+
matches << sub_klasses['md5'].new( :salt => p[2] )
|
26
|
+
else
|
27
|
+
matches << sub_klasses['plaintext'].new
|
28
|
+
matches << sub_klasses['crypt'].new( :salt => password_field[0,2] )
|
29
|
+
end
|
30
30
|
|
31
|
-
|
32
|
-
|
31
|
+
return matches
|
32
|
+
end
|
33
33
|
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
34
|
+
def inherited(sub_klass)
|
35
|
+
k = sub_klass.name.split("::").last.downcase
|
36
|
+
sub_klasses[k] = sub_klass
|
37
|
+
end
|
38
38
|
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
39
|
+
def sub_klasses
|
40
|
+
@sub_klasses ||= {}
|
41
|
+
end
|
42
|
+
end
|
43
43
|
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
# 8 bytes of random items from SALT_CHARS
|
48
|
-
def gen_salt
|
49
|
-
chars = []
|
50
|
-
8.times { chars << SALT_CHARS[rand(SALT_CHARS.size)] }
|
51
|
-
chars.join('')
|
52
|
-
end
|
44
|
+
def prefix ; end
|
45
|
+
def encode(password) ; end
|
53
46
|
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
47
|
+
# 8 bytes of random items from SALT_CHARS
|
48
|
+
def gen_salt
|
49
|
+
chars = []
|
50
|
+
8.times { chars << SALT_CHARS[rand(SALT_CHARS.size)] }
|
51
|
+
chars.join('')
|
52
|
+
end
|
53
|
+
|
54
|
+
# this is not the Base64 encoding, this is the to64() method from apr
|
55
|
+
def to_64(number, rounds)
|
56
|
+
r = StringIO.new
|
57
|
+
rounds.times do |x|
|
58
|
+
r.print(SALT_CHARS[number % 64])
|
59
|
+
number >>= 6
|
60
|
+
end
|
61
|
+
return r.string
|
63
62
|
end
|
63
|
+
end
|
64
64
|
end
|
65
|
+
|
65
66
|
require 'htauth/md5'
|
66
67
|
require 'htauth/sha1'
|
67
68
|
require 'htauth/crypt'
|
data/lib/htauth/crypt.rb
CHANGED
@@ -2,19 +2,19 @@ require 'htauth/algorithm'
|
|
2
2
|
|
3
3
|
module HTAuth
|
4
4
|
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
def initialize(params = {})
|
9
|
-
@salt = params[:salt] || params['salt'] || gen_salt
|
10
|
-
end
|
5
|
+
# The basic crypt algorithm
|
6
|
+
class Crypt < Algorithm
|
11
7
|
|
12
|
-
|
8
|
+
def initialize(params = {})
|
9
|
+
@salt = params[:salt] || params['salt'] || gen_salt
|
10
|
+
end
|
11
|
+
|
12
|
+
def prefix
|
13
13
|
""
|
14
|
-
|
14
|
+
end
|
15
15
|
|
16
|
-
|
17
|
-
|
18
|
-
end
|
16
|
+
def encode(password)
|
17
|
+
password.crypt(@salt)
|
19
18
|
end
|
19
|
+
end
|
20
20
|
end
|
data/lib/htauth/digest.rb
CHANGED
@@ -4,127 +4,126 @@ require 'htauth/digest_file'
|
|
4
4
|
require 'ostruct'
|
5
5
|
require 'optparse'
|
6
6
|
|
7
|
-
require 'rubygems'
|
8
7
|
require 'highline'
|
9
8
|
|
10
9
|
module HTAuth
|
11
|
-
|
10
|
+
class Digest
|
12
11
|
|
13
|
-
|
12
|
+
MAX_PASSWD_LENGTH = 255
|
14
13
|
|
15
|
-
|
14
|
+
attr_accessor :digest_file
|
16
15
|
|
17
|
-
|
18
|
-
|
19
|
-
|
16
|
+
def initialize
|
17
|
+
@digest_file = nil
|
18
|
+
end
|
20
19
|
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
20
|
+
def options
|
21
|
+
if @options.nil? then
|
22
|
+
@options = ::OpenStruct.new
|
23
|
+
@options.show_version = false
|
24
|
+
@options.show_help = false
|
25
|
+
@options.file_mode = DigestFile::ALTER
|
26
|
+
@options.passwdfile = nil
|
27
|
+
@options.realm = nil
|
28
|
+
@options.username = nil
|
29
|
+
@options.delete_entry = false
|
30
|
+
end
|
31
|
+
@options
|
32
|
+
end
|
34
33
|
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
end
|
55
|
-
end
|
56
|
-
@option_parser
|
34
|
+
def option_parser
|
35
|
+
if not @option_parser then
|
36
|
+
@option_parser = OptionParser.new do |op|
|
37
|
+
op.banner = "Usage: #{op.program_name} [options] passwordfile realm username"
|
38
|
+
op.on("-c", "--create", "Create a new digest password file; this overwrites an existing file.") do |c|
|
39
|
+
options.file_mode = DigestFile::CREATE
|
40
|
+
end
|
41
|
+
|
42
|
+
op.on("-D", "--delete", "Delete the specified user.") do |d|
|
43
|
+
options.delete_entry = d
|
44
|
+
end
|
45
|
+
|
46
|
+
op.on("-h", "--help", "Display this help.") do |h|
|
47
|
+
options.show_help = h
|
48
|
+
end
|
49
|
+
|
50
|
+
op.on("-v", "--version", "Show version info.") do |v|
|
51
|
+
options.show_version = v
|
52
|
+
end
|
57
53
|
end
|
54
|
+
end
|
55
|
+
@option_parser
|
56
|
+
end
|
58
57
|
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
58
|
+
def show_help
|
59
|
+
$stdout.puts option_parser
|
60
|
+
exit 1
|
61
|
+
end
|
63
62
|
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
63
|
+
def show_version
|
64
|
+
$stdout.puts "#{option_parser.program_name}: version #{HTAuth::VERSION}"
|
65
|
+
exit 1
|
66
|
+
end
|
68
67
|
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
68
|
+
def parse_options(argv)
|
69
|
+
begin
|
70
|
+
option_parser.parse!(argv)
|
71
|
+
show_version if options.show_version
|
72
|
+
show_help if options.show_help or argv.size < 3
|
73
|
+
|
74
|
+
options.passwdfile = argv.shift
|
75
|
+
options.realm = argv.shift
|
76
|
+
options.username = argv.shift
|
77
|
+
rescue ::OptionParser::ParseError => pe
|
78
|
+
$stderr.puts "ERROR: #{option_parser.program_name} - #{pe}"
|
79
|
+
$stderr.puts "Try `#{option_parser.program_name} --help` for more information"
|
80
|
+
exit 1
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def run(argv, env = ENV)
|
85
|
+
begin
|
86
|
+
parse_options(argv)
|
87
|
+
digest_file = DigestFile.new(options.passwdfile, options.file_mode)
|
88
|
+
|
89
|
+
if options.delete_entry then
|
90
|
+
digest_file.delete(options.username, options.realm)
|
91
|
+
else
|
92
|
+
# initialize here so that if $stdin is overwritten it gets picked up
|
93
|
+
hl = ::HighLine.new
|
94
|
+
|
95
|
+
action = digest_file.has_entry?(options.username, options.realm) ? "Changing" : "Adding"
|
84
96
|
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
hl = ::HighLine.new
|
95
|
-
|
96
|
-
action = digest_file.has_entry?(options.username, options.realm) ? "Changing" : "Adding"
|
97
|
-
|
98
|
-
$stdout.puts "#{action} password for #{options.username} in realm #{options.realm}."
|
99
|
-
|
100
|
-
pw_in = hl.ask(" New password: ") { |q| q.echo = '*' }
|
101
|
-
raise PasswordError, "password '#{pw_in}' too long" if pw_in.length >= MAX_PASSWD_LENGTH
|
102
|
-
|
103
|
-
pw_validate = hl.ask("Re-type new password: ") { |q| q.echo = '*' }
|
104
|
-
raise PasswordError, "They don't match, sorry." unless pw_in == pw_validate
|
105
|
-
|
106
|
-
digest_file.add_or_update(options.username, options.realm, pw_in)
|
107
|
-
end
|
108
|
-
|
109
|
-
digest_file.save!
|
110
|
-
|
111
|
-
rescue HTAuth::FileAccessError => fae
|
112
|
-
msg = "Could not open password file #{options.passwdfile} "
|
113
|
-
$stderr.puts "#{msg}: #{fae.message}"
|
114
|
-
$stderr.puts fae.backtrace.join("\n")
|
115
|
-
exit 1
|
116
|
-
rescue HTAuth::PasswordError => pe
|
117
|
-
$stderr.puts "#{pe.message}"
|
118
|
-
exit 1
|
119
|
-
rescue HTAuth::DigestFileError => fe
|
120
|
-
$stderr.puts "#{fe.message}"
|
121
|
-
exit 1
|
122
|
-
rescue SignalException => se
|
123
|
-
$stderr.puts
|
124
|
-
$stderr.puts "Interrupted"
|
125
|
-
exit 1
|
126
|
-
end
|
127
|
-
exit 0
|
97
|
+
$stdout.puts "#{action} password for #{options.username} in realm #{options.realm}."
|
98
|
+
|
99
|
+
pw_in = hl.ask(" New password: ") { |q| q.echo = '*' }
|
100
|
+
raise PasswordError, "password '#{pw_in}' too long" if pw_in.length >= MAX_PASSWD_LENGTH
|
101
|
+
|
102
|
+
pw_validate = hl.ask("Re-type new password: ") { |q| q.echo = '*' }
|
103
|
+
raise PasswordError, "They don't match, sorry." unless pw_in == pw_validate
|
104
|
+
|
105
|
+
digest_file.add_or_update(options.username, options.realm, pw_in)
|
128
106
|
end
|
107
|
+
|
108
|
+
digest_file.save!
|
109
|
+
|
110
|
+
rescue HTAuth::FileAccessError => fae
|
111
|
+
msg = "Could not open password file #{options.passwdfile} "
|
112
|
+
$stderr.puts "#{msg}: #{fae.message}"
|
113
|
+
$stderr.puts fae.backtrace.join("\n")
|
114
|
+
exit 1
|
115
|
+
rescue HTAuth::PasswordError => pe
|
116
|
+
$stderr.puts "#{pe.message}"
|
117
|
+
exit 1
|
118
|
+
rescue HTAuth::DigestFileError => fe
|
119
|
+
$stderr.puts "#{fe.message}"
|
120
|
+
exit 1
|
121
|
+
rescue SignalException => se
|
122
|
+
$stderr.puts
|
123
|
+
$stderr.puts "Interrupted"
|
124
|
+
exit 1
|
125
|
+
end
|
126
|
+
exit 0
|
129
127
|
end
|
128
|
+
end
|
130
129
|
end
|