authlogic 1.0.0 → 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of authlogic might be problematic. Click here for more details.
- data/CHANGELOG.rdoc +19 -0
- data/Manifest +29 -15
- data/README.rdoc +17 -15
- data/Rakefile +1 -1
- data/authlogic.gemspec +7 -7
- data/lib/authlogic.rb +21 -4
- data/lib/authlogic/controller_adapters/abstract_adapter.rb +19 -4
- data/lib/authlogic/controller_adapters/merb_adapter.rb +0 -27
- data/lib/authlogic/controller_adapters/rails_adapter.rb +0 -14
- data/lib/authlogic/crypto_providers/sha1.rb +24 -0
- data/lib/authlogic/crypto_providers/sha512.rb +30 -0
- data/lib/authlogic/orm_adapters/active_record_adapter/acts_as_authentic.rb +89 -0
- data/lib/authlogic/orm_adapters/active_record_adapter/acts_as_authentic/credentials.rb +144 -0
- data/lib/authlogic/orm_adapters/active_record_adapter/acts_as_authentic/logged_in.rb +41 -0
- data/lib/authlogic/orm_adapters/active_record_adapter/acts_as_authentic/persistence.rb +62 -0
- data/lib/authlogic/orm_adapters/active_record_adapter/acts_as_authentic/session_maintenance.rb +83 -0
- data/lib/authlogic/orm_adapters/active_record_adapter/authenticates_many.rb +58 -0
- data/lib/authlogic/{active_record/scoped_session.rb → session/authenticates_many_association.rb} +12 -3
- data/lib/authlogic/session/base.rb +63 -93
- data/lib/authlogic/session/callbacks.rb +15 -3
- data/lib/authlogic/session/config.rb +130 -26
- data/lib/authlogic/session/cookies.rb +39 -0
- data/lib/authlogic/session/openid.rb +106 -0
- data/lib/authlogic/session/params.rb +28 -0
- data/lib/authlogic/session/session.rb +33 -0
- data/lib/authlogic/testing/shoulda_macros.rb +17 -0
- data/lib/authlogic/version.rb +1 -1
- data/test/fixtures/users.yml +2 -2
- data/{test_libs → test/libs}/aes128_crypto_provider.rb +0 -0
- data/{test_libs → test/libs}/mock_controller.rb +7 -0
- data/{test_libs → test/libs}/mock_cookie_jar.rb +0 -0
- data/{test_libs → test/libs}/mock_request.rb +0 -0
- data/{test_libs → test/libs}/ordered_hash.rb +0 -0
- data/test/orm_adapters_tests/active_record_adapter_tests/acts_as_authentic_test.rb +217 -0
- data/test/orm_adapters_tests/active_record_adapter_tests/authenticates_many_test.rb +32 -0
- data/test/session_tests/active_record_trickery_test.rb +14 -0
- data/test/session_tests/authenticates_many_association_test.rb +20 -0
- data/test/session_tests/base_test.rb +264 -0
- data/test/session_tests/config_test.rb +165 -0
- data/test/session_tests/cookies_test.rb +32 -0
- data/test/session_tests/params_test.rb +16 -0
- data/test/session_tests/scopes_test.rb +60 -0
- data/test/session_tests/session_test.rb +45 -0
- data/test/test_helper.rb +14 -5
- metadata +57 -29
- data/lib/authlogic/active_record/acts_as_authentic.rb +0 -297
- data/lib/authlogic/active_record/authenticates_many.rb +0 -56
- data/lib/authlogic/sha512_crypto_provider.rb +0 -18
- data/test/active_record_acts_as_authentic_test.rb +0 -213
- data/test/active_record_authenticates_many_test.rb +0 -28
- data/test/user_session_active_record_trickery_test.rb +0 -12
- data/test/user_session_base_test.rb +0 -316
- data/test/user_session_config_test.rb +0 -144
- data/test/user_session_scopes_test.rb +0 -19
@@ -0,0 +1,144 @@
|
|
1
|
+
module Authlogic
|
2
|
+
module ORMAdapters
|
3
|
+
module ActiveRecordAdapter
|
4
|
+
module Credentials # :nodoc:
|
5
|
+
def acts_as_authentic_with_credentials(options = {})
|
6
|
+
acts_as_authentic_without_credentials(options)
|
7
|
+
|
8
|
+
# The following helps extract configuration into their specific ORM adapter and allows the Session configuration to set itself based on these values
|
9
|
+
class_eval <<-"end_eval", __FILE__, __LINE__
|
10
|
+
def self.login_field
|
11
|
+
@login_field ||= #{options[:login_field].inspect} ||
|
12
|
+
(column_names.include?("login") && :login) ||
|
13
|
+
(column_names.include?("username") && :username) ||
|
14
|
+
(column_names.include?("email") && :email) ||
|
15
|
+
:login
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.password_field
|
19
|
+
@password_field ||= #{options[:password_field].inspect} ||
|
20
|
+
(column_names.include?("password") && :password) ||
|
21
|
+
(column_names.include?("pass") && :pass) ||
|
22
|
+
:password
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.crypted_password_field
|
26
|
+
@crypted_password_field ||= #{options[:crypted_password_field].inspect} ||
|
27
|
+
(column_names.include?("crypted_password") && :crypted_password) ||
|
28
|
+
(column_names.include?("encrypted_password") && :encrypted_password) ||
|
29
|
+
(column_names.include?("password_hash") && :password_hash) ||
|
30
|
+
(column_names.include?("pw_hash") && :pw_hash) ||
|
31
|
+
:crypted_password
|
32
|
+
end
|
33
|
+
|
34
|
+
def self.password_salt_field
|
35
|
+
@password_salt_field ||= #{options[:password_salt_field].inspect} ||
|
36
|
+
(column_names.include?("password_salt") && :password_salt) ||
|
37
|
+
(column_names.include?("pw_salt") && :pw_salt) ||
|
38
|
+
(column_names.include?("salt") && :salt) ||
|
39
|
+
:password_salt
|
40
|
+
end
|
41
|
+
end_eval
|
42
|
+
|
43
|
+
# The following methods allow other focused modules to alter validation behavior, such as openid as an alternate login
|
44
|
+
unless respond_to?(:allow_blank_for_login_validations?)
|
45
|
+
def self.allow_blank_for_login_validations?
|
46
|
+
false
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
options[:crypto_provider] ||= CryptoProviders::Sha512
|
51
|
+
options[:login_field_type] ||= login_field == :email ? :email : :login
|
52
|
+
|
53
|
+
# Validations
|
54
|
+
case options[:login_field_type]
|
55
|
+
when :email
|
56
|
+
validates_length_of login_field, :within => 6..100
|
57
|
+
email_name_regex = '[\w\.%\+\-]+'
|
58
|
+
domain_head_regex = '(?:[A-Z0-9\-]+\.)+'
|
59
|
+
domain_tld_regex = '(?:[A-Z]{2}|com|org|net|edu|gov|mil|biz|info|mobi|name|aero|jobs|museum)'
|
60
|
+
options[:login_field_regex] ||= /\A#{email_name_regex}@#{domain_head_regex}#{domain_tld_regex}\z/i
|
61
|
+
options[:login_field_regex_message] ||= "should look like an email address."
|
62
|
+
validates_format_of login_field, :with => options[:login_field_regex], :message => options[:login_field_regex_message]
|
63
|
+
else
|
64
|
+
validates_length_of login_field, :within => 2..100, :allow_blank => true
|
65
|
+
options[:login_field_regex] ||= /\A\w[\w\.\-_@ ]+\z/
|
66
|
+
options[:login_field_regex_message] ||= "use only letters, numbers, spaces, and .-_@ please."
|
67
|
+
validates_format_of login_field, :with => options[:login_field_regex], :message => options[:login_field_regex_message]
|
68
|
+
end
|
69
|
+
|
70
|
+
validates_uniqueness_of login_field, :scope => options[:scope]
|
71
|
+
validate :validate_password
|
72
|
+
|
73
|
+
attr_writer "confirm_#{password_field}"
|
74
|
+
attr_accessor "tried_to_set_#{password_field}"
|
75
|
+
|
76
|
+
class_eval <<-"end_eval", __FILE__, __LINE__
|
77
|
+
def self.crypto_provider
|
78
|
+
#{options[:crypto_provider]}
|
79
|
+
end
|
80
|
+
|
81
|
+
def crypto_provider
|
82
|
+
self.class.crypto_provider
|
83
|
+
end
|
84
|
+
|
85
|
+
def #{password_field}=(pass)
|
86
|
+
return if pass.blank?
|
87
|
+
self.tried_to_set_#{password_field} = true
|
88
|
+
@#{password_field} = pass
|
89
|
+
self.#{password_salt_field} = self.class.unique_token
|
90
|
+
self.#{crypted_password_field} = crypto_provider.encrypt(@#{password_field} + #{password_salt_field})
|
91
|
+
end
|
92
|
+
|
93
|
+
def valid_#{password_field}?(attempted_password)
|
94
|
+
return false if attempted_password.blank? || #{crypted_password_field}.blank? || #{password_salt_field}.blank?
|
95
|
+
attempted_password == #{crypted_password_field} ||
|
96
|
+
(crypto_provider.respond_to?(:decrypt) && crypto_provider.decrypt(#{crypted_password_field}) == attempted_password + #{password_salt_field}) ||
|
97
|
+
(!crypto_provider.respond_to?(:decrypt) && crypto_provider.encrypt(attempted_password + #{password_salt_field}) == #{crypted_password_field})
|
98
|
+
end
|
99
|
+
|
100
|
+
def #{password_field}; end
|
101
|
+
def confirm_#{password_field}; end
|
102
|
+
|
103
|
+
def reset_#{password_field}
|
104
|
+
chars = ("a".."z").to_a + ("A".."Z").to_a + ("0".."9").to_a
|
105
|
+
newpass = ""
|
106
|
+
1.upto(10) { |i| newpass << chars[rand(chars.size-1)] }
|
107
|
+
self.#{password_field} = newpass
|
108
|
+
self.confirm_#{password_field} = newpass
|
109
|
+
end
|
110
|
+
alias_method :randomize_password, :reset_password
|
111
|
+
|
112
|
+
def reset_#{password_field}!
|
113
|
+
reset_#{password_field}
|
114
|
+
save_without_session_maintenance(false)
|
115
|
+
end
|
116
|
+
alias_method :randomize_password!, :reset_password!
|
117
|
+
|
118
|
+
protected
|
119
|
+
def tried_to_set_password?
|
120
|
+
tried_to_set_password == true
|
121
|
+
end
|
122
|
+
|
123
|
+
def validate_password
|
124
|
+
if new_record? || tried_to_set_#{password_field}?
|
125
|
+
if @#{password_field}.blank?
|
126
|
+
errors.add(:#{password_field}, "can not be blank")
|
127
|
+
else
|
128
|
+
errors.add(:confirm_#{password_field}, "did not match") if @confirm_#{password_field} != @#{password_field}
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end_eval
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
ActiveRecord::Base.class_eval do
|
140
|
+
class << self
|
141
|
+
include Authlogic::ORMAdapters::ActiveRecordAdapter::Credentials
|
142
|
+
alias_method_chain :acts_as_authentic, :credentials
|
143
|
+
end
|
144
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
module Authlogic
|
2
|
+
module ORMAdapters
|
3
|
+
module ActiveRecordAdapter
|
4
|
+
module LoggedIn # :nodoc:
|
5
|
+
def acts_as_authentic_with_logged_in(options = {})
|
6
|
+
acts_as_authentic_without_logged_in(options)
|
7
|
+
|
8
|
+
options[:logged_in_timeout] ||= 10.minutes
|
9
|
+
|
10
|
+
validates_numericality_of :login_count, :only_integer => :true, :greater_than_or_equal_to => 0, :allow_nil => true if column_names.include?("login_count")
|
11
|
+
|
12
|
+
if column_names.include?("last_request_at")
|
13
|
+
named_scope :logged_in, lambda { {:conditions => ["last_request_at > ?", options[:logged_in_timeout].ago]} }
|
14
|
+
named_scope :logged_out, lambda { {:conditions => ["last_request_at is NULL or last_request_at <= ?", options[:logged_in_timeout].ago]} }
|
15
|
+
end
|
16
|
+
|
17
|
+
class_eval <<-"end_eval", __FILE__, __LINE__
|
18
|
+
def self.logged_in_timeout
|
19
|
+
#{options[:logged_in_timeout].to_i}.seconds
|
20
|
+
end
|
21
|
+
end_eval
|
22
|
+
|
23
|
+
if column_names.include?("last_request_at")
|
24
|
+
class_eval <<-"end_eval", __FILE__, __LINE__
|
25
|
+
def logged_in?
|
26
|
+
!last_request_at.nil? && last_request_at > self.class.logged_in_timeout.ago
|
27
|
+
end
|
28
|
+
end_eval
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
ActiveRecord::Base.class_eval do
|
37
|
+
class << self
|
38
|
+
include Authlogic::ORMAdapters::ActiveRecordAdapter::LoggedIn
|
39
|
+
alias_method_chain :acts_as_authentic, :logged_in
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
module Authlogic
|
2
|
+
module ORMAdapters
|
3
|
+
module ActiveRecordAdapter
|
4
|
+
module Persistence # :nodoc:
|
5
|
+
def acts_as_authentic_with_persistence(options = {})
|
6
|
+
acts_as_authentic_without_persistence(options)
|
7
|
+
|
8
|
+
class_eval <<-"end_eval", __FILE__, __LINE__
|
9
|
+
def self.remember_token_field
|
10
|
+
@remember_token_field ||= #{options[:remember_token_field].inspect} ||
|
11
|
+
(column_names.include?("remember_token") && :remember_token) ||
|
12
|
+
(column_names.include?("remember_key") && :remember_key) ||
|
13
|
+
(column_names.include?("cookie_token") && :cookie_token) ||
|
14
|
+
(column_names.include?("cookie_key") && :cookie_key) ||
|
15
|
+
:remember_token
|
16
|
+
end
|
17
|
+
end_eval
|
18
|
+
|
19
|
+
validates_uniqueness_of remember_token_field
|
20
|
+
|
21
|
+
def unique_token
|
22
|
+
# The remember token should be a unique string that is not reversible, which is what a hash is all about
|
23
|
+
# if you using encryption this defaults to Sha512.
|
24
|
+
token_class = crypto_provider.respond_to?(:decrypt) ? Authlogic::CryptoProviders::Sha512 : crypto_provider
|
25
|
+
token_class.encrypt(Time.now.to_s + (1..10).collect{ rand.to_s }.join)
|
26
|
+
end
|
27
|
+
|
28
|
+
def forget_all!
|
29
|
+
# Paginate these to save on memory
|
30
|
+
records = nil
|
31
|
+
i = 0
|
32
|
+
begin
|
33
|
+
records = find(:all, :limit => 50, :offset => i)
|
34
|
+
records.each { |record| record.forget! }
|
35
|
+
i += 50
|
36
|
+
end while !records.blank?
|
37
|
+
end
|
38
|
+
|
39
|
+
class_eval <<-"end_eval", __FILE__, __LINE__
|
40
|
+
def forget!
|
41
|
+
self.#{remember_token_field} = self.class.unique_token
|
42
|
+
save_without_session_maintenance(false)
|
43
|
+
end
|
44
|
+
|
45
|
+
def password_with_persistence=(value)
|
46
|
+
self.#{remember_token_field} = self.class.unique_token
|
47
|
+
self.password_without_persistence = value
|
48
|
+
end
|
49
|
+
alias_method_chain :password=, :persistence
|
50
|
+
end_eval
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
ActiveRecord::Base.class_eval do
|
58
|
+
class << self
|
59
|
+
include Authlogic::ORMAdapters::ActiveRecordAdapter::Persistence
|
60
|
+
alias_method_chain :acts_as_authentic, :persistence
|
61
|
+
end
|
62
|
+
end
|
data/lib/authlogic/orm_adapters/active_record_adapter/acts_as_authentic/session_maintenance.rb
ADDED
@@ -0,0 +1,83 @@
|
|
1
|
+
module Authlogic
|
2
|
+
module ORMAdapters
|
3
|
+
module ActiveRecordAdapter
|
4
|
+
module SessionMaintenance # :nodoc:
|
5
|
+
def acts_as_authentic_with_session_maintenance(options = {})
|
6
|
+
acts_as_authentic_without_session_maintenance(options)
|
7
|
+
|
8
|
+
options[:session_class] ||= "#{name}Session"
|
9
|
+
options[:session_ids] ||= [nil]
|
10
|
+
|
11
|
+
before_save :get_session_information, :if => :update_sessions?
|
12
|
+
after_save :maintain_sessions!, :if => :update_sessions?
|
13
|
+
|
14
|
+
class_eval <<-"end_eval", __FILE__, __LINE__
|
15
|
+
def save_without_session_maintenance(*args)
|
16
|
+
@skip_session_maintenance = true
|
17
|
+
result = save(*args)
|
18
|
+
@skip_session_maintenance = false
|
19
|
+
result
|
20
|
+
end
|
21
|
+
|
22
|
+
protected
|
23
|
+
def update_sessions?
|
24
|
+
!@skip_session_maintenance && #{options[:session_class]}.activated? && !#{options[:session_ids].inspect}.blank? && #{remember_token_field}_changed?
|
25
|
+
end
|
26
|
+
|
27
|
+
def get_session_information
|
28
|
+
# Need to determine if we are completely logged out, or logged in as another user
|
29
|
+
@_sessions = []
|
30
|
+
@_logged_out = true
|
31
|
+
|
32
|
+
#{options[:session_ids].inspect}.each do |session_id|
|
33
|
+
session = #{options[:session_class]}.find(*[session_id].compact)
|
34
|
+
if session
|
35
|
+
if !session.record.blank?
|
36
|
+
@_logged_out = false
|
37
|
+
@_sessions << session if session.record == self
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def maintain_sessions!
|
44
|
+
if @_logged_out
|
45
|
+
create_session!
|
46
|
+
elsif !@_sessions.blank?
|
47
|
+
update_sessions!
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def create_session!
|
52
|
+
# We only want to automatically login into the first session, since this is the main session. The other sessions are sessions
|
53
|
+
# that need to be created after logging into the main session.
|
54
|
+
session_id = #{options[:session_ids].inspect}.first
|
55
|
+
|
56
|
+
# If we are already logged in, ignore this completely. All that we care about is updating ourself.
|
57
|
+
next if #{options[:session_class]}.find(*[session_id].compact)
|
58
|
+
|
59
|
+
# Log me in
|
60
|
+
args = [self, session_id].compact
|
61
|
+
#{options[:session_class]}.create(*args)
|
62
|
+
end
|
63
|
+
|
64
|
+
def update_sessions!
|
65
|
+
# We found sessions above, let's update them with the new info
|
66
|
+
@_sessions.each do |stale_session|
|
67
|
+
stale_session.unauthorized_record = self
|
68
|
+
stale_session.save
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end_eval
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
ActiveRecord::Base.class_eval do
|
79
|
+
class << self
|
80
|
+
include Authlogic::ORMAdapters::ActiveRecordAdapter::SessionMaintenance
|
81
|
+
alias_method_chain :acts_as_authentic, :session_maintenance
|
82
|
+
end
|
83
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
module Authlogic
|
2
|
+
module ORMAdapters
|
3
|
+
module ActiveRecordAdapter
|
4
|
+
# = Authenticates Many
|
5
|
+
#
|
6
|
+
# This allows you to scope your authentication. For example, let's say all users belong to an account, you want to make sure only users
|
7
|
+
# that belong to that account can actually login into that account. Simple, just do:
|
8
|
+
#
|
9
|
+
# class Account < ActiveRecord::Base
|
10
|
+
# authenticates_many :user_sessions
|
11
|
+
# end
|
12
|
+
#
|
13
|
+
# Now you can scope sessions just like everything else in ActiveRecord:
|
14
|
+
#
|
15
|
+
# @account.user_sessions.new(*args)
|
16
|
+
# @account.user_sessions.create(*args)
|
17
|
+
# @account.user_sessions.find(*args)
|
18
|
+
# # ... etc
|
19
|
+
#
|
20
|
+
# For more information on scopes check out the scopes section in the README.
|
21
|
+
module AuthenticatesMany
|
22
|
+
# Allows you set essentially set up a relationship with your sessions. See module definition above for more details.
|
23
|
+
#
|
24
|
+
# === Options
|
25
|
+
#
|
26
|
+
# * <tt>session_class:</tt> default: "#{name}Session",
|
27
|
+
# This is the related session class.
|
28
|
+
#
|
29
|
+
# * <tt>relationship_name:</tt> default: options[:session_class].klass_name.underscore.pluralize,
|
30
|
+
# This is the name of the relationship you want to use to scope everything. For example an Account has many Users. There should be a relationship
|
31
|
+
# called :users that you defined with a has_many. The reason we use the relationship is so you don't have to repeat yourself. The relatonship
|
32
|
+
# could have all kinds of custom options. So instead of repeating yourself we essentially use the scope that the relationship creates.
|
33
|
+
#
|
34
|
+
# * <tt>find_options:</tt> default: nil,
|
35
|
+
# By default the find options are created from the relationship you specify with :relationship_name. But if you want to override this and
|
36
|
+
# manually specify find_options you can do it here. Specify options just as you would in ActiveRecord::Base.find.
|
37
|
+
#
|
38
|
+
# * <tt>scope_cookies:</tt> default: false
|
39
|
+
# By the nature of cookies they scope theirself if you are using subdomains to access accounts. If you aren't using subdomains you need to have
|
40
|
+
# separate cookies for each account, assuming a user is logging into mroe than one account. Authlogic can take care of this for you by
|
41
|
+
# prefixing the name of the cookie and sessin with the model id. You just need to tell Authlogic to do this by passing this option.
|
42
|
+
def authenticates_many(name, options = {})
|
43
|
+
options[:session_class] ||= name.to_s.classify.constantize
|
44
|
+
options[:relationship_name] ||= options[:session_class].klass_name.underscore.pluralize
|
45
|
+
class_eval <<-"end_eval", __FILE__, __LINE__
|
46
|
+
def #{name}
|
47
|
+
find_options = #{options[:find_options].inspect} || #{options[:relationship_name]}.scope(:find)
|
48
|
+
find_options.delete_if { |key, value| ![:conditions, :include, :joins].include?(key.to_sym) || value.nil? }
|
49
|
+
@#{name} ||= Authlogic::Session::AuthenticatesManyAssociation.new(#{options[:session_class]}, find_options, #{options[:scope_cookies] ? "self.class.model_name.underscore + '_' + self.send(self.class.primary_key).to_s" : "nil"})
|
50
|
+
end
|
51
|
+
end_eval
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
ActiveRecord::Base.extend Authlogic::ORMAdapters::ActiveRecordAdapter::AuthenticatesMany
|
data/lib/authlogic/{active_record/scoped_session.rb → session/authenticates_many_association.rb}
RENAMED
@@ -1,6 +1,15 @@
|
|
1
1
|
module Authlogic
|
2
|
-
module
|
3
|
-
|
2
|
+
module Session
|
3
|
+
# = Authenticates Many Association
|
4
|
+
#
|
5
|
+
# An object of this class is used as a proxy for the authenticates_many relationship. It basically allows you to "save" scope details and call them on an object, which allows you to do the following:
|
6
|
+
#
|
7
|
+
# @account.user_sessions.new
|
8
|
+
# @account.user_sessions.find
|
9
|
+
# # ... etc
|
10
|
+
#
|
11
|
+
# You can call all of the class level methods off of an object with a saved scope, so that calling the above methods scopes the user sessions down to that specific account.
|
12
|
+
class AuthenticatesManyAssociation # :nodoc:
|
4
13
|
attr_accessor :klass, :find_options, :id
|
5
14
|
|
6
15
|
def initialize(klass, find_options, id)
|
@@ -18,7 +27,7 @@ module Authlogic
|
|
18
27
|
end
|
19
28
|
end_eval
|
20
29
|
end
|
21
|
-
|
30
|
+
|
22
31
|
private
|
23
32
|
def scope_options
|
24
33
|
{:find_options => find_options, :id => id}
|
@@ -47,7 +47,7 @@ module Authlogic
|
|
47
47
|
#
|
48
48
|
# def load_user
|
49
49
|
# @user_session = UserSession.find
|
50
|
-
# @current_user = @user_session && @user_session.
|
50
|
+
# @current_user = @user_session && @user_session.user
|
51
51
|
# end
|
52
52
|
#
|
53
53
|
# Accepts a single parameter as the id. See initialize for more information on ids. Lastly, how it finds the session can be modified via configuration.
|
@@ -80,26 +80,28 @@ module Authlogic
|
|
80
80
|
end
|
81
81
|
end
|
82
82
|
|
83
|
-
attr_accessor :
|
83
|
+
attr_accessor :new_session
|
84
84
|
attr_reader :record, :unauthorized_record
|
85
|
-
attr_writer :id
|
85
|
+
attr_writer :authenticating_with, :id
|
86
86
|
|
87
87
|
# You can initialize a session by doing any of the following:
|
88
88
|
#
|
89
89
|
# UserSession.new
|
90
|
-
# UserSession.new(login, password)
|
91
|
-
# UserSession.new(:
|
92
|
-
# UserSession.new(User.first)
|
90
|
+
# UserSession.new(:login => "login", :password => "password", :remember_me => true)
|
91
|
+
# UserSession.new(:openid => "identity url", :remember_me => true)
|
92
|
+
# UserSession.new(User.first, true)
|
93
93
|
#
|
94
94
|
# If a user has more than one session you need to pass an id so that Authlogic knows how to differentiate the sessions. The id MUST be a Symbol.
|
95
95
|
#
|
96
96
|
# UserSession.new(:my_id)
|
97
|
-
# UserSession.new(login, password, :my_id)
|
98
|
-
# UserSession.new({:
|
99
|
-
# UserSession.new(User.first, :my_id)
|
97
|
+
# UserSession.new({:login => "login", :password => "password", :remember_me => true}, :my_id)
|
98
|
+
# UserSession.new({:openid => "identity url", :remember_me => true}, :my_id)
|
99
|
+
# UserSession.new(User.first, true, :my_id)
|
100
100
|
#
|
101
101
|
# Ids are rarely used, but they can be useful. For example, what if users allow other users to login into their account via proxy? Now that user can "technically" be logged into 2 accounts at once.
|
102
102
|
# To solve this just pass a id called :proxy, or whatever you want. Authlogic will separate everything out.
|
103
|
+
#
|
104
|
+
# The reason the id is separate from the first parameter hash is becuase this should be controlled by you, not by what the user passes. A usr could inject their own id and things would not work as expected.
|
103
105
|
def initialize(*args)
|
104
106
|
raise NotActivated.new(self) unless self.class.activated?
|
105
107
|
|
@@ -107,19 +109,36 @@ module Authlogic
|
|
107
109
|
|
108
110
|
self.id = args.pop if args.last.is_a?(Symbol)
|
109
111
|
|
110
|
-
|
111
|
-
when Hash
|
112
|
+
if args.first.is_a?(Hash)
|
112
113
|
self.credentials = args.first
|
113
|
-
|
114
|
-
send("#{login_field}=", args[0]) if args.size > 0
|
115
|
-
send("#{password_field}=", args[1]) if args.size > 1
|
116
|
-
self.remember_me = args[2] if args.size > 2
|
117
|
-
else
|
114
|
+
elsif !args.first.blank? && args.first.class < ::ActiveRecord::Base
|
118
115
|
self.unauthorized_record = args.first
|
119
116
|
self.remember_me = args[1] if args.size > 1
|
120
117
|
end
|
121
118
|
end
|
122
119
|
|
120
|
+
# A flag for how the user is logging in. Possible values:
|
121
|
+
#
|
122
|
+
# * :password - username and password
|
123
|
+
# * :unauthorized_record - an actual ActiveRecord object
|
124
|
+
# * :openid - OpenID
|
125
|
+
#
|
126
|
+
# By default this is :password
|
127
|
+
def authenticating_with
|
128
|
+
@authenticating_with ||= :password
|
129
|
+
end
|
130
|
+
|
131
|
+
# Returns true if logging in with credentials. Credentials mean username and password.
|
132
|
+
def authenticating_with_password?
|
133
|
+
authenticating_with == :password
|
134
|
+
end
|
135
|
+
|
136
|
+
# Returns true if logging in with an unauthorized record
|
137
|
+
def authenticating_with_unauthorized_record?
|
138
|
+
authenticating_with == :unauthorized_record
|
139
|
+
end
|
140
|
+
alias_method :authenticating_with_record?, :authenticating_with_unauthorized_record?
|
141
|
+
|
123
142
|
# Your login credentials in hash format. Usually {:login => "my login", :password => "<protected>"} depending on your configuration.
|
124
143
|
# Password is protected as a security measure. The raw password should never be publicly accessible.
|
125
144
|
def credentials
|
@@ -131,7 +150,7 @@ module Authlogic
|
|
131
150
|
return if values.blank? || !values.is_a?(Hash)
|
132
151
|
values.symbolize_keys!
|
133
152
|
[login_field.to_sym, password_field.to_sym, :remember_me].each do |field|
|
134
|
-
next if
|
153
|
+
next if values[field].blank?
|
135
154
|
send("#{field}=", values[field])
|
136
155
|
end
|
137
156
|
end
|
@@ -140,8 +159,6 @@ module Authlogic
|
|
140
159
|
def destroy
|
141
160
|
errors.clear
|
142
161
|
@record = nil
|
143
|
-
controller.cookies.delete cookie_key
|
144
|
-
controller.session[session_key] = nil
|
145
162
|
true
|
146
163
|
end
|
147
164
|
|
@@ -164,13 +181,20 @@ module Authlogic
|
|
164
181
|
|
165
182
|
# Attempts to find the record by session, then cookie, and finally basic http auth. See the class level find method if you are wanting to use this in a before_filter to persist your session.
|
166
183
|
def find_record
|
167
|
-
|
184
|
+
if record
|
185
|
+
self.new_session = false
|
186
|
+
return record
|
187
|
+
end
|
188
|
+
|
168
189
|
find_with.each do |find_method|
|
169
190
|
if send("valid_#{find_method}?")
|
170
|
-
|
191
|
+
self.new_session = false
|
192
|
+
|
193
|
+
if record.class.column_names.include?("last_request_at") && (record.last_request_at.blank? || last_request_at_threshold.ago >= record.last_request_at)
|
171
194
|
record.last_request_at = Time.now
|
172
195
|
record.save_without_session_maintenance(false)
|
173
196
|
end
|
197
|
+
|
174
198
|
return record
|
175
199
|
end
|
176
200
|
end
|
@@ -195,7 +219,7 @@ module Authlogic
|
|
195
219
|
|
196
220
|
def inspect # :nodoc:
|
197
221
|
details = {}
|
198
|
-
case
|
222
|
+
case authenticating_with
|
199
223
|
when :unauthorized_record
|
200
224
|
details[:unauthorized_record] = "<protected>"
|
201
225
|
else
|
@@ -240,12 +264,6 @@ module Authlogic
|
|
240
264
|
# 4. updates magic fields
|
241
265
|
def save
|
242
266
|
if valid?
|
243
|
-
update_session!
|
244
|
-
controller.cookies[cookie_key] = {
|
245
|
-
:value => record.send(remember_token_field),
|
246
|
-
:expires => remember_me_until
|
247
|
-
}
|
248
|
-
|
249
267
|
record.login_count = (record.login_count.blank? ? 1 : record.login_count + 1) if record.respond_to?(:login_count)
|
250
268
|
|
251
269
|
if record.respond_to?(:current_login_at)
|
@@ -275,7 +293,7 @@ module Authlogic
|
|
275
293
|
# Sometimes you don't want to create a session via credentials (login and password). Maybe you already have the record. Just set this record to this and it will be authenticated when you try to validate
|
276
294
|
# the session. Basically this is another form of credentials, you are just skipping username and password validation.
|
277
295
|
def unauthorized_record=(value)
|
278
|
-
self.
|
296
|
+
self.authenticating_with = :unauthorized_record
|
279
297
|
@unauthorized_record = value
|
280
298
|
end
|
281
299
|
|
@@ -285,6 +303,7 @@ module Authlogic
|
|
285
303
|
errors.clear
|
286
304
|
if valid_credentials?
|
287
305
|
validate
|
306
|
+
valid_record?
|
288
307
|
return true if errors.empty?
|
289
308
|
end
|
290
309
|
|
@@ -298,41 +317,7 @@ module Authlogic
|
|
298
317
|
if !login.blank? && !password.blank?
|
299
318
|
send("#{login_field}=", login)
|
300
319
|
send("#{password_field}=", password)
|
301
|
-
|
302
|
-
if result
|
303
|
-
update_session!
|
304
|
-
self.new_session = false
|
305
|
-
return result
|
306
|
-
end
|
307
|
-
end
|
308
|
-
end
|
309
|
-
|
310
|
-
false
|
311
|
-
end
|
312
|
-
|
313
|
-
# Tries to validate the session from information in the cookie
|
314
|
-
def valid_cookie?
|
315
|
-
if cookie_credentials
|
316
|
-
self.unauthorized_record = search_for_record("find_by_#{remember_token_field}", cookie_credentials)
|
317
|
-
result = valid?
|
318
|
-
if result
|
319
|
-
update_session!
|
320
|
-
self.new_session = false
|
321
|
-
return result
|
322
|
-
end
|
323
|
-
end
|
324
|
-
|
325
|
-
false
|
326
|
-
end
|
327
|
-
|
328
|
-
# Tries to validate the session from information in the session
|
329
|
-
def valid_session?
|
330
|
-
if session_credentials
|
331
|
-
self.unauthorized_record = search_for_record("find_by_#{remember_token_field}", session_credentials)
|
332
|
-
result = valid?
|
333
|
-
if result
|
334
|
-
self.new_session = false
|
335
|
-
return result
|
320
|
+
return valid?
|
336
321
|
end
|
337
322
|
end
|
338
323
|
|
@@ -348,23 +333,21 @@ module Authlogic
|
|
348
333
|
self.class.controller
|
349
334
|
end
|
350
335
|
|
351
|
-
def cookie_credentials
|
352
|
-
controller.cookies[cookie_key]
|
353
|
-
end
|
354
|
-
|
355
336
|
def create_configurable_methods!
|
356
337
|
return if respond_to?(login_field) # already created these methods
|
357
338
|
|
358
339
|
self.class.class_eval <<-"end_eval", __FILE__, __LINE__
|
340
|
+
alias_method :#{klass_name.underscore}, :record
|
341
|
+
|
359
342
|
attr_reader :#{login_field}
|
360
343
|
|
361
344
|
def #{login_field}=(value)
|
362
|
-
self.
|
345
|
+
self.authenticating_with = :password
|
363
346
|
@#{login_field} = value
|
364
347
|
end
|
365
348
|
|
366
349
|
def #{password_field}=(value)
|
367
|
-
self.
|
350
|
+
self.authenticating_with = :password
|
368
351
|
@#{password_field} = value
|
369
352
|
end
|
370
353
|
|
@@ -391,26 +374,14 @@ module Authlogic
|
|
391
374
|
end
|
392
375
|
|
393
376
|
def search_for_record(method, value)
|
394
|
-
|
395
|
-
klass.send(method, value)
|
396
|
-
rescue Exception
|
397
|
-
raise method.inspect + " " + value.inspect
|
398
|
-
end
|
399
|
-
end
|
400
|
-
|
401
|
-
def session_credentials
|
402
|
-
controller.session[session_key]
|
403
|
-
end
|
404
|
-
|
405
|
-
def update_session!
|
406
|
-
controller.session[session_key] = record && record.send(remember_token_field)
|
377
|
+
klass.send(method, value)
|
407
378
|
end
|
408
379
|
|
409
380
|
def valid_credentials?
|
410
381
|
unchecked_record = nil
|
411
382
|
|
412
|
-
case
|
413
|
-
when :
|
383
|
+
case authenticating_with
|
384
|
+
when :password
|
414
385
|
errors.add(login_field, "can not be blank") if send(login_field).blank?
|
415
386
|
errors.add(password_field, "can not be blank") if send("protected_#{password_field}").blank?
|
416
387
|
return false if errors.count > 0
|
@@ -438,20 +409,19 @@ module Authlogic
|
|
438
409
|
errors.add_to_base("You can not login with a new record.")
|
439
410
|
return false
|
440
411
|
end
|
441
|
-
else
|
442
|
-
errors.add_to_base("You must provide some form of credentials before logging in.")
|
443
|
-
return false
|
444
412
|
end
|
445
413
|
|
414
|
+
self.record = unchecked_record
|
415
|
+
true
|
416
|
+
end
|
417
|
+
|
418
|
+
def valid_record?
|
446
419
|
[:active, :approved, :confirmed].each do |required_status|
|
447
|
-
if
|
448
|
-
errors.add_to_base("Your account has not been marked as #{required_status}")
|
420
|
+
if record.respond_to?("#{required_status}?") && !record.send("#{required_status}?")
|
421
|
+
errors.add_to_base("Your account has not been marked as #{required_status}")
|
449
422
|
return false
|
450
423
|
end
|
451
424
|
end
|
452
|
-
|
453
|
-
self.record = unchecked_record
|
454
|
-
true
|
455
425
|
end
|
456
426
|
end
|
457
427
|
end
|