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.

Files changed (54) hide show
  1. data/CHANGELOG.rdoc +19 -0
  2. data/Manifest +29 -15
  3. data/README.rdoc +17 -15
  4. data/Rakefile +1 -1
  5. data/authlogic.gemspec +7 -7
  6. data/lib/authlogic.rb +21 -4
  7. data/lib/authlogic/controller_adapters/abstract_adapter.rb +19 -4
  8. data/lib/authlogic/controller_adapters/merb_adapter.rb +0 -27
  9. data/lib/authlogic/controller_adapters/rails_adapter.rb +0 -14
  10. data/lib/authlogic/crypto_providers/sha1.rb +24 -0
  11. data/lib/authlogic/crypto_providers/sha512.rb +30 -0
  12. data/lib/authlogic/orm_adapters/active_record_adapter/acts_as_authentic.rb +89 -0
  13. data/lib/authlogic/orm_adapters/active_record_adapter/acts_as_authentic/credentials.rb +144 -0
  14. data/lib/authlogic/orm_adapters/active_record_adapter/acts_as_authentic/logged_in.rb +41 -0
  15. data/lib/authlogic/orm_adapters/active_record_adapter/acts_as_authentic/persistence.rb +62 -0
  16. data/lib/authlogic/orm_adapters/active_record_adapter/acts_as_authentic/session_maintenance.rb +83 -0
  17. data/lib/authlogic/orm_adapters/active_record_adapter/authenticates_many.rb +58 -0
  18. data/lib/authlogic/{active_record/scoped_session.rb → session/authenticates_many_association.rb} +12 -3
  19. data/lib/authlogic/session/base.rb +63 -93
  20. data/lib/authlogic/session/callbacks.rb +15 -3
  21. data/lib/authlogic/session/config.rb +130 -26
  22. data/lib/authlogic/session/cookies.rb +39 -0
  23. data/lib/authlogic/session/openid.rb +106 -0
  24. data/lib/authlogic/session/params.rb +28 -0
  25. data/lib/authlogic/session/session.rb +33 -0
  26. data/lib/authlogic/testing/shoulda_macros.rb +17 -0
  27. data/lib/authlogic/version.rb +1 -1
  28. data/test/fixtures/users.yml +2 -2
  29. data/{test_libs → test/libs}/aes128_crypto_provider.rb +0 -0
  30. data/{test_libs → test/libs}/mock_controller.rb +7 -0
  31. data/{test_libs → test/libs}/mock_cookie_jar.rb +0 -0
  32. data/{test_libs → test/libs}/mock_request.rb +0 -0
  33. data/{test_libs → test/libs}/ordered_hash.rb +0 -0
  34. data/test/orm_adapters_tests/active_record_adapter_tests/acts_as_authentic_test.rb +217 -0
  35. data/test/orm_adapters_tests/active_record_adapter_tests/authenticates_many_test.rb +32 -0
  36. data/test/session_tests/active_record_trickery_test.rb +14 -0
  37. data/test/session_tests/authenticates_many_association_test.rb +20 -0
  38. data/test/session_tests/base_test.rb +264 -0
  39. data/test/session_tests/config_test.rb +165 -0
  40. data/test/session_tests/cookies_test.rb +32 -0
  41. data/test/session_tests/params_test.rb +16 -0
  42. data/test/session_tests/scopes_test.rb +60 -0
  43. data/test/session_tests/session_test.rb +45 -0
  44. data/test/test_helper.rb +14 -5
  45. metadata +57 -29
  46. data/lib/authlogic/active_record/acts_as_authentic.rb +0 -297
  47. data/lib/authlogic/active_record/authenticates_many.rb +0 -56
  48. data/lib/authlogic/sha512_crypto_provider.rb +0 -18
  49. data/test/active_record_acts_as_authentic_test.rb +0 -213
  50. data/test/active_record_authenticates_many_test.rb +0 -28
  51. data/test/user_session_active_record_trickery_test.rb +0 -12
  52. data/test/user_session_base_test.rb +0 -316
  53. data/test/user_session_config_test.rb +0 -144
  54. 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
@@ -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
@@ -1,6 +1,15 @@
1
1
  module Authlogic
2
- module ActiveRecord
3
- class ScopedSession # :nodoc:
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.record
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 :login_with, :new_session
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(:login => login, :password => password)
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({:login => loing, :password => password}, :my_id)
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
- case args.first
111
- when Hash
112
+ if args.first.is_a?(Hash)
112
113
  self.credentials = args.first
113
- when String
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 !values.key?(field)
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
- return record if record
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
- if record.class.column_names.include?("last_request_at")
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 login_with
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.login_with = :unauthorized_record
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
- result = valid?
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.login_with = :credentials
345
+ self.authenticating_with = :password
363
346
  @#{login_field} = value
364
347
  end
365
348
 
366
349
  def #{password_field}=(value)
367
- self.login_with = :credentials
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
- begin
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 login_with
413
- when :credentials
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 unchecked_record.respond_to?("#{required_status}?") && !unchecked_record.send("#{required_status}?")
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