authlogic 4.1.0 → 4.1.1
Sign up to get free protection for your applications and to get access to all the features.
- 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) ||
|