authlogic 4.1.0 → 4.1.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.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/.rubocop.yml +0 -4
- data/.rubocop_todo.yml +32 -33
- data/CHANGELOG.md +15 -0
- data/Rakefile +2 -4
- data/authlogic.gemspec +2 -1
- data/lib/authlogic/acts_as_authentic/base.rb +13 -13
- data/lib/authlogic/acts_as_authentic/logged_in_status.rb +3 -3
- data/lib/authlogic/acts_as_authentic/login.rb +4 -4
- data/lib/authlogic/acts_as_authentic/password.rb +99 -98
- data/lib/authlogic/acts_as_authentic/persistence_token.rb +3 -3
- data/lib/authlogic/acts_as_authentic/queries/find_with_case.rb +32 -32
- data/lib/authlogic/acts_as_authentic/restful_authentication.rb +14 -14
- data/lib/authlogic/acts_as_authentic/session_maintenance.rb +60 -60
- data/lib/authlogic/acts_as_authentic/single_access_token.rb +6 -6
- data/lib/authlogic/authenticates_many/association.rb +3 -3
- data/lib/authlogic/config.rb +9 -9
- data/lib/authlogic/controller_adapters/abstract_adapter.rb +28 -8
- data/lib/authlogic/controller_adapters/rails_adapter.rb +3 -3
- data/lib/authlogic/crypto_providers/aes256.rb +20 -20
- data/lib/authlogic/crypto_providers/bcrypt.rb +8 -8
- data/lib/authlogic/crypto_providers/scrypt.rb +8 -8
- data/lib/authlogic/session/activation.rb +3 -3
- data/lib/authlogic/session/brute_force_protection.rb +32 -32
- data/lib/authlogic/session/callbacks.rb +49 -35
- data/lib/authlogic/session/cookies.rb +58 -49
- data/lib/authlogic/session/foundation.rb +3 -3
- data/lib/authlogic/session/id.rb +9 -4
- data/lib/authlogic/session/klass.rb +6 -6
- data/lib/authlogic/session/magic_columns.rb +5 -17
- data/lib/authlogic/session/params.rb +3 -0
- data/lib/authlogic/session/password.rb +105 -104
- data/lib/authlogic/session/perishable_token.rb +5 -5
- data/lib/authlogic/session/persistence.rb +5 -4
- data/lib/authlogic/session/priority_record.rb +8 -8
- data/lib/authlogic/session/scopes.rb +23 -23
- data/lib/authlogic/session/timeout.rb +11 -11
- data/lib/authlogic/session/unauthorized_record.rb +6 -6
- data/lib/authlogic/session/validation.rb +9 -9
- data/lib/authlogic/test_case.rb +5 -0
- data/lib/authlogic/test_case/mock_request.rb +2 -2
- data/lib/authlogic/version.rb +4 -3
- data/test/acts_as_authentic_test/password_test.rb +23 -23
- data/test/test_helper.rb +96 -93
- metadata +18 -4
@@ -75,15 +75,15 @@ module Authlogic
|
|
75
75
|
|
76
76
|
private
|
77
77
|
|
78
|
-
|
79
|
-
|
80
|
-
|
78
|
+
def join_tokens(tokens)
|
79
|
+
tokens.flatten.join
|
80
|
+
end
|
81
81
|
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
82
|
+
def new_from_hash(hash)
|
83
|
+
::SCrypt::Password.new(hash)
|
84
|
+
rescue ::SCrypt::Errors::InvalidHash
|
85
|
+
nil
|
86
|
+
end
|
87
87
|
end
|
88
88
|
end
|
89
89
|
end
|
@@ -83,44 +83,44 @@ module Authlogic
|
|
83
83
|
|
84
84
|
private
|
85
85
|
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
86
|
+
def exceeded_failed_logins_limit?
|
87
|
+
!attempted_record.nil? &&
|
88
|
+
attempted_record.respond_to?(:failed_login_count) &&
|
89
|
+
consecutive_failed_logins_limit > 0 &&
|
90
|
+
attempted_record.failed_login_count &&
|
91
|
+
attempted_record.failed_login_count >= consecutive_failed_logins_limit
|
92
|
+
end
|
93
93
|
|
94
|
-
|
95
|
-
|
96
|
-
|
94
|
+
def reset_failed_login_count?
|
95
|
+
exceeded_failed_logins_limit? && !being_brute_force_protected?
|
96
|
+
end
|
97
97
|
|
98
|
-
|
99
|
-
|
100
|
-
|
98
|
+
def reset_failed_login_count
|
99
|
+
attempted_record.failed_login_count = 0
|
100
|
+
end
|
101
101
|
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
)
|
102
|
+
def validate_failed_logins
|
103
|
+
# Clear all other error messages, as they are irrelevant at this point and can
|
104
|
+
# only provide additional information that is not needed
|
105
|
+
errors.clear
|
106
|
+
errors.add(
|
107
|
+
:base,
|
108
|
+
I18n.t(
|
109
|
+
"error_messages.consecutive_failed_logins_limit_exceeded",
|
110
|
+
default: "Consecutive failed logins limit exceeded, account has been" +
|
111
|
+
(failed_login_ban_for.zero? ? "" : " temporarily") +
|
112
|
+
" disabled."
|
114
113
|
)
|
115
|
-
|
114
|
+
)
|
115
|
+
end
|
116
116
|
|
117
|
-
|
118
|
-
|
119
|
-
|
117
|
+
def consecutive_failed_logins_limit
|
118
|
+
self.class.consecutive_failed_logins_limit
|
119
|
+
end
|
120
120
|
|
121
|
-
|
122
|
-
|
123
|
-
|
121
|
+
def failed_login_ban_for
|
122
|
+
self.class.failed_login_ban_for
|
123
|
+
end
|
124
124
|
end
|
125
125
|
end
|
126
126
|
end
|
@@ -79,57 +79,71 @@ module Authlogic
|
|
79
79
|
after_destroy
|
80
80
|
].freeze
|
81
81
|
|
82
|
-
|
83
|
-
base
|
84
|
-
|
85
|
-
|
86
|
-
base
|
87
|
-
*METHODS + [{ terminator: ->(_target, result_lambda) { result_lambda.call == false } }]
|
88
|
-
)
|
89
|
-
base.define_callbacks(
|
90
|
-
"persist",
|
91
|
-
terminator: ->(_target, result_lambda) { result_lambda.call == true }
|
92
|
-
)
|
93
|
-
else
|
94
|
-
base.define_callbacks(
|
95
|
-
*METHODS + [{ terminator: ->(_target, result) { result == false } }]
|
96
|
-
)
|
97
|
-
base.define_callbacks("persist", terminator: ->(_target, result) { result == true })
|
82
|
+
class << self
|
83
|
+
def included(base) #:nodoc:
|
84
|
+
base.send :include, ActiveSupport::Callbacks
|
85
|
+
define_session_callbacks(base)
|
86
|
+
define_session_callback_installation_methods(base)
|
98
87
|
end
|
99
88
|
|
100
|
-
|
101
|
-
|
89
|
+
private
|
90
|
+
|
91
|
+
# Defines the "callback installation methods". Other modules will use
|
92
|
+
# these class methods to install their callbacks. Examples:
|
102
93
|
#
|
103
94
|
# ```
|
104
|
-
# #
|
95
|
+
# # session/timeout.rb, in `included`
|
105
96
|
# before_persisting :reset_stale_state
|
106
97
|
#
|
107
|
-
# #
|
98
|
+
# # session/password.rb, in `included`
|
108
99
|
# validate :validate_by_password, if: :authenticating_with_password?
|
109
100
|
# ```
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
101
|
+
def define_session_callback_installation_methods(base)
|
102
|
+
METHODS.each do |method|
|
103
|
+
base.class_eval <<-EOS, __FILE__, __LINE__ + 1
|
104
|
+
def self.#{method}(*filter_list, &block)
|
105
|
+
set_callback(:#{method}, *filter_list, &block)
|
106
|
+
end
|
107
|
+
EOS
|
108
|
+
end
|
116
109
|
end
|
117
|
-
end
|
118
110
|
|
119
|
-
|
111
|
+
# Defines session life cycle events that support callbacks.
|
112
|
+
def define_session_callbacks(base)
|
113
|
+
if Gem::Version.new(ActiveSupport::VERSION::STRING) >= Gem::Version.new("5")
|
114
|
+
base.define_callbacks(
|
115
|
+
*METHODS,
|
116
|
+
terminator: ->(_target, result_lambda) { result_lambda.call == false }
|
117
|
+
)
|
118
|
+
base.define_callbacks(
|
119
|
+
"persist",
|
120
|
+
terminator: ->(_target, result_lambda) { result_lambda.call == true }
|
121
|
+
)
|
122
|
+
else
|
123
|
+
base.define_callbacks(
|
124
|
+
*METHODS,
|
125
|
+
terminator: ->(_target, result) { result == false }
|
126
|
+
)
|
127
|
+
base.define_callbacks(
|
128
|
+
"persist",
|
129
|
+
terminator: ->(_target, result) { result == true }
|
130
|
+
)
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
120
134
|
|
121
|
-
|
122
|
-
|
135
|
+
METHODS.each do |method|
|
136
|
+
class_eval <<-EOS, __FILE__, __LINE__ + 1
|
123
137
|
def #{method}
|
124
138
|
run_callbacks(:#{method})
|
125
139
|
end
|
126
140
|
EOS
|
127
|
-
|
141
|
+
end
|
128
142
|
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
143
|
+
def save_record(alternate_record = nil)
|
144
|
+
r = alternate_record || record
|
145
|
+
r.save_without_session_maintenance(validate: false) if r && r.changed? && !r.readonly?
|
146
|
+
end
|
133
147
|
end
|
134
148
|
end
|
135
149
|
end
|
@@ -222,65 +222,74 @@ module Authlogic
|
|
222
222
|
|
223
223
|
private
|
224
224
|
|
225
|
-
|
226
|
-
|
227
|
-
|
225
|
+
def cookie_key
|
226
|
+
build_key(self.class.cookie_key)
|
227
|
+
end
|
228
228
|
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
229
|
+
# Returns an array of cookie elements. See cookie format in
|
230
|
+
# `generate_cookie_for_saving`. If no cookie is found, returns nil.
|
231
|
+
def cookie_credentials
|
232
|
+
cookie = cookie_jar[cookie_key]
|
233
|
+
cookie && cookie.split("::")
|
234
|
+
end
|
233
235
|
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
end
|
236
|
+
# The third element of the cookie indicates whether the user wanted
|
237
|
+
# to be remembered (Actually, it's a timestamp, `remember_me_until`)
|
238
|
+
# See cookie format in `generate_cookie_for_saving`.
|
239
|
+
def cookie_credentials_remember_me?
|
240
|
+
!cookie_credentials.nil? && !cookie_credentials[2].nil?
|
241
|
+
end
|
241
242
|
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
if record && record.persistence_token == persistence_token
|
248
|
-
self.unauthorized_record = record
|
249
|
-
end
|
250
|
-
valid?
|
251
|
-
else
|
252
|
-
false
|
253
|
-
end
|
243
|
+
def cookie_jar
|
244
|
+
if self.class.sign_cookie
|
245
|
+
controller.cookies.signed
|
246
|
+
else
|
247
|
+
controller.cookies
|
254
248
|
end
|
249
|
+
end
|
255
250
|
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
251
|
+
# Tries to validate the session from information in the cookie
|
252
|
+
def persist_by_cookie
|
253
|
+
persistence_token, record_id = cookie_credentials
|
254
|
+
if persistence_token.present?
|
255
|
+
record = search_for_record("find_by_#{klass.primary_key}", record_id)
|
256
|
+
if record && record.persistence_token == persistence_token
|
257
|
+
self.unauthorized_record = record
|
261
258
|
end
|
259
|
+
valid?
|
260
|
+
else
|
261
|
+
false
|
262
262
|
end
|
263
|
+
end
|
263
264
|
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
remember_me? ? "::#{remember_me_until.iso8601}" : ""
|
270
|
-
)
|
271
|
-
{
|
272
|
-
value: value,
|
273
|
-
expires: remember_me_until,
|
274
|
-
secure: secure,
|
275
|
-
httponly: httponly,
|
276
|
-
same_site: same_site,
|
277
|
-
domain: controller.cookie_domain
|
278
|
-
}
|
265
|
+
def save_cookie
|
266
|
+
if sign_cookie?
|
267
|
+
controller.cookies.signed[cookie_key] = generate_cookie_for_saving
|
268
|
+
else
|
269
|
+
controller.cookies[cookie_key] = generate_cookie_for_saving
|
279
270
|
end
|
271
|
+
end
|
280
272
|
|
281
|
-
|
282
|
-
|
283
|
-
|
273
|
+
def generate_cookie_for_saving
|
274
|
+
value = format(
|
275
|
+
"%s::%s%s",
|
276
|
+
record.persistence_token,
|
277
|
+
record.send(record.class.primary_key),
|
278
|
+
remember_me? ? "::#{remember_me_until.iso8601}" : ""
|
279
|
+
)
|
280
|
+
{
|
281
|
+
value: value,
|
282
|
+
expires: remember_me_until,
|
283
|
+
secure: secure,
|
284
|
+
httponly: httponly,
|
285
|
+
same_site: same_site,
|
286
|
+
domain: controller.cookie_domain
|
287
|
+
}
|
288
|
+
end
|
289
|
+
|
290
|
+
def destroy_cookie
|
291
|
+
controller.cookies.delete cookie_key, domain: controller.cookie_domain
|
292
|
+
end
|
284
293
|
end
|
285
294
|
end
|
286
295
|
end
|
data/lib/authlogic/session/id.rb
CHANGED
@@ -3,6 +3,11 @@ module Authlogic
|
|
3
3
|
# Allows you to separate sessions with an id, ultimately letting you create
|
4
4
|
# multiple sessions for the same user.
|
5
5
|
module Id
|
6
|
+
def initialize(*args)
|
7
|
+
@id = nil
|
8
|
+
super
|
9
|
+
end
|
10
|
+
|
6
11
|
def self.included(klass)
|
7
12
|
klass.class_eval do
|
8
13
|
attr_writer :id
|
@@ -39,10 +44,10 @@ module Authlogic
|
|
39
44
|
|
40
45
|
private
|
41
46
|
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
47
|
+
# Used for things like cookie_key, session_key, etc.
|
48
|
+
def build_key(last_part)
|
49
|
+
[id, super].compact.join("_")
|
50
|
+
end
|
46
51
|
end
|
47
52
|
end
|
48
53
|
end
|
@@ -60,13 +60,13 @@ module Authlogic
|
|
60
60
|
|
61
61
|
private
|
62
62
|
|
63
|
-
|
64
|
-
|
65
|
-
|
63
|
+
def klass
|
64
|
+
self.class.klass
|
65
|
+
end
|
66
66
|
|
67
|
-
|
68
|
-
|
69
|
-
|
67
|
+
def klass_name
|
68
|
+
self.class.klass_name
|
69
|
+
end
|
70
70
|
end
|
71
71
|
end
|
72
72
|
end
|
@@ -89,29 +89,17 @@ module Authlogic
|
|
89
89
|
end
|
90
90
|
|
91
91
|
# This method lets authlogic know whether it should allow the
|
92
|
-
# last_request_at field to be updated with the current time
|
93
|
-
# (Time.now). One thing to note here is that it also checks for the
|
94
|
-
# existence of a last_request_update_allowed? method in your
|
95
|
-
# controller. This allows you to control this method pragmatically in
|
96
|
-
# your controller.
|
92
|
+
# last_request_at field to be updated with the current time.
|
97
93
|
#
|
98
|
-
#
|
99
|
-
#
|
100
|
-
# times out. Obviously you would want to ignore this request, because
|
101
|
-
# then the user would never time out. So you can do something like
|
102
|
-
# this in your controller:
|
94
|
+
# See also `last_request_update_allowed?` in
|
95
|
+
# `Authlogic::ControllerAdapters::AbstractAdapter`
|
103
96
|
#
|
104
|
-
#
|
105
|
-
# action_name != "update_session_time_left"
|
106
|
-
# end
|
107
|
-
#
|
108
|
-
# You can do whatever you want with that method.
|
97
|
+
# @api private
|
109
98
|
def set_last_request_at?
|
110
99
|
if !record || !klass.column_names.include?("last_request_at")
|
111
100
|
return false
|
112
101
|
end
|
113
|
-
|
114
|
-
!controller.last_request_update_allowed?
|
102
|
+
unless controller.last_request_update_allowed?
|
115
103
|
return false
|
116
104
|
end
|
117
105
|
record.last_request_at.blank? ||
|
@@ -96,7 +96,10 @@ module Authlogic
|
|
96
96
|
if controller.responds_to_single_access_allowed?
|
97
97
|
return controller.single_access_allowed?
|
98
98
|
end
|
99
|
+
params_enabled_by_allowed_request_types?
|
100
|
+
end
|
99
101
|
|
102
|
+
def params_enabled_by_allowed_request_types?
|
100
103
|
case single_access_allowed_request_types
|
101
104
|
when Array
|
102
105
|
single_access_allowed_request_types.include?(controller.request_content_type) ||
|