authlogic 4.4.2 → 5.0.3

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