authlogic 2.0.1 → 2.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (86) hide show
  1. data/.gitignore +9 -0
  2. data/CHANGELOG.rdoc +110 -1
  3. data/{MIT-LICENSE → LICENSE} +1 -1
  4. data/README.rdoc +138 -161
  5. data/Rakefile +40 -19
  6. data/VERSION.yml +4 -0
  7. data/authlogic.gemspec +212 -0
  8. data/init.rb +1 -1
  9. data/lib/authlogic/acts_as_authentic/base.rb +44 -35
  10. data/lib/authlogic/acts_as_authentic/email.rb +47 -14
  11. data/lib/authlogic/acts_as_authentic/logged_in_status.rb +20 -14
  12. data/lib/authlogic/acts_as_authentic/login.rb +84 -8
  13. data/lib/authlogic/acts_as_authentic/password.rb +207 -88
  14. data/lib/authlogic/acts_as_authentic/perishable_token.rb +9 -4
  15. data/lib/authlogic/acts_as_authentic/persistence_token.rb +4 -2
  16. data/lib/authlogic/acts_as_authentic/restful_authentication.rb +5 -4
  17. data/lib/authlogic/acts_as_authentic/session_maintenance.rb +16 -4
  18. data/lib/authlogic/acts_as_authentic/single_access_token.rb +10 -3
  19. data/lib/authlogic/acts_as_authentic/validations_scope.rb +1 -1
  20. data/lib/authlogic/controller_adapters/abstract_adapter.rb +13 -1
  21. data/lib/authlogic/controller_adapters/rails_adapter.rb +14 -4
  22. data/lib/authlogic/controller_adapters/sinatra_adapter.rb +61 -0
  23. data/lib/authlogic/crypto_providers/aes256.rb +2 -2
  24. data/lib/authlogic/crypto_providers/bcrypt.rb +3 -1
  25. data/lib/authlogic/crypto_providers/sha1.rb +2 -2
  26. data/lib/authlogic/i18n/translator.rb +15 -0
  27. data/lib/authlogic/i18n.rb +43 -14
  28. data/lib/authlogic/regex.rb +25 -0
  29. data/lib/authlogic/session/activation.rb +2 -0
  30. data/lib/authlogic/session/active_record_trickery.rb +26 -5
  31. data/lib/authlogic/session/brute_force_protection.rb +30 -11
  32. data/lib/authlogic/session/callbacks.rb +18 -7
  33. data/lib/authlogic/session/cookies.rb +21 -17
  34. data/lib/authlogic/session/existence.rb +6 -2
  35. data/lib/authlogic/session/foundation.rb +1 -1
  36. data/lib/authlogic/session/http_auth.rb +46 -11
  37. data/lib/authlogic/session/magic_columns.rb +29 -9
  38. data/lib/authlogic/session/magic_states.rb +6 -5
  39. data/lib/authlogic/session/params.rb +16 -10
  40. data/lib/authlogic/session/password.rb +115 -31
  41. data/lib/authlogic/session/priority_record.rb +1 -1
  42. data/lib/authlogic/session/session.rb +5 -3
  43. data/lib/authlogic/session/timeout.rb +1 -1
  44. data/lib/authlogic/session/validation.rb +13 -7
  45. data/lib/authlogic/test_case/mock_controller.rb +45 -0
  46. data/lib/authlogic/test_case/mock_cookie_jar.rb +14 -0
  47. data/lib/authlogic/test_case/mock_logger.rb +10 -0
  48. data/lib/authlogic/test_case/mock_request.rb +19 -0
  49. data/lib/authlogic/test_case/rails_request_adapter.rb +30 -0
  50. data/lib/authlogic/test_case.rb +114 -0
  51. data/lib/authlogic.rb +4 -3
  52. data/rails/init.rb +1 -0
  53. data/shoulda_macros/authlogic.rb +3 -2
  54. data/test/acts_as_authentic_test/base_test.rb +6 -0
  55. data/test/acts_as_authentic_test/email_test.rb +25 -7
  56. data/test/acts_as_authentic_test/login_test.rb +37 -7
  57. data/test/acts_as_authentic_test/magic_columns_test.rb +4 -4
  58. data/test/acts_as_authentic_test/password_test.rb +46 -44
  59. data/test/acts_as_authentic_test/perishable_token_test.rb +36 -2
  60. data/test/acts_as_authentic_test/restful_authentication_test.rb +40 -0
  61. data/test/acts_as_authentic_test/session_maintenance_test.rb +26 -18
  62. data/test/acts_as_authentic_test/single_access_test.rb +6 -1
  63. data/test/i18n_test.rb +33 -0
  64. data/test/libs/affiliate.rb +7 -0
  65. data/test/libs/ldaper.rb +3 -0
  66. data/test/random_test.rb +2 -2
  67. data/test/session_test/activation_test.rb +1 -1
  68. data/test/session_test/active_record_trickery_test.rb +12 -2
  69. data/test/session_test/brute_force_protection_test.rb +37 -12
  70. data/test/session_test/cookies_test.rb +3 -3
  71. data/test/session_test/existence_test.rb +3 -3
  72. data/test/session_test/http_auth_test.rb +20 -8
  73. data/test/session_test/magic_columns_test.rb +9 -6
  74. data/test/session_test/magic_states_test.rb +3 -3
  75. data/test/session_test/params_test.rb +4 -4
  76. data/test/session_test/password_test.rb +23 -1
  77. data/test/session_test/session_test.rb +9 -9
  78. data/test/session_test/timeout_test.rb +10 -1
  79. data/test/test_helper.rb +53 -23
  80. metadata +42 -27
  81. data/Manifest.txt +0 -111
  82. data/lib/authlogic/testing/test_unit_helpers.rb +0 -39
  83. data/lib/authlogic/version.rb +0 -56
  84. data/test/libs/mock_controller.rb +0 -35
  85. data/test/libs/mock_cookie_jar.rb +0 -10
  86. data/test/libs/mock_request.rb +0 -5
@@ -0,0 +1,61 @@
1
+ # Authlogic bridge for Sinatra
2
+ module Authlogic
3
+ module ControllerAdapters
4
+ module SinatraAdapter
5
+ class Cookies
6
+ attr_reader :request, :response
7
+
8
+ def initialize(request, response)
9
+ @request = request
10
+ @response = response
11
+ end
12
+
13
+ def delete(key, options = {})
14
+ @request.cookies.delete(key)
15
+ end
16
+
17
+ def []=(key, options)
18
+ @response.set_cookie(key, options)
19
+ end
20
+
21
+ def method_missing(meth, *args, &block)
22
+ @request.cookies.send(meth, *args, &block)
23
+ end
24
+ end
25
+
26
+ class Controller
27
+ attr_reader :request, :response, :cookies
28
+
29
+ def initialize(request, response)
30
+ @request = request
31
+ @cookies = Cookies.new(request, response)
32
+ end
33
+
34
+ def session
35
+ env['rack.session']
36
+ end
37
+
38
+ def method_missing(meth, *args, &block)
39
+ @request.send meth, *args, &block
40
+ end
41
+ end
42
+
43
+ class Adapter < AbstractAdapter
44
+ def cookie_domain
45
+ env['SERVER_NAME']
46
+ end
47
+
48
+ module Implementation
49
+ def self.included(klass)
50
+ klass.send :before do
51
+ controller = Controller.new(request, response)
52
+ Authlogic::Session::Base.controller = Adapter.new(controller)
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
60
+
61
+ Sinatra::Request.send(:include, Authlogic::ControllerAdapters::SinatraAdapter::Adapter::Implementation)
@@ -5,7 +5,7 @@ module Authlogic
5
5
  # This encryption method is reversible if you have the supplied key. So in order to use this encryption method you must supply it with a key first.
6
6
  # In an initializer, or before your application initializes, you should do the following:
7
7
  #
8
- # Authlogic::CryptoProviders::AES256.key = "my really long and unique key, preferrable a bunch of random characters"
8
+ # Authlogic::CryptoProviders::AES256.key = "my really long and unique key, preferrably a bunch of random characters"
9
9
  #
10
10
  # My final comment is that this is a strong encryption method, but its main weakness is that its reversible. If you do not need to reverse the hash
11
11
  # then you should consider Sha512 or BCrypt instead.
@@ -34,7 +34,7 @@ module Authlogic
34
34
 
35
35
  private
36
36
  def aes
37
- raise ArgumentError.new("You provide a key like #{name}.key = my_key before using the #{name}") if @key.blank?
37
+ raise ArgumentError.new("You must provide a key like #{name}.key = my_key before using the #{name}") if @key.blank?
38
38
  @aes ||= OpenSSL::Cipher::Cipher.new("AES-256-ECB")
39
39
  end
40
40
  end
@@ -35,7 +35,9 @@ module Authlogic
35
35
  #
36
36
  # Tell acts_as_authentic to use it:
37
37
  #
38
- # acts_as_authentic :crypto_provider => Authlogic::CryptoProviders::BCrypt
38
+ # acts_as_authentic do |c|
39
+ # c.crypto_provider = Authlogic::CryptoProviders::BCrypt
40
+ # end
39
41
  #
40
42
  # You are good to go!
41
43
  class BCrypt
@@ -2,8 +2,8 @@ require "digest/sha1"
2
2
 
3
3
  module Authlogic
4
4
  module CryptoProviders
5
- # This class was made for the users transitioning from restful_authentication. I highly discourage using this crypto provider as it inferior to your other options.
6
- # Please use any other provider offered by Authlogic.
5
+ # This class was made for the users transitioning from restful_authentication. I highly discourage using this
6
+ # crypto provider as it inferior to your other options. Please use any other provider offered by Authlogic.
7
7
  class Sha1
8
8
  class << self
9
9
  def join_token
@@ -0,0 +1,15 @@
1
+ module Authlogic
2
+ module I18n
3
+ class Translator
4
+ # If the I18n gem is present, calls +I18n.translate+ passing all
5
+ # arguments, else returns +options[:default]+.
6
+ def translate(key, options = {})
7
+ if defined?(::I18n)
8
+ ::I18n.translate key, options
9
+ else
10
+ options[:default]
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -1,3 +1,5 @@
1
+ require "authlogic/i18n/translator"
2
+
1
3
  module Authlogic
2
4
  # This class allows any message in Authlogic to use internationalization. In earlier versions of Authlogic each message was translated via configuration.
3
5
  # This cluttered up the configuration and cluttered up Authlogic. So all translation has been extracted out into this class. Now all messages pass through
@@ -11,25 +13,24 @@ module Authlogic
11
13
  #
12
14
  # Authlogic::I18n.t('error_messages.password_invalid', :default => "is invalid")
13
15
  #
14
- # If you use a different I18n library or plugin just redefine the t method in the Authlogic::I18n class to do whatever you want with those options. For example:
16
+ # If you use a different I18n library just replace the build-in I18n::Translator class with your own. For example:
15
17
  #
16
- # # config/initializers/authlogic.rb
17
- # module MyAuthlogicI18nAdapter
18
- # def t(key, options = {})
18
+ # class MyAuthlogicI18nTranslator
19
+ # def translate(key, options = {})
19
20
  # # you will have key which will be something like: "error_messages.password_invalid"
20
21
  # # you will also have options[:default], which will be the default english version of the message
21
22
  # # do whatever you want here with the arguments passed to you.
22
23
  # end
23
24
  # end
24
25
  #
25
- # Authlogic::I18n.extend MyAuthlogicI18nAdapter
26
+ # Authlogic::I18n.translator = MyAuthlogicI18nTranslator.new
26
27
  #
27
28
  # That it's! Here is a complete list of the keys that are passed. Just define these however you wish:
28
29
  #
29
30
  # authlogic:
30
31
  # error_messages:
31
32
  # login_blank: can not be blank
32
- # login_not_found: does not exist
33
+ # login_not_found: is not valid
33
34
  # login_invalid: should use only letters, numbers, spaces, and .-_@ please.
34
35
  # consecutive_failed_logins_limit_exceeded: Consecutive failed logins limit exceeded, account is disabled.
35
36
  # email_invalid: should look like an email address.
@@ -39,16 +40,44 @@ module Authlogic
39
40
  # not_confirmed: Your account is not confirmed
40
41
  # not_approved: Your account is not approved
41
42
  # no_authentication_details: You did not provide any details for authentication.
42
- class I18n
43
+ # models:
44
+ # user_session: UserSession (or whatever name you are using)
45
+ # attributes:
46
+ # user_session: (or whatever name you are using)
47
+ # login: login
48
+ # email: email
49
+ # password: password
50
+ # remember_me: remember me
51
+ module I18n
52
+ @@scope = :authlogic
53
+ @@translator = nil
54
+
43
55
  class << self
56
+ # Returns the current scope. Defaults to :authlogic
57
+ def scope
58
+ @@scope
59
+ end
60
+
61
+ # Sets the current scope. Used to set a custom scope.
62
+ def scope=(scope)
63
+ @@scope = scope
64
+ end
65
+
66
+ # Returns the current translator. Defaults to +Translator+.
67
+ def translator
68
+ @@translator ||= Translator.new
69
+ end
70
+
71
+ # Sets the current translator. Used to set a custom translator.
72
+ def translator=(translator)
73
+ @@translator = translator
74
+ end
75
+
44
76
  # All message translation is passed to this method. The first argument is the key for the message. The second is options, see the rails I18n library for a list of options used.
45
- def t(key, options = {})
46
- if defined?(::I18n)
47
- ::I18n.t(key, options.merge(:scope => :authlogic))
48
- else
49
- options[:default]
50
- end
77
+ def translate(key, options = {})
78
+ translator.translate key, { :scope => I18n.scope }.merge(options)
51
79
  end
80
+ alias :t :translate
52
81
  end
53
82
  end
54
- end
83
+ end
@@ -0,0 +1,25 @@
1
+ module Authlogic
2
+ # This is a module the contains regular expressions used throughout Authlogic. The point of extracting
3
+ # them out into their own module is to make them easily available to you for other uses. Ex:
4
+ #
5
+ # validates_format_of :my_email_field, :with => Authlogic::Regex.email
6
+ module Regex
7
+ # A general email regular expression. It allows top level domains (TLD) to be from 2 - 4 in length, any
8
+ # TLD longer than that must be manually specified. The decisions behind this regular expression were made
9
+ # by reading this website: http://www.regular-expressions.info/email.html, which is an excellent resource
10
+ # for regular expressions.
11
+ def self.email
12
+ return @email_regex if @email_regex
13
+ email_name_regex = '[A-Z0-9_\.%\+\-]+'
14
+ domain_head_regex = '(?:[A-Z0-9\-]+\.)+'
15
+ domain_tld_regex = '(?:[A-Z]{2,4}|museum|travel)'
16
+ @email_regex = /\A#{email_name_regex}@#{domain_head_regex}#{domain_tld_regex}\z/i
17
+ end
18
+
19
+ # A simple regular expression that only allows for letters, numbers, spaces, and .-_@. Just a standard login / username
20
+ # regular expression.
21
+ def self.login
22
+ /\A\w[\w\.+\-_@ ]+\z/
23
+ end
24
+ end
25
+ end
@@ -29,6 +29,8 @@ module Authlogic
29
29
  # This accepts a controller object wrapped with the Authlogic controller adapter. The controller adapters close the gap
30
30
  # between the different controllers in each framework. That being said, Authlogic is expecting your object's class to
31
31
  # extend Authlogic::ControllerAdapters::AbstractAdapter. See Authlogic::ControllerAdapters for more info.
32
+ #
33
+ # Lastly, this is thread safe.
32
34
  def controller=(value)
33
35
  Thread.current[:authlogic_controller] = value
34
36
  end
@@ -11,12 +11,21 @@ module Authlogic
11
11
  end
12
12
 
13
13
  module ClassMethods
14
- def human_attribute_name(*args)
15
- klass.human_attribute_name(*args)
14
+ # How to name the attributes of Authlogic, works JUST LIKE ActiveRecord, but instead it uses the following
15
+ # namespace:
16
+ #
17
+ # authlogic.attributes.user_session.login
18
+ def human_attribute_name(attribute_key_name, options = {})
19
+ options[:count] ||= 1
20
+ options[:default] ||= attribute_key_name.to_s.humanize
21
+ I18n.t("attributes.#{name.underscore}.#{attribute_key_name}", options)
16
22
  end
17
23
 
24
+ # How to name the class, works JUST LIKE ActiveRecord, except it uses the following namespace:
25
+ #
26
+ # authlogic.models.user_session
18
27
  def human_name(*args)
19
- klass.human_name(*args)
28
+ I18n.t("models.#{name.underscore}", {:count => 1, :default => name.humanize})
20
29
  end
21
30
 
22
31
  # For rails < 2.3, mispelled
@@ -24,17 +33,29 @@ module Authlogic
24
33
  [self]
25
34
  end
26
35
 
27
- # For Rails >2.3, fix mispelling
36
+ # For rails >= 2.3, mispelling fixed
28
37
  def self_and_descendants_from_active_record
29
38
  [self]
30
39
  end
40
+
41
+ # For rails >= 3.0
42
+ def model_name
43
+ clazz = defined?(::ActiveModel) ? ::ActiveModel::Name : ::ActiveSupport::ModelName
44
+ clazz.new(self.to_s)
45
+ end
31
46
  end
32
47
 
33
48
  module InstanceMethods
49
+ # Don't use this yourself, this is to just trick some of the helpers since this is the method it calls.
34
50
  def new_record?
35
51
  new_session?
36
52
  end
53
+
54
+ # For rails >= 3.0
55
+ def to_model
56
+ self
57
+ end
37
58
  end
38
59
  end
39
60
  end
40
- end
61
+ end
@@ -19,7 +19,8 @@ module Authlogic
19
19
  klass.class_eval do
20
20
  extend Config
21
21
  include InstanceMethods
22
- validate :validate_failed_logins, :if => :protect_from_brute_force_attacks?
22
+ validate :reset_failed_login_count, :if => :reset_failed_login_count?
23
+ validate :validate_failed_logins, :if => :being_brute_force_protected?
23
24
  end
24
25
  end
25
26
 
@@ -36,7 +37,7 @@ module Authlogic
36
37
  # * <tt>Default:</tt> 50
37
38
  # * <tt>Accepts:</tt> Integer, set to 0 to disable
38
39
  def consecutive_failed_logins_limit(value = nil)
39
- config(:consecutive_failed_logins_limit, value, 50)
40
+ rw_config(:consecutive_failed_logins_limit, value, 50)
40
41
  end
41
42
  alias_method :consecutive_failed_logins_limit=, :consecutive_failed_logins_limit
42
43
 
@@ -45,31 +46,49 @@ module Authlogic
45
46
  # * <tt>Default:</tt> 2.hours
46
47
  # * <tt>Accepts:</tt> Fixnum, set to 0 for permanent ban
47
48
  def failed_login_ban_for(value = nil)
48
- config(:failed_login_ban_for, (!value.nil? && value) || value, 2.hours.to_i)
49
+ rw_config(:failed_login_ban_for, (!value.nil? && value) || value, 2.hours.to_i)
49
50
  end
50
51
  alias_method :failed_login_ban_for=, :failed_login_ban_for
51
52
  end
52
53
 
53
54
  # The methods available for an Authlogic::Session::Base object that make up the brute force protection feature.
54
55
  module InstanceMethods
56
+ # Returns true when the consecutive_failed_logins_limit has been exceeded and is being temporarily banned.
57
+ # Notice the word temporary, the user will not be permanently banned unless you choose to do so with configuration.
58
+ # By default they will be banned for 2 hours. During that 2 hour period this method will return true.
59
+ def being_brute_force_protected?
60
+ exceeded_failed_logins_limit? && (failed_login_ban_for <= 0 ||
61
+ (attempted_record.respond_to?(:updated_at) && attempted_record.updated_at >= failed_login_ban_for.seconds.ago))
62
+ end
63
+
55
64
  private
56
- def protect_from_brute_force_attacks?
65
+ def exceeded_failed_logins_limit?
57
66
  !attempted_record.nil? && attempted_record.respond_to?(:failed_login_count) && consecutive_failed_logins_limit > 0 &&
58
- attempted_record.failed_login_count && attempted_record.failed_login_count >= consecutive_failed_logins_limit &&
59
- (failed_login_ban_for <= 0 || (attempted_record.respond_to?(:updated_at) && attempted_record.updated_at >= failed_login_ban_for.seconds.ago))
67
+ attempted_record.failed_login_count && attempted_record.failed_login_count >= consecutive_failed_logins_limit
60
68
  end
61
69
 
62
- def consecutive_failed_logins_limit
63
- self.class.consecutive_failed_logins_limit
70
+ def reset_failed_login_count?
71
+ exceeded_failed_logins_limit? && !being_brute_force_protected?
64
72
  end
65
73
 
66
- def failed_login_ban_for
67
- self.class.failed_login_ban_for
74
+ def reset_failed_login_count
75
+ attempted_record.failed_login_count = 0
68
76
  end
69
77
 
70
78
  def validate_failed_logins
71
79
  errors.clear # Clear all other error messages, as they are irrelevant at this point and can only provide additional information that is not needed
72
- errors.add_to_base(I18n.t('error_messages.consecutive_failed_logins_limit_exceeded', :default => "Consecutive failed logins limit exceeded, account is disabled."))
80
+ errors.add(:base, I18n.t(
81
+ 'error_messages.consecutive_failed_logins_limit_exceeded',
82
+ :default => "Consecutive failed logins limit exceeded, account has been" + (failed_login_ban_for == 0 ? "" : " temporarily") + " disabled."
83
+ ))
84
+ end
85
+
86
+ def consecutive_failed_logins_limit
87
+ self.class.consecutive_failed_logins_limit
88
+ end
89
+
90
+ def failed_login_ban_for
91
+ self.class.failed_login_ban_for
73
92
  end
74
93
  end
75
94
  end
@@ -1,8 +1,15 @@
1
1
  module Authlogic
2
2
  module Session
3
- # Just like in ActiveRecord you have before_save, before_validation, etc. You have similar callbacks with Authlogic, see the METHODS constant below. The order of execution is as follows:
3
+ # Between these callsbacks and the configuration, this is the contract between me and you to safely
4
+ # modify Authlogic's behavior. I will do everything I can to make sure these do not change.
4
5
  #
5
- # Here is the order they execute
6
+ # Check out the sub modules of Authlogic::Session. They are very concise, clear, and to the point. More
7
+ # importantly they use the same API that you would use to extend Authlogic. That being said, they are great
8
+ # examples of how to extend Authlogic and add / modify behavior to Authlogic. These modules could easily be pulled out
9
+ # into their own plugin and become an "add on" without any change.
10
+ #
11
+ # Now to the point of this module. Just like in ActiveRecord you have before_save, before_validation, etc.
12
+ # You have similar callbacks with Authlogic, see the METHODS constant below. The order of execution is as follows:
6
13
  #
7
14
  # before_persisting
8
15
  # persist
@@ -27,11 +34,13 @@ module Authlogic
27
34
  # [save record if record.changed?]
28
35
  #
29
36
  # before_destroy
37
+ # [save record if record.changed?]
30
38
  # destroy
31
39
  # after_destroy
32
40
  #
33
- # Notice the "save record if changed?" lines above. This helps with performance. If you need to make changes to the associated record, there is no need to save the record, Authlogic will do it for you.
34
- # This allow multiple modules to modify the record and execute as few queries as possible.
41
+ # Notice the "save record if changed?" lines above. This helps with performance. If you need to make
42
+ # changes to the associated record, there is no need to save the record, Authlogic will do it for you.
43
+ # This allows multiple modules to modify the record and execute as few queries as possible.
35
44
  #
36
45
  # **WARNING**: unlike ActiveRecord, these callbacks must be set up on the class level:
37
46
  #
@@ -41,11 +50,13 @@ module Authlogic
41
50
  # # ..etc
42
51
  # end
43
52
  #
44
- # You can NOT define a "before_validation" method, this is bad practice and does not allow Authlogic to extend properly with multiple extensions. Please ONLY use the method above.
53
+ # You can NOT define a "before_validation" method, this is bad practice and does not allow Authlogic
54
+ # to extend properly with multiple extensions. Please ONLY use the method above.
45
55
  module Callbacks
46
56
  METHODS = [
47
57
  "before_persisting", "persist", "after_persisting",
48
- "before_validation", "before_validation_on_create", "before_validation_on_update", "validate", "after_validation_on_update", "after_validation_on_create", "after_validation",
58
+ "before_validation", "before_validation_on_create", "before_validation_on_update", "validate",
59
+ "after_validation_on_update", "after_validation_on_create", "after_validation",
49
60
  "before_save", "before_create", "before_update", "after_update", "after_create", "after_save",
50
61
  "before_destroy", "after_destroy"
51
62
  ]
@@ -70,7 +81,7 @@ module Authlogic
70
81
 
71
82
  def save_record(alternate_record = nil)
72
83
  r = alternate_record || record
73
- r.save_without_session_maintenance(false) if r && r.changed?
84
+ r.save_without_session_maintenance(false) if r && r.changed? && !r.readonly?
74
85
  end
75
86
  end
76
87
  end
@@ -1,6 +1,6 @@
1
1
  module Authlogic
2
2
  module Session
3
- # Handles all authentication that deals with cookies, such as persisting a session and saving / destroying a session.
3
+ # Handles all authentication that deals with cookies, such as persisting, saving, and destroying.
4
4
  module Cookies
5
5
  def self.included(klass)
6
6
  klass.class_eval do
@@ -26,7 +26,7 @@ module Authlogic
26
26
  # * <tt>Default:</tt> "#{klass_name.underscore}_credentials"
27
27
  # * <tt>Accepts:</tt> String
28
28
  def cookie_key(value = nil)
29
- config(:cookie_key, value, "#{klass_name.underscore}_credentials")
29
+ rw_config(:cookie_key, value, "#{klass_name.underscore}_credentials")
30
30
  end
31
31
  alias_method :cookie_key=, :cookie_key
32
32
 
@@ -35,7 +35,7 @@ module Authlogic
35
35
  # * <tt>Default:</tt> false
36
36
  # * <tt>Accepts:</tt> Boolean
37
37
  def remember_me(value = nil)
38
- config(:remember_me, value, false)
38
+ rw_config(:remember_me, value, false)
39
39
  end
40
40
  alias_method :remember_me=, :remember_me
41
41
 
@@ -44,36 +44,38 @@ module Authlogic
44
44
  # * <tt>Default:</tt> 3.months
45
45
  # * <tt>Accepts:</tt> Integer, length of time in seconds, such as 60 or 3.months
46
46
  def remember_me_for(value = :_read)
47
- config(:remember_me_for, value, 3.months, :_read)
47
+ rw_config(:remember_me_for, value, 3.months, :_read)
48
48
  end
49
49
  alias_method :remember_me_for=, :remember_me_for
50
50
  end
51
51
 
52
52
  # The methods available for an Authlogic::Session::Base object that make up the cookie feature set.
53
- module InstanceMethods
53
+ module InstanceMethods
54
+ # Allows you to set the remember_me option when passing credentials.
54
55
  def credentials=(value)
55
56
  super
56
57
  values = value.is_a?(Array) ? value : [value]
57
58
  case values.first
58
59
  when Hash
59
- self.remember_me = values.first.with_indifferent_access[:remember_me]
60
+ self.remember_me = values.first.with_indifferent_access[:remember_me] if values.first.with_indifferent_access.key?(:remember_me)
60
61
  else
61
62
  r = values.find { |value| value.is_a?(TrueClass) || value.is_a?(FalseClass) }
62
63
  self.remember_me = r if !r.nil?
63
64
  end
64
65
  end
65
66
 
66
- def remember_me # :nodoc:
67
+ # Is the cookie going to expire after the session is over, or will it stick around?
68
+ def remember_me
67
69
  return @remember_me if defined?(@remember_me)
68
70
  @remember_me = self.class.remember_me
69
71
  end
70
-
72
+
71
73
  # Accepts a boolean as a flag to remember the session or not. Basically to expire the cookie at the end of the session or keep it for "remember_me_until".
72
74
  def remember_me=(value)
73
75
  @remember_me = value
74
76
  end
75
-
76
- # Allows users to be remembered via a cookie.
77
+
78
+ # See remember_me
77
79
  def remember_me?
78
80
  remember_me == true || remember_me == "true" || remember_me == "1"
79
81
  end
@@ -83,7 +85,7 @@ module Authlogic
83
85
  return unless remember_me?
84
86
  self.class.remember_me_for
85
87
  end
86
-
88
+
87
89
  # When to expire the cookie. See remember_me_for configuration option to change this.
88
90
  def remember_me_until
89
91
  return unless remember_me?
@@ -96,27 +98,29 @@ module Authlogic
96
98
  end
97
99
 
98
100
  def cookie_credentials
99
- controller.cookies[cookie_key]
101
+ controller.cookies[cookie_key] && controller.cookies[cookie_key].split("::")
100
102
  end
101
103
 
102
104
  # Tries to validate the session from information in the cookie
103
105
  def persist_by_cookie
104
- if cookie_credentials
105
- self.unauthorized_record = search_for_record("find_by_persistence_token", cookie_credentials)
106
+ persistence_token, record_id = cookie_credentials
107
+ if !persistence_token.nil?
108
+ record = record_id.nil? ? search_for_record("find_by_persistence_token", persistence_token) : search_for_record("find_by_#{klass.primary_key}", record_id)
109
+ self.unauthorized_record = record if record && record.persistence_token == persistence_token
106
110
  valid?
107
111
  else
108
112
  false
109
113
  end
110
114
  end
111
-
115
+
112
116
  def save_cookie
113
117
  controller.cookies[cookie_key] = {
114
- :value => record.persistence_token,
118
+ :value => "#{record.persistence_token}::#{record.send(record.class.primary_key)}",
115
119
  :expires => remember_me_until,
116
120
  :domain => controller.cookie_domain
117
121
  }
118
122
  end
119
-
123
+
120
124
  def destroy_cookie
121
125
  controller.cookies.delete cookie_key, :domain => controller.cookie_domain
122
126
  end
@@ -20,7 +20,7 @@ module Authlogic
20
20
  # A convenince method. The same as:
21
21
  #
22
22
  # session = UserSession.new(*args)
23
- # session.create
23
+ # session.save
24
24
  #
25
25
  # Instead you can do:
26
26
  #
@@ -28,12 +28,14 @@ module Authlogic
28
28
  def create(*args, &block)
29
29
  session = new(*args)
30
30
  session.save(&block)
31
+ session
31
32
  end
32
33
 
33
34
  # Same as create but calls create!, which raises an exception when validation fails.
34
35
  def create!(*args)
35
36
  session = new(*args)
36
37
  session.save!
38
+ session
37
39
  end
38
40
  end
39
41
 
@@ -42,13 +44,15 @@ module Authlogic
42
44
  # the user to authenticate again if it is needed.
43
45
  def destroy
44
46
  before_destroy
47
+ save_record
45
48
  errors.clear
46
49
  @record = nil
47
50
  after_destroy
48
51
  true
49
52
  end
50
53
 
51
- # Returns true if the session has not been saved yet.
54
+ # Returns true if the session is new, meaning no action has been taken on it and a successful save
55
+ # has not taken place.
52
56
  def new_session?
53
57
  new_session != false
54
58
  end
@@ -13,7 +13,7 @@ module Authlogic
13
13
 
14
14
  module ClassMethods
15
15
  private
16
- def config(key, value, default_value = nil, read_value = nil)
16
+ def rw_config(key, value, default_value = nil, read_value = nil)
17
17
  if value == read_value
18
18
  return read_inheritable_attribute(key) if inheritable_attributes.include?(key)
19
19
  write_inheritable_attribute(key, default_value)