nulogy-authlogic 3.1.0.1

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 (129) hide show
  1. data/Gemfile +3 -0
  2. data/Gemfile.lock +62 -0
  3. data/LICENSE +20 -0
  4. data/README.rdoc +250 -0
  5. data/Rakefile +50 -0
  6. data/VERSION.yml +5 -0
  7. data/authlogic.gemspec +192 -0
  8. data/generators/session/session_generator.rb +9 -0
  9. data/generators/session/templates/session.rb +2 -0
  10. data/init.rb +1 -0
  11. data/lib/authlogic.rb +64 -0
  12. data/lib/authlogic/acts_as_authentic/base.rb +109 -0
  13. data/lib/authlogic/acts_as_authentic/email.rb +110 -0
  14. data/lib/authlogic/acts_as_authentic/logged_in_status.rb +59 -0
  15. data/lib/authlogic/acts_as_authentic/login.rb +142 -0
  16. data/lib/authlogic/acts_as_authentic/magic_columns.rb +24 -0
  17. data/lib/authlogic/acts_as_authentic/password.rb +355 -0
  18. data/lib/authlogic/acts_as_authentic/perishable_token.rb +105 -0
  19. data/lib/authlogic/acts_as_authentic/persistence_token.rb +68 -0
  20. data/lib/authlogic/acts_as_authentic/restful_authentication.rb +61 -0
  21. data/lib/authlogic/acts_as_authentic/session_maintenance.rb +139 -0
  22. data/lib/authlogic/acts_as_authentic/single_access_token.rb +65 -0
  23. data/lib/authlogic/acts_as_authentic/validations_scope.rb +32 -0
  24. data/lib/authlogic/authenticates_many/association.rb +42 -0
  25. data/lib/authlogic/authenticates_many/base.rb +54 -0
  26. data/lib/authlogic/controller_adapters/abstract_adapter.rb +67 -0
  27. data/lib/authlogic/controller_adapters/merb_adapter.rb +30 -0
  28. data/lib/authlogic/controller_adapters/rails_adapter.rb +50 -0
  29. data/lib/authlogic/controller_adapters/sinatra_adapter.rb +61 -0
  30. data/lib/authlogic/crypto_providers/aes256.rb +43 -0
  31. data/lib/authlogic/crypto_providers/bcrypt.rb +90 -0
  32. data/lib/authlogic/crypto_providers/md5.rb +34 -0
  33. data/lib/authlogic/crypto_providers/sha1.rb +35 -0
  34. data/lib/authlogic/crypto_providers/sha256.rb +50 -0
  35. data/lib/authlogic/crypto_providers/sha512.rb +50 -0
  36. data/lib/authlogic/crypto_providers/wordpress.rb +43 -0
  37. data/lib/authlogic/i18n.rb +84 -0
  38. data/lib/authlogic/i18n/translator.rb +15 -0
  39. data/lib/authlogic/random.rb +33 -0
  40. data/lib/authlogic/regex.rb +25 -0
  41. data/lib/authlogic/session/activation.rb +58 -0
  42. data/lib/authlogic/session/active_record_trickery.rb +72 -0
  43. data/lib/authlogic/session/base.rb +37 -0
  44. data/lib/authlogic/session/brute_force_protection.rb +96 -0
  45. data/lib/authlogic/session/callbacks.rb +96 -0
  46. data/lib/authlogic/session/cookies.rb +182 -0
  47. data/lib/authlogic/session/existence.rb +93 -0
  48. data/lib/authlogic/session/foundation.rb +77 -0
  49. data/lib/authlogic/session/http_auth.rb +99 -0
  50. data/lib/authlogic/session/id.rb +41 -0
  51. data/lib/authlogic/session/klass.rb +69 -0
  52. data/lib/authlogic/session/magic_columns.rb +95 -0
  53. data/lib/authlogic/session/magic_states.rb +59 -0
  54. data/lib/authlogic/session/params.rb +101 -0
  55. data/lib/authlogic/session/password.rb +240 -0
  56. data/lib/authlogic/session/perishable_token.rb +18 -0
  57. data/lib/authlogic/session/persistence.rb +70 -0
  58. data/lib/authlogic/session/priority_record.rb +34 -0
  59. data/lib/authlogic/session/scopes.rb +101 -0
  60. data/lib/authlogic/session/session.rb +62 -0
  61. data/lib/authlogic/session/timeout.rb +82 -0
  62. data/lib/authlogic/session/unauthorized_record.rb +50 -0
  63. data/lib/authlogic/session/validation.rb +82 -0
  64. data/lib/authlogic/test_case.rb +120 -0
  65. data/lib/authlogic/test_case/mock_controller.rb +55 -0
  66. data/lib/authlogic/test_case/mock_cookie_jar.rb +14 -0
  67. data/lib/authlogic/test_case/mock_logger.rb +10 -0
  68. data/lib/authlogic/test_case/mock_request.rb +19 -0
  69. data/lib/authlogic/test_case/rails_request_adapter.rb +30 -0
  70. data/lib/generators/authlogic/USAGE +8 -0
  71. data/lib/generators/authlogic/session_generator.rb +14 -0
  72. data/lib/generators/authlogic/templates/session.rb +2 -0
  73. data/rails/init.rb +1 -0
  74. data/shoulda_macros/authlogic.rb +69 -0
  75. data/test/acts_as_authentic_test/base_test.rb +18 -0
  76. data/test/acts_as_authentic_test/email_test.rb +116 -0
  77. data/test/acts_as_authentic_test/logged_in_status_test.rb +50 -0
  78. data/test/acts_as_authentic_test/login_test.rb +116 -0
  79. data/test/acts_as_authentic_test/magic_columns_test.rb +27 -0
  80. data/test/acts_as_authentic_test/password_test.rb +236 -0
  81. data/test/acts_as_authentic_test/perishable_token_test.rb +90 -0
  82. data/test/acts_as_authentic_test/persistence_token_test.rb +55 -0
  83. data/test/acts_as_authentic_test/restful_authentication_test.rb +40 -0
  84. data/test/acts_as_authentic_test/session_maintenance_test.rb +84 -0
  85. data/test/acts_as_authentic_test/single_access_test.rb +44 -0
  86. data/test/authenticates_many_test.rb +16 -0
  87. data/test/crypto_provider_test/aes256_test.rb +14 -0
  88. data/test/crypto_provider_test/bcrypt_test.rb +14 -0
  89. data/test/crypto_provider_test/sha1_test.rb +23 -0
  90. data/test/crypto_provider_test/sha256_test.rb +14 -0
  91. data/test/crypto_provider_test/sha512_test.rb +14 -0
  92. data/test/fixtures/companies.yml +5 -0
  93. data/test/fixtures/employees.yml +17 -0
  94. data/test/fixtures/projects.yml +3 -0
  95. data/test/fixtures/users.yml +24 -0
  96. data/test/i18n_test.rb +33 -0
  97. data/test/libs/affiliate.rb +7 -0
  98. data/test/libs/company.rb +6 -0
  99. data/test/libs/employee.rb +7 -0
  100. data/test/libs/employee_session.rb +2 -0
  101. data/test/libs/ldaper.rb +3 -0
  102. data/test/libs/ordered_hash.rb +9 -0
  103. data/test/libs/project.rb +3 -0
  104. data/test/libs/user.rb +5 -0
  105. data/test/libs/user_session.rb +5 -0
  106. data/test/random_test.rb +42 -0
  107. data/test/session_test/activation_test.rb +43 -0
  108. data/test/session_test/active_record_trickery_test.rb +46 -0
  109. data/test/session_test/brute_force_protection_test.rb +101 -0
  110. data/test/session_test/callbacks_test.rb +54 -0
  111. data/test/session_test/cookies_test.rb +136 -0
  112. data/test/session_test/credentials_test.rb +0 -0
  113. data/test/session_test/existence_test.rb +64 -0
  114. data/test/session_test/http_auth_test.rb +57 -0
  115. data/test/session_test/id_test.rb +17 -0
  116. data/test/session_test/klass_test.rb +40 -0
  117. data/test/session_test/magic_columns_test.rb +62 -0
  118. data/test/session_test/magic_states_test.rb +60 -0
  119. data/test/session_test/params_test.rb +53 -0
  120. data/test/session_test/password_test.rb +106 -0
  121. data/test/session_test/perishability_test.rb +15 -0
  122. data/test/session_test/persistence_test.rb +21 -0
  123. data/test/session_test/scopes_test.rb +60 -0
  124. data/test/session_test/session_test.rb +59 -0
  125. data/test/session_test/timeout_test.rb +52 -0
  126. data/test/session_test/unauthorized_record_test.rb +13 -0
  127. data/test/session_test/validation_test.rb +23 -0
  128. data/test/test_helper.rb +168 -0
  129. metadata +252 -0
@@ -0,0 +1,24 @@
1
+ module Authlogic
2
+ module ActsAsAuthentic
3
+ # Magic columns are like ActiveRecord's created_at and updated_at columns. They are "magically" maintained for
4
+ # you. Authlogic has the same thing, but these are maintained on the session side. Please see Authlogic::Session::MagicColumns
5
+ # for more details. This module merely adds validations for the magic columns if they exist.
6
+ module MagicColumns
7
+ def self.included(klass)
8
+ klass.class_eval do
9
+ add_acts_as_authentic_module(Methods)
10
+ end
11
+ end
12
+
13
+ # Methods relating to the magic columns
14
+ module Methods
15
+ def self.included(klass)
16
+ klass.class_eval do
17
+ validates_numericality_of :login_count, :only_integer => :true, :greater_than_or_equal_to => 0, :allow_nil => true if column_names.include?("login_count")
18
+ validates_numericality_of :failed_login_count, :only_integer => :true, :greater_than_or_equal_to => 0, :allow_nil => true if column_names.include?("failed_login_count")
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,355 @@
1
+ module Authlogic
2
+ module ActsAsAuthentic
3
+ # This module has a lot of neat functionality. It is responsible for encrypting your password, salting it, and verifying it.
4
+ # It can also help you transition to a new encryption algorithm. See the Config sub module for configuration options.
5
+ module Password
6
+ def self.included(klass)
7
+ klass.class_eval do
8
+ extend Config
9
+ add_acts_as_authentic_module(Callbacks)
10
+ add_acts_as_authentic_module(Methods)
11
+ end
12
+ end
13
+
14
+ # All configuration for the password aspect of acts_as_authentic.
15
+ module Config
16
+ # The name of the crypted_password field in the database.
17
+ #
18
+ # * <tt>Default:</tt> :crypted_password, :encrypted_password, :password_hash, or :pw_hash
19
+ # * <tt>Accepts:</tt> Symbol
20
+ def crypted_password_field(value = nil)
21
+ rw_config(:crypted_password_field, value, first_column_to_exist(nil, :crypted_password, :encrypted_password, :password_hash, :pw_hash))
22
+ end
23
+ alias_method :crypted_password_field=, :crypted_password_field
24
+
25
+ # The name of the password_salt field in the database.
26
+ #
27
+ # * <tt>Default:</tt> :password_salt, :pw_salt, :salt, nil if none exist
28
+ # * <tt>Accepts:</tt> Symbol
29
+ def password_salt_field(value = nil)
30
+ rw_config(:password_salt_field, value, first_column_to_exist(nil, :password_salt, :pw_salt, :salt))
31
+ end
32
+ alias_method :password_salt_field=, :password_salt_field
33
+
34
+ # Whether or not to require a password confirmation. If you don't want your users to confirm their password
35
+ # just set this to false.
36
+ #
37
+ # * <tt>Default:</tt> true
38
+ # * <tt>Accepts:</tt> Boolean
39
+ def require_password_confirmation(value = nil)
40
+ rw_config(:require_password_confirmation, value, true)
41
+ end
42
+ alias_method :require_password_confirmation=, :require_password_confirmation
43
+
44
+ # By default passwords are required when a record is new or the crypted_password is blank, but if both of these things
45
+ # are met a password is not required. In this case, blank passwords are ignored.
46
+ #
47
+ # Think about a profile page, where the user can edit all of their information, including changing their password.
48
+ # If they do not want to change their password they just leave the fields blank. This will try to set the password to
49
+ # a blank value, in which case is incorrect behavior. As such, Authlogic ignores this. But let's say you have a completely
50
+ # separate page for resetting passwords, you might not want to ignore blank passwords. If this is the case for you, then
51
+ # just set this value to false.
52
+ #
53
+ # * <tt>Default:</tt> true
54
+ # * <tt>Accepts:</tt> Boolean
55
+ def ignore_blank_passwords(value = nil)
56
+ rw_config(:ignore_blank_passwords, value, true)
57
+ end
58
+ alias_method :ignore_blank_passwords=, :ignore_blank_passwords
59
+
60
+ # When calling valid_password?("some pass") do you want to check that password against what's in that object or whats in
61
+ # the database. Take this example:
62
+ #
63
+ # u = User.first
64
+ # u.password = "new pass"
65
+ # u.valid_password?("old pass")
66
+ #
67
+ # Should the last line above return true or false? The record hasn't been saved yet, so most would assume true.
68
+ # Other would assume false. So I let you decide by giving you this option.
69
+ #
70
+ # * <tt>Default:</tt> true
71
+ # * <tt>Accepts:</tt> Boolean
72
+ def check_passwords_against_database(value = nil)
73
+ rw_config(:check_passwords_against_database, value, true)
74
+ end
75
+ alias_method :check_passwords_against_database=, :check_passwords_against_database
76
+
77
+ # Whether or not to validate the password field.
78
+ #
79
+ # * <tt>Default:</tt> true
80
+ # * <tt>Accepts:</tt> Boolean
81
+ def validate_password_field(value = nil)
82
+ rw_config(:validate_password_field, value, true)
83
+ end
84
+ alias_method :validate_password_field=, :validate_password_field
85
+
86
+ # A hash of options for the validates_length_of call for the password field. Allows you to change this however you want.
87
+ #
88
+ # <b>Keep in mind this is ruby. I wanted to keep this as flexible as possible, so you can completely replace the hash or
89
+ # merge options into it. Checkout the convenience function merge_validates_length_of_password_field_options to merge
90
+ # options.</b>
91
+ #
92
+ # * <tt>Default:</tt> {:minimum => 4, :if => :require_password?}
93
+ # * <tt>Accepts:</tt> Hash of options accepted by validates_length_of
94
+ def validates_length_of_password_field_options(value = nil)
95
+ rw_config(:validates_length_of_password_field_options, value, {:minimum => 4, :if => :require_password?})
96
+ end
97
+ alias_method :validates_length_of_password_field_options=, :validates_length_of_password_field_options
98
+
99
+ # A convenience function to merge options into the validates_length_of_login_field_options. So intead of:
100
+ #
101
+ # self.validates_length_of_password_field_options = validates_length_of_password_field_options.merge(:my_option => my_value)
102
+ #
103
+ # You can do this:
104
+ #
105
+ # merge_validates_length_of_password_field_options :my_option => my_value
106
+ def merge_validates_length_of_password_field_options(options = {})
107
+ self.validates_length_of_password_field_options = validates_length_of_password_field_options.merge(options)
108
+ end
109
+
110
+ # A hash of options for the validates_confirmation_of call for the password field. Allows you to change this however you want.
111
+ #
112
+ # <b>Keep in mind this is ruby. I wanted to keep this as flexible as possible, so you can completely replace the hash or
113
+ # merge options into it. Checkout the convenience function merge_validates_length_of_password_field_options to merge
114
+ # options.</b>
115
+ #
116
+ # * <tt>Default:</tt> {:if => :require_password?}
117
+ # * <tt>Accepts:</tt> Hash of options accepted by validates_confirmation_of
118
+ def validates_confirmation_of_password_field_options(value = nil)
119
+ rw_config(:validates_confirmation_of_password_field_options, value, {:if => :require_password?})
120
+ end
121
+ alias_method :validates_confirmation_of_password_field_options=, :validates_confirmation_of_password_field_options
122
+
123
+ # See merge_validates_length_of_password_field_options. The same thing, except for validates_confirmation_of_password_field_options
124
+ def merge_validates_confirmation_of_password_field_options(options = {})
125
+ self.validates_confirmation_of_password_field_options = validates_confirmation_of_password_field_options.merge(options)
126
+ end
127
+
128
+ # A hash of options for the validates_length_of call for the password_confirmation field. Allows you to change this however you want.
129
+ #
130
+ # <b>Keep in mind this is ruby. I wanted to keep this as flexible as possible, so you can completely replace the hash or
131
+ # merge options into it. Checkout the convenience function merge_validates_length_of_password_field_options to merge
132
+ # options.</b>
133
+ #
134
+ # * <tt>Default:</tt> validates_length_of_password_field_options
135
+ # * <tt>Accepts:</tt> Hash of options accepted by validates_length_of
136
+ def validates_length_of_password_confirmation_field_options(value = nil)
137
+ rw_config(:validates_length_of_password_confirmation_field_options, value, validates_length_of_password_field_options)
138
+ end
139
+ alias_method :validates_length_of_password_confirmation_field_options=, :validates_length_of_password_confirmation_field_options
140
+
141
+ # See merge_validates_length_of_password_field_options. The same thing, except for validates_length_of_password_confirmation_field_options
142
+ def merge_validates_length_of_password_confirmation_field_options(options = {})
143
+ self.validates_length_of_password_confirmation_field_options = validates_length_of_password_confirmation_field_options.merge(options)
144
+ end
145
+
146
+ # The class you want to use to encrypt and verify your encrypted passwords. See the Authlogic::CryptoProviders module for more info
147
+ # on the available methods and how to create your own.
148
+ #
149
+ # * <tt>Default:</tt> CryptoProviders::Sha512
150
+ # * <tt>Accepts:</tt> Class
151
+ def crypto_provider(value = nil)
152
+ rw_config(:crypto_provider, value, CryptoProviders::Sha512)
153
+ end
154
+ alias_method :crypto_provider=, :crypto_provider
155
+
156
+ # Let's say you originally encrypted your passwords with Sha1. Sha1 is starting to join the party with MD5 and you want to switch
157
+ # to something stronger. No problem, just specify your new and improved algorithm with the crypt_provider option and then let
158
+ # Authlogic know you are transitioning from Sha1 using this option. Authlogic will take care of everything, including transitioning
159
+ # your users to the new algorithm. The next time a user logs in, they will be granted access using the old algorithm and their
160
+ # password will be resaved with the new algorithm. All new users will obviously use the new algorithm as well.
161
+ #
162
+ # Lastly, if you want to transition again, you can pass an array of crypto providers. So you can transition from as many algorithms
163
+ # as you want.
164
+ #
165
+ # * <tt>Default:</tt> nil
166
+ # * <tt>Accepts:</tt> Class or Array
167
+ def transition_from_crypto_providers(value = nil)
168
+ rw_config(:transition_from_crypto_providers, (!value.nil? && [value].flatten.compact) || value, [])
169
+ end
170
+ alias_method :transition_from_crypto_providers=, :transition_from_crypto_providers
171
+ end
172
+
173
+ # Callbacks / hooks to allow other modules to modify the behavior of this module.
174
+ module Callbacks
175
+ METHODS = [
176
+ "before_password_set", "after_password_set",
177
+ "before_password_verification", "after_password_verification"
178
+ ]
179
+
180
+ def self.included(klass)
181
+ return if klass.crypted_password_field.nil?
182
+ klass.define_callbacks *METHODS
183
+
184
+ # If Rails 3, support the new callback syntax
185
+ if klass.send(klass.respond_to?(:singleton_class) ? :singleton_class : :metaclass).method_defined?(:set_callback)
186
+ METHODS.each do |method|
187
+ klass.class_eval <<-"end_eval", __FILE__, __LINE__
188
+ def self.#{method}(*methods, &block)
189
+ set_callback :#{method}, *methods, &block
190
+ end
191
+ end_eval
192
+ end
193
+ end
194
+ end
195
+
196
+ private
197
+ METHODS.each do |method|
198
+ class_eval <<-"end_eval", __FILE__, __LINE__
199
+ def #{method}
200
+ run_callbacks(:#{method}) { |result, object| result == false }
201
+ end
202
+ end_eval
203
+ end
204
+ end
205
+
206
+ # The methods related to the password field.
207
+ module Methods
208
+ def self.included(klass)
209
+ return if klass.crypted_password_field.nil?
210
+
211
+ klass.class_eval do
212
+ include InstanceMethods
213
+
214
+ if validate_password_field
215
+ validates_length_of :password, validates_length_of_password_field_options
216
+
217
+ if require_password_confirmation
218
+ validates_confirmation_of :password, validates_confirmation_of_password_field_options
219
+ validates_length_of :password_confirmation, validates_length_of_password_confirmation_field_options
220
+ end
221
+ end
222
+
223
+ after_save :reset_password_changed
224
+ end
225
+ end
226
+
227
+ module InstanceMethods
228
+ # The password
229
+ def password
230
+ @password
231
+ end
232
+
233
+ # This is a virtual method. Once a password is passed to it, it will create new password salt as well as encrypt
234
+ # the password.
235
+ def password=(pass)
236
+ return if ignore_blank_passwords? && pass.blank?
237
+ before_password_set
238
+ @password = pass
239
+ send("#{password_salt_field}=", Authlogic::Random.friendly_token) if password_salt_field
240
+ send("#{crypted_password_field}=", crypto_provider.encrypt(*encrypt_arguments(@password, false, act_like_restful_authentication? ? :restful_authentication : nil)))
241
+ @password_changed = true
242
+ after_password_set
243
+ end
244
+
245
+ # Accepts a raw password to determine if it is the correct password or not. Notice the second argument. That defaults to the value of
246
+ # check_passwords_against_database. See that method for more information, but basically it just tells Authlogic to check the password
247
+ # against the value in the database or the value in the object.
248
+ def valid_password?(attempted_password, check_against_database = check_passwords_against_database?)
249
+ crypted = check_against_database && send("#{crypted_password_field}_changed?") ? send("#{crypted_password_field}_was") : send(crypted_password_field)
250
+ return false if attempted_password.blank? || crypted.blank?
251
+ before_password_verification
252
+
253
+ crypto_providers.each_with_index do |encryptor, index|
254
+ # The arguments_type of for the transitioning from restful_authentication
255
+ arguments_type = (act_like_restful_authentication? && index == 0) ||
256
+ (transition_from_restful_authentication? && index > 0 && encryptor == Authlogic::CryptoProviders::Sha1) ?
257
+ :restful_authentication : nil
258
+
259
+ if encryptor.matches?(crypted, *encrypt_arguments(attempted_password, check_against_database, arguments_type))
260
+ transition_password(attempted_password) if transition_password?(index, encryptor, crypted, check_against_database)
261
+ after_password_verification
262
+ return true
263
+ end
264
+ end
265
+
266
+ false
267
+ end
268
+
269
+ # Resets the password to a random friendly token.
270
+ def reset_password
271
+ friendly_token = Authlogic::Random.friendly_token
272
+ self.password = friendly_token
273
+ self.password_confirmation = friendly_token
274
+ end
275
+ alias_method :randomize_password, :reset_password
276
+
277
+ # Resets the password to a random friendly token and then saves the record.
278
+ def reset_password!
279
+ reset_password
280
+ save_without_session_maintenance(:validate => false)
281
+ end
282
+ alias_method :randomize_password!, :reset_password!
283
+
284
+ private
285
+ def check_passwords_against_database?
286
+ self.class.check_passwords_against_database == true
287
+ end
288
+
289
+ def crypto_providers
290
+ [crypto_provider] + transition_from_crypto_providers
291
+ end
292
+
293
+ def encrypt_arguments(raw_password, check_against_database, arguments_type = nil)
294
+ salt = nil
295
+ salt = (check_against_database && send("#{password_salt_field}_changed?") ? send("#{password_salt_field}_was") : send(password_salt_field)) if password_salt_field
296
+
297
+ case arguments_type
298
+ when :restful_authentication
299
+ [REST_AUTH_SITE_KEY, salt, raw_password, REST_AUTH_SITE_KEY].compact
300
+ else
301
+ [raw_password, salt].compact
302
+ end
303
+ end
304
+
305
+ # Determines if we need to tranisiton the password.
306
+ # If the index > 0 then we are using an "transition from" crypto provider.
307
+ # If the encryptor has a cost and the cost it outdated.
308
+ # If we aren't using database values
309
+ # If we are using database values, only if the password hasnt change so we don't overwrite any changes
310
+ def transition_password?(index, encryptor, crypted, check_against_database)
311
+ (index > 0 || (encryptor.respond_to?(:cost_matches?) && !encryptor.cost_matches?(send(crypted_password_field)))) &&
312
+ (!check_against_database || !send("#{crypted_password_field}_changed?"))
313
+ end
314
+
315
+ def transition_password(attempted_password)
316
+ self.password = attempted_password
317
+ save(:validate => false)
318
+ end
319
+
320
+ def require_password?
321
+ new_record? || password_changed? || send(crypted_password_field).blank?
322
+ end
323
+
324
+ def ignore_blank_passwords?
325
+ self.class.ignore_blank_passwords == true
326
+ end
327
+
328
+ def password_changed?
329
+ @password_changed == true
330
+ end
331
+
332
+ def reset_password_changed
333
+ @password_changed = nil
334
+ end
335
+
336
+ def crypted_password_field
337
+ self.class.crypted_password_field
338
+ end
339
+
340
+ def password_salt_field
341
+ self.class.password_salt_field
342
+ end
343
+
344
+ def crypto_provider
345
+ self.class.crypto_provider
346
+ end
347
+
348
+ def transition_from_crypto_providers
349
+ self.class.transition_from_crypto_providers
350
+ end
351
+ end
352
+ end
353
+ end
354
+ end
355
+ end
@@ -0,0 +1,105 @@
1
+ module Authlogic
2
+ module ActsAsAuthentic
3
+ # This provides a handy token that is "perishable". Meaning the token is only good for a certain amount of time. This is perfect for
4
+ # resetting password, confirming accounts, etc. Typically during these actions you send them this token in via their email. Once they
5
+ # use the token and do what they need to do, that token should expire. Don't worry about maintaining this, changing it, or expiring it
6
+ # yourself. Authlogic does all of this for you. See the sub modules for all of the tools Authlogic provides to you.
7
+ module PerishableToken
8
+ def self.included(klass)
9
+ klass.class_eval do
10
+ extend Config
11
+ add_acts_as_authentic_module(Methods)
12
+ end
13
+ end
14
+
15
+ # Change how the perishable token works.
16
+ module Config
17
+ # When using the find_using_perishable_token method the token can expire. If the token is expired, no
18
+ # record will be returned. Use this option to specify how long the token is valid for.
19
+ #
20
+ # * <tt>Default:</tt> 10.minutes
21
+ # * <tt>Accepts:</tt> Fixnum
22
+ def perishable_token_valid_for(value = nil)
23
+ rw_config(:perishable_token_valid_for, (!value.nil? && value.to_i) || value, 10.minutes.to_i)
24
+ end
25
+ alias_method :perishable_token_valid_for=, :perishable_token_valid_for
26
+
27
+ # Authlogic tries to expire and change the perishable token as much as possible, without comprising
28
+ # it's purpose. This is for security reasons. If you want to manage it yourself, you can stop
29
+ # Authlogic from getting your in way by setting this to true.
30
+ #
31
+ # * <tt>Default:</tt> false
32
+ # * <tt>Accepts:</tt> Boolean
33
+ def disable_perishable_token_maintenance(value = nil)
34
+ rw_config(:disable_perishable_token_maintenance, value, false)
35
+ end
36
+ alias_method :disable_perishable_token_maintenance=, :disable_perishable_token_maintenance
37
+ end
38
+
39
+ # All methods relating to the perishable token.
40
+ module Methods
41
+ def self.included(klass)
42
+ return if !klass.column_names.include?("perishable_token")
43
+
44
+ klass.class_eval do
45
+ extend ClassMethods
46
+ include InstanceMethods
47
+
48
+ validates_uniqueness_of :perishable_token, :if => :perishable_token_changed?
49
+ before_save :reset_perishable_token, :unless => :disable_perishable_token_maintenance?
50
+ end
51
+ end
52
+
53
+ # Class level methods for the perishable token
54
+ module ClassMethods
55
+ # Use this methdo to find a record with a perishable token. This method does 2 things for you:
56
+ #
57
+ # 1. It ignores blank tokens
58
+ # 2. It enforces the perishable_token_valid_for configuration option.
59
+ #
60
+ # If you want to use a different timeout value, just pass it as the second parameter:
61
+ #
62
+ # User.find_using_perishable_token(token, 1.hour)
63
+ def find_using_perishable_token(token, age = self.perishable_token_valid_for)
64
+ return if token.blank?
65
+ age = age.to_i
66
+
67
+ conditions_sql = "perishable_token = ?"
68
+ conditions_subs = [token]
69
+
70
+ if column_names.include?("updated_at") && age > 0
71
+ conditions_sql += " and updated_at > ?"
72
+ conditions_subs << age.seconds.ago
73
+ end
74
+
75
+ where(conditions_sql, *conditions_subs).first
76
+ end
77
+
78
+ # This method will raise ActiveRecord::NotFound if no record is found.
79
+ def find_using_perishable_token!(token, age = perishable_token_valid_for)
80
+ find_using_perishable_token(token, age) || raise(ActiveRecord::RecordNotFound)
81
+ end
82
+ end
83
+
84
+ # Instance level methods for the perishable token.
85
+ module InstanceMethods
86
+ # Resets the perishable token to a random friendly token.
87
+ def reset_perishable_token
88
+ self.perishable_token = Random.friendly_token
89
+ end
90
+
91
+ # Same as reset_perishable_token, but then saves the record afterwards.
92
+ def reset_perishable_token!
93
+ reset_perishable_token
94
+ save_without_session_maintenance(:validate => false)
95
+ end
96
+
97
+ # A convenience method based on the disable_perishable_token_maintenance configuration option.
98
+ def disable_perishable_token_maintenance?
99
+ self.class.disable_perishable_token_maintenance == true
100
+ end
101
+ end
102
+ end
103
+ end
104
+ end
105
+ end