authlogic 1.4.3 → 2.0.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 (131) hide show
  1. data/CHANGELOG.rdoc +19 -0
  2. data/Manifest.txt +111 -0
  3. data/README.rdoc +116 -389
  4. data/Rakefile +14 -7
  5. data/lib/authlogic.rb +33 -35
  6. data/lib/authlogic/acts_as_authentic/base.rb +91 -0
  7. data/lib/authlogic/acts_as_authentic/email.rb +77 -0
  8. data/lib/authlogic/acts_as_authentic/logged_in_status.rb +54 -0
  9. data/lib/authlogic/acts_as_authentic/login.rb +65 -0
  10. data/lib/authlogic/acts_as_authentic/magic_columns.rb +24 -0
  11. data/lib/authlogic/acts_as_authentic/password.rb +215 -0
  12. data/lib/authlogic/acts_as_authentic/perishable_token.rb +100 -0
  13. data/lib/authlogic/acts_as_authentic/persistence_token.rb +66 -0
  14. data/lib/authlogic/acts_as_authentic/restful_authentication.rb +60 -0
  15. data/lib/authlogic/acts_as_authentic/session_maintenance.rb +127 -0
  16. data/lib/authlogic/acts_as_authentic/single_access_token.rb +58 -0
  17. data/lib/authlogic/acts_as_authentic/validations_scope.rb +32 -0
  18. data/lib/authlogic/{session/authenticates_many_association.rb → authenticates_many/association.rb} +10 -6
  19. data/lib/authlogic/authenticates_many/base.rb +55 -0
  20. data/lib/authlogic/controller_adapters/abstract_adapter.rb +2 -3
  21. data/lib/authlogic/controller_adapters/merb_adapter.rb +0 -4
  22. data/lib/authlogic/controller_adapters/rails_adapter.rb +0 -4
  23. data/lib/authlogic/crypto_providers/aes256.rb +0 -2
  24. data/lib/authlogic/crypto_providers/bcrypt.rb +0 -2
  25. data/lib/authlogic/crypto_providers/md5.rb +34 -0
  26. data/lib/authlogic/crypto_providers/sha1.rb +0 -2
  27. data/lib/authlogic/crypto_providers/sha512.rb +1 -3
  28. data/lib/authlogic/i18n.rb +1 -4
  29. data/lib/authlogic/random.rb +33 -0
  30. data/lib/authlogic/session/activation.rb +56 -0
  31. data/lib/authlogic/session/active_record_trickery.rb +15 -7
  32. data/lib/authlogic/session/base.rb +31 -456
  33. data/lib/authlogic/session/brute_force_protection.rb +50 -27
  34. data/lib/authlogic/session/callbacks.rb +24 -15
  35. data/lib/authlogic/session/cookies.rb +108 -22
  36. data/lib/authlogic/session/existence.rb +89 -0
  37. data/lib/authlogic/session/foundation.rb +63 -0
  38. data/lib/authlogic/session/http_auth.rb +23 -0
  39. data/lib/authlogic/session/id.rb +41 -0
  40. data/lib/authlogic/session/klass.rb +75 -0
  41. data/lib/authlogic/session/magic_columns.rb +75 -0
  42. data/lib/authlogic/session/magic_states.rb +58 -0
  43. data/lib/authlogic/session/params.rb +82 -19
  44. data/lib/authlogic/session/password.rb +156 -0
  45. data/lib/authlogic/session/{perishability.rb → perishable_token.rb} +4 -4
  46. data/lib/authlogic/session/persistence.rb +70 -0
  47. data/lib/authlogic/session/priority_record.rb +34 -0
  48. data/lib/authlogic/session/scopes.rb +57 -53
  49. data/lib/authlogic/session/session.rb +46 -31
  50. data/lib/authlogic/session/timeout.rb +65 -31
  51. data/lib/authlogic/session/unauthorized_record.rb +50 -0
  52. data/lib/authlogic/session/validation.rb +76 -0
  53. data/lib/authlogic/testing/test_unit_helpers.rb +3 -3
  54. data/lib/authlogic/version.rb +3 -3
  55. data/test/acts_as_authentic_test/base_test.rb +12 -0
  56. data/test/acts_as_authentic_test/email_test.rb +79 -0
  57. data/test/acts_as_authentic_test/logged_in_status_test.rb +36 -0
  58. data/test/acts_as_authentic_test/login_test.rb +79 -0
  59. data/test/acts_as_authentic_test/magic_columns_test.rb +27 -0
  60. data/test/acts_as_authentic_test/password_test.rb +212 -0
  61. data/test/acts_as_authentic_test/perishable_token_test.rb +56 -0
  62. data/test/acts_as_authentic_test/persistence_token_test.rb +55 -0
  63. data/test/acts_as_authentic_test/session_maintenance_test.rb +68 -0
  64. data/test/acts_as_authentic_test/single_access_test.rb +39 -0
  65. data/test/authenticates_many_test.rb +16 -0
  66. data/test/{crypto_provider_tests → crypto_provider_test}/aes256_test.rb +1 -1
  67. data/test/{crypto_provider_tests → crypto_provider_test}/bcrypt_test.rb +1 -1
  68. data/test/{crypto_provider_tests → crypto_provider_test}/sha1_test.rb +1 -1
  69. data/test/{crypto_provider_tests → crypto_provider_test}/sha512_test.rb +1 -1
  70. data/test/fixtures/employees.yml +4 -4
  71. data/test/fixtures/users.yml +6 -6
  72. data/test/libs/company.rb +6 -0
  73. data/test/libs/employee.rb +7 -0
  74. data/test/libs/employee_session.rb +2 -0
  75. data/test/libs/project.rb +3 -0
  76. data/test/libs/user_session.rb +2 -0
  77. data/test/random_test.rb +49 -0
  78. data/test/session_test/activation_test.rb +43 -0
  79. data/test/session_test/active_record_trickery_test.rb +26 -0
  80. data/test/session_test/brute_force_protection_test.rb +76 -0
  81. data/test/session_test/callbacks_test.rb +6 -0
  82. data/test/session_test/cookies_test.rb +107 -0
  83. data/test/session_test/credentials_test.rb +0 -0
  84. data/test/session_test/existence_test.rb +64 -0
  85. data/test/session_test/http_auth_test.rb +16 -0
  86. data/test/session_test/id_test.rb +17 -0
  87. data/test/session_test/klass_test.rb +35 -0
  88. data/test/session_test/magic_columns_test.rb +59 -0
  89. data/test/session_test/magic_states_test.rb +60 -0
  90. data/test/session_test/params_test.rb +53 -0
  91. data/test/session_test/password_test.rb +84 -0
  92. data/test/{session_tests → session_test}/perishability_test.rb +1 -1
  93. data/test/session_test/persistence_test.rb +21 -0
  94. data/test/{session_tests → session_test}/scopes_test.rb +2 -3
  95. data/test/session_test/session_test.rb +59 -0
  96. data/test/session_test/timeout_test.rb +43 -0
  97. data/test/session_test/unauthorized_record_test.rb +13 -0
  98. data/test/session_test/validation_test.rb +23 -0
  99. data/test/test_helper.rb +14 -29
  100. metadata +120 -112
  101. data/Manifest +0 -76
  102. data/authlogic.gemspec +0 -38
  103. data/lib/authlogic/orm_adapters/active_record_adapter/acts_as_authentic/base.rb +0 -22
  104. data/lib/authlogic/orm_adapters/active_record_adapter/acts_as_authentic/config.rb +0 -238
  105. data/lib/authlogic/orm_adapters/active_record_adapter/acts_as_authentic/credentials.rb +0 -155
  106. data/lib/authlogic/orm_adapters/active_record_adapter/acts_as_authentic/logged_in.rb +0 -51
  107. data/lib/authlogic/orm_adapters/active_record_adapter/acts_as_authentic/perishability.rb +0 -71
  108. data/lib/authlogic/orm_adapters/active_record_adapter/acts_as_authentic/persistence.rb +0 -94
  109. data/lib/authlogic/orm_adapters/active_record_adapter/acts_as_authentic/session_maintenance.rb +0 -87
  110. data/lib/authlogic/orm_adapters/active_record_adapter/acts_as_authentic/single_access.rb +0 -61
  111. data/lib/authlogic/orm_adapters/active_record_adapter/authenticates_many.rb +0 -58
  112. data/lib/authlogic/session/config.rb +0 -421
  113. data/lib/authlogic/session/errors.rb +0 -18
  114. data/lib/authlogic/session/record_info.rb +0 -24
  115. data/test/orm_adapters_tests/active_record_adapter_tests/acts_as_authentic_tests/config_test.rb +0 -154
  116. data/test/orm_adapters_tests/active_record_adapter_tests/acts_as_authentic_tests/credentials_test.rb +0 -157
  117. data/test/orm_adapters_tests/active_record_adapter_tests/acts_as_authentic_tests/logged_in_test.rb +0 -24
  118. data/test/orm_adapters_tests/active_record_adapter_tests/acts_as_authentic_tests/perishability_test.rb +0 -41
  119. data/test/orm_adapters_tests/active_record_adapter_tests/acts_as_authentic_tests/persistence_test.rb +0 -54
  120. data/test/orm_adapters_tests/active_record_adapter_tests/acts_as_authentic_tests/session_maintenance_test.rb +0 -62
  121. data/test/orm_adapters_tests/active_record_adapter_tests/acts_as_authentic_tests/single_access_test.rb +0 -41
  122. data/test/orm_adapters_tests/active_record_adapter_tests/authenticates_many_test.rb +0 -32
  123. data/test/session_tests/active_record_trickery_test.rb +0 -14
  124. data/test/session_tests/authenticates_many_association_test.rb +0 -28
  125. data/test/session_tests/base_test.rb +0 -307
  126. data/test/session_tests/brute_force_protection_test.rb +0 -53
  127. data/test/session_tests/config_test.rb +0 -184
  128. data/test/session_tests/cookies_test.rb +0 -32
  129. data/test/session_tests/params_test.rb +0 -32
  130. data/test/session_tests/session_test.rb +0 -45
  131. data/test/session_tests/timeout_test.rb +0 -71
@@ -1,17 +1,17 @@
1
1
  module Authlogic
2
2
  module Session
3
- # = Perishability
4
- #
5
3
  # Maintains the perishable token, which is helpful for confirming records or authorizing records to reset their password. All that this
6
4
  # module does is reset it after a session have been saved, just keep it changing. The more it changes, the tighter the security.
7
- module Perishability
5
+ #
6
+ # See Authlogic::ActsAsAuthentic::PerishableToken for more information.
7
+ module PerishableToken
8
8
  def self.included(klass)
9
9
  klass.after_save :reset_perishable_token!
10
10
  end
11
11
 
12
12
  private
13
13
  def reset_perishable_token!
14
- record.send("reset_#{perishable_token_field}") if record.respond_to?("reset_#{perishable_token_field}") && !record.send("disable_#{perishable_token_field}_maintenance?")
14
+ record.reset_perishable_token if record.respond_to?(:reset_perishable_token) && !record.disable_perishable_token_maintenance?
15
15
  end
16
16
  end
17
17
  end
@@ -0,0 +1,70 @@
1
+ module Authlogic
2
+ module Session
3
+ # Responsible for allowing you to persist your sessions.
4
+ module Persistence
5
+ def self.included(klass)
6
+ klass.class_eval do
7
+ extend ClassMethods
8
+ include InstanceMethods
9
+ end
10
+ end
11
+
12
+ module ClassMethods
13
+ # This is how you persist a session. This finds the record for the current session using
14
+ # a variety of methods. It basically tries to "log in" the user without the user having
15
+ # to explicitly log in. Check out the other Authlogic::Session modules for more information.
16
+ #
17
+ # The best way to use this method is something like:
18
+ #
19
+ # helper_method :current_user_session, :current_user
20
+ #
21
+ # def current_user_session
22
+ # return @current_user_session if defined?(@current_user_session)
23
+ # @current_user_session = UserSession.find
24
+ # end
25
+ #
26
+ # def current_user
27
+ # return @current_user if defined?(@current_user)
28
+ # @current_user = current_user_session && current_user_session.user
29
+ # end
30
+ #
31
+ # Also, this method accepts a single parameter as the id, to find session that you marked with an id:
32
+ #
33
+ # UserSession.find(:secure)
34
+ #
35
+ # See the id method for more information on ids.
36
+ def find(id = nil, priority_record = nil)
37
+ session = new({:priority_record => priority_record}, id)
38
+ session.priority_record = priority_record
39
+ if session.persisting?
40
+ session
41
+ else
42
+ nil
43
+ end
44
+ end
45
+ end
46
+
47
+ module InstanceMethods
48
+ # Let's you know if the session is being persisted or not, meaning the user does not have to explicitly log in
49
+ # in order to be logged in. If the session has no associated record, it will try to find a record and persis
50
+ # the session. This is the method that the class level method find uses to ultimately persist the session.
51
+ def persisting?
52
+ return true if !record.nil?
53
+ self.attempted_record = nil
54
+ before_persisting
55
+ persist
56
+ ensure_authentication_attempted
57
+ if errors.empty? && !attempted_record.nil?
58
+ self.record = attempted_record
59
+ after_persisting
60
+ save_record
61
+ self.new_session = false
62
+ true
63
+ else
64
+ false
65
+ end
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,34 @@
1
+ module Authlogic
2
+ module Session
3
+ # The point of this module is to avoid the StaleObjectError raised when lock_version is implemented in ActiveRecord.
4
+ # We accomplish this by using a "priority record". Meaning this record is used if possible, it gets priority.
5
+ # This way we don't save a record behind the scenes thus making an object being used stale.
6
+ module PriorityRecord
7
+ def self.included(klass)
8
+ klass.class_eval do
9
+ attr_accessor :priority_record
10
+ end
11
+ end
12
+
13
+ # Setting priority record if it is passed. The only way it can be passed is through an array:
14
+ #
15
+ # session.credentials = [real_user_object, priority_user_object]
16
+ def credentials=(value)
17
+ super
18
+ values = value.is_a?(Array) ? value : [value]
19
+ self.priority_record = values.second if values.second.class < ::ActiveRecord::Base
20
+ end
21
+
22
+ private
23
+ def attempted_record=(value)
24
+ value = priority_record if value == priority_record
25
+ super
26
+ end
27
+
28
+ def save_record(alternate_record = nil)
29
+ r = alternate_record || record
30
+ super if r != priority_record
31
+ end
32
+ end
33
+ end
34
+ end
@@ -1,55 +1,15 @@
1
1
  module Authlogic
2
2
  module Session
3
- # = Scopes
3
+ # Authentication can be scoped, and it's easy, you just need to define how you want to scope everything. This should help you:
4
4
  #
5
- # Authentication can be scoped, but scoping authentication can get a little tricky. Checkout the section "Scoping" in the readme for more details.
6
- #
7
- # What with_scopes focuses on is scoping the query when finding the object and the name of the cookie / session. It works very similar to
8
- # ActiveRecord::Base#with_scopes. It accepts a hash with any of the following options:
9
- #
10
- # * <tt>find_options:</tt> any options you can pass into ActiveRecord::Base.find. This is used when trying to find the record.
11
- # * <tt>id:</tt> The id of the session, this gets merged with the real id. For information ids see the id method.
12
- #
13
- # Here is how you use it:
14
- #
15
- # UserSession.with_scope(:find_options => {:conditions => "account_id = 2"}, :id => "account_2") do
16
- # UserSession.find
17
- # end
18
- #
19
- # Eseentially what the above does is scope the searching of the object with the sql you provided. So instead of:
20
- #
21
- # User.find(:first, :conditions => "login = 'ben'")
22
- #
23
- # it would be:
24
- #
25
- # User.find(:first, :conditions => "login = 'ben' and account_id = 2")
26
- #
27
- # You will also notice the :id option. This works just like the id method. It scopes your cookies. So the name of your cookie will be:
28
- #
29
- # account_2_user_credentials
30
- #
31
- # instead of:
32
- #
33
- # user_credentials
34
- #
35
- # What is also nifty about scoping with an :id is that it merges your id's. So if you do:
36
- #
37
- # UserSession.with_scope(:find_options => {:conditions => "account_id = 2"}, :id => "account_2") do
38
- # session = UserSession.new
39
- # session.id = :secure
40
- # end
41
- #
42
- # The name of your cookies will be:
43
- #
44
- # secure_account_2_user_credentials
5
+ # 1. Want to scope by a parent object? Ex: An account has many users. Checkout Authlogic::AuthenticatesMany
6
+ # 2. Want to scope the validations in your model? Ex: 2 users can have the same login under different accounts. See Authlogic::ActsAsAuthentic::Scope
45
7
  module Scopes # :nodoc:
46
8
  def self.included(klass)
47
- klass.extend(ClassMethods)
48
- klass.send(:include, InstanceMethods)
49
9
  klass.class_eval do
10
+ extend ClassMethods
11
+ include InstanceMethods
50
12
  attr_writer :scope
51
- alias_method_chain :initialize, :scopes
52
- alias_method_chain :search_for_record, :scopes
53
13
  end
54
14
  end
55
15
 
@@ -60,7 +20,44 @@ module Authlogic
60
20
  Thread.current[:authlogic_scope]
61
21
  end
62
22
 
63
- # See the documentation for this class for more information on how to use this method.
23
+ # What with_scopes focuses on is scoping the query when finding the object and the name of the cookie / session. It works very similar to
24
+ # ActiveRecord::Base#with_scopes. It accepts a hash with any of the following options:
25
+ #
26
+ # * <tt>find_options:</tt> any options you can pass into ActiveRecord::Base.find. This is used when trying to find the record.
27
+ # * <tt>id:</tt> The id of the session, this gets merged with the real id. For information ids see the id method.
28
+ #
29
+ # Here is how you use it:
30
+ #
31
+ # UserSession.with_scope(:find_options => {:conditions => "account_id = 2"}, :id => "account_2") do
32
+ # UserSession.find
33
+ # end
34
+ #
35
+ # Eseentially what the above does is scope the searching of the object with the sql you provided. So instead of:
36
+ #
37
+ # User.find(:first, :conditions => "login = 'ben'")
38
+ #
39
+ # it would be:
40
+ #
41
+ # User.find(:first, :conditions => "login = 'ben' and account_id = 2")
42
+ #
43
+ # You will also notice the :id option. This works just like the id method. It scopes your cookies. So the name of your cookie will be:
44
+ #
45
+ # account_2_user_credentials
46
+ #
47
+ # instead of:
48
+ #
49
+ # user_credentials
50
+ #
51
+ # What is also nifty about scoping with an :id is that it merges your id's. So if you do:
52
+ #
53
+ # UserSession.with_scope(:find_options => {:conditions => "account_id = 2"}, :id => "account_2") do
54
+ # session = UserSession.new
55
+ # session.id = :secure
56
+ # end
57
+ #
58
+ # The name of your cookies will be:
59
+ #
60
+ # secure_account_2_user_credentials
64
61
  def with_scope(options = {}, &block)
65
62
  raise ArgumentError.new("You must provide a block") unless block_given?
66
63
  self.scope = options
@@ -76,21 +73,28 @@ module Authlogic
76
73
  end
77
74
 
78
75
  module InstanceMethods
79
- def initialize_with_scopes(*args) # :nodoc:
76
+ # Setting the scope if it exists upon instantiation.
77
+ def initialize(*args)
80
78
  self.scope = self.class.scope
81
- initialize_without_scopes(*args)
79
+ super
82
80
  end
83
81
 
84
- # See the documentation for this class for more information on how to use this method.
82
+ # The scope of the current object
85
83
  def scope
86
84
  @scope ||= {}
87
85
  end
88
86
 
89
- def search_for_record_with_scopes(*args) # :nodoc:
90
- klass.send(:with_scope, :find => (scope[:find_options] || {})) do
91
- search_for_record_without_scopes(*args)
87
+ private
88
+ # Used for things like cookie_key, session_key, etc.
89
+ def build_key(last_part)
90
+ [scope[:id], super].compact.join("_")
91
+ end
92
+
93
+ def search_for_record(*args)
94
+ klass.send(:with_scope, :find => (scope[:find_options] || {})) do
95
+ klass.send(*args)
96
+ end
92
97
  end
93
- end
94
98
  end
95
99
  end
96
100
  end
@@ -1,45 +1,60 @@
1
1
  module Authlogic
2
2
  module Session
3
- # = Session
4
- #
5
3
  # Handles all parts of authentication that deal with sessions. Such as persisting a session and saving / destroy a session.
6
4
  module Session
7
5
  def self.included(klass)
8
- klass.after_save :update_session, :if => :persisting?
9
- klass.after_destroy :update_session, :if => :persisting?
10
- klass.after_find :update_session, :if => :persisting? # to continue persisting the session after an http_auth request
6
+ klass.class_eval do
7
+ extend Config
8
+ include InstanceMethods
9
+ persist :persist_by_session
10
+ after_save :update_session
11
+ after_destroy :update_session
12
+ after_persisting :update_session, :unless => :single_access?
13
+ end
11
14
  end
12
15
 
13
- # Tries to validate the session from information in the session
14
- def valid_session?
15
- persistence_token, record_id = session_credentials
16
- if !persistence_token.blank?
17
- if record_id
18
- record = search_for_record("find_by_#{klass.primary_key}", record_id)
19
- self.unauthorized_record = record if record && record.send(persistence_token_field) == persistence_token
20
- else
21
- # For backwards compatibility, will eventually be removed, just need to let the sessions update theirself
22
- record = search_for_record("find_by_#{persistence_token_field}", persistence_token)
23
- if record
24
- controller.session["#{session_key}_id"] = record.send(record.class.primary_key)
25
- self.unauthorized_record = record
26
- end
27
- end
28
- valid?
29
- else
30
- false
16
+ # Configuration for the session feature.
17
+ module Config
18
+ # Works exactly like cookie_key, but for sessions. See cookie_key for more info.
19
+ #
20
+ # * <tt>Default:</tt> cookie_key
21
+ # * <tt>Accepts:</tt> Symbol or String
22
+ def session_key(value = nil)
23
+ config(:session_key, value, cookie_key)
31
24
  end
25
+ alias_method :session_key=, :session_key
32
26
  end
33
27
 
34
- private
35
- def session_credentials
36
- [controller.session[session_key], controller.session["#{session_key}_id"]].compact
37
- end
28
+ # Instance methods for the session feature.
29
+ module InstanceMethods
30
+ private
31
+ # Tries to validate the session from information in the session
32
+ def persist_by_session
33
+ persistence_token, record_id = session_credentials
34
+ if !persistence_token.nil?
35
+ # Allow finding by persistence token, because when records are created the session is maintained in a before_save, when there is no id.
36
+ # This is done for performance reasons and to save on queries.
37
+ record = record_id.nil? ? search_for_record("find_by_persistence_token", persistence_token) : search_for_record("find_by_#{klass.primary_key}", record_id)
38
+ self.unauthorized_record = record if record && record.persistence_token == persistence_token
39
+ valid?
40
+ else
41
+ false
42
+ end
43
+ end
44
+
45
+ def session_credentials
46
+ [controller.session[session_key], controller.session["#{session_key}_#{klass.primary_key}"]].compact
47
+ end
48
+
49
+ def session_key
50
+ build_key(self.class.session_key)
51
+ end
38
52
 
39
- def update_session
40
- controller.session[session_key] = record && record.send(persistence_token_field)
41
- controller.session["#{session_key}_id"] = record && record.send(record.class.primary_key)
42
- end
53
+ def update_session
54
+ controller.session[session_key] = record && record.persistence_token
55
+ controller.session["#{session_key}_#{klass.primary_key}"] = record && record.send(record.class.primary_key)
56
+ end
57
+ end
43
58
  end
44
59
  end
45
60
  end
@@ -1,48 +1,82 @@
1
1
  module Authlogic
2
2
  module Session
3
- # = Timeout
3
+ # Think about financial websites, if you are inactive for a certain period of time you will be asked to
4
+ # log back in on your next request. You can do this with Authlogic easily, there are 2 parts to this:
4
5
  #
5
- # This is reponsibile for determining if the session is stale or fresh. It is also responsible for maintaining the last_request_at value if the column is present.
6
+ # 1. Define the timeout threshold:
6
7
  #
7
- # Think about how financial websites work. If you are inactive after a certain period of time you must log back in. By default this is disabled, but if enabled this
8
- # module kicks in. See the logout_on_timeout configuration option for how to turn this on.
8
+ # acts_as_authentic do |c|
9
+ # c.logged_in_timeout = 10.minutes # default is 10.minutes
10
+ # end
11
+ #
12
+ # 2. Enable logging out on timeouts
13
+ #
14
+ # class UserSession < Authlogic::Session::Base
15
+ # logout_on_timeout true # default if false
16
+ # end
17
+ #
18
+ # This will require a user to log back in if they are inactive for more than 10 minutes. In order for
19
+ # this feature to be used you must have a last_request_at datetime column in your table for whatever model
20
+ # you are authenticating with.
9
21
  module Timeout
10
22
  def self.included(klass)
11
23
  klass.class_eval do
12
- alias_method_chain :find_record, :timeout
13
- before_find :reset_stale_state
14
- after_find :set_last_request_at
15
- before_save :set_last_request_at
24
+ extend Config
25
+ include InstanceMethods
26
+ before_persisting :reset_stale_state
27
+ after_persisting :enforce_timeout
28
+ attr_accessor :stale_record
16
29
  end
17
30
  end
18
31
 
19
- # This implements the stale functionality when trying to find a session. If the session is stale the record will be cleared, but the session object will still be
20
- # returned. This allows you to perform a current_user_session.stale? query in order to inform your users of why they need to log back in.
21
- def find_record_with_timeout
22
- result = find_record_without_timeout
23
- if result && stale?
24
- self.record = nil
25
- @stale = true
32
+ # Configuration for the timeout feature.
33
+ module Config
34
+ # With acts_as_authentic you get a :logged_in_timeout configuration option. If this is set, after this amount of time has passed the user
35
+ # will be marked as logged out. Obviously, since web based apps are on a per request basis, we have to define a time limit threshold that
36
+ # determines when we consider a user to be "logged out". Meaning, if they login and then leave the website, when do mark them as logged out?
37
+ # I recommend just using this as a fun feature on your website or reports, giving you a ballpark number of users logged in and active. This is
38
+ # not meant to be a dead accurate representation of a users logged in state, since there is really no real way to do this with web based apps.
39
+ # Think about a user that logs in and doesn't log out. There is no action that tells you that the user isn't technically still logged in and
40
+ # active.
41
+ #
42
+ # That being said, you can use that feature to require a new login if their session timesout. Similar to how financial sites work. Just set this option to
43
+ # true and if your record returns true for stale? then they will be required to log back in.
44
+ #
45
+ # Lastly, UserSession.find will still return a object is the session is stale, but you will not get a record. This allows you to determine if the
46
+ # user needs to log back in because their session went stale, or because they just aren't logged in. Just call current_user_session.stale? as your flag.
47
+ #
48
+ # * <tt>Default:</tt> false
49
+ # * <tt>Accepts:</tt> Boolean
50
+ def logout_on_timeout(value = nil)
51
+ config(:logout_on_timeout, value, false)
26
52
  end
27
- result
53
+ alias_method :logout_on_timeout=, :logout_on_timeout
28
54
  end
29
-
30
- # Tells you if the record is stale or not. Meaning the record has timed out. This will only return true if you set logout_on_timeout to true in your configuration.
31
- # Basically how a bank website works. If you aren't active over a certain period of time your session becomes stale and requires you to log back in.
32
- def stale?
33
- @stale == true || (logout_on_timeout? && record && record.logged_out?)
34
- end
35
-
36
- private
37
- def reset_stale_state
38
- @stale = nil
55
+
56
+ # Instance methods for the timeout feature.
57
+ module InstanceMethods
58
+ # Tells you if the record is stale or not. Meaning the record has timed out. This will only return true if you set logout_on_timeout to true in your configuration.
59
+ # Basically how a bank website works. If you aren't active over a certain period of time your session becomes stale and requires you to log back in.
60
+ def stale?
61
+ !stale_record.nil? || (logout_on_timeout? && record && record.logged_out?)
39
62
  end
40
-
41
- def set_last_request_at
42
- if record && record.class.column_names.include?("last_request_at") && (record.last_request_at.blank? || last_request_at_threshold.to_i.seconds.ago >= record.last_request_at)
43
- record.last_request_at = klass.default_timezone == :utc ? Time.now.utc : Time.now
63
+
64
+ private
65
+ def reset_stale_state
66
+ self.stale_record = nil
44
67
  end
45
- end
68
+
69
+ def enforce_timeout
70
+ if stale?
71
+ self.stale_record = record
72
+ self.record = nil
73
+ end
74
+ end
75
+
76
+ def logout_on_timeout?
77
+ self.class.logout_on_timeout == true
78
+ end
79
+ end
46
80
  end
47
81
  end
48
82
  end