authlogic 3.8.0 → 4.5.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.github/ISSUE_TEMPLATE/bug_report.md +28 -0
- data/.github/ISSUE_TEMPLATE/feature_proposal.md +32 -0
- data/.github/triage.md +86 -0
- data/.gitignore +4 -3
- data/.rubocop.yml +109 -9
- data/.rubocop_todo.yml +38 -355
- data/.travis.yml +11 -35
- data/CHANGELOG.md +345 -2
- data/CONTRIBUTING.md +45 -14
- data/Gemfile +3 -2
- data/README.md +244 -90
- data/Rakefile +10 -10
- data/UPGRADING.md +22 -0
- data/authlogic.gemspec +34 -21
- data/doc/use_normal_rails_validation.md +82 -0
- data/gemfiles/Gemfile.rails-4.2.x +6 -0
- data/{test/gemfiles → gemfiles}/Gemfile.rails-5.1.x +2 -2
- data/{test/gemfiles → gemfiles}/Gemfile.rails-5.2.x +2 -2
- data/lib/authlogic/acts_as_authentic/base.rb +36 -24
- data/lib/authlogic/acts_as_authentic/email.rb +65 -31
- data/lib/authlogic/acts_as_authentic/logged_in_status.rb +14 -9
- data/lib/authlogic/acts_as_authentic/login.rb +61 -45
- data/lib/authlogic/acts_as_authentic/magic_columns.rb +6 -6
- data/lib/authlogic/acts_as_authentic/password.rb +267 -146
- data/lib/authlogic/acts_as_authentic/perishable_token.rb +24 -19
- data/lib/authlogic/acts_as_authentic/persistence_token.rb +10 -15
- data/lib/authlogic/acts_as_authentic/queries/find_with_case.rb +67 -0
- data/lib/authlogic/acts_as_authentic/restful_authentication.rb +50 -14
- data/lib/authlogic/acts_as_authentic/session_maintenance.rb +88 -60
- data/lib/authlogic/acts_as_authentic/single_access_token.rb +23 -11
- data/lib/authlogic/acts_as_authentic/validations_scope.rb +9 -6
- data/lib/authlogic/authenticates_many/association.rb +7 -7
- data/lib/authlogic/authenticates_many/base.rb +37 -21
- data/lib/authlogic/config.rb +21 -10
- data/lib/authlogic/controller_adapters/abstract_adapter.rb +38 -11
- data/lib/authlogic/controller_adapters/rack_adapter.rb +9 -5
- data/lib/authlogic/controller_adapters/rails_adapter.rb +12 -7
- data/lib/authlogic/controller_adapters/sinatra_adapter.rb +2 -2
- data/lib/authlogic/crypto_providers/aes256.rb +37 -32
- data/lib/authlogic/crypto_providers/bcrypt.rb +21 -15
- data/lib/authlogic/crypto_providers/md5.rb +4 -2
- data/lib/authlogic/crypto_providers/scrypt.rb +22 -17
- data/lib/authlogic/crypto_providers/sha1.rb +11 -5
- data/lib/authlogic/crypto_providers/sha256.rb +13 -9
- data/lib/authlogic/crypto_providers/sha512.rb +0 -21
- data/lib/authlogic/crypto_providers/wordpress.rb +32 -3
- data/lib/authlogic/crypto_providers.rb +91 -0
- data/lib/authlogic/i18n.rb +26 -19
- data/lib/authlogic/random.rb +10 -28
- data/lib/authlogic/regex.rb +59 -28
- data/lib/authlogic/session/activation.rb +10 -7
- data/lib/authlogic/session/active_record_trickery.rb +13 -9
- data/lib/authlogic/session/base.rb +15 -4
- data/lib/authlogic/session/brute_force_protection.rb +40 -33
- data/lib/authlogic/session/callbacks.rb +94 -46
- data/lib/authlogic/session/cookies.rb +130 -45
- data/lib/authlogic/session/existence.rb +21 -11
- data/lib/authlogic/session/foundation.rb +64 -14
- data/lib/authlogic/session/http_auth.rb +35 -28
- data/lib/authlogic/session/id.rb +9 -4
- data/lib/authlogic/session/klass.rb +15 -12
- data/lib/authlogic/session/magic_columns.rb +58 -55
- data/lib/authlogic/session/magic_states.rb +25 -19
- data/lib/authlogic/session/params.rb +42 -28
- data/lib/authlogic/session/password.rb +130 -120
- data/lib/authlogic/session/perishable_token.rb +5 -4
- data/lib/authlogic/session/persistence.rb +18 -12
- data/lib/authlogic/session/priority_record.rb +15 -12
- data/lib/authlogic/session/scopes.rb +51 -32
- data/lib/authlogic/session/session.rb +38 -28
- data/lib/authlogic/session/timeout.rb +13 -13
- data/lib/authlogic/session/unauthorized_record.rb +18 -13
- data/lib/authlogic/session/validation.rb +9 -9
- data/lib/authlogic/test_case/mock_controller.rb +5 -4
- data/lib/authlogic/test_case/mock_cookie_jar.rb +47 -3
- data/lib/authlogic/test_case/mock_request.rb +6 -3
- data/lib/authlogic/test_case/rails_request_adapter.rb +3 -2
- data/lib/authlogic/test_case.rb +70 -2
- data/lib/authlogic/version.rb +21 -0
- data/lib/authlogic.rb +51 -49
- data/test/acts_as_authentic_test/base_test.rb +3 -1
- data/test/acts_as_authentic_test/email_test.rb +43 -42
- data/test/acts_as_authentic_test/logged_in_status_test.rb +6 -4
- data/test/acts_as_authentic_test/login_test.rb +77 -80
- data/test/acts_as_authentic_test/magic_columns_test.rb +3 -1
- data/test/acts_as_authentic_test/password_test.rb +51 -37
- data/test/acts_as_authentic_test/perishable_token_test.rb +13 -5
- data/test/acts_as_authentic_test/persistence_token_test.rb +7 -1
- data/test/acts_as_authentic_test/restful_authentication_test.rb +14 -3
- data/test/acts_as_authentic_test/session_maintenance_test.rb +69 -15
- data/test/acts_as_authentic_test/single_access_test.rb +3 -1
- data/test/adapter_test.rb +23 -0
- data/test/authenticates_many_test.rb +3 -1
- data/test/config_test.rb +11 -9
- data/test/crypto_provider_test/aes256_test.rb +3 -1
- data/test/crypto_provider_test/bcrypt_test.rb +3 -1
- data/test/crypto_provider_test/scrypt_test.rb +3 -1
- data/test/crypto_provider_test/sha1_test.rb +3 -1
- data/test/crypto_provider_test/sha256_test.rb +3 -1
- data/test/crypto_provider_test/sha512_test.rb +3 -1
- data/test/crypto_provider_test/wordpress_test.rb +26 -0
- data/test/fixtures/companies.yml +2 -2
- data/test/fixtures/employees.yml +1 -1
- data/test/i18n_test.rb +6 -4
- data/test/libs/affiliate.rb +2 -0
- data/test/libs/company.rb +4 -2
- data/test/libs/employee.rb +2 -0
- data/test/libs/employee_session.rb +2 -0
- data/test/libs/ldaper.rb +2 -0
- data/test/libs/project.rb +2 -0
- data/test/libs/user.rb +2 -0
- data/test/libs/user_session.rb +4 -2
- data/test/random_test.rb +10 -38
- data/test/session_test/activation_test.rb +3 -1
- data/test/session_test/active_record_trickery_test.rb +7 -4
- data/test/session_test/brute_force_protection_test.rb +11 -9
- data/test/session_test/callbacks_test.rb +12 -4
- data/test/session_test/cookies_test.rb +48 -5
- data/test/session_test/existence_test.rb +18 -5
- data/test/session_test/foundation_test.rb +19 -1
- data/test/session_test/http_auth_test.rb +11 -7
- data/test/session_test/id_test.rb +3 -1
- data/test/session_test/klass_test.rb +3 -1
- data/test/session_test/magic_columns_test.rb +13 -13
- data/test/session_test/magic_states_test.rb +3 -1
- data/test/session_test/params_test.rb +13 -5
- data/test/session_test/password_test.rb +10 -8
- data/test/session_test/perishability_test.rb +3 -1
- data/test/session_test/persistence_test.rb +4 -1
- data/test/session_test/scopes_test.rb +16 -8
- data/test/session_test/session_test.rb +6 -4
- data/test/session_test/timeout_test.rb +4 -2
- data/test/session_test/unauthorized_record_test.rb +4 -2
- data/test/session_test/validation_test.rb +3 -1
- data/test/test_helper.rb +84 -45
- metadata +87 -73
- data/.github/ISSUE_TEMPLATE.md +0 -13
- data/test/gemfiles/Gemfile.rails-3.2.x +0 -7
- data/test/gemfiles/Gemfile.rails-4.0.x +0 -7
- data/test/gemfiles/Gemfile.rails-4.1.x +0 -7
- data/test/gemfiles/Gemfile.rails-4.2.x +0 -7
- data/test/gemfiles/Gemfile.rails-5.0.x +0 -6
@@ -6,7 +6,7 @@ module Authlogic
|
|
6
6
|
klass.class_eval do
|
7
7
|
extend Config
|
8
8
|
include InstanceMethods
|
9
|
-
validate :validate_by_password, :
|
9
|
+
validate :validate_by_password, if: :authenticating_with_password?
|
10
10
|
|
11
11
|
class << self
|
12
12
|
attr_accessor :configured_password_methods
|
@@ -119,7 +119,7 @@ module Authlogic
|
|
119
119
|
# should be an instance method. It should also be prepared to accept a
|
120
120
|
# raw password and a crytped password.
|
121
121
|
#
|
122
|
-
# * <tt>Default:</tt> "valid_password?"
|
122
|
+
# * <tt>Default:</tt> "valid_password?" defined in acts_as_authentic/password.rb
|
123
123
|
# * <tt>Accepts:</tt> Symbol or String
|
124
124
|
def verify_password_method(value = nil)
|
125
125
|
rw_config(:verify_password_method, value, "valid_password?")
|
@@ -127,45 +127,14 @@ module Authlogic
|
|
127
127
|
alias_method :verify_password_method=, :verify_password_method
|
128
128
|
end
|
129
129
|
|
130
|
-
# Password
|
130
|
+
# Password related instance methods
|
131
131
|
module InstanceMethods
|
132
|
-
E_AC_PARAMETERS = <<-STR.strip_heredoc.freeze
|
133
|
-
You have passed an ActionController::Parameters to Authlogic 3. That's
|
134
|
-
OK for now, but in Authlogic 4, it will raise an error. Please
|
135
|
-
replace:
|
136
|
-
|
137
|
-
UserSession.new(user_session_params)
|
138
|
-
UserSession.create(user_session_params)
|
139
|
-
|
140
|
-
with
|
141
|
-
|
142
|
-
UserSession.new(user_session_params.to_h)
|
143
|
-
UserSession.create(user_session_params.to_h)
|
144
|
-
|
145
|
-
And don't forget to `permit`!
|
146
|
-
|
147
|
-
During the transition of rails to Strong Parameters, it has been
|
148
|
-
common for Authlogic users to forget to `permit` their params. They
|
149
|
-
would pass their params into Authlogic, we'd call `to_h`, and they'd
|
150
|
-
be surprised when authentication failed.
|
151
|
-
|
152
|
-
In 2018, people are still making this mistake. We'd like to help them
|
153
|
-
and make authlogic a little simpler at the same time, so in Authlogic
|
154
|
-
3.7.0, we deprecated the use of ActionController::Parameters.
|
155
|
-
|
156
|
-
We discussed this issue thoroughly between late 2016 and early
|
157
|
-
2018. Notable discussions include:
|
158
|
-
|
159
|
-
- https://github.com/binarylogic/authlogic/issues/512
|
160
|
-
- https://github.com/binarylogic/authlogic/pull/558
|
161
|
-
- https://github.com/binarylogic/authlogic/pull/577
|
162
|
-
STR
|
163
|
-
|
164
132
|
def initialize(*args)
|
165
|
-
|
133
|
+
unless self.class.configured_password_methods
|
166
134
|
configure_password_methods
|
167
135
|
self.class.configured_password_methods = true
|
168
136
|
end
|
137
|
+
instance_variable_set("@#{password_field}", nil)
|
169
138
|
super
|
170
139
|
end
|
171
140
|
|
@@ -184,14 +153,23 @@ module Authlogic
|
|
184
153
|
|
185
154
|
# Accepts the login_field / password_field credentials combination in
|
186
155
|
# hash form.
|
156
|
+
#
|
157
|
+
# You must pass an actual Hash, `ActionController::Parameters` is
|
158
|
+
# specifically not allowed.
|
159
|
+
#
|
160
|
+
# See `Authlogic::Session::Foundation#credentials=` for an overview of
|
161
|
+
# all method signatures.
|
187
162
|
def credentials=(value)
|
188
163
|
super
|
189
|
-
values =
|
190
|
-
|
164
|
+
values = Array.wrap(value)
|
191
165
|
if values.first.is_a?(Hash)
|
192
|
-
|
193
|
-
|
194
|
-
|
166
|
+
sliced = values
|
167
|
+
.first
|
168
|
+
.with_indifferent_access
|
169
|
+
.slice(login_field, password_field)
|
170
|
+
sliced.each do |field, val|
|
171
|
+
next if val.blank?
|
172
|
+
send("#{field}=", val)
|
195
173
|
end
|
196
174
|
end
|
197
175
|
end
|
@@ -202,106 +180,138 @@ module Authlogic
|
|
202
180
|
|
203
181
|
private
|
204
182
|
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
self.class.send(:define_method, password_field) {} if !respond_to?(password_field)
|
214
|
-
|
215
|
-
# The password should not be accessible publicly. This way forms
|
216
|
-
# using form_for don't fill the password with the attempted
|
217
|
-
# password. To prevent this we just create this method that is
|
218
|
-
# private.
|
219
|
-
self.class.class_eval <<-"end_eval", __FILE__, __LINE__
|
220
|
-
private
|
221
|
-
def protected_#{password_field}
|
222
|
-
@#{password_field}
|
223
|
-
end
|
224
|
-
end_eval
|
225
|
-
end
|
183
|
+
def add_invalid_password_error
|
184
|
+
if generalize_credentials_error_messages?
|
185
|
+
add_general_credentials_error
|
186
|
+
else
|
187
|
+
errors.add(
|
188
|
+
password_field,
|
189
|
+
I18n.t("error_messages.password_invalid", default: "is not valid")
|
190
|
+
)
|
226
191
|
end
|
192
|
+
end
|
227
193
|
|
228
|
-
|
229
|
-
|
194
|
+
def add_login_not_found_error
|
195
|
+
if generalize_credentials_error_messages?
|
196
|
+
add_general_credentials_error
|
197
|
+
else
|
198
|
+
errors.add(
|
199
|
+
login_field,
|
200
|
+
I18n.t("error_messages.login_not_found", default: "is not valid")
|
201
|
+
)
|
230
202
|
end
|
203
|
+
end
|
231
204
|
|
232
|
-
|
233
|
-
|
205
|
+
def authenticating_with_password?
|
206
|
+
login_field && (!send(login_field).nil? || !send("protected_#{password_field}").nil?)
|
207
|
+
end
|
234
208
|
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
if send("protected_#{password_field}").blank?
|
240
|
-
errors.add(password_field, I18n.t('error_messages.password_blank', :default => "cannot be blank"))
|
241
|
-
end
|
242
|
-
return if errors.count > 0
|
209
|
+
def configure_password_methods
|
210
|
+
define_login_field_methods
|
211
|
+
define_password_field_methods
|
212
|
+
end
|
243
213
|
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
return
|
250
|
-
end
|
214
|
+
def define_login_field_methods
|
215
|
+
return unless login_field
|
216
|
+
self.class.send(:attr_writer, login_field) unless respond_to?("#{login_field}=")
|
217
|
+
self.class.send(:attr_reader, login_field) unless respond_to?(login_field)
|
218
|
+
end
|
251
219
|
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
220
|
+
def define_password_field_methods
|
221
|
+
return unless password_field
|
222
|
+
self.class.send(:attr_writer, password_field) unless respond_to?("#{password_field}=")
|
223
|
+
self.class.send(:define_method, password_field) {} unless respond_to?(password_field)
|
224
|
+
|
225
|
+
# The password should not be accessible publicly. This way forms
|
226
|
+
# using form_for don't fill the password with the attempted
|
227
|
+
# password. To prevent this we just create this method that is
|
228
|
+
# private.
|
229
|
+
self.class.class_eval(
|
230
|
+
<<-EOS, __FILE__, __LINE__ + 1
|
231
|
+
private
|
232
|
+
def protected_#{password_field}
|
233
|
+
@#{password_field}
|
234
|
+
end
|
235
|
+
EOS
|
236
|
+
)
|
237
|
+
end
|
261
238
|
|
262
|
-
|
239
|
+
# In keeping with the metaphor of ActiveRecord, verification of the
|
240
|
+
# password is referred to as a "validation".
|
241
|
+
def validate_by_password
|
242
|
+
self.invalid_password = false
|
243
|
+
validate_by_password__blank_fields
|
244
|
+
return if errors.count > 0
|
245
|
+
self.attempted_record = search_for_record(find_by_login_method, send(login_field))
|
246
|
+
if attempted_record.blank?
|
247
|
+
add_login_not_found_error
|
248
|
+
return
|
249
|
+
end
|
250
|
+
validate_by_password__invalid_password
|
251
|
+
end
|
263
252
|
|
264
|
-
|
265
|
-
|
253
|
+
def validate_by_password__blank_fields
|
254
|
+
if send(login_field).blank?
|
255
|
+
errors.add(
|
256
|
+
login_field,
|
257
|
+
I18n.t("error_messages.login_blank", default: "cannot be blank")
|
258
|
+
)
|
259
|
+
end
|
260
|
+
if send("protected_#{password_field}").blank?
|
261
|
+
errors.add(
|
262
|
+
password_field,
|
263
|
+
I18n.t("error_messages.password_blank", default: "cannot be blank")
|
264
|
+
)
|
266
265
|
end
|
266
|
+
end
|
267
267
|
|
268
|
-
|
269
|
-
|
268
|
+
# Verify the password, usually using `valid_password?` in
|
269
|
+
# `acts_as_authentic/password.rb`. If it cannot be verified, we
|
270
|
+
# refer to it as "invalid".
|
271
|
+
def validate_by_password__invalid_password
|
272
|
+
unless attempted_record.send(
|
273
|
+
verify_password_method,
|
274
|
+
send("protected_#{password_field}")
|
275
|
+
)
|
276
|
+
self.invalid_password = true
|
277
|
+
add_invalid_password_error
|
270
278
|
end
|
279
|
+
end
|
280
|
+
|
281
|
+
attr_accessor :invalid_password
|
282
|
+
|
283
|
+
def find_by_login_method
|
284
|
+
self.class.find_by_login_method
|
285
|
+
end
|
286
|
+
|
287
|
+
def login_field
|
288
|
+
self.class.login_field
|
289
|
+
end
|
271
290
|
|
272
|
-
|
273
|
-
|
291
|
+
def add_general_credentials_error
|
292
|
+
error_message =
|
274
293
|
if self.class.generalize_credentials_error_messages.is_a? String
|
275
294
|
self.class.generalize_credentials_error_messages
|
276
295
|
else
|
277
296
|
"#{login_field.to_s.humanize}/Password combination is not valid"
|
278
297
|
end
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
end
|
298
|
+
errors.add(
|
299
|
+
:base,
|
300
|
+
I18n.t("error_messages.general_credentials_error", default: error_message)
|
301
|
+
)
|
302
|
+
end
|
285
303
|
|
286
|
-
|
287
|
-
|
288
|
-
|
304
|
+
def generalize_credentials_error_messages?
|
305
|
+
self.class.generalize_credentials_error_messages
|
306
|
+
end
|
289
307
|
|
290
|
-
|
291
|
-
|
292
|
-
|
308
|
+
def password_field
|
309
|
+
self.class.password_field
|
310
|
+
end
|
293
311
|
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
def parse_param_val(value)
|
298
|
-
if value.first.class.name == "ActionController::Parameters"
|
299
|
-
ActiveSupport::Deprecation.warn(E_AC_PARAMETERS)
|
300
|
-
[value.first.to_h]
|
301
|
-
else
|
302
|
-
value.is_a?(Array) ? value : [value]
|
303
|
-
end
|
304
|
-
end
|
312
|
+
def verify_password_method
|
313
|
+
self.class.verify_password_method
|
314
|
+
end
|
305
315
|
end
|
306
316
|
end
|
307
317
|
end
|
@@ -13,11 +13,12 @@ module Authlogic
|
|
13
13
|
|
14
14
|
private
|
15
15
|
|
16
|
-
|
17
|
-
|
18
|
-
record.
|
19
|
-
|
16
|
+
def reset_perishable_token!
|
17
|
+
if record.respond_to?(:reset_perishable_token) &&
|
18
|
+
!record.disable_perishable_token_maintenance?
|
19
|
+
record.reset_perishable_token
|
20
20
|
end
|
21
|
+
end
|
21
22
|
end
|
22
23
|
end
|
23
24
|
end
|
@@ -10,9 +10,10 @@ module Authlogic
|
|
10
10
|
end
|
11
11
|
|
12
12
|
module ClassMethods
|
13
|
-
# This is how you persist a session. This finds the record for the
|
14
|
-
# a variety of methods. It basically tries to "log
|
15
|
-
# to explicitly log in. Check out
|
13
|
+
# This is how you persist a session. This finds the record for the
|
14
|
+
# current session using a variety of methods. It basically tries to "log
|
15
|
+
# in" the user without the user having to explicitly log in. Check out
|
16
|
+
# the other Authlogic::Session modules for more information.
|
16
17
|
#
|
17
18
|
# The best way to use this method is something like:
|
18
19
|
#
|
@@ -28,30 +29,35 @@ module Authlogic
|
|
28
29
|
# @current_user = current_user_session && current_user_session.user
|
29
30
|
# end
|
30
31
|
#
|
31
|
-
# Also, this method accepts a single parameter as the id, to find
|
32
|
+
# Also, this method accepts a single parameter as the id, to find
|
33
|
+
# session that you marked with an id:
|
32
34
|
#
|
33
35
|
# UserSession.find(:secure)
|
34
36
|
#
|
35
37
|
# See the id method for more information on ids.
|
36
38
|
def find(id = nil, priority_record = nil)
|
37
|
-
session = new({ :
|
39
|
+
session = new({ priority_record: priority_record }, id)
|
38
40
|
session.priority_record = priority_record
|
39
41
|
if session.persisting?
|
40
42
|
session
|
41
|
-
else
|
42
|
-
nil
|
43
43
|
end
|
44
44
|
end
|
45
45
|
end
|
46
46
|
|
47
47
|
module InstanceMethods
|
48
|
-
#
|
49
|
-
#
|
50
|
-
#
|
48
|
+
# Returns boolean indicating if the session is being persisted or not,
|
49
|
+
# meaning the user does not have to explicitly log in in order to be
|
50
|
+
# logged in.
|
51
|
+
#
|
52
|
+
# If the session has no associated record, it will try to find a record
|
53
|
+
# and persist the session.
|
54
|
+
#
|
55
|
+
# This is the method that the class level method find uses to ultimately
|
56
|
+
# persist the session.
|
51
57
|
def persisting?
|
52
|
-
return true
|
58
|
+
return true unless record.nil?
|
53
59
|
self.attempted_record = nil
|
54
|
-
self.remember_me =
|
60
|
+
self.remember_me = cookie_credentials_remember_me?
|
55
61
|
before_persisting
|
56
62
|
persist
|
57
63
|
ensure_authentication_attempted
|
@@ -1,8 +1,10 @@
|
|
1
1
|
module Authlogic
|
2
2
|
module Session
|
3
|
-
# The point of this module is to avoid the StaleObjectError raised when
|
4
|
-
# We accomplish this by using a
|
5
|
-
#
|
3
|
+
# The point of this module is to avoid the StaleObjectError raised when
|
4
|
+
# lock_version is implemented in ActiveRecord. We accomplish this by using a
|
5
|
+
# "priority record". Meaning this record is used if possible, it gets
|
6
|
+
# priority. This way we don't save a record behind the scenes thus making an
|
7
|
+
# object being used stale.
|
6
8
|
module PriorityRecord
|
7
9
|
def self.included(klass)
|
8
10
|
klass.class_eval do
|
@@ -10,7 +12,8 @@ module Authlogic
|
|
10
12
|
end
|
11
13
|
end
|
12
14
|
|
13
|
-
# Setting priority record if it is passed. The only way it can be passed
|
15
|
+
# Setting priority record if it is passed. The only way it can be passed
|
16
|
+
# is through an array:
|
14
17
|
#
|
15
18
|
# session.credentials = [real_user_object, priority_user_object]
|
16
19
|
def credentials=(value)
|
@@ -21,15 +24,15 @@ module Authlogic
|
|
21
24
|
|
22
25
|
private
|
23
26
|
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
27
|
+
def attempted_record=(value)
|
28
|
+
value = priority_record if value == priority_record
|
29
|
+
super
|
30
|
+
end
|
28
31
|
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
32
|
+
def save_record(alternate_record = nil)
|
33
|
+
r = alternate_record || record
|
34
|
+
super if r != priority_record
|
35
|
+
end
|
33
36
|
end
|
34
37
|
end
|
35
38
|
end
|
@@ -1,4 +1,4 @@
|
|
1
|
-
require
|
1
|
+
require "request_store"
|
2
2
|
|
3
3
|
module Authlogic
|
4
4
|
module Session
|
@@ -25,10 +25,10 @@ module Authlogic
|
|
25
25
|
RequestStore.store[:authlogic_scope]
|
26
26
|
end
|
27
27
|
|
28
|
-
# What with_scopes focuses on is scoping the query when finding the
|
29
|
-
# name of the cookie / session. It works very similar to
|
30
|
-
# ActiveRecord::Base#with_scopes. It accepts a hash with any of the
|
31
|
-
# options:
|
28
|
+
# What with_scopes focuses on is scoping the query when finding the
|
29
|
+
# object and the name of the cookie / session. It works very similar to
|
30
|
+
# ActiveRecord::Base#with_scopes. It accepts a hash with any of the
|
31
|
+
# following options:
|
32
32
|
#
|
33
33
|
# * <tt>find_options:</tt> any options you can pass into ActiveRecord::Base.find.
|
34
34
|
# This is used when trying to find the record.
|
@@ -37,21 +37,27 @@ module Authlogic
|
|
37
37
|
#
|
38
38
|
# Here is how you use it:
|
39
39
|
#
|
40
|
-
#
|
41
|
-
#
|
42
|
-
#
|
40
|
+
# ```
|
41
|
+
# UserSession.with_scope(find_options: {conditions: "account_id = 2"}, id: "account_2") do
|
42
|
+
# UserSession.find
|
43
|
+
# end
|
44
|
+
# ```
|
43
45
|
#
|
44
|
-
# Essentially what the above does is scope the searching of the object
|
45
|
-
# sql you provided. So instead of:
|
46
|
+
# Essentially what the above does is scope the searching of the object
|
47
|
+
# with the sql you provided. So instead of:
|
46
48
|
#
|
47
|
-
#
|
49
|
+
# ```
|
50
|
+
# User.where("login = 'ben'").first
|
51
|
+
# ```
|
48
52
|
#
|
49
53
|
# it would be:
|
50
54
|
#
|
51
|
-
#
|
55
|
+
# ```
|
56
|
+
# User.where("login = 'ben' and account_id = 2").first
|
57
|
+
# ```
|
52
58
|
#
|
53
|
-
# You will also notice the :id option. This works just like the id
|
54
|
-
# scopes your cookies. So the name of your cookie will be:
|
59
|
+
# You will also notice the :id option. This works just like the id
|
60
|
+
# method. It scopes your cookies. So the name of your cookie will be:
|
55
61
|
#
|
56
62
|
# account_2_user_credentials
|
57
63
|
#
|
@@ -59,9 +65,13 @@ module Authlogic
|
|
59
65
|
#
|
60
66
|
# user_credentials
|
61
67
|
#
|
62
|
-
# What is also nifty about scoping with an :id is that it merges your
|
68
|
+
# What is also nifty about scoping with an :id is that it merges your
|
69
|
+
# id's. So if you do:
|
63
70
|
#
|
64
|
-
# UserSession.with_scope(
|
71
|
+
# UserSession.with_scope(
|
72
|
+
# find_options: { conditions: "account_id = 2"},
|
73
|
+
# id: "account_2"
|
74
|
+
# ) do
|
65
75
|
# session = UserSession.new
|
66
76
|
# session.id = :secure
|
67
77
|
# end
|
@@ -69,7 +79,7 @@ module Authlogic
|
|
69
79
|
# The name of your cookies will be:
|
70
80
|
#
|
71
81
|
# secure_account_2_user_credentials
|
72
|
-
def with_scope(options = {}
|
82
|
+
def with_scope(options = {})
|
73
83
|
raise ArgumentError.new("You must provide a block") unless block_given?
|
74
84
|
self.scope = options
|
75
85
|
result = yield
|
@@ -79,9 +89,9 @@ module Authlogic
|
|
79
89
|
|
80
90
|
private
|
81
91
|
|
82
|
-
|
83
|
-
|
84
|
-
|
92
|
+
def scope=(value)
|
93
|
+
RequestStore.store[:authlogic_scope] = value
|
94
|
+
end
|
85
95
|
end
|
86
96
|
|
87
97
|
module InstanceMethods
|
@@ -98,21 +108,30 @@ module Authlogic
|
|
98
108
|
|
99
109
|
private
|
100
110
|
|
101
|
-
|
102
|
-
|
103
|
-
|
111
|
+
# Used for things like cookie_key, session_key, etc.
|
112
|
+
def build_key(last_part)
|
113
|
+
[scope[:id], super].compact.join("_")
|
114
|
+
end
|
115
|
+
|
116
|
+
# `args[0]` is the name of an AR method, like
|
117
|
+
# `find_by_single_access_token`.
|
118
|
+
def search_for_record(*args)
|
119
|
+
search_scope.scoping do
|
120
|
+
klass.send(*args)
|
104
121
|
end
|
122
|
+
end
|
105
123
|
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
124
|
+
# Returns an AR relation representing the scope of the search. The
|
125
|
+
# relation is either provided directly by, or defined by
|
126
|
+
# `find_options`.
|
127
|
+
def search_scope
|
128
|
+
if scope[:find_options].is_a?(ActiveRecord::Relation)
|
129
|
+
scope[:find_options]
|
130
|
+
else
|
131
|
+
conditions = scope[:find_options] && scope[:find_options][:conditions] || {}
|
132
|
+
klass.send(:where, conditions)
|
115
133
|
end
|
134
|
+
end
|
116
135
|
end
|
117
136
|
end
|
118
137
|
end
|
@@ -1,6 +1,7 @@
|
|
1
1
|
module Authlogic
|
2
2
|
module Session
|
3
|
-
# Handles all parts of authentication that deal with sessions. Such as persisting a
|
3
|
+
# Handles all parts of authentication that deal with sessions. Such as persisting a
|
4
|
+
# session and saving / destroy a session.
|
4
5
|
module Session
|
5
6
|
def self.included(klass)
|
6
7
|
klass.class_eval do
|
@@ -9,7 +10,7 @@ module Authlogic
|
|
9
10
|
persist :persist_by_session
|
10
11
|
after_save :update_session
|
11
12
|
after_destroy :update_session
|
12
|
-
after_persisting :update_session, :
|
13
|
+
after_persisting :update_session, unless: :single_access?
|
13
14
|
end
|
14
15
|
end
|
15
16
|
|
@@ -29,38 +30,47 @@ module Authlogic
|
|
29
30
|
module InstanceMethods
|
30
31
|
private
|
31
32
|
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
record = record_id.nil? ?
|
40
|
-
search_for_record("find_by_persistence_token", persistence_token.to_s) :
|
41
|
-
search_for_record("find_by_#{klass.primary_key}", record_id.to_s)
|
42
|
-
self.unauthorized_record = record if record && record.persistence_token == persistence_token
|
43
|
-
valid?
|
44
|
-
else
|
45
|
-
false
|
33
|
+
# Tries to validate the session from information in the session
|
34
|
+
def persist_by_session
|
35
|
+
persistence_token, record_id = session_credentials
|
36
|
+
if !persistence_token.nil?
|
37
|
+
record = persist_by_session_search(persistence_token, record_id)
|
38
|
+
if record && record.persistence_token == persistence_token
|
39
|
+
self.unauthorized_record = record
|
46
40
|
end
|
41
|
+
valid?
|
42
|
+
else
|
43
|
+
false
|
47
44
|
end
|
45
|
+
end
|
48
46
|
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
47
|
+
# Allow finding by persistence token, because when records are created
|
48
|
+
# the session is maintained in a before_save, when there is no id.
|
49
|
+
# This is done for performance reasons and to save on queries.
|
50
|
+
def persist_by_session_search(persistence_token, record_id)
|
51
|
+
if record_id.nil?
|
52
|
+
search_for_record("find_by_persistence_token", persistence_token.to_s)
|
53
|
+
else
|
54
|
+
search_for_record("find_by_#{klass.primary_key}", record_id.to_s)
|
54
55
|
end
|
56
|
+
end
|
55
57
|
|
56
|
-
|
57
|
-
|
58
|
-
|
58
|
+
def session_credentials
|
59
|
+
[
|
60
|
+
controller.session[session_key],
|
61
|
+
controller.session["#{session_key}_#{klass.primary_key}"]
|
62
|
+
].collect { |i| i.nil? ? i : i.to_s }.compact
|
63
|
+
end
|
59
64
|
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
65
|
+
def session_key
|
66
|
+
build_key(self.class.session_key)
|
67
|
+
end
|
68
|
+
|
69
|
+
def update_session
|
70
|
+
controller.session[session_key] = record && record.persistence_token
|
71
|
+
compound_key = "#{session_key}_#{klass.primary_key}"
|
72
|
+
controller.session[compound_key] = record && record.send(record.class.primary_key)
|
73
|
+
end
|
64
74
|
end
|
65
75
|
end
|
66
76
|
end
|