htauth 1.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.
- data/CHANGES +4 -0
- data/LICENSE +19 -0
- data/README +94 -0
- data/bin/htdigest-ruby +12 -0
- data/bin/htpasswd-ruby +12 -0
- data/lib/htauth/algorithm.rb +67 -0
- data/lib/htauth/crypt.rb +20 -0
- data/lib/htauth/digest.rb +128 -0
- data/lib/htauth/digest_entry.rb +72 -0
- data/lib/htauth/digest_file.rb +85 -0
- data/lib/htauth/entry.rb +9 -0
- data/lib/htauth/file.rb +102 -0
- data/lib/htauth/gemspec.rb +52 -0
- data/lib/htauth/md5.rb +82 -0
- data/lib/htauth/passwd.rb +174 -0
- data/lib/htauth/passwd_entry.rb +97 -0
- data/lib/htauth/passwd_file.rb +86 -0
- data/lib/htauth/plaintext.rb +18 -0
- data/lib/htauth/sha1.rb +23 -0
- data/lib/htauth/specification.rb +128 -0
- data/lib/htauth/version.rb +18 -0
- data/lib/htauth.rb +27 -0
- data/spec/crypt_spec.rb +18 -0
- data/spec/digest_entry_spec.rb +61 -0
- data/spec/digest_file_spec.rb +66 -0
- data/spec/digest_spec.rb +150 -0
- data/spec/md5_spec.rb +17 -0
- data/spec/passwd_entry_spec.rb +139 -0
- data/spec/passwd_file_spec.rb +67 -0
- data/spec/passwd_spec.rb +208 -0
- data/spec/plaintext_spec.rb +18 -0
- data/spec/sha1_spec.rb +17 -0
- data/spec/spec_helper.rb +17 -0
- data/spec/test.add.digest +3 -0
- data/spec/test.add.passwd +3 -0
- data/spec/test.delete.digest +1 -0
- data/spec/test.delete.passwd +1 -0
- data/spec/test.original.digest +2 -0
- data/spec/test.original.passwd +2 -0
- data/spec/test.update.digest +2 -0
- data/spec/test.update.passwd +2 -0
- metadata +122 -0
data/lib/htauth/md5.rb
ADDED
@@ -0,0 +1,82 @@
|
|
1
|
+
require 'htauth/algorithm'
|
2
|
+
require 'digest/md5'
|
3
|
+
|
4
|
+
module HTAuth
|
5
|
+
|
6
|
+
# an implementation of the MD5 based encoding algorithm
|
7
|
+
# as used in the apache htpasswd -m option
|
8
|
+
class Md5 < Algorithm
|
9
|
+
|
10
|
+
DIGEST_LENGTH = 16
|
11
|
+
|
12
|
+
def initialize(params = {})
|
13
|
+
@salt = params['salt'] || params[:salt] || gen_salt
|
14
|
+
end
|
15
|
+
|
16
|
+
def prefix
|
17
|
+
"$apr1$"
|
18
|
+
end
|
19
|
+
|
20
|
+
# this algorigthm pulled straight from apr_md5_encode() and converted to ruby syntax
|
21
|
+
def encode(password)
|
22
|
+
primary = ::Digest::MD5.new
|
23
|
+
primary << password
|
24
|
+
primary << prefix
|
25
|
+
primary << @salt
|
26
|
+
|
27
|
+
md5_t = ::Digest::MD5.digest("#{password}#{@salt}#{password}")
|
28
|
+
|
29
|
+
l = password.length
|
30
|
+
while l > 0 do
|
31
|
+
slice_size = ( l > DIGEST_LENGTH ) ? DIGEST_LENGTH : l
|
32
|
+
primary << md5_t[0, slice_size]
|
33
|
+
l -= DIGEST_LENGTH
|
34
|
+
end
|
35
|
+
|
36
|
+
# weirdness
|
37
|
+
l = password.length
|
38
|
+
while l != 0
|
39
|
+
case (l & 1)
|
40
|
+
when 1
|
41
|
+
primary << 0.chr
|
42
|
+
when 0
|
43
|
+
primary << password[0,1]
|
44
|
+
end
|
45
|
+
l >>= 1
|
46
|
+
end
|
47
|
+
|
48
|
+
pd = primary.digest
|
49
|
+
|
50
|
+
encoded_password = "#{prefix}#{@salt}$"
|
51
|
+
|
52
|
+
# apr_md5_encode has this comment about a 60Mhz Pentium above this loop.
|
53
|
+
1000.times do |x|
|
54
|
+
ctx = ::Digest::MD5.new
|
55
|
+
ctx << (( ( x & 1 ) == 1 ) ? password : pd[0,DIGEST_LENGTH])
|
56
|
+
(ctx << @salt) unless ( x % 3 ) == 0
|
57
|
+
(ctx << password) unless ( x % 7 ) == 0
|
58
|
+
ctx << (( ( x & 1 ) == 0 ) ? password : pd[0,DIGEST_LENGTH])
|
59
|
+
pd = ctx.digest
|
60
|
+
end
|
61
|
+
|
62
|
+
|
63
|
+
l = (pd[ 0]<<16) | (pd[ 6]<<8) | pd[12]
|
64
|
+
encoded_password << to_64(l, 4)
|
65
|
+
|
66
|
+
l = (pd[ 1]<<16) | (pd[ 7]<<8) | pd[13]
|
67
|
+
encoded_password << to_64(l, 4)
|
68
|
+
|
69
|
+
l = (pd[ 2]<<16) | (pd[ 8]<<8) | pd[14]
|
70
|
+
encoded_password << to_64(l, 4)
|
71
|
+
|
72
|
+
l = (pd[ 3]<<16) | (pd[ 9]<<8) | pd[15]
|
73
|
+
encoded_password << to_64(l, 4)
|
74
|
+
|
75
|
+
l = (pd[ 4]<<16) | (pd[10]<<8) | pd[ 5]
|
76
|
+
encoded_password << to_64(l, 4)
|
77
|
+
encoded_password << to_64(pd[11],2)
|
78
|
+
|
79
|
+
return encoded_password
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
@@ -0,0 +1,174 @@
|
|
1
|
+
require 'htauth/passwd_file'
|
2
|
+
require 'ostruct'
|
3
|
+
require 'optparse'
|
4
|
+
|
5
|
+
require 'rubygems'
|
6
|
+
require 'highline'
|
7
|
+
|
8
|
+
module HTAuth
|
9
|
+
class Passwd
|
10
|
+
|
11
|
+
MAX_PASSWD_LENGTH = 255
|
12
|
+
|
13
|
+
attr_accessor :passwd_file
|
14
|
+
|
15
|
+
def initialize
|
16
|
+
@passwd_file = nil
|
17
|
+
end
|
18
|
+
|
19
|
+
def options
|
20
|
+
if @options.nil? then
|
21
|
+
@options = ::OpenStruct.new
|
22
|
+
@options.batch_mode = false
|
23
|
+
@options.file_mode = File::ALTER
|
24
|
+
@options.passwdfile = nil
|
25
|
+
@options.algorithm = Algorithm::EXISTING
|
26
|
+
@options.send_to_stdout = false
|
27
|
+
@options.show_version = false
|
28
|
+
@options.show_help = false
|
29
|
+
@options.username = nil
|
30
|
+
@options.delete_entry = false
|
31
|
+
@options.password = ""
|
32
|
+
end
|
33
|
+
@options
|
34
|
+
end
|
35
|
+
|
36
|
+
def option_parser
|
37
|
+
if not @option_parser then
|
38
|
+
@option_parser = OptionParser.new do |op|
|
39
|
+
op.banner = <<EOB
|
40
|
+
Usage:
|
41
|
+
#{op.program_name} [-cmdpsD] passwordfile username
|
42
|
+
#{op.program_name} -b[cmdpsD] passwordfile username password
|
43
|
+
|
44
|
+
#{op.program_name} -n[mdps] username
|
45
|
+
#{op.program_name} -nb[mdps] username password
|
46
|
+
EOB
|
47
|
+
|
48
|
+
op.separator ""
|
49
|
+
|
50
|
+
op.on("-b", "--batch", "Batch mode, get the password from the command line, rather than prompt") do |b|
|
51
|
+
options.batch_mode = b
|
52
|
+
end
|
53
|
+
|
54
|
+
op.on("-c", "--create", "Create a new file; this overwrites an existing file.") do |c|
|
55
|
+
options.file_mode = HTAuth::File::CREATE
|
56
|
+
end
|
57
|
+
|
58
|
+
op.on("-d", "--crypt", "Force CRYPT encryption of the password (default).") do |c|
|
59
|
+
options.algorithm = "crypt"
|
60
|
+
end
|
61
|
+
|
62
|
+
op.on("-D", "--delete", "Delete the specified user.") do |d|
|
63
|
+
options.delete_entry = d
|
64
|
+
end
|
65
|
+
|
66
|
+
op.on("-h", "--help", "Display this help.") do |h|
|
67
|
+
options.show_help = h
|
68
|
+
end
|
69
|
+
|
70
|
+
op.on("-m", "--md5", "Force MD5 encryption of the password (default on Windows).") do |m|
|
71
|
+
options.algorithm = "md5"
|
72
|
+
end
|
73
|
+
|
74
|
+
op.on("-n", "--stdout", "Do not update the file; Display the results on stdout instead.") do |n|
|
75
|
+
options.send_to_stdout = true
|
76
|
+
options.passwdfile = HTAuth::File::STDOUT_FLAG
|
77
|
+
end
|
78
|
+
|
79
|
+
op.on("-p", "--plaintext", "Do not encrypt the password (plaintext).") do |p|
|
80
|
+
options.algorithm = "plaintext"
|
81
|
+
end
|
82
|
+
|
83
|
+
op.on("-s", "--sha1", "Force SHA encryption of the password.") do |s|
|
84
|
+
options.algorithm = "sha1"
|
85
|
+
end
|
86
|
+
|
87
|
+
op.on("-v", "--version", "Show version info.") do |v|
|
88
|
+
options.show_version = v
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
@option_parser
|
93
|
+
end
|
94
|
+
|
95
|
+
def show_help
|
96
|
+
$stdout.puts option_parser
|
97
|
+
exit 1
|
98
|
+
end
|
99
|
+
|
100
|
+
def show_version
|
101
|
+
$stdout.puts "#{option_parser.program_name}: version #{HTAuth::VERSION}"
|
102
|
+
exit 1
|
103
|
+
end
|
104
|
+
|
105
|
+
def parse_options(argv)
|
106
|
+
begin
|
107
|
+
option_parser.parse!(argv)
|
108
|
+
show_version if options.show_version
|
109
|
+
show_help if options.show_help
|
110
|
+
|
111
|
+
raise ::OptionParser::ParseError, "Unable to send to stdout AND create a new file" if options.send_to_stdout and (options.file_mode == File::CREATE)
|
112
|
+
raise ::OptionParser::ParseError, "a username is needed" if options.send_to_stdout and argv.size < 1
|
113
|
+
raise ::OptionParser::ParseError, "a username and password are needed" if options.send_to_stdout and options.batch_mode and ( argv.size < 2 )
|
114
|
+
raise ::OptionParser::ParseError, "a passwordfile, username and password are needed " if not options.send_to_stdout and options.batch_mode and ( argv.size < 3 )
|
115
|
+
raise ::OptionParser::ParseError, "a passwordfile and username are needed" if argv.size < 2
|
116
|
+
|
117
|
+
options.passwdfile = argv.shift unless options.send_to_stdout
|
118
|
+
options.username = argv.shift
|
119
|
+
options.password = argv.shift if options.batch_mode
|
120
|
+
|
121
|
+
rescue ::OptionParser::ParseError => pe
|
122
|
+
$stderr.puts "ERROR: #{option_parser.program_name} - #{pe}"
|
123
|
+
show_help
|
124
|
+
exit 1
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
def run(argv)
|
129
|
+
begin
|
130
|
+
parse_options(argv)
|
131
|
+
passwd_file = PasswdFile.new(options.passwdfile, options.file_mode)
|
132
|
+
|
133
|
+
if options.delete_entry then
|
134
|
+
passwd_file.delete(options.username)
|
135
|
+
else
|
136
|
+
unless options.batch_mode
|
137
|
+
# initialize here so that if $stdin is overwritten it gest picked up
|
138
|
+
hl = ::HighLine.new
|
139
|
+
|
140
|
+
action = passwd_file.has_entry?(options.username) ? "Changing" : "Adding"
|
141
|
+
|
142
|
+
$stdout.puts "#{action} password for #{options.username}."
|
143
|
+
|
144
|
+
pw_in = hl.ask(" New password: ") { |q| q.echo = '*' }
|
145
|
+
raise PasswordError, "password '#{pw_in}' too long" if pw_in.length >= MAX_PASSWD_LENGTH
|
146
|
+
|
147
|
+
pw_validate = hl.ask("Re-type new password: ") { |q| q.echo = '*' }
|
148
|
+
raise PasswordError, "They don't match, sorry." unless pw_in == pw_validate
|
149
|
+
options.password = pw_in
|
150
|
+
end
|
151
|
+
passwd_file.add_or_update(options.username, options.password, options.algorithm)
|
152
|
+
end
|
153
|
+
|
154
|
+
passwd_file.save!
|
155
|
+
|
156
|
+
rescue HTAuth::FileAccessError => fae
|
157
|
+
msg = "Password file failure (#{options.passwdfile}) "
|
158
|
+
$stderr.puts "#{msg}: #{fae.message}"
|
159
|
+
exit 1
|
160
|
+
rescue HTAuth::PasswordError => pe
|
161
|
+
$stderr.puts "#{pe.message}"
|
162
|
+
exit 1
|
163
|
+
rescue HTAuth::PasswdFileError => fe
|
164
|
+
$stderr.puts "#{fe.message}"
|
165
|
+
exit 1
|
166
|
+
rescue SignalException => se
|
167
|
+
$stderr.puts
|
168
|
+
$stderr.puts "Interrupted"
|
169
|
+
exit 1
|
170
|
+
end
|
171
|
+
exit 0
|
172
|
+
end
|
173
|
+
end
|
174
|
+
end
|
@@ -0,0 +1,97 @@
|
|
1
|
+
|
2
|
+
require 'htauth/entry'
|
3
|
+
|
4
|
+
module HTAuth
|
5
|
+
class InvalidPasswdEntry < StandardError ; end
|
6
|
+
|
7
|
+
# A single record in an htdigest file.
|
8
|
+
class PasswdEntry < Entry
|
9
|
+
|
10
|
+
attr_accessor :user
|
11
|
+
attr_accessor :digest
|
12
|
+
attr_reader :algorithm
|
13
|
+
|
14
|
+
class << self
|
15
|
+
def from_line(line)
|
16
|
+
parts = is_entry!(line)
|
17
|
+
d = PasswdEntry.new(parts[0])
|
18
|
+
d.digest = parts[1]
|
19
|
+
d.algorithm = Algorithm.algorithms_from_field(parts[1])
|
20
|
+
return d
|
21
|
+
end
|
22
|
+
|
23
|
+
# test if a line is an entry, raise InvalidPasswdEntry if it is not.
|
24
|
+
# an entry must be composed of 2 parts, username:encrypted_password
|
25
|
+
# where username, and password do not contain the ':' character
|
26
|
+
def is_entry!(line)
|
27
|
+
raise InvalidPasswdEntry, "line commented out" if line =~ /\A#/
|
28
|
+
parts = line.strip.split(":")
|
29
|
+
raise InvalidPasswdEntry, "line must be of the format username:pssword" if parts.size != 2
|
30
|
+
return parts
|
31
|
+
end
|
32
|
+
|
33
|
+
# test if a line is an entry and return true or false
|
34
|
+
def is_entry?(line)
|
35
|
+
begin
|
36
|
+
is_entry!(line)
|
37
|
+
return true
|
38
|
+
rescue InvalidPasswdEntry
|
39
|
+
return false
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def initialize(user, password = "", alg = Algorithm::DEFAULT, alg_params = {} )
|
45
|
+
@user = user
|
46
|
+
alg = Algorithm::DEFAULT if alg == Algorithm::EXISTING
|
47
|
+
@algorithm = Algorithm.algorithm_from_name(alg, alg_params)
|
48
|
+
@digest = algorithm.encode(password)
|
49
|
+
end
|
50
|
+
|
51
|
+
def algorithm=(alg)
|
52
|
+
if alg.kind_of?(Array) then
|
53
|
+
if alg.size == 1 then
|
54
|
+
@algorithm = alg.first
|
55
|
+
else
|
56
|
+
@algorithm = alg
|
57
|
+
end
|
58
|
+
else
|
59
|
+
@algorithm = Algorithm.algorithm_from_name(alg) unless Algorithm::EXISTING == alg
|
60
|
+
end
|
61
|
+
return @algorithm
|
62
|
+
end
|
63
|
+
|
64
|
+
def password=(new_password)
|
65
|
+
if algorithm.kind_of?(Array) then
|
66
|
+
@algorithm = Algorithm.algorithm_from_name("crypt")
|
67
|
+
end
|
68
|
+
@digest = algorithm.encode(new_password)
|
69
|
+
end
|
70
|
+
|
71
|
+
# check the password and make sure it works, in the case that the algorithm is unknown it
|
72
|
+
# tries all of the ones that it thinks it could be, and marks the algorithm if it matches
|
73
|
+
def authenticated?(check_password)
|
74
|
+
authed = false
|
75
|
+
if algorithm.kind_of?(Array) then
|
76
|
+
algorithm.each do |alg|
|
77
|
+
if alg.encode(check_password) == digest then
|
78
|
+
@algorithm = alg
|
79
|
+
authed = true
|
80
|
+
break
|
81
|
+
end
|
82
|
+
end
|
83
|
+
else
|
84
|
+
authed = digest == algorithm.encode(check_password)
|
85
|
+
end
|
86
|
+
return authed
|
87
|
+
end
|
88
|
+
|
89
|
+
def key
|
90
|
+
return "#{user}"
|
91
|
+
end
|
92
|
+
|
93
|
+
def to_s
|
94
|
+
"#{user}:#{digest}"
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
require 'stringio'
|
2
|
+
require 'tempfile'
|
3
|
+
|
4
|
+
require 'htauth/passwd_entry'
|
5
|
+
|
6
|
+
module HTAuth
|
7
|
+
class PasswdFileError < StandardError ; end
|
8
|
+
|
9
|
+
# PasswdFile provides API style access to an +htpasswd+ produced file
|
10
|
+
class PasswdFile < HTAuth::File
|
11
|
+
|
12
|
+
ENTRY_KLASS = HTAuth::PasswdEntry
|
13
|
+
|
14
|
+
# does the entry the the specified username and realm exist in the file
|
15
|
+
def has_entry?(username)
|
16
|
+
test_entry = PasswdEntry.new(username)
|
17
|
+
@entries.has_key?(test_entry.key)
|
18
|
+
end
|
19
|
+
|
20
|
+
# remove an entry from the file
|
21
|
+
def delete(username)
|
22
|
+
if has_entry?(username) then
|
23
|
+
ir = internal_record(username)
|
24
|
+
line_index = ir['line_index']
|
25
|
+
@entries.delete(ir['entry'].key)
|
26
|
+
@lines[line_index] = nil
|
27
|
+
dirty!
|
28
|
+
end
|
29
|
+
nil
|
30
|
+
end
|
31
|
+
|
32
|
+
# add or update an entry as appropriate
|
33
|
+
def add_or_update(username, password, algorithm = Algorithm::DEFAULT)
|
34
|
+
if has_entry?(username) then
|
35
|
+
update(username, password, algorithm)
|
36
|
+
else
|
37
|
+
add(username, password, algorithm)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
# add an new record. raises an error if the entry exists.
|
42
|
+
def add(username, password, algorithm = Algorithm::DEFAULT)
|
43
|
+
raise PasswdFileError, "Unable to add already existing user #{username}" if has_entry?(username)
|
44
|
+
new_entry = PasswdEntry.new(username, password, algorithm)
|
45
|
+
new_index = @lines.size
|
46
|
+
@lines << new_entry.to_s
|
47
|
+
@entries[new_entry.key] = { 'entry' => new_entry, 'line_index' => new_index }
|
48
|
+
dirty!
|
49
|
+
return nil
|
50
|
+
end
|
51
|
+
|
52
|
+
# update an already existing entry with a new password. raises an error if the entry does not exist
|
53
|
+
def update(username, password, algorithm = Algorithm::EXISTING)
|
54
|
+
raise PasswdFileError, "Unable to update non-existent user #{username}" unless has_entry?(username)
|
55
|
+
ir = internal_record(username)
|
56
|
+
ir['entry'].algorithm = algorithm
|
57
|
+
ir['entry'].password = password
|
58
|
+
@lines[ir['line_index']] = ir['entry'].to_s
|
59
|
+
dirty!
|
60
|
+
return nil
|
61
|
+
end
|
62
|
+
|
63
|
+
# fetches a copy of an entry from the file. Updateing the entry returned from fetch will NOT
|
64
|
+
# propogate back to the file.
|
65
|
+
def fetch(username)
|
66
|
+
return nil unless has_entry?(username)
|
67
|
+
ir = internal_record(username)
|
68
|
+
return ir['entry'].dup
|
69
|
+
end
|
70
|
+
|
71
|
+
def entry_klass
|
72
|
+
ENTRY_KLASS
|
73
|
+
end
|
74
|
+
|
75
|
+
def file_type
|
76
|
+
"passwd"
|
77
|
+
end
|
78
|
+
|
79
|
+
private
|
80
|
+
|
81
|
+
def internal_record(username)
|
82
|
+
e = PasswdEntry.new(username)
|
83
|
+
@entries[e.key]
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'htauth/algorithm'
|
2
|
+
|
3
|
+
module HTAuth
|
4
|
+
|
5
|
+
# the plaintext algorithm, which does absolutly nothing
|
6
|
+
class Plaintext < Algorithm
|
7
|
+
# ignore parameters
|
8
|
+
def initialize(params = {})
|
9
|
+
end
|
10
|
+
def prefix
|
11
|
+
""
|
12
|
+
end
|
13
|
+
|
14
|
+
def encode(password)
|
15
|
+
"#{password}"
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
data/lib/htauth/sha1.rb
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'htauth/algorithm'
|
2
|
+
require 'digest/sha1'
|
3
|
+
require 'base64'
|
4
|
+
|
5
|
+
module HTAuth
|
6
|
+
|
7
|
+
# an implementation of the SHA based encoding algorithm
|
8
|
+
# as used in the apache htpasswd -s option
|
9
|
+
class Sha1 < Algorithm
|
10
|
+
|
11
|
+
# ignore the params
|
12
|
+
def initialize(params = {})
|
13
|
+
end
|
14
|
+
|
15
|
+
def prefix
|
16
|
+
"{SHA}"
|
17
|
+
end
|
18
|
+
|
19
|
+
def encode(password)
|
20
|
+
"#{prefix}#{Base64.encode64(::Digest::SHA1.digest(password)).strip}"
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,128 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rubygems/specification'
|
3
|
+
require 'rake'
|
4
|
+
|
5
|
+
module HTAuth
|
6
|
+
# Add some additional items to Gem::Specification
|
7
|
+
# A HTAuth::Specification adds additional pieces of information the
|
8
|
+
# typical gem specification
|
9
|
+
class Specification
|
10
|
+
|
11
|
+
RUBYFORGE_ROOT = "/var/www/gforge-projects/"
|
12
|
+
|
13
|
+
# user that accesses remote site
|
14
|
+
attr_accessor :remote_user
|
15
|
+
|
16
|
+
# remote host, default 'rubyforge.org'
|
17
|
+
attr_accessor :remote_host
|
18
|
+
|
19
|
+
# name the rdoc main
|
20
|
+
attr_accessor :rdoc_main
|
21
|
+
|
22
|
+
# local directory in development holding the generated rdoc
|
23
|
+
# default 'doc'
|
24
|
+
attr_accessor :local_rdoc_dir
|
25
|
+
|
26
|
+
# remote directory for storing rdoc, default 'doc'
|
27
|
+
attr_accessor :remote_rdoc_dir
|
28
|
+
|
29
|
+
# local directory for coverage report
|
30
|
+
attr_accessor :local_coverage_dir
|
31
|
+
|
32
|
+
# remote directory for storing coverage reports
|
33
|
+
# This defaults to 'coverage'
|
34
|
+
attr_accessor :remote_coverage_dir
|
35
|
+
|
36
|
+
# local directory for generated website, default +site/public+
|
37
|
+
attr_accessor :local_site_dir
|
38
|
+
|
39
|
+
# remote directory relative to +remote_root+ for the website.
|
40
|
+
# website.
|
41
|
+
attr_accessor :remote_site_dir
|
42
|
+
|
43
|
+
# is a .tgz to be created?, default 'true'
|
44
|
+
attr_accessor :need_tar
|
45
|
+
|
46
|
+
# is a .zip to be created, default 'true'
|
47
|
+
attr_accessor :need_zip
|
48
|
+
|
49
|
+
|
50
|
+
def initialize
|
51
|
+
@remote_user = nil
|
52
|
+
@remote_host = "rubyforge.org"
|
53
|
+
|
54
|
+
@rdoc_main = "README"
|
55
|
+
@local_rdoc_dir = "doc"
|
56
|
+
@remote_rdoc_dir = "doc"
|
57
|
+
@local_coverage_dir = "coverage"
|
58
|
+
@remote_coverage_dir = "coverage"
|
59
|
+
@local_site_dir = "site/public"
|
60
|
+
@remote_site_dir = "."
|
61
|
+
|
62
|
+
@need_tar = true
|
63
|
+
@need_zip = true
|
64
|
+
|
65
|
+
@spec = Gem::Specification.new
|
66
|
+
|
67
|
+
yield self if block_given?
|
68
|
+
|
69
|
+
# update rdoc options to take care of the rdoc_main if it is
|
70
|
+
# there, and add a default title if one is not given
|
71
|
+
if not @spec.rdoc_options.include?("--main") then
|
72
|
+
@spec.rdoc_options.concat(["--main", rdoc_main])
|
73
|
+
end
|
74
|
+
|
75
|
+
if not @spec.rdoc_options.include?("--title") then
|
76
|
+
@spec.rdoc_options.concat(["--title","'#{name} -- #{summary}'"])
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
# if this gets set then it overwrites what would be the
|
81
|
+
# rubyforge default. If rubyforge project is not set then use
|
82
|
+
# name. If rubyforge project and name are set, but they are
|
83
|
+
# different then assume that name is a subproject of the
|
84
|
+
# rubyforge project
|
85
|
+
def remote_root
|
86
|
+
if rubyforge_project.nil? or
|
87
|
+
rubyforge_project == name then
|
88
|
+
return RUBYFORGE_ROOT + "#{name}/"
|
89
|
+
else
|
90
|
+
return RUBYFORGE_ROOT + "#{rubyforge_project}/#{name}/"
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
# rdoc files is the same as what would be generated during gem
|
95
|
+
# installation. That is, everything in the require paths plus
|
96
|
+
# the rdoc_extra_files
|
97
|
+
#
|
98
|
+
def rdoc_files
|
99
|
+
flist = extra_rdoc_files.dup
|
100
|
+
@spec.require_paths.each do |rp|
|
101
|
+
flist << FileList["#{rp}/**/*.rb"]
|
102
|
+
end
|
103
|
+
flist.flatten.uniq
|
104
|
+
end
|
105
|
+
|
106
|
+
# calculate the remote directories
|
107
|
+
def remote_root_location
|
108
|
+
"#{remote_user}@#{remote_host}:#{remote_root}"
|
109
|
+
end
|
110
|
+
|
111
|
+
def remote_rdoc_location
|
112
|
+
remote_root_location + @remote_rdoc_dir
|
113
|
+
end
|
114
|
+
|
115
|
+
def remote_coverage_location
|
116
|
+
remote_root_loation + @remote_coverage_dir
|
117
|
+
end
|
118
|
+
|
119
|
+
def remote_site_location
|
120
|
+
remote_root_location + @remote_site_dir
|
121
|
+
end
|
122
|
+
|
123
|
+
# we delegate any other calls to spec
|
124
|
+
def method_missing(method_id,*params,&block)
|
125
|
+
@spec.send method_id, *params, &block
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
data/lib/htauth.rb
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
module HTAuth
|
2
|
+
|
3
|
+
ROOT_DIR = ::File.expand_path(::File.join(::File.dirname(__FILE__),".."))
|
4
|
+
LIB_DIR = ::File.join(ROOT_DIR,"lib").freeze
|
5
|
+
|
6
|
+
#
|
7
|
+
# Utility method to require all files ending in .rb in the directory
|
8
|
+
# with the same name as this file minus .rb
|
9
|
+
#
|
10
|
+
def require_all_libs_relative_to(fname)
|
11
|
+
prepend = ::File.basename(fname,".rb")
|
12
|
+
search_me = ::File.join(::File.dirname(fname),prepend)
|
13
|
+
|
14
|
+
Dir.entries(search_me).each do |rb|
|
15
|
+
if ::File.extname(rb) == ".rb" then
|
16
|
+
require "#{prepend}/#{::File.basename(rb,".rb")}"
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
module_function :require_all_libs_relative_to
|
21
|
+
|
22
|
+
class FileAccessError < StandardError ; end
|
23
|
+
class TempFileError < StandardError ; end
|
24
|
+
class PasswordError < StandardError ; end
|
25
|
+
end
|
26
|
+
|
27
|
+
HTAuth.require_all_libs_relative_to(__FILE__)
|
data/spec/crypt_spec.rb
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
|
2
|
+
require File.join(File.dirname(__FILE__),"spec_helper.rb")
|
3
|
+
|
4
|
+
require 'htauth/crypt'
|
5
|
+
|
6
|
+
describe HTAuth::Crypt do
|
7
|
+
it "has a prefix" do
|
8
|
+
HTAuth::Crypt.new.prefix.should == ""
|
9
|
+
end
|
10
|
+
|
11
|
+
it "encrypts the same way that apache does" do
|
12
|
+
apache_salt = "L0LDd/.."
|
13
|
+
apache_result = "L0ekWYm59LT1M"
|
14
|
+
crypt = HTAuth::Crypt.new({ :salt => apache_salt} )
|
15
|
+
crypt.encode("a secret").should == apache_result
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|