authlogic 3.8.0 → 6.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (150) hide show
  1. checksums.yaml +7 -0
  2. data/lib/authlogic/acts_as_authentic/base.rb +33 -36
  3. data/lib/authlogic/acts_as_authentic/email.rb +8 -141
  4. data/lib/authlogic/acts_as_authentic/logged_in_status.rb +17 -10
  5. data/lib/authlogic/acts_as_authentic/login.rb +14 -165
  6. data/lib/authlogic/acts_as_authentic/magic_columns.rb +13 -10
  7. data/lib/authlogic/acts_as_authentic/password.rb +186 -254
  8. data/lib/authlogic/acts_as_authentic/perishable_token.rb +30 -22
  9. data/lib/authlogic/acts_as_authentic/persistence_token.rb +19 -18
  10. data/lib/authlogic/acts_as_authentic/queries/case_sensitivity.rb +53 -0
  11. data/lib/authlogic/acts_as_authentic/queries/find_with_case.rb +83 -0
  12. data/lib/authlogic/acts_as_authentic/session_maintenance.rb +94 -62
  13. data/lib/authlogic/acts_as_authentic/single_access_token.rb +28 -14
  14. data/lib/authlogic/config.rb +29 -10
  15. data/lib/authlogic/controller_adapters/abstract_adapter.rb +43 -13
  16. data/lib/authlogic/controller_adapters/rack_adapter.rb +11 -5
  17. data/lib/authlogic/controller_adapters/rails_adapter.rb +11 -29
  18. data/lib/authlogic/controller_adapters/sinatra_adapter.rb +8 -2
  19. data/lib/authlogic/cookie_credentials.rb +63 -0
  20. data/lib/authlogic/crypto_providers/bcrypt.rb +24 -18
  21. data/lib/authlogic/crypto_providers/md5/v2.rb +35 -0
  22. data/lib/authlogic/crypto_providers/md5.rb +8 -6
  23. data/lib/authlogic/crypto_providers/scrypt.rb +24 -17
  24. data/lib/authlogic/crypto_providers/sha1/v2.rb +41 -0
  25. data/lib/authlogic/crypto_providers/sha1.rb +12 -5
  26. data/lib/authlogic/crypto_providers/sha256/v2.rb +58 -0
  27. data/lib/authlogic/crypto_providers/sha256.rb +18 -9
  28. data/lib/authlogic/crypto_providers/sha512/v2.rb +39 -0
  29. data/lib/authlogic/crypto_providers/sha512.rb +9 -26
  30. data/lib/authlogic/crypto_providers.rb +77 -1
  31. data/lib/authlogic/errors.rb +35 -0
  32. data/lib/authlogic/i18n/translator.rb +4 -1
  33. data/lib/authlogic/i18n.rb +29 -20
  34. data/lib/authlogic/random.rb +12 -28
  35. data/lib/authlogic/session/base.rb +2087 -33
  36. data/lib/authlogic/session/magic_column/assigns_last_request_at.rb +46 -0
  37. data/lib/authlogic/test_case/mock_controller.rb +7 -4
  38. data/lib/authlogic/test_case/mock_cookie_jar.rb +19 -3
  39. data/lib/authlogic/test_case/mock_logger.rb +2 -0
  40. data/lib/authlogic/test_case/mock_request.rb +8 -3
  41. data/lib/authlogic/test_case/rails_request_adapter.rb +5 -2
  42. data/lib/authlogic/test_case.rb +74 -2
  43. data/lib/authlogic/version.rb +22 -0
  44. data/lib/authlogic.rb +33 -54
  45. metadata +208 -234
  46. data/.github/ISSUE_TEMPLATE.md +0 -13
  47. data/.gitignore +0 -14
  48. data/.rubocop.yml +0 -33
  49. data/.rubocop_todo.yml +0 -391
  50. data/.travis.yml +0 -48
  51. data/CHANGELOG.md +0 -5
  52. data/CONTRIBUTING.md +0 -60
  53. data/Gemfile +0 -5
  54. data/LICENSE +0 -20
  55. data/README.md +0 -294
  56. data/Rakefile +0 -21
  57. data/authlogic.gemspec +0 -27
  58. data/lib/authlogic/acts_as_authentic/restful_authentication.rb +0 -70
  59. data/lib/authlogic/acts_as_authentic/validations_scope.rb +0 -32
  60. data/lib/authlogic/authenticates_many/association.rb +0 -50
  61. data/lib/authlogic/authenticates_many/base.rb +0 -65
  62. data/lib/authlogic/crypto_providers/aes256.rb +0 -66
  63. data/lib/authlogic/crypto_providers/wordpress.rb +0 -43
  64. data/lib/authlogic/regex.rb +0 -48
  65. data/lib/authlogic/session/activation.rb +0 -70
  66. data/lib/authlogic/session/active_record_trickery.rb +0 -61
  67. data/lib/authlogic/session/brute_force_protection.rb +0 -120
  68. data/lib/authlogic/session/callbacks.rb +0 -105
  69. data/lib/authlogic/session/cookies.rb +0 -244
  70. data/lib/authlogic/session/existence.rb +0 -93
  71. data/lib/authlogic/session/foundation.rb +0 -55
  72. data/lib/authlogic/session/http_auth.rb +0 -100
  73. data/lib/authlogic/session/id.rb +0 -48
  74. data/lib/authlogic/session/klass.rb +0 -70
  75. data/lib/authlogic/session/magic_columns.rb +0 -116
  76. data/lib/authlogic/session/magic_states.rb +0 -76
  77. data/lib/authlogic/session/params.rb +0 -116
  78. data/lib/authlogic/session/password.rb +0 -308
  79. data/lib/authlogic/session/perishable_token.rb +0 -23
  80. data/lib/authlogic/session/persistence.rb +0 -71
  81. data/lib/authlogic/session/priority_record.rb +0 -35
  82. data/lib/authlogic/session/scopes.rb +0 -119
  83. data/lib/authlogic/session/session.rb +0 -67
  84. data/lib/authlogic/session/timeout.rb +0 -103
  85. data/lib/authlogic/session/unauthorized_record.rb +0 -51
  86. data/lib/authlogic/session/validation.rb +0 -93
  87. data/test/acts_as_authentic_test/base_test.rb +0 -25
  88. data/test/acts_as_authentic_test/email_test.rb +0 -240
  89. data/test/acts_as_authentic_test/logged_in_status_test.rb +0 -62
  90. data/test/acts_as_authentic_test/login_test.rb +0 -156
  91. data/test/acts_as_authentic_test/magic_columns_test.rb +0 -27
  92. data/test/acts_as_authentic_test/password_test.rb +0 -249
  93. data/test/acts_as_authentic_test/perishable_token_test.rb +0 -90
  94. data/test/acts_as_authentic_test/persistence_token_test.rb +0 -56
  95. data/test/acts_as_authentic_test/restful_authentication_test.rb +0 -37
  96. data/test/acts_as_authentic_test/session_maintenance_test.rb +0 -96
  97. data/test/acts_as_authentic_test/single_access_test.rb +0 -44
  98. data/test/authenticates_many_test.rb +0 -31
  99. data/test/config_test.rb +0 -36
  100. data/test/crypto_provider_test/aes256_test.rb +0 -14
  101. data/test/crypto_provider_test/bcrypt_test.rb +0 -14
  102. data/test/crypto_provider_test/scrypt_test.rb +0 -14
  103. data/test/crypto_provider_test/sha1_test.rb +0 -23
  104. data/test/crypto_provider_test/sha256_test.rb +0 -14
  105. data/test/crypto_provider_test/sha512_test.rb +0 -14
  106. data/test/fixtures/companies.yml +0 -5
  107. data/test/fixtures/employees.yml +0 -17
  108. data/test/fixtures/projects.yml +0 -3
  109. data/test/fixtures/users.yml +0 -41
  110. data/test/gemfiles/Gemfile.rails-3.2.x +0 -7
  111. data/test/gemfiles/Gemfile.rails-4.0.x +0 -7
  112. data/test/gemfiles/Gemfile.rails-4.1.x +0 -7
  113. data/test/gemfiles/Gemfile.rails-4.2.x +0 -7
  114. data/test/gemfiles/Gemfile.rails-5.0.x +0 -6
  115. data/test/gemfiles/Gemfile.rails-5.1.x +0 -6
  116. data/test/gemfiles/Gemfile.rails-5.2.x +0 -6
  117. data/test/i18n/lol.yml +0 -4
  118. data/test/i18n_test.rb +0 -33
  119. data/test/libs/affiliate.rb +0 -7
  120. data/test/libs/company.rb +0 -6
  121. data/test/libs/employee.rb +0 -7
  122. data/test/libs/employee_session.rb +0 -2
  123. data/test/libs/ldaper.rb +0 -3
  124. data/test/libs/project.rb +0 -3
  125. data/test/libs/user.rb +0 -7
  126. data/test/libs/user_session.rb +0 -25
  127. data/test/random_test.rb +0 -43
  128. data/test/session_test/activation_test.rb +0 -43
  129. data/test/session_test/active_record_trickery_test.rb +0 -75
  130. data/test/session_test/brute_force_protection_test.rb +0 -108
  131. data/test/session_test/callbacks_test.rb +0 -34
  132. data/test/session_test/cookies_test.rb +0 -201
  133. data/test/session_test/credentials_test.rb +0 -0
  134. data/test/session_test/existence_test.rb +0 -75
  135. data/test/session_test/foundation_test.rb +0 -6
  136. data/test/session_test/http_auth_test.rb +0 -56
  137. data/test/session_test/id_test.rb +0 -17
  138. data/test/session_test/klass_test.rb +0 -40
  139. data/test/session_test/magic_columns_test.rb +0 -62
  140. data/test/session_test/magic_states_test.rb +0 -58
  141. data/test/session_test/params_test.rb +0 -53
  142. data/test/session_test/password_test.rb +0 -105
  143. data/test/session_test/perishability_test.rb +0 -15
  144. data/test/session_test/persistence_test.rb +0 -32
  145. data/test/session_test/scopes_test.rb +0 -60
  146. data/test/session_test/session_test.rb +0 -78
  147. data/test/session_test/timeout_test.rb +0 -82
  148. data/test/session_test/unauthorized_record_test.rb +0 -13
  149. data/test/session_test/validation_test.rb +0 -23
  150. data/test/test_helper.rb +0 -233
@@ -1,38 +1,2092 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "request_store"
4
+
1
5
  module Authlogic
2
- module Session # :nodoc:
3
- # This is the base class Authlogic, where all modules are included. For information on functionality see the various
4
- # sub modules.
6
+ module Session
7
+ module Activation
8
+ # :nodoc:
9
+ class NotActivatedError < ::StandardError
10
+ def initialize
11
+ super(
12
+ "You must activate the Authlogic::Session::Base.controller with " \
13
+ "a controller object before creating objects"
14
+ )
15
+ end
16
+ end
17
+ end
18
+
19
+ module Existence
20
+ # :nodoc:
21
+ class SessionInvalidError < ::StandardError
22
+ def initialize(session)
23
+ message = I18n.t(
24
+ "error_messages.session_invalid",
25
+ default: "Your session is invalid and has the following errors:"
26
+ )
27
+ message += " #{session.errors.full_messages.to_sentence}"
28
+ super message
29
+ end
30
+ end
31
+ end
32
+
33
+ # This is the most important class in Authlogic. You will inherit this class
34
+ # for your own eg. `UserSession`.
35
+ #
36
+ # Ongoing consolidation of modules
37
+ # ================================
38
+ #
39
+ # We are consolidating modules into this class (inlining mixins). When we
40
+ # are done, there will only be this one file. It will be quite large, but it
41
+ # will be easier to trace execution.
42
+ #
43
+ # Once consolidation is complete, we hope to identify and extract
44
+ # collaborating objects. For example, there may be a "session adapter" that
45
+ # connects this class with the existing `ControllerAdapters`. Perhaps a
46
+ # data object or a state machine will reveal itself.
47
+ #
48
+ # Activation
49
+ # ==========
50
+ #
51
+ # Activating Authlogic requires that you pass it an
52
+ # Authlogic::ControllerAdapters::AbstractAdapter object, or a class that
53
+ # extends it. This is sort of like a database connection for an ORM library,
54
+ # Authlogic can't do anything until it is "connected" to a controller. If
55
+ # you are using a supported framework, Authlogic takes care of this for you.
56
+ #
57
+ # ActiveRecord Trickery
58
+ # =====================
59
+ #
60
+ # Authlogic looks like ActiveRecord, sounds like ActiveRecord, but its not
61
+ # ActiveRecord. That's the goal here. This is useful for the various rails
62
+ # helper methods such as form_for, error_messages_for, or any method that
63
+ # expects an ActiveRecord object. The point is to disguise the object as an
64
+ # ActiveRecord object so we can take advantage of the many ActiveRecord
65
+ # tools.
66
+ #
67
+ # Brute Force Protection
68
+ # ======================
69
+ #
70
+ # A brute force attacks is executed by hammering a login with as many password
71
+ # combinations as possible, until one works. A brute force attacked is generally
72
+ # combated with a slow hashing algorithm such as BCrypt. You can increase the cost,
73
+ # which makes the hash generation slower, and ultimately increases the time it takes
74
+ # to execute a brute force attack. Just to put this into perspective, if a hacker was
75
+ # to gain access to your server and execute a brute force attack locally, meaning
76
+ # there is no network lag, it would probably take decades to complete. Now throw in
77
+ # network lag and it would take MUCH longer.
78
+ #
79
+ # But for those that are extra paranoid and can't get enough protection, why not stop
80
+ # them as soon as you realize something isn't right? That's what this module is all
81
+ # about. By default the consecutive_failed_logins_limit configuration option is set to
82
+ # 50, if someone consecutively fails to login after 50 attempts their account will be
83
+ # suspended. This is a very liberal number and at this point it should be obvious that
84
+ # something is not right. If you wish to lower this number just set the configuration
85
+ # to a lower number:
86
+ #
87
+ # class UserSession < Authlogic::Session::Base
88
+ # consecutive_failed_logins_limit 10
89
+ # end
90
+ #
91
+ # Callbacks
92
+ # =========
93
+ #
94
+ # Between these callbacks and the configuration, this is the contract between me and
95
+ # you to safely modify Authlogic's behavior. I will do everything I can to make sure
96
+ # these do not change.
97
+ #
98
+ # Check out the sub modules of Authlogic::Session. They are very concise, clear, and
99
+ # to the point. More importantly they use the same API that you would use to extend
100
+ # Authlogic. That being said, they are great examples of how to extend Authlogic and
101
+ # add / modify behavior to Authlogic. These modules could easily be pulled out into
102
+ # their own plugin and become an "add on" without any change.
103
+ #
104
+ # Now to the point of this module. Just like in ActiveRecord you have before_save,
105
+ # before_validation, etc. You have similar callbacks with Authlogic, see the METHODS
106
+ # constant below. The order of execution is as follows:
107
+ #
108
+ # before_persisting
109
+ # persist
110
+ # after_persisting
111
+ # [save record if record.has_changes_to_save?]
112
+ #
113
+ # before_validation
114
+ # before_validation_on_create
115
+ # before_validation_on_update
116
+ # validate
117
+ # after_validation_on_update
118
+ # after_validation_on_create
119
+ # after_validation
120
+ # [save record if record.has_changes_to_save?]
121
+ #
122
+ # before_save
123
+ # before_create
124
+ # before_update
125
+ # after_update
126
+ # after_create
127
+ # after_save
128
+ # [save record if record.has_changes_to_save?]
129
+ #
130
+ # before_destroy
131
+ # [save record if record.has_changes_to_save?]
132
+ # after_destroy
133
+ #
134
+ # Notice the "save record if has_changes_to_save" lines above. This helps with performance. If
135
+ # you need to make changes to the associated record, there is no need to save the
136
+ # record, Authlogic will do it for you. This allows multiple modules to modify the
137
+ # record and execute as few queries as possible.
138
+ #
139
+ # **WARNING**: unlike ActiveRecord, these callbacks must be set up on the class level:
140
+ #
141
+ # class UserSession < Authlogic::Session::Base
142
+ # before_validation :my_method
143
+ # validate :another_method
144
+ # # ..etc
145
+ # end
146
+ #
147
+ # You can NOT define a "before_validation" method, this is bad practice and does not
148
+ # allow Authlogic to extend properly with multiple extensions. Please ONLY use the
149
+ # method above.
150
+ #
151
+ # HTTP Basic Authentication
152
+ # =========================
153
+ #
154
+ # Handles all authentication that deals with basic HTTP auth. Which is
155
+ # authentication built into the HTTP protocol:
156
+ #
157
+ # http://username:password@whatever.com
158
+ #
159
+ # Also, if you are not comfortable letting users pass their raw username and
160
+ # password you can use a single access token, as described below.
161
+ #
162
+ # Magic Columns
163
+ # =============
164
+ #
165
+ # Just like ActiveRecord has "magic" columns, such as: created_at and updated_at.
166
+ # Authlogic has its own "magic" columns too:
167
+ #
168
+ # * login_count - Increased every time an explicit login is made. This will *NOT*
169
+ # increase if logging in by a session, cookie, or basic http auth
170
+ # * failed_login_count - This increases for each consecutive failed login. See
171
+ # the consecutive_failed_logins_limit option for details.
172
+ # * last_request_at - Updates every time the user logs in, either by explicitly
173
+ # logging in, or logging in by cookie, session, or http auth
174
+ # * current_login_at - Updates with the current time when an explicit login is made.
175
+ # * last_login_at - Updates with the value of current_login_at before it is reset.
176
+ # * current_login_ip - Updates with the request ip when an explicit login is made.
177
+ # * last_login_ip - Updates with the value of current_login_ip before it is reset.
178
+ #
179
+ # Multiple Simultaneous Sessions
180
+ # ==============================
181
+ #
182
+ # See `id`. Allows you to separate sessions with an id, ultimately letting
183
+ # you create multiple sessions for the same user.
184
+ #
185
+ # Timeout
186
+ # =======
187
+ #
188
+ # Think about financial websites, if you are inactive for a certain period
189
+ # of time you will be asked to log back in on your next request. You can do
190
+ # this with Authlogic easily, there are 2 parts to this:
191
+ #
192
+ # 1. Define the timeout threshold:
193
+ #
194
+ # acts_as_authentic do |c|
195
+ # c.logged_in_timeout = 10.minutes # default is 10.minutes
196
+ # end
197
+ #
198
+ # 2. Enable logging out on timeouts
199
+ #
200
+ # class UserSession < Authlogic::Session::Base
201
+ # logout_on_timeout true # default is false
202
+ # end
203
+ #
204
+ # This will require a user to log back in if they are inactive for more than
205
+ # 10 minutes. In order for this feature to be used you must have a
206
+ # last_request_at datetime column in your table for whatever model you are
207
+ # authenticating with.
208
+ #
209
+ # Params
210
+ # ======
211
+ #
212
+ # This module is responsible for authenticating the user via params, which ultimately
213
+ # allows the user to log in using a URL like the following:
214
+ #
215
+ # https://www.domain.com?user_credentials=4LiXF7FiGUppIPubBPey
216
+ #
217
+ # Notice the token in the URL, this is a single access token. A single access token is
218
+ # used for single access only, it is not persisted. Meaning the user provides it,
219
+ # Authlogic grants them access, and that's it. If they want access again they need to
220
+ # provide the token again. Authlogic will *NEVER* try to persist the session after
221
+ # authenticating through this method.
222
+ #
223
+ # For added security, this token is *ONLY* allowed for RSS and ATOM requests. You can
224
+ # change this with the configuration. You can also define if it is allowed dynamically
225
+ # by defining a single_access_allowed? method in your controller. For example:
226
+ #
227
+ # class UsersController < ApplicationController
228
+ # private
229
+ # def single_access_allowed?
230
+ # action_name == "index"
231
+ # end
232
+ #
233
+ # Also, by default, this token is permanent. Meaning if the user changes their
234
+ # password, this token will remain the same. It will only change when it is explicitly
235
+ # reset.
236
+ #
237
+ # You can modify all of this behavior with the Config sub module.
238
+ #
239
+ # Perishable Token
240
+ # ================
241
+ #
242
+ # Maintains the perishable token, which is helpful for confirming records or
243
+ # authorizing records to reset their password. All that this module does is
244
+ # reset it after a session have been saved, just keep it changing. The more
245
+ # it changes, the tighter the security.
246
+ #
247
+ # See Authlogic::ActsAsAuthentic::PerishableToken for more information.
248
+ #
249
+ # Scopes
250
+ # ======
251
+ #
252
+ # Authentication can be scoped, and it's easy, you just need to define how you want to
253
+ # scope everything. See `.with_scope`.
254
+ #
255
+ # Unauthorized Record
256
+ # ===================
257
+ #
258
+ # Allows you to create session with an object. Ex:
259
+ #
260
+ # UserSession.create(my_user_object)
261
+ #
262
+ # Be careful with this, because Authlogic is assuming that you have already
263
+ # confirmed that the user is who he says he is.
264
+ #
265
+ # For example, this is the method used to persist the session internally.
266
+ # Authlogic finds the user with the persistence token. At this point we know
267
+ # the user is who he says he is, so Authlogic just creates a session with
268
+ # the record. This is particularly useful for 3rd party authentication
269
+ # methods, such as OpenID. Let that method verify the identity, once it's
270
+ # verified, pass the object and create a session.
271
+ #
272
+ # Magic States
273
+ # ============
274
+ #
275
+ # Authlogic tries to check the state of the record before creating the session. If
276
+ # your record responds to the following methods and any of them return false,
277
+ # validation will fail:
278
+ #
279
+ # Method name Description
280
+ # active? Is the record marked as active?
281
+ # approved? Has the record been approved?
282
+ # confirmed? Has the record been confirmed?
283
+ #
284
+ # Authlogic does nothing to define these methods for you, its up to you to define what
285
+ # they mean. If your object responds to these methods Authlogic will use them,
286
+ # otherwise they are ignored.
287
+ #
288
+ # What's neat about this is that these are checked upon any type of login. When
289
+ # logging in explicitly, by cookie, session, or basic http auth. So if you mark a user
290
+ # inactive in the middle of their session they wont be logged back in next time they
291
+ # refresh the page. Giving you complete control.
292
+ #
293
+ # Need Authlogic to check your own "state"? No problem, check out the hooks section
294
+ # below. Add in a before_validation to do your own checking. The sky is the limit.
295
+ #
296
+ # Validation
297
+ # ==========
298
+ #
299
+ # The errors in Authlogic work just like ActiveRecord. In fact, it uses
300
+ # the `ActiveModel::Errors` class. Use it the same way:
301
+ #
302
+ # ```
303
+ # class UserSession
304
+ # validate :check_if_awesome
305
+ #
306
+ # private
307
+ #
308
+ # def check_if_awesome
309
+ # if login && !login.include?("awesome")
310
+ # errors.add(:login, "must contain awesome")
311
+ # end
312
+ # unless attempted_record.awesome?
313
+ # errors.add(:base, "You must be awesome to log in")
314
+ # end
315
+ # end
316
+ # end
317
+ # ```
5
318
  class Base
6
- include Foundation
7
- include Callbacks
8
-
9
- # Included first so that the session resets itself to nil
10
- include Timeout
11
-
12
- # Included in a specific order so they are tried in this order when persisting
13
- include Params
14
- include Cookies
15
- include Session
16
- include HttpAuth
17
-
18
- # Included in a specific order so magic states gets ran after a record is found
19
- # TODO: What does "magic states gets ran" mean? Be specific.
20
- include Password
21
- include UnauthorizedRecord
22
- include MagicStates
23
-
24
- include Activation
25
- include ActiveRecordTrickery
26
- include BruteForceProtection
27
- include Existence
28
- include Klass
29
- include MagicColumns
30
- include PerishableToken
31
- include Persistence
32
- include Scopes
33
- include Id
34
- include Validation
35
- include PriorityRecord
319
+ extend ActiveModel::Naming
320
+ extend ActiveModel::Translation
321
+ extend Authlogic::Config
322
+ include ActiveSupport::Callbacks
323
+
324
+ E_AC_PARAMETERS = <<~EOS
325
+ Passing an ActionController::Parameters to Authlogic is not allowed.
326
+
327
+ In Authlogic 3, especially during the transition of rails to Strong
328
+ Parameters, it was common for Authlogic users to forget to `permit`
329
+ their params. They would pass their params into Authlogic, we'd call
330
+ `to_h`, and they'd be surprised when authentication failed.
331
+
332
+ In 2018, people are still making this mistake. We'd like to help them
333
+ and make authlogic a little simpler at the same time, so in Authlogic
334
+ 3.7.0, we deprecated the use of ActionController::Parameters. Instead,
335
+ pass a plain Hash. Please replace:
336
+
337
+ UserSession.new(user_session_params)
338
+ UserSession.create(user_session_params)
339
+
340
+ with
341
+
342
+ UserSession.new(user_session_params.to_h)
343
+ UserSession.create(user_session_params.to_h)
344
+
345
+ And don't forget to `permit`!
346
+
347
+ We discussed this issue thoroughly between late 2016 and early
348
+ 2018. Notable discussions include:
349
+
350
+ - https://github.com/binarylogic/authlogic/issues/512
351
+ - https://github.com/binarylogic/authlogic/pull/558
352
+ - https://github.com/binarylogic/authlogic/pull/577
353
+ EOS
354
+ VALID_SAME_SITE_VALUES = [nil, "Lax", "Strict", "None"].freeze
355
+
356
+ # Callbacks
357
+ # =========
358
+
359
+ METHODS = %w[
360
+ before_persisting
361
+ persist
362
+ after_persisting
363
+ before_validation
364
+ before_validation_on_create
365
+ before_validation_on_update
366
+ validate
367
+ after_validation_on_update
368
+ after_validation_on_create
369
+ after_validation
370
+ before_save
371
+ before_create
372
+ before_update
373
+ after_update
374
+ after_create
375
+ after_save
376
+ before_destroy
377
+ after_destroy
378
+ ].freeze
379
+
380
+ # Defines the "callback installation methods" used below.
381
+ METHODS.each do |method|
382
+ class_eval <<-EOS, __FILE__, __LINE__ + 1
383
+ def self.#{method}(*filter_list, &block)
384
+ set_callback(:#{method}, *filter_list, &block)
385
+ end
386
+ EOS
387
+ end
388
+
389
+ # Defines session life cycle events that support callbacks.
390
+ define_callbacks(
391
+ *METHODS,
392
+ terminator: ->(_target, result_lambda) { result_lambda.call == false }
393
+ )
394
+ define_callbacks(
395
+ "persist",
396
+ terminator: ->(_target, result_lambda) { result_lambda.call == true }
397
+ )
398
+
399
+ # Use the "callback installation methods" defined above
400
+ # -----------------------------------------------------
401
+
402
+ before_persisting :reset_stale_state
403
+
404
+ # `persist` callbacks, in order of priority
405
+ persist :persist_by_params
406
+ persist :persist_by_cookie
407
+ persist :persist_by_session
408
+ persist :persist_by_http_auth, if: :persist_by_http_auth?
409
+
410
+ after_persisting :enforce_timeout
411
+ after_persisting :update_session, unless: :single_access?
412
+ after_persisting :set_last_request_at
413
+
414
+ before_save :update_info
415
+ before_save :set_last_request_at
416
+
417
+ after_save :reset_perishable_token!
418
+ after_save :save_cookie
419
+ after_save :update_session
420
+
421
+ after_destroy :destroy_cookie
422
+ after_destroy :update_session
423
+
424
+ # `validate` callbacks, in deliberate order. For example,
425
+ # validate_magic_states must run *after* a record is found.
426
+ validate :validate_by_password, if: :authenticating_with_password?
427
+ validate(
428
+ :validate_by_unauthorized_record,
429
+ if: :authenticating_with_unauthorized_record?
430
+ )
431
+ validate :validate_magic_states, unless: :disable_magic_states?
432
+ validate :reset_failed_login_count, if: :reset_failed_login_count?
433
+ validate :validate_failed_logins, if: :being_brute_force_protected?
434
+ validate :increase_failed_login_count
435
+
436
+ # Accessors
437
+ # =========
438
+
439
+ class << self
440
+ attr_accessor(
441
+ :configured_password_methods,
442
+ :configured_klass_methods
443
+ )
444
+ end
445
+ attr_accessor(
446
+ :invalid_password,
447
+ :new_session,
448
+ :priority_record,
449
+ :record,
450
+ :single_access,
451
+ :stale_record,
452
+ :unauthorized_record
453
+ )
454
+ attr_writer(
455
+ :scope,
456
+ :id
457
+ )
458
+
459
+ # Public class methods
460
+ # ====================
461
+
462
+ class << self
463
+ # Returns true if a controller has been set and can be used properly.
464
+ # This MUST be set before anything can be done. Similar to how
465
+ # ActiveRecord won't allow you to do anything without establishing a DB
466
+ # connection. In your framework environment this is done for you, but if
467
+ # you are using Authlogic outside of your framework, you need to assign
468
+ # a controller object to Authlogic via
469
+ # Authlogic::Session::Base.controller = obj. See the controller= method
470
+ # for more information.
471
+ def activated?
472
+ !controller.nil?
473
+ end
474
+
475
+ # Allow users to log in via HTTP basic authentication.
476
+ #
477
+ # * <tt>Default:</tt> false
478
+ # * <tt>Accepts:</tt> Boolean
479
+ def allow_http_basic_auth(value = nil)
480
+ rw_config(:allow_http_basic_auth, value, false)
481
+ end
482
+ alias allow_http_basic_auth= allow_http_basic_auth
483
+
484
+ # Lets you change which model to use for authentication.
485
+ #
486
+ # * <tt>Default:</tt> inferred from the class name. UserSession would
487
+ # automatically try User
488
+ # * <tt>Accepts:</tt> an ActiveRecord class
489
+ def authenticate_with(klass)
490
+ @klass_name = klass.name
491
+ @klass = klass
492
+ end
493
+ alias authenticate_with= authenticate_with
494
+
495
+ # The current controller object
496
+ def controller
497
+ RequestStore.store[:authlogic_controller]
498
+ end
499
+
500
+ # This accepts a controller object wrapped with the Authlogic controller
501
+ # adapter. The controller adapters close the gap between the different
502
+ # controllers in each framework. That being said, Authlogic is expecting
503
+ # your object's class to extend
504
+ # Authlogic::ControllerAdapters::AbstractAdapter. See
505
+ # Authlogic::ControllerAdapters for more info.
506
+ #
507
+ # Lastly, this is thread safe.
508
+ def controller=(value)
509
+ RequestStore.store[:authlogic_controller] = value
510
+ end
511
+
512
+ # To help protect from brute force attacks you can set a limit on the
513
+ # allowed number of consecutive failed logins. By default this is 50,
514
+ # this is a very liberal number, and if someone fails to login after 50
515
+ # tries it should be pretty obvious that it's a machine trying to login
516
+ # in and very likely a brute force attack.
517
+ #
518
+ # In order to enable this field your model MUST have a
519
+ # failed_login_count (integer) field.
520
+ #
521
+ # If you don't know what a brute force attack is, it's when a machine
522
+ # tries to login into a system using every combination of character
523
+ # possible. Thus resulting in possibly millions of attempts to log into
524
+ # an account.
525
+ #
526
+ # * <tt>Default:</tt> 50
527
+ # * <tt>Accepts:</tt> Integer, set to 0 to disable
528
+ def consecutive_failed_logins_limit(value = nil)
529
+ rw_config(:consecutive_failed_logins_limit, value, 50)
530
+ end
531
+ alias consecutive_failed_logins_limit= consecutive_failed_logins_limit
532
+
533
+ # The name of the cookie or the key in the cookies hash. Be sure and use
534
+ # a unique name. If you have multiple sessions and they use the same
535
+ # cookie it will cause problems. Also, if a id is set it will be
536
+ # inserted into the beginning of the string. Example:
537
+ #
538
+ # session = UserSession.new
539
+ # session.cookie_key => "user_credentials"
540
+ #
541
+ # session = UserSession.new(:super_high_secret)
542
+ # session.cookie_key => "super_high_secret_user_credentials"
543
+ #
544
+ # * <tt>Default:</tt> "#{klass_name.underscore}_credentials"
545
+ # * <tt>Accepts:</tt> String
546
+ def cookie_key(value = nil)
547
+ rw_config(:cookie_key, value, "#{klass_name.underscore}_credentials")
548
+ end
549
+ alias cookie_key= cookie_key
550
+
551
+ # A convenience method. The same as:
552
+ #
553
+ # session = UserSession.new(*args)
554
+ # session.save
555
+ #
556
+ # Instead you can do:
557
+ #
558
+ # UserSession.create(*args)
559
+ def create(*args, &block)
560
+ session = new(*args)
561
+ session.save(&block)
562
+ session
563
+ end
564
+
565
+ # Same as create but calls create!, which raises an exception when
566
+ # validation fails.
567
+ def create!(*args)
568
+ session = new(*args)
569
+ session.save!
570
+ session
571
+ end
572
+
573
+ # Set this to true if you want to disable the checking of active?, approved?, and
574
+ # confirmed? on your record. This is more or less of a convenience feature, since
575
+ # 99% of the time if those methods exist and return false you will not want the
576
+ # user logging in. You could easily accomplish this same thing with a
577
+ # before_validation method or other callbacks.
578
+ #
579
+ # * <tt>Default:</tt> false
580
+ # * <tt>Accepts:</tt> Boolean
581
+ def disable_magic_states(value = nil)
582
+ rw_config(:disable_magic_states, value, false)
583
+ end
584
+ alias disable_magic_states= disable_magic_states
585
+
586
+ # Once the failed logins limit has been exceed, how long do you want to
587
+ # ban the user? This can be a temporary or permanent ban.
588
+ #
589
+ # * <tt>Default:</tt> 2.hours
590
+ # * <tt>Accepts:</tt> Fixnum, set to 0 for permanent ban
591
+ def failed_login_ban_for(value = nil)
592
+ rw_config(:failed_login_ban_for, (!value.nil? && value) || value, 2.hours.to_i)
593
+ end
594
+ alias failed_login_ban_for= failed_login_ban_for
595
+
596
+ # This is how you persist a session. This finds the record for the
597
+ # current session using a variety of methods. It basically tries to "log
598
+ # in" the user without the user having to explicitly log in. Check out
599
+ # the other Authlogic::Session modules for more information.
600
+ #
601
+ # The best way to use this method is something like:
602
+ #
603
+ # helper_method :current_user_session, :current_user
604
+ #
605
+ # def current_user_session
606
+ # return @current_user_session if defined?(@current_user_session)
607
+ # @current_user_session = UserSession.find
608
+ # end
609
+ #
610
+ # def current_user
611
+ # return @current_user if defined?(@current_user)
612
+ # @current_user = current_user_session && current_user_session.user
613
+ # end
614
+ #
615
+ # Also, this method accepts a single parameter as the id, to find
616
+ # session that you marked with an id:
617
+ #
618
+ # UserSession.find(:secure)
619
+ #
620
+ # See the id method for more information on ids.
621
+ #
622
+ # Priority Record
623
+ # ===============
624
+ #
625
+ # This internal feature supports ActiveRecord's optimistic locking feature,
626
+ # which is automatically enabled when a table has a `lock_version` column.
627
+ #
628
+ # ```
629
+ # # https://api.rubyonrails.org/classes/ActiveRecord/Locking/Optimistic.html
630
+ # p1 = Person.find(1)
631
+ # p2 = Person.find(1)
632
+ # p1.first_name = "Michael"
633
+ # p1.save
634
+ # p2.first_name = "should fail"
635
+ # p2.save # Raises an ActiveRecord::StaleObjectError
636
+ # ```
637
+ #
638
+ # Now, consider the following Authlogic scenario:
639
+ #
640
+ # ```
641
+ # User.log_in_after_password_change = true
642
+ # ben = User.find(1)
643
+ # UserSession.create(ben)
644
+ # ben.password = "newpasswd"
645
+ # ben.password_confirmation = "newpasswd"
646
+ # ben.save
647
+ # ```
648
+ #
649
+ # We've used one of Authlogic's session maintenance features,
650
+ # `log_in_after_password_change`. So, when we call `ben.save`, there is a
651
+ # `before_save` callback that logs Ben in (`UserSession.find`). Well, when
652
+ # we log Ben in, we update his user record, eg. `login_count`. When we're
653
+ # done logging Ben in, then the normal `ben.save` happens. So, there were
654
+ # two `update` queries. If those two updates came from different User
655
+ # instances, we would get a `StaleObjectError`.
656
+ #
657
+ # Our solution is to carefully pass around a single `User` instance, using
658
+ # it for all `update` queries, thus avoiding the `StaleObjectError`.
659
+ def find(id = nil, priority_record = nil)
660
+ session = new({ priority_record: priority_record }, id)
661
+ session.priority_record = priority_record
662
+ if session.persisting?
663
+ session
664
+ end
665
+ end
666
+
667
+ # Authlogic tries to validate the credentials passed to it. One part of
668
+ # validation is actually finding the user and making sure it exists.
669
+ # What method it uses the do this is up to you.
670
+ #
671
+ # Let's say you have a UserSession that is authenticating a User. By
672
+ # default UserSession will call User.find_by_login(login). You can
673
+ # change what method UserSession calls by specifying it here. Then in
674
+ # your User model you can make that method do anything you want, giving
675
+ # you complete control of how users are found by the UserSession.
676
+ #
677
+ # Let's take an example: You want to allow users to login by username or
678
+ # email. Set this to the name of the class method that does this in the
679
+ # User model. Let's call it "find_by_username_or_email"
680
+ #
681
+ # class User < ActiveRecord::Base
682
+ # def self.find_by_username_or_email(login)
683
+ # find_by_username(login) || find_by_email(login)
684
+ # end
685
+ # end
686
+ #
687
+ # Now just specify the name of this method for this configuration option
688
+ # and you are all set. You can do anything you want here. Maybe you
689
+ # allow users to have multiple logins and you want to search a has_many
690
+ # relationship, etc. The sky is the limit.
691
+ #
692
+ # * <tt>Default:</tt> "find_by_smart_case_login_field"
693
+ # * <tt>Accepts:</tt> Symbol or String
694
+ def find_by_login_method(value = nil)
695
+ rw_config(:find_by_login_method, value, "find_by_smart_case_login_field")
696
+ end
697
+ alias find_by_login_method= find_by_login_method
698
+
699
+ # The text used to identify credentials (username/password) combination
700
+ # when a bad login attempt occurs. When you show error messages for a
701
+ # bad login, it's considered good security practice to hide which field
702
+ # the user has entered incorrectly (the login field or the password
703
+ # field). For a full explanation, see
704
+ # http://www.gnucitizen.org/blog/username-enumeration-vulnerabilities/
705
+ #
706
+ # Example of use:
707
+ #
708
+ # class UserSession < Authlogic::Session::Base
709
+ # generalize_credentials_error_messages true
710
+ # end
711
+ #
712
+ # This would make the error message for bad logins and bad passwords
713
+ # look identical:
714
+ #
715
+ # Login/Password combination is not valid
716
+ #
717
+ # Alternatively you may use a custom message:
718
+ #
719
+ # class UserSession < AuthLogic::Session::Base
720
+ # generalize_credentials_error_messages "Your login information is invalid"
721
+ # end
722
+ #
723
+ # This will instead show your custom error message when the UserSession is invalid.
724
+ #
725
+ # The downside to enabling this is that is can be too vague for a user
726
+ # that has a hard time remembering their username and password
727
+ # combinations. It also disables the ability to to highlight the field
728
+ # with the error when you use form_for.
729
+ #
730
+ # If you are developing an app where security is an extreme priority
731
+ # (such as a financial application), then you should enable this.
732
+ # Otherwise, leaving this off is fine.
733
+ #
734
+ # * <tt>Default</tt> false
735
+ # * <tt>Accepts:</tt> Boolean
736
+ def generalize_credentials_error_messages(value = nil)
737
+ rw_config(:generalize_credentials_error_messages, value, false)
738
+ end
739
+ alias generalize_credentials_error_messages= generalize_credentials_error_messages
740
+
741
+ # HTTP authentication realm
742
+ #
743
+ # Sets the HTTP authentication realm.
744
+ #
745
+ # Note: This option has no effect unless request_http_basic_auth is true
746
+ #
747
+ # * <tt>Default:</tt> 'Application'
748
+ # * <tt>Accepts:</tt> String
749
+ def http_basic_auth_realm(value = nil)
750
+ rw_config(:http_basic_auth_realm, value, "Application")
751
+ end
752
+ alias http_basic_auth_realm= http_basic_auth_realm
753
+
754
+ # Should the cookie be set as httponly? If true, the cookie will not be
755
+ # accessible from javascript
756
+ #
757
+ # * <tt>Default:</tt> true
758
+ # * <tt>Accepts:</tt> Boolean
759
+ def httponly(value = nil)
760
+ rw_config(:httponly, value, true)
761
+ end
762
+ alias httponly= httponly
763
+
764
+ # How to name the class, works JUST LIKE ActiveRecord, except it uses
765
+ # the following namespace:
766
+ #
767
+ # authlogic.models.user_session
768
+ def human_name(*)
769
+ I18n.t("models.#{name.underscore}", count: 1, default: name.humanize)
770
+ end
771
+
772
+ def i18n_scope
773
+ I18n.scope
774
+ end
775
+
776
+ # The name of the class that this session is authenticating with. For
777
+ # example, the UserSession class will authenticate with the User class
778
+ # unless you specify otherwise in your configuration. See
779
+ # authenticate_with for information on how to change this value.
780
+ def klass
781
+ @klass ||= klass_name ? klass_name.constantize : nil
782
+ end
783
+
784
+ # The string of the model name class guessed from the actual session class name.
785
+ def klass_name
786
+ return @klass_name if defined?(@klass_name)
787
+ @klass_name = name.scan(/(.*)Session/)[0]
788
+ @klass_name = klass_name ? klass_name[0] : nil
789
+ end
790
+
791
+ # The name of the method you want Authlogic to create for storing the
792
+ # login / username. Keep in mind this is just for your
793
+ # Authlogic::Session, if you want it can be something completely
794
+ # different than the field in your model. So if you wanted people to
795
+ # login with a field called "login" and then find users by email this is
796
+ # completely doable. See the find_by_login_method configuration option
797
+ # for more details.
798
+ #
799
+ # * <tt>Default:</tt> klass.login_field || klass.email_field
800
+ # * <tt>Accepts:</tt> Symbol or String
801
+ def login_field(value = nil)
802
+ rw_config(:login_field, value, klass.login_field || klass.email_field)
803
+ end
804
+ alias login_field= login_field
805
+
806
+ # With acts_as_authentic you get a :logged_in_timeout configuration
807
+ # option. If this is set, after this amount of time has passed the user
808
+ # will be marked as logged out. Obviously, since web based apps are on a
809
+ # per request basis, we have to define a time limit threshold that
810
+ # determines when we consider a user to be "logged out". Meaning, if
811
+ # they login and then leave the website, when do mark them as logged
812
+ # out? I recommend just using this as a fun feature on your website or
813
+ # reports, giving you a ballpark number of users logged in and active.
814
+ # This is not meant to be a dead accurate representation of a user's
815
+ # logged in state, since there is really no real way to do this with web
816
+ # based apps. Think about a user that logs in and doesn't log out. There
817
+ # is no action that tells you that the user isn't technically still
818
+ # logged in and active.
819
+ #
820
+ # That being said, you can use that feature to require a new login if
821
+ # their session times out. Similar to how financial sites work. Just set
822
+ # this option to true and if your record returns true for stale? then
823
+ # they will be required to log back in.
824
+ #
825
+ # Lastly, UserSession.find will still return an object if the session is
826
+ # stale, but you will not get a record. This allows you to determine if
827
+ # the user needs to log back in because their session went stale, or
828
+ # because they just aren't logged in. Just call
829
+ # current_user_session.stale? as your flag.
830
+ #
831
+ # * <tt>Default:</tt> false
832
+ # * <tt>Accepts:</tt> Boolean
833
+ def logout_on_timeout(value = nil)
834
+ rw_config(:logout_on_timeout, value, false)
835
+ end
836
+ alias logout_on_timeout= logout_on_timeout
837
+
838
+ # Every time a session is found the last_request_at field for that record is
839
+ # updated with the current time, if that field exists. If you want to limit how
840
+ # frequent that field is updated specify the threshold here. For example, if your
841
+ # user is making a request every 5 seconds, and you feel this is too frequent, and
842
+ # feel a minute is a good threshold. Set this to 1.minute. Once a minute has
843
+ # passed in between requests the field will be updated.
844
+ #
845
+ # * <tt>Default:</tt> 0
846
+ # * <tt>Accepts:</tt> integer representing time in seconds
847
+ def last_request_at_threshold(value = nil)
848
+ rw_config(:last_request_at_threshold, value, 0)
849
+ end
850
+ alias last_request_at_threshold= last_request_at_threshold
851
+
852
+ # Works exactly like cookie_key, but for params. So a user can login via
853
+ # params just like a cookie or a session. Your URL would look like:
854
+ #
855
+ # http://www.domain.com?user_credentials=my_single_access_key
856
+ #
857
+ # You can change the "user_credentials" key above with this
858
+ # configuration option. Keep in mind, just like cookie_key, if you
859
+ # supply an id the id will be appended to the front. Check out
860
+ # cookie_key for more details. Also checkout the "Single Access /
861
+ # Private Feeds Access" section in the README.
862
+ #
863
+ # * <tt>Default:</tt> cookie_key
864
+ # * <tt>Accepts:</tt> String
865
+ def params_key(value = nil)
866
+ rw_config(:params_key, value, cookie_key)
867
+ end
868
+ alias params_key= params_key
869
+
870
+ # Works exactly like login_field, but for the password instead. Returns
871
+ # :password if a login_field exists.
872
+ #
873
+ # * <tt>Default:</tt> :password
874
+ # * <tt>Accepts:</tt> Symbol or String
875
+ def password_field(value = nil)
876
+ rw_config(:password_field, value, login_field && :password)
877
+ end
878
+ alias password_field= password_field
879
+
880
+ # Whether or not to request HTTP authentication
881
+ #
882
+ # If set to true and no HTTP authentication credentials are sent with
883
+ # the request, the Rails controller method
884
+ # authenticate_or_request_with_http_basic will be used and a '401
885
+ # Authorization Required' header will be sent with the response. In
886
+ # most cases, this will cause the classic HTTP authentication popup to
887
+ # appear in the users browser.
888
+ #
889
+ # If set to false, the Rails controller method
890
+ # authenticate_with_http_basic is used and no 401 header is sent.
891
+ #
892
+ # Note: This parameter has no effect unless allow_http_basic_auth is
893
+ # true
894
+ #
895
+ # * <tt>Default:</tt> false
896
+ # * <tt>Accepts:</tt> Boolean
897
+ def request_http_basic_auth(value = nil)
898
+ rw_config(:request_http_basic_auth, value, false)
899
+ end
900
+ alias request_http_basic_auth= request_http_basic_auth
901
+
902
+ # If sessions should be remembered by default or not.
903
+ #
904
+ # * <tt>Default:</tt> false
905
+ # * <tt>Accepts:</tt> Boolean
906
+ def remember_me(value = nil)
907
+ rw_config(:remember_me, value, false)
908
+ end
909
+ alias remember_me= remember_me
910
+
911
+ # The length of time until the cookie expires.
912
+ #
913
+ # * <tt>Default:</tt> 3.months
914
+ # * <tt>Accepts:</tt> Integer, length of time in seconds, such as 60 or 3.months
915
+ def remember_me_for(value = nil)
916
+ rw_config(:remember_me_for, value, 3.months)
917
+ end
918
+ alias remember_me_for= remember_me_for
919
+
920
+ # Should the cookie be prevented from being send along with cross-site
921
+ # requests?
922
+ #
923
+ # * <tt>Default:</tt> nil
924
+ # * <tt>Accepts:</tt> String, one of nil, 'Lax' or 'Strict'
925
+ def same_site(value = nil)
926
+ unless VALID_SAME_SITE_VALUES.include?(value)
927
+ msg = "Invalid same_site value: #{value}. Valid: #{VALID_SAME_SITE_VALUES.inspect}"
928
+ raise ArgumentError, msg
929
+ end
930
+ rw_config(:same_site, value)
931
+ end
932
+ alias same_site= same_site
933
+
934
+ # The current scope set, should be used in the block passed to with_scope.
935
+ def scope
936
+ RequestStore.store[:authlogic_scope]
937
+ end
938
+
939
+ # Should the cookie be set as secure? If true, the cookie will only be sent over
940
+ # SSL connections
941
+ #
942
+ # * <tt>Default:</tt> true
943
+ # * <tt>Accepts:</tt> Boolean
944
+ def secure(value = nil)
945
+ rw_config(:secure, value, true)
946
+ end
947
+ alias secure= secure
948
+
949
+ # Should the cookie be signed? If the controller adapter supports it, this is a
950
+ # measure against cookie tampering.
951
+ def sign_cookie(value = nil)
952
+ if value && !controller.cookies.respond_to?(:signed)
953
+ raise "Signed cookies not supported with #{controller.class}!"
954
+ end
955
+ rw_config(:sign_cookie, value, false)
956
+ end
957
+ alias sign_cookie= sign_cookie
958
+
959
+ # Works exactly like cookie_key, but for sessions. See cookie_key for more info.
960
+ #
961
+ # * <tt>Default:</tt> cookie_key
962
+ # * <tt>Accepts:</tt> Symbol or String
963
+ def session_key(value = nil)
964
+ rw_config(:session_key, value, cookie_key)
965
+ end
966
+ alias session_key= session_key
967
+
968
+ # Authentication is allowed via a single access token, but maybe this is
969
+ # something you don't want for your application as a whole. Maybe this
970
+ # is something you only want for specific request types. Specify a list
971
+ # of allowed request types and single access authentication will only be
972
+ # allowed for the ones you specify.
973
+ #
974
+ # * <tt>Default:</tt> ["application/rss+xml", "application/atom+xml"]
975
+ # * <tt>Accepts:</tt> String of a request type, or :all or :any to
976
+ # allow single access authentication for any and all request types
977
+ def single_access_allowed_request_types(value = nil)
978
+ rw_config(
979
+ :single_access_allowed_request_types,
980
+ value,
981
+ ["application/rss+xml", "application/atom+xml"]
982
+ )
983
+ end
984
+ alias single_access_allowed_request_types= single_access_allowed_request_types
985
+
986
+ # The name of the method in your model used to verify the password. This
987
+ # should be an instance method. It should also be prepared to accept a
988
+ # raw password and a crytped password.
989
+ #
990
+ # * <tt>Default:</tt> "valid_password?" defined in acts_as_authentic/password.rb
991
+ # * <tt>Accepts:</tt> Symbol or String
992
+ def verify_password_method(value = nil)
993
+ rw_config(:verify_password_method, value, "valid_password?")
994
+ end
995
+ alias verify_password_method= verify_password_method
996
+
997
+ # What with_scopes focuses on is scoping the query when finding the
998
+ # object and the name of the cookie / session. It works very similar to
999
+ # ActiveRecord::Base#with_scopes. It accepts a hash with any of the
1000
+ # following options:
1001
+ #
1002
+ # * <tt>find_options:</tt> any options you can pass into ActiveRecord::Base.find.
1003
+ # This is used when trying to find the record.
1004
+ # * <tt>id:</tt> The id of the session, this gets merged with the real id. For
1005
+ # information ids see the id method.
1006
+ #
1007
+ # Here is how you use it:
1008
+ #
1009
+ # ```
1010
+ # UserSession.with_scope(find_options: User.where(account_id: 2), id: "account_2") do
1011
+ # UserSession.find
1012
+ # end
1013
+ # ```
1014
+ #
1015
+ # Essentially what the above does is scope the searching of the object
1016
+ # with the sql you provided. So instead of:
1017
+ #
1018
+ # ```
1019
+ # User.where("login = 'ben'").first
1020
+ # ```
1021
+ #
1022
+ # it would effectively be:
1023
+ #
1024
+ # ```
1025
+ # User.where("login = 'ben' and account_id = 2").first
1026
+ # ```
1027
+ #
1028
+ # You will also notice the :id option. This works just like the id
1029
+ # method. It scopes your cookies. So the name of your cookie will be:
1030
+ #
1031
+ # account_2_user_credentials
1032
+ #
1033
+ # instead of:
1034
+ #
1035
+ # user_credentials
1036
+ #
1037
+ # What is also nifty about scoping with an :id is that it merges your
1038
+ # id's. So if you do:
1039
+ #
1040
+ # UserSession.with_scope(
1041
+ # find_options: { conditions: "account_id = 2"},
1042
+ # id: "account_2"
1043
+ # ) do
1044
+ # session = UserSession.new
1045
+ # session.id = :secure
1046
+ # end
1047
+ #
1048
+ # The name of your cookies will be:
1049
+ #
1050
+ # secure_account_2_user_credentials
1051
+ def with_scope(options = {})
1052
+ raise ArgumentError, "You must provide a block" unless block_given?
1053
+ self.scope = options
1054
+ result = yield
1055
+ self.scope = nil
1056
+ result
1057
+ end
1058
+ end
1059
+
1060
+ # Constructor
1061
+ # ===========
1062
+
1063
+ # rubocop:disable Metrics/AbcSize
1064
+ def initialize(*args)
1065
+ @id = nil
1066
+ self.scope = self.class.scope
1067
+
1068
+ # Creating an alias method for the "record" method based on the klass
1069
+ # name, so that we can do:
1070
+ #
1071
+ # session.user
1072
+ #
1073
+ # instead of:
1074
+ #
1075
+ # session.record
1076
+ unless self.class.configured_klass_methods
1077
+ self.class.send(:alias_method, klass_name.demodulize.underscore.to_sym, :record)
1078
+ self.class.configured_klass_methods = true
1079
+ end
1080
+
1081
+ raise Activation::NotActivatedError unless self.class.activated?
1082
+ unless self.class.configured_password_methods
1083
+ configure_password_methods
1084
+ self.class.configured_password_methods = true
1085
+ end
1086
+ instance_variable_set("@#{password_field}", nil)
1087
+ self.credentials = args
1088
+ end
1089
+ # rubocop:enable Metrics/AbcSize
1090
+
1091
+ # Public instance methods
1092
+ # =======================
1093
+
1094
+ # You should use this as a place holder for any records that you find
1095
+ # during validation. The main reason for this is to allow other modules to
1096
+ # use it if needed. Take the failed_login_count feature, it needs this in
1097
+ # order to increase the failed login count.
1098
+ def attempted_record
1099
+ @attempted_record
1100
+ end
1101
+
1102
+ # See attempted_record
1103
+ def attempted_record=(value)
1104
+ value = priority_record if value == priority_record # See notes in `.find`
1105
+ @attempted_record = value
1106
+ end
1107
+
1108
+ # Returns true when the consecutive_failed_logins_limit has been
1109
+ # exceeded and is being temporarily banned. Notice the word temporary,
1110
+ # the user will not be permanently banned unless you choose to do so
1111
+ # with configuration. By default they will be banned for 2 hours. During
1112
+ # that 2 hour period this method will return true.
1113
+ def being_brute_force_protected?
1114
+ exceeded_failed_logins_limit? &&
1115
+ (
1116
+ failed_login_ban_for <= 0 ||
1117
+ attempted_record.respond_to?(:updated_at) &&
1118
+ attempted_record.updated_at >= failed_login_ban_for.seconds.ago
1119
+ )
1120
+ end
1121
+
1122
+ # The credentials you passed to create your session, in a redacted format
1123
+ # intended for output (debugging, logging). See credentials= for more
1124
+ # info.
1125
+ #
1126
+ # @api private
1127
+ def credentials
1128
+ if authenticating_with_unauthorized_record?
1129
+ { unauthorized_record: "<protected>" }
1130
+ elsif authenticating_with_password?
1131
+ {
1132
+ login_field.to_sym => send(login_field),
1133
+ password_field.to_sym => "<protected>"
1134
+ }
1135
+ else
1136
+ {}
1137
+ end
1138
+ end
1139
+
1140
+ # Set your credentials before you save your session. There are many
1141
+ # method signatures.
1142
+ #
1143
+ # ```
1144
+ # # A hash of credentials is most common
1145
+ # session.credentials = { login: "foo", password: "bar", remember_me: true }
1146
+ #
1147
+ # # You must pass an actual Hash, `ActionController::Parameters` is
1148
+ # # specifically not allowed.
1149
+ #
1150
+ # # You can pass an array of objects:
1151
+ # session.credentials = [my_user_object, true]
1152
+ #
1153
+ # # If you need to set an id (see `#id`) pass it last.
1154
+ # session.credentials = [
1155
+ # {:login => "foo", :password => "bar", :remember_me => true},
1156
+ # :my_id
1157
+ # ]
1158
+ # session.credentials = [my_user_object, true, :my_id]
1159
+ #
1160
+ # The `id` is something that you control yourself, it should never be
1161
+ # set from a hash or a form.
1162
+ #
1163
+ # # Finally, there's priority_record
1164
+ # [{ priority_record: my_object }, :my_id]
1165
+ # ```
1166
+ #
1167
+ # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
1168
+ def credentials=(value)
1169
+ normalized = Array.wrap(value)
1170
+ if normalized.first.class.name == "ActionController::Parameters"
1171
+ raise TypeError, E_AC_PARAMETERS
1172
+ end
1173
+
1174
+ # Allows you to set the remember_me option when passing credentials.
1175
+ values = value.is_a?(Array) ? value : [value]
1176
+ case values.first
1177
+ when Hash
1178
+ if values.first.with_indifferent_access.key?(:remember_me)
1179
+ self.remember_me = values.first.with_indifferent_access[:remember_me]
1180
+ end
1181
+ else
1182
+ r = values.find { |val| val.is_a?(TrueClass) || val.is_a?(FalseClass) }
1183
+ self.remember_me = r unless r.nil?
1184
+ end
1185
+
1186
+ # Accepts the login_field / password_field credentials combination in
1187
+ # hash form.
1188
+ #
1189
+ # You must pass an actual Hash, `ActionController::Parameters` is
1190
+ # specifically not allowed.
1191
+ values = Array.wrap(value)
1192
+ if values.first.is_a?(Hash)
1193
+ sliced = values
1194
+ .first
1195
+ .with_indifferent_access
1196
+ .slice(login_field, password_field)
1197
+ sliced.each do |field, val|
1198
+ next if val.blank?
1199
+ send("#{field}=", val)
1200
+ end
1201
+ end
1202
+
1203
+ # Setting the unauthorized record if it exists in the credentials passed.
1204
+ values = value.is_a?(Array) ? value : [value]
1205
+ self.unauthorized_record = values.first if values.first.class < ::ActiveRecord::Base
1206
+
1207
+ # Setting the id if it is passed in the credentials.
1208
+ values = value.is_a?(Array) ? value : [value]
1209
+ self.id = values.last if values.last.is_a?(Symbol)
1210
+
1211
+ # Setting priority record if it is passed. The only way it can be passed
1212
+ # is through an array:
1213
+ #
1214
+ # session.credentials = [real_user_object, priority_user_object]
1215
+ #
1216
+ # See notes in `.find`
1217
+ values = value.is_a?(Array) ? value : [value]
1218
+ self.priority_record = values[1] if values[1].class < ::ActiveRecord::Base
1219
+ end
1220
+ # rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
1221
+
1222
+ # Clears all errors and the associated record, you should call this
1223
+ # terminate a session, thus requiring the user to authenticate again if
1224
+ # it is needed.
1225
+ def destroy
1226
+ run_callbacks :before_destroy
1227
+ save_record
1228
+ errors.clear
1229
+ @record = nil
1230
+ run_callbacks :after_destroy
1231
+ true
1232
+ end
1233
+
1234
+ def destroyed?
1235
+ record.nil?
1236
+ end
1237
+
1238
+ # @api public
1239
+ def errors
1240
+ @errors ||= ::ActiveModel::Errors.new(self)
1241
+ end
1242
+
1243
+ # If the cookie should be marked as httponly (not accessible via javascript)
1244
+ def httponly
1245
+ return @httponly if defined?(@httponly)
1246
+ @httponly = self.class.httponly
1247
+ end
1248
+
1249
+ # Accepts a boolean as to whether the cookie should be marked as
1250
+ # httponly. If true, the cookie will not be accessible from javascript
1251
+ def httponly=(value)
1252
+ @httponly = value
1253
+ end
1254
+
1255
+ # See httponly
1256
+ def httponly?
1257
+ httponly == true || httponly == "true" || httponly == "1"
1258
+ end
1259
+
1260
+ # Allows you to set a unique identifier for your session, so that you can
1261
+ # have more than 1 session at a time.
1262
+ #
1263
+ # For example, you may want to have simultaneous private and public
1264
+ # sessions. Or, a normal user session and a "secure" user session. The
1265
+ # secure user session would be created only when they want to modify their
1266
+ # billing information, or other sensitive information.
1267
+ #
1268
+ # You can set the id during initialization (see initialize for more
1269
+ # information), or as an attribute:
1270
+ #
1271
+ # session.id = :my_id
1272
+ #
1273
+ # Set your id before you save your session.
1274
+ #
1275
+ # Lastly, to retrieve your session with the id, use the `.find` method.
1276
+ def id
1277
+ @id
1278
+ end
1279
+
1280
+ def inspect
1281
+ format(
1282
+ "#<%s: %s>",
1283
+ self.class.name,
1284
+ credentials.blank? ? "no credentials provided" : credentials.inspect
1285
+ )
1286
+ end
1287
+
1288
+ def invalid_password?
1289
+ invalid_password == true
1290
+ end
1291
+
1292
+ # Don't use this yourself, this is to just trick some of the helpers
1293
+ # since this is the method it calls.
1294
+ def new_record?
1295
+ new_session?
1296
+ end
1297
+
1298
+ # Returns true if the session is new, meaning no action has been taken
1299
+ # on it and a successful save has not taken place.
1300
+ def new_session?
1301
+ new_session != false
1302
+ end
1303
+
1304
+ def persisted?
1305
+ !(new_record? || destroyed?)
1306
+ end
1307
+
1308
+ # Returns boolean indicating if the session is being persisted or not,
1309
+ # meaning the user does not have to explicitly log in in order to be
1310
+ # logged in.
1311
+ #
1312
+ # If the session has no associated record, it will try to find a record
1313
+ # and persist the session.
1314
+ #
1315
+ # This is the method that the class level method find uses to ultimately
1316
+ # persist the session.
1317
+ def persisting?
1318
+ return true unless record.nil?
1319
+ self.attempted_record = nil
1320
+ self.remember_me = cookie_credentials&.remember_me?
1321
+ run_callbacks :before_persisting
1322
+ run_callbacks :persist
1323
+ ensure_authentication_attempted
1324
+ if errors.empty? && !attempted_record.nil?
1325
+ self.record = attempted_record
1326
+ run_callbacks :after_persisting
1327
+ save_record
1328
+ self.new_session = false
1329
+ true
1330
+ else
1331
+ false
1332
+ end
1333
+ end
1334
+
1335
+ def save_record(alternate_record = nil)
1336
+ r = alternate_record || record
1337
+ if r != priority_record
1338
+ if r&.has_changes_to_save? && !r.readonly?
1339
+ r.save_without_session_maintenance(validate: false)
1340
+ end
1341
+ end
1342
+ end
1343
+
1344
+ # Tells you if the record is stale or not. Meaning the record has timed
1345
+ # out. This will only return true if you set logout_on_timeout to true
1346
+ # in your configuration. Basically how a bank website works. If you
1347
+ # aren't active over a certain period of time your session becomes stale
1348
+ # and requires you to log back in.
1349
+ def stale?
1350
+ if remember_me?
1351
+ remember_me_expired?
1352
+ else
1353
+ !stale_record.nil? || (logout_on_timeout? && record && record.logged_out?)
1354
+ end
1355
+ end
1356
+
1357
+ # Is the cookie going to expire after the session is over, or will it stick around?
1358
+ def remember_me
1359
+ return @remember_me if defined?(@remember_me)
1360
+ @remember_me = self.class.remember_me
1361
+ end
1362
+
1363
+ # Accepts a boolean as a flag to remember the session or not. Basically
1364
+ # to expire the cookie at the end of the session or keep it for
1365
+ # "remember_me_until".
1366
+ def remember_me=(value)
1367
+ @remember_me = value
1368
+ end
1369
+
1370
+ # See remember_me
1371
+ def remember_me?
1372
+ remember_me == true || remember_me == "true" || remember_me == "1"
1373
+ end
1374
+
1375
+ # Has the cookie expired due to current time being greater than remember_me_until.
1376
+ def remember_me_expired?
1377
+ return unless remember_me?
1378
+ cookie_credentials.remember_me_until < ::Time.now
1379
+ end
1380
+
1381
+ # How long to remember the user if remember_me is true. This is based on the class
1382
+ # level configuration: remember_me_for
1383
+ def remember_me_for
1384
+ return unless remember_me?
1385
+ self.class.remember_me_for
1386
+ end
1387
+
1388
+ # When to expire the cookie. See remember_me_for configuration option to change
1389
+ # this.
1390
+ def remember_me_until
1391
+ return unless remember_me?
1392
+ remember_me_for.from_now
1393
+ end
1394
+
1395
+ # After you have specified all of the details for your session you can
1396
+ # try to save it. This will run validation checks and find the
1397
+ # associated record, if all validation passes. If validation does not
1398
+ # pass, the save will fail and the errors will be stored in the errors
1399
+ # object.
1400
+ def save
1401
+ result = nil
1402
+ if valid?
1403
+ self.record = attempted_record
1404
+
1405
+ run_callbacks :before_save
1406
+ run_callbacks(new_session? ? :before_create : :before_update)
1407
+ run_callbacks(new_session? ? :after_create : :after_update)
1408
+ run_callbacks :after_save
1409
+
1410
+ save_record
1411
+ self.new_session = false
1412
+ result = true
1413
+ else
1414
+ result = false
1415
+ end
1416
+
1417
+ yield result if block_given?
1418
+ result
1419
+ end
1420
+
1421
+ # Same as save but raises an exception of validation errors when
1422
+ # validation fails
1423
+ def save!
1424
+ result = save
1425
+ raise Existence::SessionInvalidError, self unless result
1426
+ result
1427
+ end
1428
+
1429
+ # If the cookie should be marked as secure (SSL only)
1430
+ def secure
1431
+ return @secure if defined?(@secure)
1432
+ @secure = self.class.secure
1433
+ end
1434
+
1435
+ # Accepts a boolean as to whether the cookie should be marked as secure. If true
1436
+ # the cookie will only ever be sent over an SSL connection.
1437
+ def secure=(value)
1438
+ @secure = value
1439
+ end
1440
+
1441
+ # See secure
1442
+ def secure?
1443
+ secure == true || secure == "true" || secure == "1"
1444
+ end
1445
+
1446
+ # If the cookie should be marked as SameSite with 'Lax' or 'Strict' flag.
1447
+ def same_site
1448
+ return @same_site if defined?(@same_site)
1449
+ @same_site = self.class.same_site(nil)
1450
+ end
1451
+
1452
+ # Accepts nil, 'Lax' or 'Strict' as possible flags.
1453
+ def same_site=(value)
1454
+ unless VALID_SAME_SITE_VALUES.include?(value)
1455
+ msg = "Invalid same_site value: #{value}. Valid: #{VALID_SAME_SITE_VALUES.inspect}"
1456
+ raise ArgumentError, msg
1457
+ end
1458
+ @same_site = value
1459
+ end
1460
+
1461
+ # If the cookie should be signed
1462
+ def sign_cookie
1463
+ return @sign_cookie if defined?(@sign_cookie)
1464
+ @sign_cookie = self.class.sign_cookie
1465
+ end
1466
+
1467
+ # Accepts a boolean as to whether the cookie should be signed. If true
1468
+ # the cookie will be saved and verified using a signature.
1469
+ def sign_cookie=(value)
1470
+ @sign_cookie = value
1471
+ end
1472
+
1473
+ # See sign_cookie
1474
+ def sign_cookie?
1475
+ sign_cookie == true || sign_cookie == "true" || sign_cookie == "1"
1476
+ end
1477
+
1478
+ # The scope of the current object
1479
+ def scope
1480
+ @scope ||= {}
1481
+ end
1482
+
1483
+ def to_key
1484
+ new_record? ? nil : record.to_key
1485
+ end
1486
+
1487
+ # For rails >= 3.0
1488
+ def to_model
1489
+ self
1490
+ end
1491
+
1492
+ # Determines if the information you provided for authentication is valid
1493
+ # or not. If there is a problem with the information provided errors will
1494
+ # be added to the errors object and this method will return false.
1495
+ def valid?
1496
+ errors.clear
1497
+ self.attempted_record = nil
1498
+
1499
+ run_callbacks(:before_validation)
1500
+ run_callbacks(new_session? ? :before_validation_on_create : :before_validation_on_update)
1501
+
1502
+ # Run the `validate` callbacks, eg. `validate_by_password`.
1503
+ # This is when `attempted_record` is set.
1504
+ run_callbacks(:validate)
1505
+
1506
+ ensure_authentication_attempted
1507
+
1508
+ if errors.empty?
1509
+ run_callbacks(new_session? ? :after_validation_on_create : :after_validation_on_update)
1510
+ run_callbacks(:after_validation)
1511
+ end
1512
+
1513
+ save_record(attempted_record)
1514
+ errors.empty?
1515
+ end
1516
+
1517
+ # Private class methods
1518
+ # =====================
1519
+
1520
+ class << self
1521
+ private
1522
+
1523
+ def scope=(value)
1524
+ RequestStore.store[:authlogic_scope] = value
1525
+ end
1526
+ end
1527
+
1528
+ # Private instance methods
1529
+ # ========================
1530
+
1531
+ private
1532
+
1533
+ def add_general_credentials_error
1534
+ error_message =
1535
+ if self.class.generalize_credentials_error_messages.is_a? String
1536
+ self.class.generalize_credentials_error_messages
1537
+ else
1538
+ "#{login_field.to_s.humanize}/Password combination is not valid"
1539
+ end
1540
+ errors.add(
1541
+ :base,
1542
+ I18n.t("error_messages.general_credentials_error", default: error_message)
1543
+ )
1544
+ end
1545
+
1546
+ def add_invalid_password_error
1547
+ if generalize_credentials_error_messages?
1548
+ add_general_credentials_error
1549
+ else
1550
+ errors.add(
1551
+ password_field,
1552
+ I18n.t("error_messages.password_invalid", default: "is not valid")
1553
+ )
1554
+ end
1555
+ end
1556
+
1557
+ def add_login_not_found_error
1558
+ if generalize_credentials_error_messages?
1559
+ add_general_credentials_error
1560
+ else
1561
+ errors.add(
1562
+ login_field,
1563
+ I18n.t("error_messages.login_not_found", default: "is not valid")
1564
+ )
1565
+ end
1566
+ end
1567
+
1568
+ def allow_http_basic_auth?
1569
+ self.class.allow_http_basic_auth == true
1570
+ end
1571
+
1572
+ def authenticating_with_password?
1573
+ login_field && (!send(login_field).nil? || !send("protected_#{password_field}").nil?)
1574
+ end
1575
+
1576
+ def authenticating_with_unauthorized_record?
1577
+ !unauthorized_record.nil?
1578
+ end
1579
+
1580
+ # Used for things like cookie_key, session_key, etc.
1581
+ # Examples:
1582
+ # - user_credentials
1583
+ # - ziggity_zack_user_credentials
1584
+ # - ziggity_zack is an "id"
1585
+ # - see persistence_token_test.rb
1586
+ def build_key(last_part)
1587
+ [id, scope[:id], last_part].compact.join("_")
1588
+ end
1589
+
1590
+ def clear_failed_login_count
1591
+ if record.respond_to?(:failed_login_count)
1592
+ record.failed_login_count = 0
1593
+ end
1594
+ end
1595
+
1596
+ def consecutive_failed_logins_limit
1597
+ self.class.consecutive_failed_logins_limit
1598
+ end
1599
+
1600
+ def controller
1601
+ self.class.controller
1602
+ end
1603
+
1604
+ def cookie_key
1605
+ build_key(self.class.cookie_key)
1606
+ end
1607
+
1608
+ # Look in the `cookie_jar`, find the cookie that contains authlogic
1609
+ # credentials (`cookie_key`).
1610
+ #
1611
+ # @api private
1612
+ # @return ::Authlogic::CookieCredentials or if no cookie is found, nil
1613
+ def cookie_credentials
1614
+ cookie_value = cookie_jar[cookie_key]
1615
+ unless cookie_value.nil?
1616
+ ::Authlogic::CookieCredentials.parse(cookie_value)
1617
+ end
1618
+ end
1619
+
1620
+ def cookie_jar
1621
+ if self.class.sign_cookie
1622
+ controller.cookies.signed
1623
+ else
1624
+ controller.cookies
1625
+ end
1626
+ end
1627
+
1628
+ def configure_password_methods
1629
+ define_login_field_methods
1630
+ define_password_field_methods
1631
+ end
1632
+
1633
+ def define_login_field_methods
1634
+ return unless login_field
1635
+ self.class.send(:attr_writer, login_field) unless respond_to?("#{login_field}=")
1636
+ self.class.send(:attr_reader, login_field) unless respond_to?(login_field)
1637
+ end
1638
+
1639
+ def define_password_field_methods
1640
+ return unless password_field
1641
+ self.class.send(:attr_writer, password_field) unless respond_to?("#{password_field}=")
1642
+ self.class.send(:define_method, password_field) {} unless respond_to?(password_field)
1643
+
1644
+ # The password should not be accessible publicly. This way forms
1645
+ # using form_for don't fill the password with the attempted
1646
+ # password. To prevent this we just create this method that is
1647
+ # private.
1648
+ self.class.class_eval(
1649
+ <<-EOS, __FILE__, __LINE__ + 1
1650
+ private
1651
+ def protected_#{password_field}
1652
+ @#{password_field}
1653
+ end
1654
+ EOS
1655
+ )
1656
+ end
1657
+
1658
+ def destroy_cookie
1659
+ controller.cookies.delete cookie_key, domain: controller.cookie_domain
1660
+ end
1661
+
1662
+ def disable_magic_states?
1663
+ self.class.disable_magic_states == true
1664
+ end
1665
+
1666
+ def enforce_timeout
1667
+ if stale?
1668
+ self.stale_record = record
1669
+ self.record = nil
1670
+ end
1671
+ end
1672
+
1673
+ def ensure_authentication_attempted
1674
+ if errors.empty? && attempted_record.nil?
1675
+ errors.add(
1676
+ :base,
1677
+ I18n.t(
1678
+ "error_messages.no_authentication_details",
1679
+ default: "You did not provide any details for authentication."
1680
+ )
1681
+ )
1682
+ end
1683
+ end
1684
+
1685
+ def exceeded_failed_logins_limit?
1686
+ !attempted_record.nil? &&
1687
+ attempted_record.respond_to?(:failed_login_count) &&
1688
+ consecutive_failed_logins_limit > 0 &&
1689
+ attempted_record.failed_login_count &&
1690
+ attempted_record.failed_login_count >= consecutive_failed_logins_limit
1691
+ end
1692
+
1693
+ def find_by_login_method
1694
+ self.class.find_by_login_method
1695
+ end
1696
+
1697
+ def generalize_credentials_error_messages?
1698
+ self.class.generalize_credentials_error_messages
1699
+ end
1700
+
1701
+ # @api private
1702
+ def generate_cookie_for_saving
1703
+ creds = ::Authlogic::CookieCredentials.new(
1704
+ record.persistence_token,
1705
+ record.send(record.class.primary_key),
1706
+ remember_me? ? remember_me_until : nil
1707
+ )
1708
+ {
1709
+ value: creds.to_s,
1710
+ expires: remember_me_until,
1711
+ secure: secure,
1712
+ httponly: httponly,
1713
+ same_site: same_site,
1714
+ domain: controller.cookie_domain
1715
+ }
1716
+ end
1717
+
1718
+ # Returns a Proc to be executed by
1719
+ # `ActionController::HttpAuthentication::Basic` when credentials are
1720
+ # present in the HTTP request.
1721
+ #
1722
+ # @api private
1723
+ # @return Proc
1724
+ def http_auth_login_proc
1725
+ proc do |login, password|
1726
+ if !login.blank? && !password.blank?
1727
+ send("#{login_field}=", login)
1728
+ send("#{password_field}=", password)
1729
+ valid?
1730
+ end
1731
+ end
1732
+ end
1733
+
1734
+ def failed_login_ban_for
1735
+ self.class.failed_login_ban_for
1736
+ end
1737
+
1738
+ def increase_failed_login_count
1739
+ if invalid_password? && attempted_record.respond_to?(:failed_login_count)
1740
+ attempted_record.failed_login_count ||= 0
1741
+ attempted_record.failed_login_count += 1
1742
+ end
1743
+ end
1744
+
1745
+ def increment_login_cout
1746
+ if record.respond_to?(:login_count)
1747
+ record.login_count = (record.login_count.blank? ? 1 : record.login_count + 1)
1748
+ end
1749
+ end
1750
+
1751
+ def klass
1752
+ self.class.klass
1753
+ end
1754
+
1755
+ def klass_name
1756
+ self.class.klass_name
1757
+ end
1758
+
1759
+ def last_request_at_threshold
1760
+ self.class.last_request_at_threshold
1761
+ end
1762
+
1763
+ def login_field
1764
+ self.class.login_field
1765
+ end
1766
+
1767
+ def logout_on_timeout?
1768
+ self.class.logout_on_timeout == true
1769
+ end
1770
+
1771
+ def params_credentials
1772
+ controller.params[params_key]
1773
+ end
1774
+
1775
+ def params_enabled?
1776
+ if !params_credentials || !klass.column_names.include?("single_access_token")
1777
+ return false
1778
+ end
1779
+ if controller.responds_to_single_access_allowed?
1780
+ return controller.single_access_allowed?
1781
+ end
1782
+ params_enabled_by_allowed_request_types?
1783
+ end
1784
+
1785
+ def params_enabled_by_allowed_request_types?
1786
+ case single_access_allowed_request_types
1787
+ when Array
1788
+ single_access_allowed_request_types.include?(controller.request_content_type) ||
1789
+ single_access_allowed_request_types.include?(:all)
1790
+ else
1791
+ %i[all any].include?(single_access_allowed_request_types)
1792
+ end
1793
+ end
1794
+
1795
+ def params_key
1796
+ build_key(self.class.params_key)
1797
+ end
1798
+
1799
+ def password_field
1800
+ self.class.password_field
1801
+ end
1802
+
1803
+ # Tries to validate the session from information in the cookie
1804
+ def persist_by_cookie
1805
+ creds = cookie_credentials
1806
+ if creds&.persistence_token.present?
1807
+ record = search_for_record("find_by_#{klass.primary_key}", creds.record_id)
1808
+ if record && record.persistence_token == creds.persistence_token
1809
+ self.unauthorized_record = record
1810
+ end
1811
+ valid?
1812
+ else
1813
+ false
1814
+ end
1815
+ end
1816
+
1817
+ def persist_by_params
1818
+ return false unless params_enabled?
1819
+ self.unauthorized_record = search_for_record(
1820
+ "find_by_single_access_token",
1821
+ params_credentials
1822
+ )
1823
+ self.single_access = valid?
1824
+ end
1825
+
1826
+ def persist_by_http_auth
1827
+ login_proc = http_auth_login_proc
1828
+
1829
+ if self.class.request_http_basic_auth
1830
+ controller.authenticate_or_request_with_http_basic(
1831
+ self.class.http_basic_auth_realm,
1832
+ &login_proc
1833
+ )
1834
+ else
1835
+ controller.authenticate_with_http_basic(&login_proc)
1836
+ end
1837
+
1838
+ false
1839
+ end
1840
+
1841
+ def persist_by_http_auth?
1842
+ allow_http_basic_auth? && login_field && password_field
1843
+ end
1844
+
1845
+ # Tries to validate the session from information in the session
1846
+ def persist_by_session
1847
+ persistence_token, record_id = session_credentials
1848
+ if !persistence_token.nil?
1849
+ record = persist_by_session_search(persistence_token, record_id)
1850
+ if record && record.persistence_token == persistence_token
1851
+ self.unauthorized_record = record
1852
+ end
1853
+ valid?
1854
+ else
1855
+ false
1856
+ end
1857
+ end
1858
+
1859
+ # Allow finding by persistence token, because when records are created
1860
+ # the session is maintained in a before_save, when there is no id.
1861
+ # This is done for performance reasons and to save on queries.
1862
+ def persist_by_session_search(persistence_token, record_id)
1863
+ if record_id.nil?
1864
+ search_for_record("find_by_persistence_token", persistence_token.to_s)
1865
+ else
1866
+ search_for_record("find_by_#{klass.primary_key}", record_id.to_s)
1867
+ end
1868
+ end
1869
+
1870
+ def reset_stale_state
1871
+ self.stale_record = nil
1872
+ end
1873
+
1874
+ def reset_perishable_token!
1875
+ if record.respond_to?(:reset_perishable_token) &&
1876
+ !record.disable_perishable_token_maintenance?
1877
+ record.reset_perishable_token
1878
+ end
1879
+ end
1880
+
1881
+ # @api private
1882
+ def required_magic_states_for(record)
1883
+ %i[active approved confirmed].select { |state|
1884
+ record.respond_to?("#{state}?")
1885
+ }
1886
+ end
1887
+
1888
+ def reset_failed_login_count?
1889
+ exceeded_failed_logins_limit? && !being_brute_force_protected?
1890
+ end
1891
+
1892
+ def reset_failed_login_count
1893
+ attempted_record.failed_login_count = 0
1894
+ end
1895
+
1896
+ # `args[0]` is the name of a model method, like
1897
+ # `find_by_single_access_token` or `find_by_smart_case_login_field`.
1898
+ def search_for_record(*args)
1899
+ search_scope.scoping do
1900
+ klass.send(*args)
1901
+ end
1902
+ end
1903
+
1904
+ # Returns an AR relation representing the scope of the search. The
1905
+ # relation is either provided directly by, or defined by
1906
+ # `find_options`.
1907
+ def search_scope
1908
+ if scope[:find_options].is_a?(ActiveRecord::Relation)
1909
+ scope[:find_options]
1910
+ else
1911
+ conditions = scope[:find_options] && scope[:find_options][:conditions] || {}
1912
+ klass.send(:where, conditions)
1913
+ end
1914
+ end
1915
+
1916
+ # @api private
1917
+ def set_last_request_at
1918
+ current_time = klass.default_timezone == :utc ? Time.now.utc : Time.now
1919
+ MagicColumn::AssignsLastRequestAt
1920
+ .new(current_time, record, controller, last_request_at_threshold)
1921
+ .assign
1922
+ end
1923
+
1924
+ def single_access?
1925
+ single_access == true
1926
+ end
1927
+
1928
+ def single_access_allowed_request_types
1929
+ self.class.single_access_allowed_request_types
1930
+ end
1931
+
1932
+ def save_cookie
1933
+ if sign_cookie?
1934
+ controller.cookies.signed[cookie_key] = generate_cookie_for_saving
1935
+ else
1936
+ controller.cookies[cookie_key] = generate_cookie_for_saving
1937
+ end
1938
+ end
1939
+
1940
+ # @api private
1941
+ # @return [String] - Examples:
1942
+ # - user_credentials_id
1943
+ # - ziggity_zack_user_credentials_id
1944
+ # - ziggity_zack is an "id", see `#id`
1945
+ # - see persistence_token_test.rb
1946
+ def session_compound_key
1947
+ "#{session_key}_#{klass.primary_key}"
1948
+ end
1949
+
1950
+ def session_credentials
1951
+ [
1952
+ controller.session[session_key],
1953
+ controller.session[session_compound_key]
1954
+ ].collect { |i| i.nil? ? i : i.to_s }.compact
1955
+ end
1956
+
1957
+ # @return [String] - Examples:
1958
+ # - user_credentials
1959
+ # - ziggity_zack_user_credentials
1960
+ # - ziggity_zack is an "id", see `#id`
1961
+ # - see persistence_token_test.rb
1962
+ def session_key
1963
+ build_key(self.class.session_key)
1964
+ end
1965
+
1966
+ def update_info
1967
+ increment_login_cout
1968
+ clear_failed_login_count
1969
+ update_login_timestamps
1970
+ update_login_ip_addresses
1971
+ end
1972
+
1973
+ def update_login_ip_addresses
1974
+ if record.respond_to?(:current_login_ip)
1975
+ record.last_login_ip = record.current_login_ip if record.respond_to?(:last_login_ip)
1976
+ record.current_login_ip = controller.request.ip
1977
+ end
1978
+ end
1979
+
1980
+ def update_login_timestamps
1981
+ if record.respond_to?(:current_login_at)
1982
+ record.last_login_at = record.current_login_at if record.respond_to?(:last_login_at)
1983
+ record.current_login_at = klass.default_timezone == :utc ? Time.now.utc : Time.now
1984
+ end
1985
+ end
1986
+
1987
+ def update_session
1988
+ update_session_set_persistence_token
1989
+ update_session_set_primary_key
1990
+ end
1991
+
1992
+ # Updates the session, setting the primary key (usually `id`) of the
1993
+ # record.
1994
+ #
1995
+ # @api private
1996
+ def update_session_set_primary_key
1997
+ compound_key = session_compound_key
1998
+ controller.session[compound_key] = record && record.send(record.class.primary_key)
1999
+ end
2000
+
2001
+ # Updates the session, setting the `persistence_token` of the record.
2002
+ #
2003
+ # @api private
2004
+ def update_session_set_persistence_token
2005
+ controller.session[session_key] = record && record.persistence_token
2006
+ end
2007
+
2008
+ # In keeping with the metaphor of ActiveRecord, verification of the
2009
+ # password is referred to as a "validation".
2010
+ def validate_by_password
2011
+ self.invalid_password = false
2012
+ validate_by_password__blank_fields
2013
+ return if errors.count > 0
2014
+ self.attempted_record = search_for_record(find_by_login_method, send(login_field))
2015
+ if attempted_record.blank?
2016
+ add_login_not_found_error
2017
+ return
2018
+ end
2019
+ validate_by_password__invalid_password
2020
+ end
2021
+
2022
+ def validate_by_password__blank_fields
2023
+ if send(login_field).blank?
2024
+ errors.add(
2025
+ login_field,
2026
+ I18n.t("error_messages.login_blank", default: "cannot be blank")
2027
+ )
2028
+ end
2029
+ if send("protected_#{password_field}").blank?
2030
+ errors.add(
2031
+ password_field,
2032
+ I18n.t("error_messages.password_blank", default: "cannot be blank")
2033
+ )
2034
+ end
2035
+ end
2036
+
2037
+ # Verify the password, usually using `valid_password?` in
2038
+ # `acts_as_authentic/password.rb`. If it cannot be verified, we
2039
+ # refer to it as "invalid".
2040
+ def validate_by_password__invalid_password
2041
+ unless attempted_record.send(
2042
+ verify_password_method,
2043
+ send("protected_#{password_field}")
2044
+ )
2045
+ self.invalid_password = true
2046
+ add_invalid_password_error
2047
+ end
2048
+ end
2049
+
2050
+ def validate_by_unauthorized_record
2051
+ self.attempted_record = unauthorized_record
2052
+ end
2053
+
2054
+ def validate_magic_states
2055
+ return true if attempted_record.nil?
2056
+ required_magic_states_for(attempted_record).each do |required_status|
2057
+ next if attempted_record.send("#{required_status}?")
2058
+ errors.add(
2059
+ :base,
2060
+ I18n.t(
2061
+ "error_messages.not_#{required_status}",
2062
+ default: "Your account is not #{required_status}"
2063
+ )
2064
+ )
2065
+ return false
2066
+ end
2067
+ true
2068
+ end
2069
+
2070
+ def validate_failed_logins
2071
+ # Clear all other error messages, as they are irrelevant at this point and can
2072
+ # only provide additional information that is not needed
2073
+ errors.clear
2074
+ duration = failed_login_ban_for == 0 ? "" : " temporarily"
2075
+ errors.add(
2076
+ :base,
2077
+ I18n.t(
2078
+ "error_messages.consecutive_failed_logins_limit_exceeded",
2079
+ default: format(
2080
+ "Consecutive failed logins limit exceeded, account has been%s disabled.",
2081
+ duration
2082
+ )
2083
+ )
2084
+ )
2085
+ end
2086
+
2087
+ def verify_password_method
2088
+ self.class.verify_password_method
2089
+ end
36
2090
  end
37
2091
  end
38
2092
  end