htauth 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
|