maiha-htpasswd 0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/README +117 -0
- data/Rakefile +22 -0
- data/init.rb +15 -0
- data/install.rb +1 -0
- data/lib/htpasswd/acls/active_record.rb +25 -0
- data/lib/htpasswd/acls/base.rb +76 -0
- data/lib/htpasswd/acls/crypted.rb +11 -0
- data/lib/htpasswd/acls/digest.rb +10 -0
- data/lib/htpasswd/acls/htdigest.rb +17 -0
- data/lib/htpasswd/acls/htpasswd.rb +17 -0
- data/lib/htpasswd/acls/plain.rb +11 -0
- data/lib/htpasswd/auths/base.rb +108 -0
- data/lib/htpasswd/auths/basic.rb +21 -0
- data/lib/htpasswd/auths/digest.rb +152 -0
- data/lib/htpasswd/class_methods.rb +80 -0
- data/tasks/htpasswd_tasks.rake +4 -0
- data/test/digest_test.rb +38 -0
- data/test/fixtures/htdigest.berryz +2 -0
- data/test/fixtures/htdigest.cute +3 -0
- data/test/fixtures/htpasswd.berryz +2 -0
- data/test/fixtures/htpasswd.cute +2 -0
- data/test/htdigest_test.rb +211 -0
- data/test/htpasswd_test.rb +210 -0
- data/test/test_helper.rb +25 -0
- metadata +76 -0
data/README
ADDED
@@ -0,0 +1,117 @@
|
|
1
|
+
Htpasswd
|
2
|
+
========
|
3
|
+
This plugin allows controllers to use HTTP Basic and Digest access authentication.
|
4
|
+
You can specify user passwords like this.
|
5
|
+
|
6
|
+
* inline plain password
|
7
|
+
* inline crypted password
|
8
|
+
* external password file
|
9
|
+
|
10
|
+
|
11
|
+
Syntax
|
12
|
+
======
|
13
|
+
|
14
|
+
htpasswd *options*
|
15
|
+
htdigest *options*
|
16
|
+
|
17
|
+
|
18
|
+
Options
|
19
|
+
=======
|
20
|
+
|
21
|
+
* user : user name # default: nil
|
22
|
+
* pass : password (format depends on :type) # default: nil
|
23
|
+
* type : one of ( :plain | :crypted ) # default: :plain
|
24
|
+
* file : external file path # default: nil
|
25
|
+
* realm : realm value # default: "Authorization"
|
26
|
+
* class : specify ActiveRecord class for account # default: nil
|
27
|
+
* scheme : auth scheme # default: automatically set by method name
|
28
|
+
|
29
|
+
|
30
|
+
Usage
|
31
|
+
=====
|
32
|
+
|
33
|
+
(1) Basic Access Authentication
|
34
|
+
|
35
|
+
class AdminController < ApplicationController
|
36
|
+
htpasswd :user=>"maiha", :pass=>"berryz"
|
37
|
+
htpasswd :user=>"maiha", :pass=>"7Et1Y7tCawx32", :type=>:crypted
|
38
|
+
htpasswd :user=>"maiha", :pass=>"berryz", :realm=>"Member Only"
|
39
|
+
htpasswd :file=>"/usr/local/apache/passwd/.htpasswd"
|
40
|
+
htpasswd :class=>"Account" # authorize user with Account#username and Account#password
|
41
|
+
htpasswd :class=>"Account", :user=>"login", :pass=>"secret" # use "login" and "secret" columns
|
42
|
+
end
|
43
|
+
|
44
|
+
|
45
|
+
(2) Digest Access Authentication
|
46
|
+
|
47
|
+
class AdminController < ApplicationController
|
48
|
+
htdigest :user=>"maiha", :pass=>"berryz"
|
49
|
+
htdigest :user=>"maiha", :pass=>"812b1d067e9ce1e44f09215339e3cd69", :type=>:crypted
|
50
|
+
htdigest :file=>"/usr/local/apache/passwd/.htdigest"
|
51
|
+
htdigest :class=>"Account" # Account#password should be realm-considered value.
|
52
|
+
end
|
53
|
+
|
54
|
+
|
55
|
+
(3) Multiple Access Authentications
|
56
|
+
|
57
|
+
class AdminController < ApplicationController
|
58
|
+
htpasswd :user=>"maiha", :pass=>"berryz"
|
59
|
+
htdigest :user=>"airi" , :pass=>"cute"
|
60
|
+
end
|
61
|
+
|
62
|
+
Although user 'maiha' is authorized by Basic auth,
|
63
|
+
user 'airi' is authorized by Digest auth in this case.
|
64
|
+
And this controller returns Digest one as a 401 response
|
65
|
+
because it is strongest auth-scheme in above schemes.
|
66
|
+
|
67
|
+
|
68
|
+
(4) Authorized User Name
|
69
|
+
|
70
|
+
class AdminController < ApplicationController
|
71
|
+
htpasswd :user=>"maiha", :pass=>"berryz"
|
72
|
+
def index
|
73
|
+
render :text=>"current_user: #{@htpasswd_authorized_username}"
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
Authorized user name is set in @htpasswd_authorized_username.
|
78
|
+
|
79
|
+
|
80
|
+
(0) Creating a htdigest file
|
81
|
+
|
82
|
+
>> Htpasswd::Auths::Digest.new(:user=>"maiha", :pass=>"berryz").entry
|
83
|
+
=> "maiha:Authorization:812b1d067e9ce1e44f09215339e3cd69"
|
84
|
+
|
85
|
+
This acts same as following unix command.
|
86
|
+
|
87
|
+
% htdigest -c filename maiha
|
88
|
+
|
89
|
+
|
90
|
+
Restrictions
|
91
|
+
============
|
92
|
+
|
93
|
+
* 'realm' value should not contain any commas and semicolons.
|
94
|
+
|
95
|
+
|
96
|
+
Rails
|
97
|
+
=====
|
98
|
+
|
99
|
+
1.2 : OK
|
100
|
+
2.1 : OK
|
101
|
+
2.2 : OK
|
102
|
+
|
103
|
+
|
104
|
+
Test
|
105
|
+
====
|
106
|
+
|
107
|
+
Just type.
|
108
|
+
|
109
|
+
% ruby vendor/plugins/htpasswd/test/htpasswd_test.rb
|
110
|
+
% ruby vendor/plugins/htpasswd/test/htdigest_test.rb
|
111
|
+
|
112
|
+
|
113
|
+
Author
|
114
|
+
======
|
115
|
+
The original author is Kawamura.
|
116
|
+
Composed by maiha@wota.jp
|
117
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'rake'
|
2
|
+
require 'rake/testtask'
|
3
|
+
require 'rake/rdoctask'
|
4
|
+
|
5
|
+
desc 'Default: run unit tests.'
|
6
|
+
task :default => :test
|
7
|
+
|
8
|
+
desc 'Test the htpasswd plugin.'
|
9
|
+
Rake::TestTask.new(:test) do |t|
|
10
|
+
t.libs << 'lib'
|
11
|
+
t.pattern = 'test/**/*_test.rb'
|
12
|
+
t.verbose = true
|
13
|
+
end
|
14
|
+
|
15
|
+
desc 'Generate documentation for the htpasswd plugin.'
|
16
|
+
Rake::RDocTask.new(:rdoc) do |rdoc|
|
17
|
+
rdoc.rdoc_dir = 'rdoc'
|
18
|
+
rdoc.title = 'Htpasswd'
|
19
|
+
rdoc.options << '--line-numbers' << '--inline-source'
|
20
|
+
rdoc.rdoc_files.include('README')
|
21
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
22
|
+
end
|
data/init.rb
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'htpasswd/class_methods'
|
2
|
+
|
3
|
+
require 'htpasswd/acls/base'
|
4
|
+
require 'htpasswd/acls/plain'
|
5
|
+
require 'htpasswd/acls/crypted'
|
6
|
+
require 'htpasswd/acls/digest'
|
7
|
+
require 'htpasswd/acls/htpasswd'
|
8
|
+
require 'htpasswd/acls/htdigest'
|
9
|
+
require 'htpasswd/acls/active_record'
|
10
|
+
|
11
|
+
require 'htpasswd/auths/base'
|
12
|
+
require 'htpasswd/auths/basic'
|
13
|
+
require 'htpasswd/auths/digest'
|
14
|
+
|
15
|
+
ActionController::Base.send(:include, Htpasswd)
|
data/install.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
# Install hook code here
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Htpasswd
|
2
|
+
module Acls
|
3
|
+
class ActiveRecord < Base
|
4
|
+
def authorize_user(scheme)
|
5
|
+
active_record.send("find_by_%s" % (@options[:user] || :username), scheme.user)
|
6
|
+
end
|
7
|
+
|
8
|
+
def authorize_pass(scheme)
|
9
|
+
obj = active_record.send("find_by_%s" % (@options[:user] || :username), scheme.user)
|
10
|
+
scheme.authorize_pass(obj[@options[:pass] || :password])
|
11
|
+
rescue
|
12
|
+
raise IncorrectPassword
|
13
|
+
end
|
14
|
+
|
15
|
+
protected
|
16
|
+
def active_record
|
17
|
+
@active_record ||= @options[:class].to_s.classify.constantize
|
18
|
+
rescue
|
19
|
+
raise ConfigurationError, "invalid active_record class name: '%s'" % @options[:class].to_s
|
20
|
+
end
|
21
|
+
|
22
|
+
register self
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
module Htpasswd
|
2
|
+
module Acls
|
3
|
+
class Base
|
4
|
+
delegate :logger, :to=>"ActionController::Base"
|
5
|
+
|
6
|
+
@@entries = {}
|
7
|
+
|
8
|
+
class << self
|
9
|
+
def [](name)
|
10
|
+
name = name.to_s.classify
|
11
|
+
@@entries[name] or raise UnknownAccessControl, name
|
12
|
+
end
|
13
|
+
|
14
|
+
def register(klass)
|
15
|
+
@@entries[klass.name.demodulize.classify] = klass
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def initialize(options)
|
20
|
+
@options = options
|
21
|
+
end
|
22
|
+
|
23
|
+
def user
|
24
|
+
@options[:user]
|
25
|
+
end
|
26
|
+
|
27
|
+
def pass
|
28
|
+
@options[:pass]
|
29
|
+
end
|
30
|
+
|
31
|
+
def type
|
32
|
+
@options[:type]
|
33
|
+
end
|
34
|
+
|
35
|
+
def authorized?(scheme)
|
36
|
+
authorize_type(scheme) && authorize_user(scheme) && authorize_pass(scheme) && scheme.user
|
37
|
+
end
|
38
|
+
|
39
|
+
def authorize_type(scheme)
|
40
|
+
scheme.authorize_type(type)
|
41
|
+
end
|
42
|
+
|
43
|
+
def authorize_user(scheme)
|
44
|
+
scheme.authorize_user(user)
|
45
|
+
end
|
46
|
+
|
47
|
+
def authorize_pass(scheme)
|
48
|
+
scheme.authorize_pass(pass)
|
49
|
+
end
|
50
|
+
|
51
|
+
register self
|
52
|
+
end
|
53
|
+
|
54
|
+
class CompositeBase < Base
|
55
|
+
delegate :each, :<<, :to=>"@entries"
|
56
|
+
|
57
|
+
def initialize(*args)
|
58
|
+
@entries = []
|
59
|
+
initialize_composite(*args)
|
60
|
+
end
|
61
|
+
|
62
|
+
def initialize_composite(*args)
|
63
|
+
raise NotImplementedError
|
64
|
+
end
|
65
|
+
|
66
|
+
def authorized?(scheme)
|
67
|
+
each do |entry|
|
68
|
+
if user = entry.authorized?(scheme)
|
69
|
+
return user
|
70
|
+
end
|
71
|
+
end
|
72
|
+
return false
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Htpasswd
|
2
|
+
module Acls
|
3
|
+
class Htdigest < CompositeBase
|
4
|
+
def initialize_composite(options)
|
5
|
+
IO.foreach(options[:file]) do |line|
|
6
|
+
user, realm, pass = line.chomp.split(':', 3)
|
7
|
+
self << Acls::Digest.new(:user=>user, :realm=>realm, :pass=>pass)
|
8
|
+
end
|
9
|
+
rescue => err
|
10
|
+
logger.debug("%s: [%s]%s" % [self.class, err.class, err])
|
11
|
+
raise ConfigurationError, "Cannot read password file. '#{options[:file]}'"
|
12
|
+
end
|
13
|
+
|
14
|
+
register self
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Htpasswd
|
2
|
+
module Acls
|
3
|
+
class Htpasswd < CompositeBase
|
4
|
+
def initialize_composite(options)
|
5
|
+
IO.foreach(options[:file]) do |line|
|
6
|
+
user, pass = line.chomp.split(':', 2)
|
7
|
+
self << Crypted.new(:type=>:crypted, :user=>user, :pass=>pass)
|
8
|
+
end
|
9
|
+
rescue => err
|
10
|
+
logger.debug("%s: [%s]%s" % [self.class, err.class, err])
|
11
|
+
raise ConfigurationError, "Cannot read password file. '#{options[:file]}'"
|
12
|
+
end
|
13
|
+
|
14
|
+
register self
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,108 @@
|
|
1
|
+
module Htpasswd
|
2
|
+
module Auths
|
3
|
+
@@schemas = {}
|
4
|
+
|
5
|
+
module_function
|
6
|
+
def [](auth_scheme)
|
7
|
+
@@schemas[auth_scheme.to_s.classify]
|
8
|
+
end
|
9
|
+
|
10
|
+
def scheme(controller)
|
11
|
+
case controller
|
12
|
+
when ActionController::Base
|
13
|
+
returning authorization = instantiate(extract_header(controller.request.env)) do
|
14
|
+
authorization.set_controller(controller)
|
15
|
+
end
|
16
|
+
else
|
17
|
+
instantiate(controller.to_s)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def instantiate(header)
|
22
|
+
raise HeaderNotFound if header.blank?
|
23
|
+
ActionController::Base.logger.debug "Htpasswd accepts authorization header: '#{header}'"
|
24
|
+
type, data = header.to_s.split(' ', 2)
|
25
|
+
klass = self[type] or raise UnknownSchemeError, type
|
26
|
+
klass.parse(data)
|
27
|
+
end
|
28
|
+
|
29
|
+
def extract_header(hash)
|
30
|
+
[
|
31
|
+
'X-HTTP_AUTHORIZATION', # for Apache/mod_rewrite
|
32
|
+
'REDIRECT_X_HTTP_AUTHORIZATION', # for Apache2/mod_rewrite
|
33
|
+
'Authorization', # for Apace/mod_fastcgi with -pass-header Authorization
|
34
|
+
'HTTP_AUTHORIZATION', # this is the regular location
|
35
|
+
].map{|name| hash[name]}.compact.first
|
36
|
+
end
|
37
|
+
|
38
|
+
def default_realm
|
39
|
+
"Authorization"
|
40
|
+
end
|
41
|
+
|
42
|
+
class Base
|
43
|
+
delegate :logger, :to=>"ActionController::Base"
|
44
|
+
|
45
|
+
class << self
|
46
|
+
def parse(data)
|
47
|
+
raise NotImplementedError, "subclass responsibility"
|
48
|
+
end
|
49
|
+
|
50
|
+
def <=> (other)
|
51
|
+
strength <=> other.strength
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
attr_accessor :options
|
56
|
+
def initialize(options = {})
|
57
|
+
@options = options.with_indifferent_access
|
58
|
+
end
|
59
|
+
|
60
|
+
# return authorized username or raise exception
|
61
|
+
def authorize(entries)
|
62
|
+
entries.each do |entry|
|
63
|
+
if user = entry.authorized?(self)
|
64
|
+
return user
|
65
|
+
end
|
66
|
+
end
|
67
|
+
raise UnknownUserAccount
|
68
|
+
end
|
69
|
+
|
70
|
+
def authorize_type(type)
|
71
|
+
true
|
72
|
+
end
|
73
|
+
|
74
|
+
def authorize_user(user)
|
75
|
+
options[:user] == user
|
76
|
+
end
|
77
|
+
|
78
|
+
def authorize_pass(pass)
|
79
|
+
options[:pass] == pass or raise IncorrectPassword
|
80
|
+
end
|
81
|
+
|
82
|
+
def set_controller(controller)
|
83
|
+
# nop
|
84
|
+
end
|
85
|
+
|
86
|
+
def realm
|
87
|
+
options[:realm] || Auths.default_realm
|
88
|
+
end
|
89
|
+
|
90
|
+
def scheme
|
91
|
+
self.class.name.demodulize
|
92
|
+
end
|
93
|
+
|
94
|
+
def user
|
95
|
+
options[:user]
|
96
|
+
end
|
97
|
+
|
98
|
+
def pass
|
99
|
+
options[:pass]
|
100
|
+
end
|
101
|
+
|
102
|
+
def random(size = 16, array = nil)
|
103
|
+
array ||= (0..9).to_a + ('a'..'f').to_a
|
104
|
+
(0...size).map{array[rand(array.size)]}.join
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Htpasswd
|
2
|
+
module Auths
|
3
|
+
class Basic < Base
|
4
|
+
class << self
|
5
|
+
def parse(data)
|
6
|
+
user, pass = Base64.decode64(data.to_s).split(':', 2)
|
7
|
+
new(:user=>user, :pass=>pass)
|
8
|
+
end
|
9
|
+
|
10
|
+
def strength
|
11
|
+
10
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def server_header
|
16
|
+
%Q|Basic realm="%s"| % realm
|
17
|
+
end
|
18
|
+
end
|
19
|
+
@@schemas["Basic"] = Basic
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,152 @@
|
|
1
|
+
module Htpasswd
|
2
|
+
module Auths
|
3
|
+
class Digest < Base
|
4
|
+
class << self
|
5
|
+
PARAMETER_VALID_KEY_MAPPINGS = {
|
6
|
+
:username => :user,
|
7
|
+
:realm => :realm,
|
8
|
+
:qop => :qop,
|
9
|
+
:algorithm => :algorithm,
|
10
|
+
:uri => :uri,
|
11
|
+
:nonce => :nonce,
|
12
|
+
:nc => :nc,
|
13
|
+
:cnonce => :cnonce,
|
14
|
+
:response => :response,
|
15
|
+
}
|
16
|
+
|
17
|
+
def parse(data, forced_options = {})
|
18
|
+
options = HashWithIndifferentAccess.new
|
19
|
+
data.to_s.split(/,\s*/).each do |query|
|
20
|
+
key, val = query.split('=', 2)
|
21
|
+
if valid_key_name = PARAMETER_VALID_KEY_MAPPINGS[key.to_s.intern]
|
22
|
+
options[valid_key_name] = val ? val.to_s.delete('"') : nil
|
23
|
+
# ActionController::Base.logger.debug("parse: %s => %s" % [valid_key_name,options[valid_key_name]])
|
24
|
+
end
|
25
|
+
end
|
26
|
+
new(options.merge(forced_options))
|
27
|
+
end
|
28
|
+
|
29
|
+
def strength
|
30
|
+
100
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def set_controller(controller)
|
35
|
+
options[:uri] = controller.request.path
|
36
|
+
options[:method] = controller.request.method.to_s.upcase
|
37
|
+
end
|
38
|
+
|
39
|
+
def nonce
|
40
|
+
value = options[:nonce]
|
41
|
+
unless value
|
42
|
+
time = Time.now.to_i
|
43
|
+
etag = options[:session_key] || random(16)
|
44
|
+
pkey = options[:private_key] || random(16)
|
45
|
+
value = Base64.encode64("%s:%s:%s" % [time, etag, pkey]).chomp
|
46
|
+
end
|
47
|
+
return value
|
48
|
+
end
|
49
|
+
|
50
|
+
def algorithm
|
51
|
+
options[:algorithm] ||= "MD5"
|
52
|
+
end
|
53
|
+
|
54
|
+
def qop
|
55
|
+
options[:qop] ||= "auth"
|
56
|
+
end
|
57
|
+
|
58
|
+
def uri
|
59
|
+
options[:uri] ||= '/'
|
60
|
+
end
|
61
|
+
|
62
|
+
def nc
|
63
|
+
options[:nc] ||= '00000001'
|
64
|
+
end
|
65
|
+
|
66
|
+
def cnonce
|
67
|
+
options[:cnonce] ||= random(32)
|
68
|
+
end
|
69
|
+
|
70
|
+
def method
|
71
|
+
options[:method] ||= "GET"
|
72
|
+
end
|
73
|
+
|
74
|
+
def response
|
75
|
+
options[:response]
|
76
|
+
end
|
77
|
+
|
78
|
+
def digest_algorithm
|
79
|
+
klass_name = algorithm.to_s.upcase
|
80
|
+
if klass_name == "SHA1"
|
81
|
+
::Digest::SHA1
|
82
|
+
else
|
83
|
+
::Digest::MD5
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
# A1 = unq(username-value) ":" unq(realm-value) ":" passwd
|
88
|
+
def a1
|
89
|
+
digest_algorithm.hexdigest([user, realm, pass] * ":")
|
90
|
+
end
|
91
|
+
|
92
|
+
# A2 = Method ":" digest-uri-value
|
93
|
+
def a2
|
94
|
+
# debug_digest("a2", %w( method uri))
|
95
|
+
digest_algorithm.hexdigest([method, uri] * ":")
|
96
|
+
end
|
97
|
+
|
98
|
+
#request-digest = <"> < KD ( H(A1), unq(nonce-value)
|
99
|
+
# ":" nc-value
|
100
|
+
# ":" unq(cnonce-value)
|
101
|
+
# ":" unq(qop-value)
|
102
|
+
# ":" H(A2)
|
103
|
+
# ) <">
|
104
|
+
def request_digest
|
105
|
+
# debug_digest("request_digest", %w( a1 nonce nc cnonce qop a2 ))
|
106
|
+
digest_algorithm.hexdigest([a1, nonce, nc, cnonce, qop, a2] * ":")
|
107
|
+
end
|
108
|
+
|
109
|
+
def response_digest(htdigest)
|
110
|
+
# debug_digest("response_digest", %w( htdigest nonce nc cnonce qop a2 ), :htdigest=>htdigest)
|
111
|
+
digest_algorithm.hexdigest([htdigest, nonce, nc, cnonce, qop, a2] * ":")
|
112
|
+
end
|
113
|
+
|
114
|
+
def server_header
|
115
|
+
%Q|Digest realm="%s", nonce="%s", algorithm=%s, qop=%s| % [realm, nonce, algorithm, qop]
|
116
|
+
end
|
117
|
+
|
118
|
+
def client_header
|
119
|
+
%Q|Digest username="%s", realm="%s", qop=%s, algorithm=%s, uri="%s", nonce="%s", nc=%s, cnonce="%s", response="%s"| %
|
120
|
+
[user, realm, qop, algorithm, uri, nonce, nc, cnonce, request_digest]
|
121
|
+
end
|
122
|
+
|
123
|
+
######################################################################
|
124
|
+
### Debug
|
125
|
+
def debug_digest(name, element_names, values = {})
|
126
|
+
sources = []
|
127
|
+
max = element_names.map(&:size).max
|
128
|
+
element_names.each do |method|
|
129
|
+
value = values[method.intern] || self.send(method)
|
130
|
+
logger.debug("[DIGEST] %s#%-*s : %s" % [name, max, method, value])
|
131
|
+
sources << value
|
132
|
+
end
|
133
|
+
digest = digest_algorithm.hexdigest(sources * ':')
|
134
|
+
logger.debug("[DIGEST] %s => : %s" % [name, digest])
|
135
|
+
return digest
|
136
|
+
end
|
137
|
+
|
138
|
+
######################################################################
|
139
|
+
### Aliases
|
140
|
+
def entry
|
141
|
+
[user, realm, a1] * ':'
|
142
|
+
end
|
143
|
+
|
144
|
+
######################################################################
|
145
|
+
### Authorization
|
146
|
+
def authorize_pass(pass)
|
147
|
+
response_digest(pass) == response or raise IncorrectPassword
|
148
|
+
end
|
149
|
+
end
|
150
|
+
@@schemas["Digest"] = Digest
|
151
|
+
end
|
152
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
module Htpasswd
|
2
|
+
def self.included(base)
|
3
|
+
base.extend(ClassMethods)
|
4
|
+
end
|
5
|
+
|
6
|
+
class Error < StandardError; end
|
7
|
+
class HeaderNotFound < Error; end
|
8
|
+
class UnknownSchemeError < Error; end
|
9
|
+
class NotAuthorizedError < Error; end
|
10
|
+
class ConfigurationError < Error; end
|
11
|
+
|
12
|
+
class UnknownAccessControl < ConfigurationError; end
|
13
|
+
class AuthSchemesNotDefined < ConfigurationError; end
|
14
|
+
class IncorrectPassword < NotAuthorizedError; end
|
15
|
+
class UnknownUserAccount < NotAuthorizedError; end
|
16
|
+
|
17
|
+
module ClassMethods
|
18
|
+
def htpasswd(options={})
|
19
|
+
options = options.dup
|
20
|
+
options[:user] ||= options[:username]
|
21
|
+
options[:pass] ||= options[:password]
|
22
|
+
options[:acl] ||= options[:type]
|
23
|
+
|
24
|
+
if options[:exclusive]
|
25
|
+
write_inheritable_array(:htpasswd_acls, [])
|
26
|
+
write_inheritable_hash(:htpasswd_options, {})
|
27
|
+
end
|
28
|
+
|
29
|
+
(htpasswd_options[:schemes] ||= Set.new) << (options[:scheme] || :basic)
|
30
|
+
|
31
|
+
options[:acl] ||= (options[:class] && :active_record)
|
32
|
+
options[:acl] ||= (options[:file] && :htpasswd)
|
33
|
+
options[:acl] ||= (options[:user] && options[:pass] && :plain)
|
34
|
+
|
35
|
+
htpasswd_acls << Acls::Base[options[:acl]].new(options) if options[:acl]
|
36
|
+
htpasswd_options.merge!(:realm=>options[:realm]) if options[:realm]
|
37
|
+
|
38
|
+
skip_before_filter :htpasswd_authorize rescue nil
|
39
|
+
delegate :htpasswd_options, :htpasswd_acls, :to=>"self.class" unless instance_methods.include?("htpasswd_options")
|
40
|
+
before_filter :htpasswd_authorize
|
41
|
+
end
|
42
|
+
|
43
|
+
def htdigest(options={})
|
44
|
+
options = options.dup
|
45
|
+
if options[:user] && options[:pass] && (options[:acl] || options[:type]) != :crypted && options[:class].blank?
|
46
|
+
options[:pass] = Auths::Digest.new(options).a1
|
47
|
+
end
|
48
|
+
options[:scheme] = :digest
|
49
|
+
options[:acl] ||= (options[:class] && :active_record)
|
50
|
+
options[:acl] ||= (options[:file] ? :htdigest : :digest)
|
51
|
+
|
52
|
+
htpasswd(options)
|
53
|
+
end
|
54
|
+
|
55
|
+
def htpasswd_options
|
56
|
+
read_inheritable_attribute(:htpasswd_options) or write_inheritable_hash(:htpasswd_options, {})
|
57
|
+
end
|
58
|
+
|
59
|
+
def htpasswd_acls
|
60
|
+
read_inheritable_attribute(:htpasswd_acls) or write_inheritable_array(:htpasswd_acls, [])
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
protected
|
65
|
+
def htpasswd_authorize
|
66
|
+
logger.debug "Htpasswd is enabled with %s" % htpasswd_options.inspect
|
67
|
+
username = Auths.scheme(self).authorize(htpasswd_acls)
|
68
|
+
logger.debug "Htpasswd authorize user '%s'" % username
|
69
|
+
@htpasswd_authorized_username = username
|
70
|
+
return true
|
71
|
+
rescue Htpasswd::Error => error
|
72
|
+
logger.debug "Htpasswd error(%s): %s" % [error.class, error.message]
|
73
|
+
strongest_auth = htpasswd_options[:schemes].map{|scheme| Auths[scheme]}.sort.last or raise AuthSchemesNotDefined
|
74
|
+
response.headers['WWW-Authenticate'] = strongest_auth.new(htpasswd_options).server_header
|
75
|
+
logger.debug "Htpasswd sending authenticate header: '%s'"% response.headers['WWW-Authenticate']
|
76
|
+
|
77
|
+
render :nothing => true, :status => 401
|
78
|
+
return false
|
79
|
+
end
|
80
|
+
end
|
data/test/digest_test.rb
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/test_helper'
|
2
|
+
|
3
|
+
class DigestTestCase < Test::Unit::TestCase
|
4
|
+
def test_calculate_request_digest
|
5
|
+
options = {
|
6
|
+
:username => "maiha",
|
7
|
+
:password => "berryz",
|
8
|
+
:realm => "Authorization",
|
9
|
+
:nonce => "MTE0ODYxODUxNDozNGJhZDVhMzQ2YjBiM2QzOmQwMTVhYjdlNGJkZjkzMjk=",
|
10
|
+
:uri => "/test",
|
11
|
+
:algorithm => "MD5",
|
12
|
+
:cnonce => "cnonce",
|
13
|
+
:qop => "auth",
|
14
|
+
:nc => "00000001",
|
15
|
+
}
|
16
|
+
expected = "4d7849b4faab53fd3eff731c24cc24d1"
|
17
|
+
digest = Htpasswd::Authorization::Digest.new(options)
|
18
|
+
assert_equal expected, digest.request_digest
|
19
|
+
end
|
20
|
+
|
21
|
+
def test_calculate_response_digest
|
22
|
+
options = {
|
23
|
+
:username => "maiha",
|
24
|
+
:password => "berryz",
|
25
|
+
:realm => "Authorization",
|
26
|
+
:nonce => "MTE0ODYxODUxNDozNGJhZDVhMzQ2YjBiM2QzOmQwMTVhYjdlNGJkZjkzMjk=",
|
27
|
+
:uri => "/test",
|
28
|
+
:algorithm => "MD5",
|
29
|
+
:cnonce => "cnonce",
|
30
|
+
:qop => "auth",
|
31
|
+
:nc => "00000001",
|
32
|
+
}
|
33
|
+
expected = "4d7849b4faab53fd3eff731c24cc24d1"
|
34
|
+
htdigest = "812b1d067e9ce1e44f09215339e3cd69"
|
35
|
+
digest = Htpasswd::Authorization::Digest.new(options)
|
36
|
+
assert_equal expected, digest.response_digest(htdigest)
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,211 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/test_helper'
|
2
|
+
|
3
|
+
class TestController < ActionController::Base
|
4
|
+
def self.external_password_path(file)
|
5
|
+
File.dirname(__FILE__) + '/fixtures/' + file
|
6
|
+
end
|
7
|
+
|
8
|
+
def index
|
9
|
+
render :nothing=>true
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
class HtdigestTestCase < Test::Unit::TestCase
|
14
|
+
class Controller < TestController; end
|
15
|
+
|
16
|
+
def setup
|
17
|
+
@request = ActionController::TestRequest.new
|
18
|
+
@response = ActionController::TestResponse.new
|
19
|
+
@controller = self.class::Controller.new
|
20
|
+
end
|
21
|
+
|
22
|
+
def digest_authorization(username, password, method = :get)
|
23
|
+
send(method, :index)
|
24
|
+
digest = Htpasswd::Auths::Digest.parse(@response.headers['WWW-Authenticate'])
|
25
|
+
digest.set_controller(@controller)
|
26
|
+
digest.options[:user] = username
|
27
|
+
digest.options[:pass] = password
|
28
|
+
|
29
|
+
setup
|
30
|
+
@request.env["HTTP_AUTHORIZATION"] = digest.client_header
|
31
|
+
end
|
32
|
+
|
33
|
+
def test_dummy
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
|
38
|
+
class InlineEntryTest < HtdigestTestCase
|
39
|
+
class Controller < TestController
|
40
|
+
htdigest :user=>"maiha", :pass=>"berryz", :realm=>"Authorization"
|
41
|
+
end
|
42
|
+
|
43
|
+
def test_content_should_be_authorized
|
44
|
+
get :index
|
45
|
+
assert_response 401
|
46
|
+
assert /\ADigest / === @response.headers["WWW-Authenticate"].to_s
|
47
|
+
end
|
48
|
+
|
49
|
+
def test_authorize_valid_request
|
50
|
+
digest_authorization('maiha', 'berryz')
|
51
|
+
get :index
|
52
|
+
assert_response :success
|
53
|
+
end
|
54
|
+
|
55
|
+
def test_authorize_valid_request
|
56
|
+
digest_authorization('maiha', 'berryz')
|
57
|
+
get :index
|
58
|
+
assert_response :success
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
|
63
|
+
class InlineMultiEntriesTest < HtdigestTestCase
|
64
|
+
class Controller < TestController
|
65
|
+
htdigest :user=>"maiha", :pass=>"berryz"
|
66
|
+
htdigest :user=>"airi", :pass=>"cute"
|
67
|
+
end
|
68
|
+
|
69
|
+
def test_content_should_be_authorized
|
70
|
+
get :index
|
71
|
+
assert_response 401
|
72
|
+
end
|
73
|
+
|
74
|
+
def test_authorize_valid_request_written_in_first_entry
|
75
|
+
digest_authorization('maiha', 'berryz')
|
76
|
+
get :index
|
77
|
+
assert_response :success
|
78
|
+
end
|
79
|
+
|
80
|
+
def test_authorize_valid_request_written_in_second_entry
|
81
|
+
digest_authorization('airi', 'cute')
|
82
|
+
get :index
|
83
|
+
assert_response :success
|
84
|
+
end
|
85
|
+
|
86
|
+
def test_authorize_invalid_request
|
87
|
+
digest_authorization('maiha', 'xxx')
|
88
|
+
get :index
|
89
|
+
assert_response 401
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
|
94
|
+
class InlineCryptedEntryTest < HtdigestTestCase
|
95
|
+
class Controller < TestController
|
96
|
+
htdigest :user=>"maiha", :pass=>"812b1d067e9ce1e44f09215339e3cd69", :realm=>"Authorization", :type=>:crypted
|
97
|
+
end
|
98
|
+
|
99
|
+
def test_authorize_valid_request
|
100
|
+
digest_authorization('maiha', 'berryz')
|
101
|
+
get :index
|
102
|
+
assert_response :success
|
103
|
+
end
|
104
|
+
|
105
|
+
def test_authorize_invalid_request
|
106
|
+
digest_authorization('maiha', 'xxx')
|
107
|
+
get :index
|
108
|
+
assert_response 401
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
|
113
|
+
class ExternalFileTest < HtdigestTestCase
|
114
|
+
class Controller < TestController
|
115
|
+
htdigest :file=>external_password_path('htdigest.berryz')
|
116
|
+
end
|
117
|
+
|
118
|
+
def test_content_should_be_authorized
|
119
|
+
get :index
|
120
|
+
assert_response 401
|
121
|
+
end
|
122
|
+
|
123
|
+
def test_authorize_valid_request_written_in_first_entry
|
124
|
+
digest_authorization('maiha', 'berryz')
|
125
|
+
get :index
|
126
|
+
assert_response :success
|
127
|
+
end
|
128
|
+
|
129
|
+
def test_authorize_valid_request_written_in_second_entry
|
130
|
+
digest_authorization('saki', 'berryz')
|
131
|
+
get :index
|
132
|
+
assert_response :success
|
133
|
+
end
|
134
|
+
|
135
|
+
def test_authorize_invalid_request
|
136
|
+
digest_authorization('airi', 'cute')
|
137
|
+
get :index
|
138
|
+
assert_response 401
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
|
143
|
+
class CompositeTest < HtdigestTestCase
|
144
|
+
class Controller < TestController
|
145
|
+
htdigest :user=>"risako" , :pass=>"berryz"
|
146
|
+
htdigest :user=>"yurina" , :pass=>"f278b3900164e31fcf005a79a8b2da2e", :type=>:crypted
|
147
|
+
htdigest :file=>external_password_path('htdigest.berryz')
|
148
|
+
htdigest :file=>external_password_path('htdigest.cute')
|
149
|
+
end
|
150
|
+
|
151
|
+
def test_content_should_be_authorized
|
152
|
+
get :index
|
153
|
+
assert_response 401
|
154
|
+
end
|
155
|
+
|
156
|
+
def test_authorize_inline_entry
|
157
|
+
digest_authorization('risako', 'berryz')
|
158
|
+
get :index
|
159
|
+
assert_response :success
|
160
|
+
end
|
161
|
+
|
162
|
+
def test_authorize_inline_crypted_entry
|
163
|
+
digest_authorization('yurina', 'berryz')
|
164
|
+
get :index
|
165
|
+
assert_response :success
|
166
|
+
end
|
167
|
+
|
168
|
+
def test_authorize_external_first_file
|
169
|
+
# first entry
|
170
|
+
digest_authorization('maiha', 'berryz')
|
171
|
+
get :index
|
172
|
+
assert_response :success
|
173
|
+
|
174
|
+
# second entry
|
175
|
+
setup
|
176
|
+
digest_authorization('saki', 'berryz')
|
177
|
+
get :index
|
178
|
+
assert_response :success
|
179
|
+
end
|
180
|
+
|
181
|
+
def test_authorize_external_second_file
|
182
|
+
# first entry
|
183
|
+
digest_authorization('airi', 'cute')
|
184
|
+
get :index
|
185
|
+
assert_response :success
|
186
|
+
|
187
|
+
# second entry
|
188
|
+
setup
|
189
|
+
digest_authorization('maimi', 'cute')
|
190
|
+
get :index
|
191
|
+
assert_response :success
|
192
|
+
end
|
193
|
+
|
194
|
+
def test_authorize_invalid_request
|
195
|
+
digest_authorization('maiha', 'cute')
|
196
|
+
get :index
|
197
|
+
assert_response 401
|
198
|
+
end
|
199
|
+
end
|
200
|
+
|
201
|
+
class InlineEntryTest < HtdigestTestCase
|
202
|
+
class Controller < TestController
|
203
|
+
htdigest :user=>"maiha" , :pass=>"berryz"
|
204
|
+
end
|
205
|
+
|
206
|
+
def test_authorize_with_post
|
207
|
+
digest_authorization('maiha', 'berryz', :post)
|
208
|
+
post :index
|
209
|
+
assert_response :success
|
210
|
+
end
|
211
|
+
end
|
@@ -0,0 +1,210 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/test_helper'
|
2
|
+
|
3
|
+
class TestController < ActionController::Base
|
4
|
+
def self.external_password_path(file)
|
5
|
+
File.dirname(__FILE__) + '/fixtures/' + file
|
6
|
+
end
|
7
|
+
|
8
|
+
def index
|
9
|
+
render :nothing=>true
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
class HtpasswdTestCase < Test::Unit::TestCase
|
14
|
+
class Controller < TestController; end
|
15
|
+
|
16
|
+
def setup
|
17
|
+
@request = ActionController::TestRequest.new
|
18
|
+
@response = ActionController::TestResponse.new
|
19
|
+
@controller = self.class::Controller.new
|
20
|
+
end
|
21
|
+
|
22
|
+
def basic_authorization(user, pass)
|
23
|
+
@request.env["HTTP_AUTHORIZATION"] = "Basic %s" % Base64.encode64("#{user}:#{pass}")
|
24
|
+
end
|
25
|
+
|
26
|
+
def test_dummy
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
|
31
|
+
class InlineEntryTest < HtpasswdTestCase
|
32
|
+
class Controller < TestController
|
33
|
+
htpasswd :user=>"maiha", :pass=>"berryz"
|
34
|
+
end
|
35
|
+
|
36
|
+
def test_content_should_be_authorized
|
37
|
+
get :index
|
38
|
+
assert_response 401
|
39
|
+
assert_equal 'Basic realm="Authorization"', @response.headers["WWW-Authenticate"].to_s
|
40
|
+
end
|
41
|
+
|
42
|
+
def test_authorize_valid_request
|
43
|
+
basic_authorization('maiha', 'berryz')
|
44
|
+
get :index
|
45
|
+
assert_response :success
|
46
|
+
end
|
47
|
+
|
48
|
+
def test_authorize_invalid_request
|
49
|
+
basic_authorization('maiha', 'xxx')
|
50
|
+
get :index
|
51
|
+
assert_response 401
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
|
56
|
+
class InlineMultiEntriesTest < HtpasswdTestCase
|
57
|
+
class Controller < TestController
|
58
|
+
htpasswd :user=>"maiha", :pass=>"berryz"
|
59
|
+
htpasswd :user=>"airi", :pass=>"cute"
|
60
|
+
end
|
61
|
+
|
62
|
+
def test_content_should_be_authorized
|
63
|
+
get :index
|
64
|
+
assert_response 401
|
65
|
+
end
|
66
|
+
|
67
|
+
def test_authorize_valid_request_written_in_first_entry
|
68
|
+
basic_authorization('maiha', 'berryz')
|
69
|
+
get :index
|
70
|
+
assert_response :success
|
71
|
+
end
|
72
|
+
|
73
|
+
def test_authorize_valid_request_written_in_second_entry
|
74
|
+
basic_authorization('airi', 'cute')
|
75
|
+
get :index
|
76
|
+
assert_response :success
|
77
|
+
end
|
78
|
+
|
79
|
+
def test_authorize_invalid_request
|
80
|
+
basic_authorization('maiha', 'xxx')
|
81
|
+
get :index
|
82
|
+
assert_response 401
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
|
87
|
+
class InlineCryptedEntryTest < HtpasswdTestCase
|
88
|
+
class Controller < TestController
|
89
|
+
htpasswd :user=>"maiha", :pass=>"7Et1Y7tCawx32", :type=>:crypted
|
90
|
+
end
|
91
|
+
|
92
|
+
def test_content_should_be_authorized
|
93
|
+
get :index
|
94
|
+
assert_response 401
|
95
|
+
end
|
96
|
+
|
97
|
+
def test_authorize_valid_request
|
98
|
+
basic_authorization('maiha', 'berryz')
|
99
|
+
get :index
|
100
|
+
assert_response :success
|
101
|
+
end
|
102
|
+
|
103
|
+
def test_authorize_invalid_request
|
104
|
+
basic_authorization('maiha', 'xxx')
|
105
|
+
get :index
|
106
|
+
assert_response 401
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
|
111
|
+
class ExternalFileTest < HtpasswdTestCase
|
112
|
+
class Controller < TestController
|
113
|
+
htpasswd :file=>external_password_path('htpasswd.berryz')
|
114
|
+
end
|
115
|
+
|
116
|
+
def test_content_should_be_authorized
|
117
|
+
get :index
|
118
|
+
assert_response 401
|
119
|
+
end
|
120
|
+
|
121
|
+
def test_authorize_valid_request_written_in_first_entry
|
122
|
+
basic_authorization('maiha', 'berryz')
|
123
|
+
get :index
|
124
|
+
assert_response :success
|
125
|
+
end
|
126
|
+
|
127
|
+
def test_authorize_valid_request_written_in_second_entry
|
128
|
+
basic_authorization('saki', 'berryz')
|
129
|
+
get :index
|
130
|
+
assert_response :success
|
131
|
+
end
|
132
|
+
|
133
|
+
def test_authorize_invalid_request
|
134
|
+
basic_authorization('airi', 'cute')
|
135
|
+
get :index
|
136
|
+
assert_response 401
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
|
141
|
+
class CompositeTest < HtpasswdTestCase
|
142
|
+
class Controller < TestController
|
143
|
+
htpasswd :user=>"risako" , :pass=>"berryz"
|
144
|
+
htpasswd :user=>"yurina" , :pass=>"7Et1Y7tCawx32", :type=>:crypted
|
145
|
+
htpasswd :file=>external_password_path('htpasswd.berryz')
|
146
|
+
htpasswd :file=>external_password_path('htpasswd.cute')
|
147
|
+
end
|
148
|
+
|
149
|
+
def test_content_should_be_authorized
|
150
|
+
get :index
|
151
|
+
assert_response 401
|
152
|
+
end
|
153
|
+
|
154
|
+
def test_authorize_inline_entry
|
155
|
+
basic_authorization('risako', 'berryz')
|
156
|
+
get :index
|
157
|
+
assert_response :success
|
158
|
+
end
|
159
|
+
|
160
|
+
def test_authorize_inline_crypted_entry
|
161
|
+
basic_authorization('yurina', 'berryz')
|
162
|
+
get :index
|
163
|
+
assert_response :success
|
164
|
+
end
|
165
|
+
|
166
|
+
def test_authorize_external_first_file
|
167
|
+
# first entry
|
168
|
+
basic_authorization('maiha', 'berryz')
|
169
|
+
get :index
|
170
|
+
assert_response :success
|
171
|
+
|
172
|
+
# second entry
|
173
|
+
setup
|
174
|
+
basic_authorization('saki', 'berryz')
|
175
|
+
get :index
|
176
|
+
assert_response :success
|
177
|
+
end
|
178
|
+
|
179
|
+
def test_authorize_external_second_file
|
180
|
+
# first entry
|
181
|
+
basic_authorization('airi', 'cute')
|
182
|
+
get :index
|
183
|
+
assert_response :success
|
184
|
+
|
185
|
+
# second entry
|
186
|
+
setup
|
187
|
+
basic_authorization('maimi', 'cute')
|
188
|
+
get :index
|
189
|
+
assert_response :success
|
190
|
+
end
|
191
|
+
|
192
|
+
def test_authorize_invalid_request
|
193
|
+
basic_authorization('maiha', 'cute')
|
194
|
+
get :index
|
195
|
+
assert_response 401
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
199
|
+
|
200
|
+
class RealmTest < HtpasswdTestCase
|
201
|
+
class Controller < TestController
|
202
|
+
htpasswd :user=>"maiha", :pass=>"berryz", :realm=>"Member Only"
|
203
|
+
end
|
204
|
+
|
205
|
+
def test_set_realm
|
206
|
+
get :index
|
207
|
+
assert_equal 'Basic realm="Member Only"', @response.headers["WWW-Authenticate"].to_s
|
208
|
+
end
|
209
|
+
end
|
210
|
+
|
data/test/test_helper.rb
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
def __DIR__; File.dirname(__FILE__); end
|
2
|
+
|
3
|
+
require 'test/unit'
|
4
|
+
$:.unshift(__DIR__ + '/../lib')
|
5
|
+
begin
|
6
|
+
require 'rubygems'
|
7
|
+
rescue LoadError
|
8
|
+
$:.unshift(__DIR__ + '/../../../rails/activesupport/lib')
|
9
|
+
$:.unshift(__DIR__ + '/../../../rails/actionpack/lib')
|
10
|
+
end
|
11
|
+
|
12
|
+
require 'active_support'
|
13
|
+
require 'action_controller'
|
14
|
+
require 'action_controller/test_process'
|
15
|
+
|
16
|
+
ActionController::Routing::Routes.reload rescue nil
|
17
|
+
class ActionController::Base; def rescue_action(e) raise e end; end
|
18
|
+
|
19
|
+
logfile = __DIR__ + '/debug.log'
|
20
|
+
File.unlink(logfile) if File.exists?(logfile)
|
21
|
+
logger = Logger.new(logfile)
|
22
|
+
logger.level = Logger::DEBUG
|
23
|
+
ActionController::Base.logger = logger
|
24
|
+
|
25
|
+
require __DIR__ + '/../init'
|
metadata
ADDED
@@ -0,0 +1,76 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: maiha-htpasswd
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: "0.1"
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- maiha
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2008-11-08 00:00:00 -08:00
|
13
|
+
default_executable:
|
14
|
+
dependencies: []
|
15
|
+
|
16
|
+
description: ""
|
17
|
+
email: maiha@wota.jp
|
18
|
+
executables: []
|
19
|
+
|
20
|
+
extensions: []
|
21
|
+
|
22
|
+
extra_rdoc_files: []
|
23
|
+
|
24
|
+
files:
|
25
|
+
- README
|
26
|
+
- Rakefile
|
27
|
+
- init.rb
|
28
|
+
- install.rb
|
29
|
+
- lib/htpasswd/acls/active_record.rb
|
30
|
+
- lib/htpasswd/acls/base.rb
|
31
|
+
- lib/htpasswd/acls/crypted.rb
|
32
|
+
- lib/htpasswd/acls/digest.rb
|
33
|
+
- lib/htpasswd/acls/htdigest.rb
|
34
|
+
- lib/htpasswd/acls/htpasswd.rb
|
35
|
+
- lib/htpasswd/acls/plain.rb
|
36
|
+
- lib/htpasswd/auths/base.rb
|
37
|
+
- lib/htpasswd/auths/basic.rb
|
38
|
+
- lib/htpasswd/auths/digest.rb
|
39
|
+
- lib/htpasswd/class_methods.rb
|
40
|
+
- tasks/htpasswd_tasks.rake
|
41
|
+
- test/digest_test.rb
|
42
|
+
- test/fixtures/htdigest.berryz
|
43
|
+
- test/fixtures/htdigest.cute
|
44
|
+
- test/fixtures/htpasswd.berryz
|
45
|
+
- test/fixtures/htpasswd.cute
|
46
|
+
- test/htdigest_test.rb
|
47
|
+
- test/htpasswd_test.rb
|
48
|
+
- test/test_helper.rb
|
49
|
+
has_rdoc: true
|
50
|
+
homepage: http://github.com/maiha/htpasswd
|
51
|
+
post_install_message:
|
52
|
+
rdoc_options: []
|
53
|
+
|
54
|
+
require_paths:
|
55
|
+
- lib
|
56
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
57
|
+
requirements:
|
58
|
+
- - ">="
|
59
|
+
- !ruby/object:Gem::Version
|
60
|
+
version: "0"
|
61
|
+
version:
|
62
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
63
|
+
requirements:
|
64
|
+
- - ">="
|
65
|
+
- !ruby/object:Gem::Version
|
66
|
+
version: "0"
|
67
|
+
version:
|
68
|
+
requirements: []
|
69
|
+
|
70
|
+
rubyforge_project:
|
71
|
+
rubygems_version: 1.2.0
|
72
|
+
signing_key:
|
73
|
+
specification_version: 2
|
74
|
+
summary: ""
|
75
|
+
test_files: []
|
76
|
+
|