authlogic 3.4.6 → 4.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (140) hide show
  1. checksums.yaml +5 -5
  2. data/.github/ISSUE_TEMPLATE.md +13 -0
  3. data/.github/triage.md +87 -0
  4. data/.gitignore +4 -0
  5. data/.rubocop.yml +127 -0
  6. data/.rubocop_todo.yml +65 -0
  7. data/.travis.yml +18 -10
  8. data/CHANGELOG.md +156 -6
  9. data/CONTRIBUTING.md +71 -3
  10. data/Gemfile +2 -2
  11. data/README.md +386 -0
  12. data/Rakefile +13 -7
  13. data/UPGRADING.md +22 -0
  14. data/authlogic.gemspec +33 -22
  15. data/lib/authlogic.rb +60 -52
  16. data/lib/authlogic/acts_as_authentic/base.rb +40 -26
  17. data/lib/authlogic/acts_as_authentic/email.rb +96 -32
  18. data/lib/authlogic/acts_as_authentic/logged_in_status.rb +36 -12
  19. data/lib/authlogic/acts_as_authentic/login.rb +114 -49
  20. data/lib/authlogic/acts_as_authentic/magic_columns.rb +17 -6
  21. data/lib/authlogic/acts_as_authentic/password.rb +296 -139
  22. data/lib/authlogic/acts_as_authentic/perishable_token.rb +34 -20
  23. data/lib/authlogic/acts_as_authentic/persistence_token.rb +20 -24
  24. data/lib/authlogic/acts_as_authentic/queries/find_with_case.rb +67 -0
  25. data/lib/authlogic/acts_as_authentic/restful_authentication.rb +68 -23
  26. data/lib/authlogic/acts_as_authentic/session_maintenance.rb +128 -85
  27. data/lib/authlogic/acts_as_authentic/single_access_token.rb +41 -25
  28. data/lib/authlogic/acts_as_authentic/validations_scope.rb +8 -8
  29. data/lib/authlogic/authenticates_many/association.rb +22 -14
  30. data/lib/authlogic/authenticates_many/base.rb +35 -16
  31. data/lib/authlogic/config.rb +10 -10
  32. data/lib/authlogic/controller_adapters/abstract_adapter.rb +40 -12
  33. data/lib/authlogic/controller_adapters/rack_adapter.rb +15 -8
  34. data/lib/authlogic/controller_adapters/rails_adapter.rb +42 -22
  35. data/lib/authlogic/controller_adapters/sinatra_adapter.rb +3 -3
  36. data/lib/authlogic/crypto_providers.rb +91 -0
  37. data/lib/authlogic/crypto_providers/aes256.rb +42 -14
  38. data/lib/authlogic/crypto_providers/bcrypt.rb +35 -20
  39. data/lib/authlogic/crypto_providers/md5.rb +11 -9
  40. data/lib/authlogic/crypto_providers/scrypt.rb +26 -13
  41. data/lib/authlogic/crypto_providers/sha1.rb +14 -8
  42. data/lib/authlogic/crypto_providers/sha256.rb +16 -12
  43. data/lib/authlogic/crypto_providers/sha512.rb +8 -24
  44. data/lib/authlogic/crypto_providers/wordpress.rb +44 -15
  45. data/lib/authlogic/i18n.rb +33 -20
  46. data/lib/authlogic/i18n/translator.rb +1 -1
  47. data/lib/authlogic/random.rb +12 -29
  48. data/lib/authlogic/regex.rb +59 -27
  49. data/lib/authlogic/session/activation.rb +36 -23
  50. data/lib/authlogic/session/active_record_trickery.rb +13 -10
  51. data/lib/authlogic/session/base.rb +20 -8
  52. data/lib/authlogic/session/brute_force_protection.rb +87 -56
  53. data/lib/authlogic/session/callbacks.rb +99 -49
  54. data/lib/authlogic/session/cookies.rb +128 -59
  55. data/lib/authlogic/session/existence.rb +29 -19
  56. data/lib/authlogic/session/foundation.rb +70 -16
  57. data/lib/authlogic/session/http_auth.rb +39 -31
  58. data/lib/authlogic/session/id.rb +27 -15
  59. data/lib/authlogic/session/klass.rb +17 -13
  60. data/lib/authlogic/session/magic_columns.rb +78 -59
  61. data/lib/authlogic/session/magic_states.rb +50 -27
  62. data/lib/authlogic/session/params.rb +79 -50
  63. data/lib/authlogic/session/password.rb +197 -118
  64. data/lib/authlogic/session/perishable_token.rb +12 -6
  65. data/lib/authlogic/session/persistence.rb +20 -14
  66. data/lib/authlogic/session/priority_record.rb +20 -16
  67. data/lib/authlogic/session/scopes.rb +63 -33
  68. data/lib/authlogic/session/session.rb +40 -25
  69. data/lib/authlogic/session/timeout.rb +51 -34
  70. data/lib/authlogic/session/unauthorized_record.rb +24 -18
  71. data/lib/authlogic/session/validation.rb +32 -21
  72. data/lib/authlogic/test_case.rb +123 -35
  73. data/lib/authlogic/test_case/mock_controller.rb +14 -13
  74. data/lib/authlogic/test_case/mock_cookie_jar.rb +14 -5
  75. data/lib/authlogic/test_case/mock_logger.rb +1 -1
  76. data/lib/authlogic/test_case/mock_request.rb +9 -4
  77. data/lib/authlogic/test_case/rails_request_adapter.rb +8 -7
  78. data/lib/authlogic/version.rb +21 -0
  79. data/test/acts_as_authentic_test/base_test.rb +1 -1
  80. data/test/acts_as_authentic_test/email_test.rb +80 -63
  81. data/test/acts_as_authentic_test/logged_in_status_test.rb +14 -8
  82. data/test/acts_as_authentic_test/login_test.rb +91 -49
  83. data/test/acts_as_authentic_test/magic_columns_test.rb +13 -13
  84. data/test/acts_as_authentic_test/password_test.rb +82 -60
  85. data/test/acts_as_authentic_test/perishable_token_test.rb +31 -25
  86. data/test/acts_as_authentic_test/persistence_token_test.rb +9 -5
  87. data/test/acts_as_authentic_test/restful_authentication_test.rb +18 -9
  88. data/test/acts_as_authentic_test/session_maintenance_test.rb +86 -22
  89. data/test/acts_as_authentic_test/single_access_test.rb +15 -15
  90. data/test/adapter_test.rb +21 -0
  91. data/test/authenticates_many_test.rb +26 -11
  92. data/test/config_test.rb +9 -9
  93. data/test/crypto_provider_test/aes256_test.rb +3 -3
  94. data/test/crypto_provider_test/bcrypt_test.rb +1 -1
  95. data/test/crypto_provider_test/scrypt_test.rb +2 -2
  96. data/test/crypto_provider_test/sha1_test.rb +4 -4
  97. data/test/crypto_provider_test/sha256_test.rb +2 -2
  98. data/test/crypto_provider_test/sha512_test.rb +3 -3
  99. data/test/crypto_provider_test/wordpress_test.rb +24 -0
  100. data/test/gemfiles/Gemfile.rails-4.2.x +2 -2
  101. data/test/gemfiles/Gemfile.rails-5.0.x +6 -0
  102. data/test/gemfiles/Gemfile.rails-5.1.x +6 -0
  103. data/test/gemfiles/Gemfile.rails-5.2.x +6 -0
  104. data/test/gemfiles/Gemfile.rails-master +6 -0
  105. data/test/i18n_test.rb +9 -9
  106. data/test/libs/affiliate.rb +2 -2
  107. data/test/libs/company.rb +4 -4
  108. data/test/libs/employee.rb +2 -2
  109. data/test/libs/employee_session.rb +1 -1
  110. data/test/libs/ldaper.rb +1 -1
  111. data/test/libs/project.rb +1 -1
  112. data/test/libs/user_session.rb +2 -2
  113. data/test/random_test.rb +9 -38
  114. data/test/session_test/activation_test.rb +7 -7
  115. data/test/session_test/active_record_trickery_test.rb +9 -6
  116. data/test/session_test/brute_force_protection_test.rb +26 -21
  117. data/test/session_test/callbacks_test.rb +10 -4
  118. data/test/session_test/cookies_test.rb +54 -20
  119. data/test/session_test/existence_test.rb +45 -23
  120. data/test/session_test/foundation_test.rb +17 -1
  121. data/test/session_test/http_auth_test.rb +11 -12
  122. data/test/session_test/id_test.rb +3 -3
  123. data/test/session_test/klass_test.rb +2 -2
  124. data/test/session_test/magic_columns_test.rb +15 -17
  125. data/test/session_test/magic_states_test.rb +17 -19
  126. data/test/session_test/params_test.rb +26 -20
  127. data/test/session_test/password_test.rb +11 -12
  128. data/test/session_test/perishability_test.rb +5 -5
  129. data/test/session_test/persistence_test.rb +4 -3
  130. data/test/session_test/scopes_test.rb +15 -9
  131. data/test/session_test/session_test.rb +7 -6
  132. data/test/session_test/timeout_test.rb +16 -14
  133. data/test/session_test/unauthorized_record_test.rb +3 -3
  134. data/test/session_test/validation_test.rb +5 -5
  135. data/test/test_helper.rb +115 -49
  136. metadata +107 -36
  137. data/README.rdoc +0 -232
  138. data/test/gemfiles/Gemfile.rails-3.2.x +0 -7
  139. data/test/gemfiles/Gemfile.rails-4.0.x +0 -7
  140. data/test/gemfiles/Gemfile.rails-4.1.x +0 -7
@@ -1,9 +1,10 @@
1
1
  module Authlogic
2
2
  module ActsAsAuthentic
3
- # Since web applications are stateless there is not sure fire way to tell if a user is logged in or not,
4
- # from the database perspective. The best way to do this is to provide a "timeout" based on inactivity.
5
- # So if that user is inactive for a certain amount of time we assume they are logged out. That's what this
6
- # module is all about.
3
+ # Since web applications are stateless there is not sure fire way to tell if
4
+ # a user is logged in or not, from the database perspective. The best way to
5
+ # do this is to provide a "timeout" based on inactivity. So if that user is
6
+ # inactive for a certain amount of time we assume they are logged out.
7
+ # That's what this module is all about.
7
8
  module LoggedInStatus
8
9
  def self.included(klass)
9
10
  klass.class_eval do
@@ -27,19 +28,41 @@ module Authlogic
27
28
  # All methods for the logged in status feature seat.
28
29
  module Methods
29
30
  def self.included(klass)
30
- return if !klass.column_names.include?("last_request_at")
31
+ return unless klass.column_names.include?("last_request_at")
31
32
 
32
33
  klass.class_eval do
33
34
  include InstanceMethods
34
- scope :logged_in, lambda{ where("last_request_at > ? and current_login_at IS NOT NULL", logged_in_timeout.seconds.ago) }
35
- scope :logged_out, lambda{ where("last_request_at is NULL or last_request_at <= ?", logged_in_timeout.seconds.ago) }
35
+ scope(
36
+ :logged_in,
37
+ lambda do
38
+ where(
39
+ "last_request_at > ? and current_login_at IS NOT NULL",
40
+ logged_in_timeout.seconds.ago
41
+ )
42
+ end
43
+ )
44
+ scope(
45
+ :logged_out,
46
+ lambda do
47
+ where(
48
+ "last_request_at is NULL or last_request_at <= ?",
49
+ logged_in_timeout.seconds.ago
50
+ )
51
+ end
52
+ )
36
53
  end
37
54
  end
38
55
 
56
+ # :nodoc:
39
57
  module InstanceMethods
40
58
  # Returns true if the last_request_at > logged_in_timeout.
41
59
  def logged_in?
42
- raise "Can not determine the records login state because there is no last_request_at column" if !respond_to?(:last_request_at)
60
+ unless respond_to?(:last_request_at)
61
+ raise(
62
+ "Can not determine the records login state because " \
63
+ "there is no last_request_at column"
64
+ )
65
+ end
43
66
  !last_request_at.nil? && last_request_at > logged_in_timeout.seconds.ago
44
67
  end
45
68
 
@@ -49,11 +72,12 @@ module Authlogic
49
72
  end
50
73
 
51
74
  private
52
- def logged_in_timeout
53
- self.class.logged_in_timeout
54
- end
75
+
76
+ def logged_in_timeout
77
+ self.class.logged_in_timeout
78
+ end
55
79
  end
56
80
  end
57
81
  end
58
82
  end
59
- end
83
+ end
@@ -1,3 +1,5 @@
1
+ require "authlogic/acts_as_authentic/queries/find_with_case"
2
+
1
3
  module Authlogic
2
4
  module ActsAsAuthentic
3
5
  # Handles everything related to the login field.
@@ -29,102 +31,165 @@ module Authlogic
29
31
  end
30
32
  alias_method :validate_login_field=, :validate_login_field
31
33
 
32
- # A hash of options for the validates_length_of call for the login field. Allows you to change this however you want.
34
+ # A hash of options for the validates_length_of call for the login
35
+ # field. Allows you to change this however you want.
33
36
  #
34
- # <b>Keep in mind this is ruby. I wanted to keep this as flexible as possible, so you can completely replace the hash or
35
- # merge options into it. Checkout the convenience function merge_validates_length_of_login_field_options to merge
36
- # options.</b>
37
+ # <b>Keep in mind this is ruby. I wanted to keep this as flexible as
38
+ # possible, so you can completely replace the hash or merge options into
39
+ # it. Checkout the convenience function
40
+ # merge_validates_length_of_login_field_options to merge options.</b>
37
41
  #
38
42
  # * <tt>Default:</tt> {:within => 3..100}
39
43
  # * <tt>Accepts:</tt> Hash of options accepted by validates_length_of
40
44
  def validates_length_of_login_field_options(value = nil)
41
- rw_config(:validates_length_of_login_field_options, value, {:within => 3..100})
45
+ rw_config(:validates_length_of_login_field_options, value, within: 3..100)
42
46
  end
43
- alias_method :validates_length_of_login_field_options=, :validates_length_of_login_field_options
47
+ alias_method(
48
+ :validates_length_of_login_field_options=,
49
+ :validates_length_of_login_field_options
50
+ )
44
51
 
45
- # A convenience function to merge options into the validates_length_of_login_field_options. So instead of:
52
+ # A convenience function to merge options into the
53
+ # validates_length_of_login_field_options. So instead of:
46
54
  #
47
- # self.validates_length_of_login_field_options = validates_length_of_login_field_options.merge(:my_option => my_value)
55
+ # self.validates_length_of_login_field_options =
56
+ # validates_length_of_login_field_options.merge(:my_option => my_value)
48
57
  #
49
58
  # You can do this:
50
59
  #
51
60
  # merge_validates_length_of_login_field_options :my_option => my_value
52
61
  def merge_validates_length_of_login_field_options(options = {})
53
- self.validates_length_of_login_field_options = validates_length_of_login_field_options.merge(options)
62
+ self.validates_length_of_login_field_options =
63
+ validates_length_of_login_field_options.merge(options)
54
64
  end
55
65
 
56
- # A hash of options for the validates_format_of call for the login field. Allows you to change this however you want.
66
+ # A hash of options for the validates_format_of call for the login
67
+ # field. Allows you to change this however you want.
68
+ #
69
+ # <b>Keep in mind this is ruby. I wanted to keep this as flexible as
70
+ # possible, so you can completely replace the hash or merge options into
71
+ # it. Checkout the convenience function
72
+ # merge_validates_format_of_login_field_options to merge options.</b>
73
+ #
74
+ # * <tt>Default:</tt>
57
75
  #
58
- # <b>Keep in mind this is ruby. I wanted to keep this as flexible as possible, so you can completely replace the hash or
59
- # merge options into it. Checkout the convenience function merge_validates_format_of_login_field_options to merge
60
- # options.</b>
76
+ # {
77
+ # :with => Authlogic::Regex.login,
78
+ # :message => lambda {
79
+ # I18n.t(
80
+ # 'error_messages.login_invalid',
81
+ # :default => "should use only letters, numbers, spaces, and .-_@+ please."
82
+ # )
83
+ # }
84
+ # }
61
85
  #
62
- # * <tt>Default:</tt> {:with => Authlogic::Regex.login, :message => lambda {I18n.t('error_messages.login_invalid', :default => "should use only letters, numbers, spaces, and .-_@ please.")}}
63
86
  # * <tt>Accepts:</tt> Hash of options accepted by validates_format_of
64
87
  def validates_format_of_login_field_options(value = nil)
65
- rw_config(:validates_format_of_login_field_options, value, {:with => Authlogic::Regex.login, :message => I18n.t('error_messages.login_invalid', :default => "should use only letters, numbers, spaces, and .-_@ please.")})
88
+ rw_config(
89
+ :validates_format_of_login_field_options,
90
+ value,
91
+ with: Authlogic::Regex::LOGIN,
92
+ message: proc do
93
+ I18n.t(
94
+ "error_messages.login_invalid",
95
+ default: "should use only letters, numbers, spaces, and .-_@+ please."
96
+ )
97
+ end
98
+ )
66
99
  end
67
- alias_method :validates_format_of_login_field_options=, :validates_format_of_login_field_options
100
+ alias_method(
101
+ :validates_format_of_login_field_options=,
102
+ :validates_format_of_login_field_options
103
+ )
68
104
 
69
- # See merge_validates_length_of_login_field_options. The same thing, except for validates_format_of_login_field_options
105
+ # See merge_validates_length_of_login_field_options. The same thing,
106
+ # except for validates_format_of_login_field_options
70
107
  def merge_validates_format_of_login_field_options(options = {})
71
- self.validates_format_of_login_field_options = validates_format_of_login_field_options.merge(options)
108
+ self.validates_format_of_login_field_options =
109
+ validates_format_of_login_field_options.merge(options)
72
110
  end
73
111
 
74
- # A hash of options for the validates_uniqueness_of call for the login field. Allows you to change this however you want.
112
+ # A hash of options for the validates_uniqueness_of call for the login
113
+ # field. Allows you to change this however you want.
114
+ #
115
+ # <b>Keep in mind this is ruby. I wanted to keep this as flexible as
116
+ # possible, so you can completely replace the hash or merge options into
117
+ # it. Checkout the convenience function
118
+ # merge_validates_format_of_login_field_options to merge options.</b>
119
+ #
120
+ # * <tt>Default:</tt>
75
121
  #
76
- # <b>Keep in mind this is ruby. I wanted to keep this as flexible as possible, so you can completely replace the hash or
77
- # merge options into it. Checkout the convenience function merge_validates_format_of_login_field_options to merge
78
- # options.</b>
122
+ # {
123
+ # :case_sensitive => false,
124
+ # :scope => validations_scope,
125
+ # :if => "#{login_field}_changed?".to_sym
126
+ # }
79
127
  #
80
- # * <tt>Default:</tt> {:case_sensitive => false, :scope => validations_scope, :if => "#{login_field}_changed?".to_sym}
81
128
  # * <tt>Accepts:</tt> Hash of options accepted by validates_uniqueness_of
82
129
  def validates_uniqueness_of_login_field_options(value = nil)
83
- rw_config(:validates_uniqueness_of_login_field_options, value, {:case_sensitive => false, :scope => validations_scope, :if => "#{login_field}_changed?".to_sym})
130
+ rw_config(
131
+ :validates_uniqueness_of_login_field_options,
132
+ value,
133
+ case_sensitive: false,
134
+ scope: validations_scope,
135
+ if: "#{login_field}_changed?".to_sym
136
+ )
84
137
  end
85
- alias_method :validates_uniqueness_of_login_field_options=, :validates_uniqueness_of_login_field_options
138
+ alias_method(
139
+ :validates_uniqueness_of_login_field_options=,
140
+ :validates_uniqueness_of_login_field_options
141
+ )
86
142
 
87
- # See merge_validates_length_of_login_field_options. The same thing, except for validates_uniqueness_of_login_field_options
143
+ # See merge_validates_length_of_login_field_options. The same thing,
144
+ # except for validates_uniqueness_of_login_field_options
88
145
  def merge_validates_uniqueness_of_login_field_options(options = {})
89
- self.validates_uniqueness_of_login_field_options = validates_uniqueness_of_login_field_options.merge(options)
146
+ self.validates_uniqueness_of_login_field_options =
147
+ validates_uniqueness_of_login_field_options.merge(options)
90
148
  end
91
149
 
92
- # This method allows you to find a record with the given login. If you notice, with Active Record you have the
93
- # UniquenessValidator class. They give you a :case_sensitive option. I handle this in the same
94
- # manner that they handle that. If you are using the login field, set false for the :case_sensitive option in
95
- # validates_uniqueness_of_login_field_options and the column doesn't have a case-insensitive collation,
96
- # this method will modify the query to look something like:
150
+ # This method allows you to find a record with the given login. If you
151
+ # notice, with Active Record you have the UniquenessValidator class.
152
+ # They give you a :case_sensitive option. I handle this in the same
153
+ # manner that they handle that. If you are using the login field, set
154
+ # false for the :case_sensitive option in
155
+ # validates_uniqueness_of_login_field_options and the column doesn't
156
+ # have a case-insensitive collation, this method will modify the query
157
+ # to look something like:
97
158
  #
98
159
  # "LOWER(#{quoted_table_name}.#{login_field}) = LOWER(#{login})"
99
160
  #
100
- # If you don't specify this it just uses a regular case-sensitive search (with the binary modifier if necessary):
161
+ # If you don't specify this it just uses a regular case-sensitive search
162
+ # (with the binary modifier if necessary):
101
163
  #
102
164
  # "BINARY #{login_field} = #{login}"
103
165
  #
104
- # The above also applies for using email as your login, except that you need to set the :case_sensitive in
166
+ # The above also applies for using email as your login, except that you
167
+ # need to set the :case_sensitive in
105
168
  # validates_uniqueness_of_email_field_options to false.
169
+ #
170
+ # @api public
106
171
  def find_by_smart_case_login_field(login)
107
172
  if login_field
108
- find_with_case(login_field, login, validates_uniqueness_of_login_field_options[:case_sensitive] != false)
173
+ find_with_case(
174
+ login_field,
175
+ login,
176
+ validates_uniqueness_of_login_field_options[:case_sensitive] != false
177
+ )
109
178
  else
110
- find_with_case(email_field, login, validates_uniqueness_of_email_field_options[:case_sensitive] != false)
179
+ find_with_case(
180
+ email_field,
181
+ login,
182
+ validates_uniqueness_of_email_field_options[:case_sensitive] != false
183
+ )
111
184
  end
112
185
  end
113
186
 
114
187
  private
115
- def find_with_case(field, value, sensitivity = true)
116
- relation = if not sensitivity
117
- connection.case_insensitive_comparison(arel_table, field.to_s, columns_hash[field.to_s], value)
118
- else
119
- if Gem::Version.new(Rails.version) < Gem::Version.new('4.2')
120
- value = connection.case_sensitive_modifier(value)
121
- else
122
- value = connection.case_sensitive_modifier(value, field.to_s)
123
- end
124
- relation = arel_table[field.to_s].eq(value)
125
- end
126
- where(relation).first
127
- end
188
+
189
+ # @api private
190
+ def find_with_case(field, value, sensitive)
191
+ Queries::FindWithCase.new(self, field, value, sensitive).execute
192
+ end
128
193
  end
129
194
 
130
195
  # All methods relating to the login field
@@ -1,21 +1,32 @@
1
1
  module Authlogic
2
2
  module ActsAsAuthentic
3
- # Magic columns are like ActiveRecord's created_at and updated_at columns. They are "magically" maintained for
4
- # you. Authlogic has the same thing, but these are maintained on the session side. Please see Authlogic::Session::MagicColumns
5
- # for more details. This module merely adds validations for the magic columns if they exist.
3
+ # Magic columns are like ActiveRecord's created_at and updated_at columns. They are
4
+ # "magically" maintained for you. Authlogic has the same thing, but these are
5
+ # maintained on the session side. Please see Authlogic::Session::MagicColumns for more
6
+ # details. This module merely adds validations for the magic columns if they exist.
6
7
  module MagicColumns
7
8
  def self.included(klass)
8
9
  klass.class_eval do
9
10
  add_acts_as_authentic_module(Methods)
10
11
  end
11
12
  end
12
-
13
+
13
14
  # Methods relating to the magic columns
14
15
  module Methods
15
16
  def self.included(klass)
16
17
  klass.class_eval do
17
- validates_numericality_of :login_count, :only_integer => true, :greater_than_or_equal_to => 0, :allow_nil => true if column_names.include?("login_count")
18
- validates_numericality_of :failed_login_count, :only_integer => true, :greater_than_or_equal_to => 0, :allow_nil => true if column_names.include?("failed_login_count")
18
+ if column_names.include?("login_count")
19
+ validates_numericality_of :login_count,
20
+ only_integer: true,
21
+ greater_than_or_equal_to: 0,
22
+ allow_nil: true
23
+ end
24
+ if column_names.include?("failed_login_count")
25
+ validates_numericality_of :failed_login_count,
26
+ only_integer: true,
27
+ greater_than_or_equal_to: 0,
28
+ allow_nil: true
29
+ end
19
30
  end
20
31
  end
21
32
  end
@@ -1,7 +1,8 @@
1
1
  module Authlogic
2
2
  module ActsAsAuthentic
3
- # This module has a lot of neat functionality. It is responsible for encrypting your password, salting it, and verifying it.
4
- # It can also help you transition to a new encryption algorithm. See the Config sub module for configuration options.
3
+ # This module has a lot of neat functionality. It is responsible for encrypting your
4
+ # password, salting it, and verifying it. It can also help you transition to a new
5
+ # encryption algorithm. See the Config sub module for configuration options.
5
6
  module Password
6
7
  def self.included(klass)
7
8
  klass.class_eval do
@@ -18,7 +19,17 @@ module Authlogic
18
19
  # * <tt>Default:</tt> :crypted_password, :encrypted_password, :password_hash, or :pw_hash
19
20
  # * <tt>Accepts:</tt> Symbol
20
21
  def crypted_password_field(value = nil)
21
- rw_config(:crypted_password_field, value, first_column_to_exist(nil, :crypted_password, :encrypted_password, :password_hash, :pw_hash))
22
+ rw_config(
23
+ :crypted_password_field,
24
+ value,
25
+ first_column_to_exist(
26
+ nil,
27
+ :crypted_password,
28
+ :encrypted_password,
29
+ :password_hash,
30
+ :pw_hash
31
+ )
32
+ )
22
33
  end
23
34
  alias_method :crypted_password_field=, :crypted_password_field
24
35
 
@@ -27,12 +38,16 @@ module Authlogic
27
38
  # * <tt>Default:</tt> :password_salt, :pw_salt, :salt, nil if none exist
28
39
  # * <tt>Accepts:</tt> Symbol
29
40
  def password_salt_field(value = nil)
30
- rw_config(:password_salt_field, value, first_column_to_exist(nil, :password_salt, :pw_salt, :salt))
41
+ rw_config(
42
+ :password_salt_field,
43
+ value,
44
+ first_column_to_exist(nil, :password_salt, :pw_salt, :salt)
45
+ )
31
46
  end
32
47
  alias_method :password_salt_field=, :password_salt_field
33
48
 
34
- # Whether or not to require a password confirmation. If you don't want your users to confirm their password
35
- # just set this to false.
49
+ # Whether or not to require a password confirmation. If you don't want your users
50
+ # to confirm their password just set this to false.
36
51
  #
37
52
  # * <tt>Default:</tt> true
38
53
  # * <tt>Accepts:</tt> Boolean
@@ -41,14 +56,17 @@ module Authlogic
41
56
  end
42
57
  alias_method :require_password_confirmation=, :require_password_confirmation
43
58
 
44
- # By default passwords are required when a record is new or the crypted_password is blank, but if both of these things
45
- # are met a password is not required. In this case, blank passwords are ignored.
59
+ # By default passwords are required when a record is new or the crypted_password
60
+ # is blank, but if both of these things are met a password is not required. In
61
+ # this case, blank passwords are ignored.
46
62
  #
47
- # Think about a profile page, where the user can edit all of their information, including changing their password.
48
- # If they do not want to change their password they just leave the fields blank. This will try to set the password to
49
- # a blank value, in which case is incorrect behavior. As such, Authlogic ignores this. But let's say you have a completely
50
- # separate page for resetting passwords, you might not want to ignore blank passwords. If this is the case for you, then
51
- # just set this value to false.
63
+ # Think about a profile page, where the user can edit all of their information,
64
+ # including changing their password. If they do not want to change their password
65
+ # they just leave the fields blank. This will try to set the password to a blank
66
+ # value, in which case is incorrect behavior. As such, Authlogic ignores this. But
67
+ # let's say you have a completely separate page for resetting passwords, you might
68
+ # not want to ignore blank passwords. If this is the case for you, then just set
69
+ # this value to false.
52
70
  #
53
71
  # * <tt>Default:</tt> true
54
72
  # * <tt>Accepts:</tt> Boolean
@@ -57,15 +75,16 @@ module Authlogic
57
75
  end
58
76
  alias_method :ignore_blank_passwords=, :ignore_blank_passwords
59
77
 
60
- # When calling valid_password?("some pass") do you want to check that password against what's in that object or whats in
61
- # the database. Take this example:
78
+ # When calling valid_password?("some pass") do you want to check that password
79
+ # against what's in that object or whats in the database. Take this example:
62
80
  #
63
81
  # u = User.first
64
82
  # u.password = "new pass"
65
83
  # u.valid_password?("old pass")
66
84
  #
67
- # Should the last line above return true or false? The record hasn't been saved yet, so most would assume true.
68
- # Other would assume false. So I let you decide by giving you this option.
85
+ # Should the last line above return true or false? The record hasn't been saved
86
+ # yet, so most would assume true. Other would assume false. So I let you decide by
87
+ # giving you this option.
69
88
  #
70
89
  # * <tt>Default:</tt> true
71
90
  # * <tt>Accepts:</tt> Boolean
@@ -83,124 +102,184 @@ module Authlogic
83
102
  end
84
103
  alias_method :validate_password_field=, :validate_password_field
85
104
 
86
- # A hash of options for the validates_length_of call for the password field. Allows you to change this however you want.
105
+ # A hash of options for the validates_length_of call for the password field.
106
+ # Allows you to change this however you want.
87
107
  #
88
- # <b>Keep in mind this is ruby. I wanted to keep this as flexible as possible, so you can completely replace the hash or
89
- # merge options into it. Checkout the convenience function merge_validates_length_of_password_field_options to merge
90
- # options.</b>
108
+ # **Keep in mind this is ruby. I wanted to keep this as flexible as
109
+ # possible, so you can completely replace the hash or merge options into
110
+ # it. Checkout the convenience function
111
+ # merge_validates_length_of_password_field_options to merge options.**
91
112
  #
92
- # * <tt>Default:</tt> {:minimum => 4, :if => :require_password?}
113
+ # * <tt>Default:</tt> {:minimum => 8, :if => :require_password?}
93
114
  # * <tt>Accepts:</tt> Hash of options accepted by validates_length_of
94
115
  def validates_length_of_password_field_options(value = nil)
95
- rw_config(:validates_length_of_password_field_options, value, {:minimum => 4, :if => :require_password?})
116
+ rw_config(
117
+ :validates_length_of_password_field_options,
118
+ value,
119
+ minimum: 8,
120
+ if: :require_password?
121
+ )
96
122
  end
97
- alias_method :validates_length_of_password_field_options=, :validates_length_of_password_field_options
123
+ alias_method(
124
+ :validates_length_of_password_field_options=,
125
+ :validates_length_of_password_field_options
126
+ )
98
127
 
99
- # A convenience function to merge options into the validates_length_of_login_field_options. So intead of:
128
+ # A convenience function to merge options into the
129
+ # validates_length_of_login_field_options. So instead of:
100
130
  #
101
- # self.validates_length_of_password_field_options = validates_length_of_password_field_options.merge(:my_option => my_value)
131
+ # self.validates_length_of_password_field_options =
132
+ # validates_length_of_password_field_options.merge(:my_option => my_value)
102
133
  #
103
134
  # You can do this:
104
135
  #
105
136
  # merge_validates_length_of_password_field_options :my_option => my_value
106
137
  def merge_validates_length_of_password_field_options(options = {})
107
- self.validates_length_of_password_field_options = validates_length_of_password_field_options.merge(options)
138
+ self.validates_length_of_password_field_options =
139
+ validates_length_of_password_field_options.merge(options)
108
140
  end
109
141
 
110
- # A hash of options for the validates_confirmation_of call for the password field. Allows you to change this however you want.
142
+ # A hash of options for the validates_confirmation_of call for the
143
+ # password field. Allows you to change this however you want.
111
144
  #
112
- # <b>Keep in mind this is ruby. I wanted to keep this as flexible as possible, so you can completely replace the hash or
113
- # merge options into it. Checkout the convenience function merge_validates_length_of_password_field_options to merge
114
- # options.</b>
145
+ # **Keep in mind this is ruby. I wanted to keep this as flexible as
146
+ # possible, so you can completely replace the hash or merge options into
147
+ # it. Checkout the convenience function
148
+ # merge_validates_length_of_password_field_options to merge options.**
115
149
  #
116
150
  # * <tt>Default:</tt> {:if => :require_password?}
117
151
  # * <tt>Accepts:</tt> Hash of options accepted by validates_confirmation_of
118
152
  def validates_confirmation_of_password_field_options(value = nil)
119
- rw_config(:validates_confirmation_of_password_field_options, value, {:if => :require_password?})
153
+ rw_config(
154
+ :validates_confirmation_of_password_field_options,
155
+ value,
156
+ if: :require_password?
157
+ )
120
158
  end
121
- alias_method :validates_confirmation_of_password_field_options=, :validates_confirmation_of_password_field_options
159
+ alias_method :validates_confirmation_of_password_field_options=,
160
+ :validates_confirmation_of_password_field_options
122
161
 
123
- # See merge_validates_length_of_password_field_options. The same thing, except for validates_confirmation_of_password_field_options
162
+ # See merge_validates_length_of_password_field_options. The same thing, except for
163
+ # validates_confirmation_of_password_field_options
124
164
  def merge_validates_confirmation_of_password_field_options(options = {})
125
- self.validates_confirmation_of_password_field_options = validates_confirmation_of_password_field_options.merge(options)
165
+ self.validates_confirmation_of_password_field_options =
166
+ validates_confirmation_of_password_field_options.merge(options)
126
167
  end
127
168
 
128
- # A hash of options for the validates_length_of call for the password_confirmation field. Allows you to change this however you want.
169
+ # A hash of options for the validates_length_of call for the password_confirmation
170
+ # field. Allows you to change this however you want.
129
171
  #
130
- # <b>Keep in mind this is ruby. I wanted to keep this as flexible as possible, so you can completely replace the hash or
131
- # merge options into it. Checkout the convenience function merge_validates_length_of_password_field_options to merge
172
+ # <b>Keep in mind this is ruby. I wanted to keep this as flexible as possible, so
173
+ # you can completely replace the hash or merge options into it. Checkout the
174
+ # convenience function merge_validates_length_of_password_field_options to merge
132
175
  # options.</b>
133
176
  #
134
177
  # * <tt>Default:</tt> validates_length_of_password_field_options
135
178
  # * <tt>Accepts:</tt> Hash of options accepted by validates_length_of
136
179
  def validates_length_of_password_confirmation_field_options(value = nil)
137
- rw_config(:validates_length_of_password_confirmation_field_options, value, validates_length_of_password_field_options)
180
+ rw_config(
181
+ :validates_length_of_password_confirmation_field_options,
182
+ value,
183
+ validates_length_of_password_field_options
184
+ )
138
185
  end
139
- alias_method :validates_length_of_password_confirmation_field_options=, :validates_length_of_password_confirmation_field_options
186
+ alias_method(
187
+ :validates_length_of_password_confirmation_field_options=,
188
+ :validates_length_of_password_confirmation_field_options
189
+ )
140
190
 
141
- # See merge_validates_length_of_password_field_options. The same thing, except for validates_length_of_password_confirmation_field_options
191
+ # See merge_validates_length_of_password_field_options. The same thing, except for
192
+ # validates_length_of_password_confirmation_field_options
142
193
  def merge_validates_length_of_password_confirmation_field_options(options = {})
143
- self.validates_length_of_password_confirmation_field_options = validates_length_of_password_confirmation_field_options.merge(options)
194
+ self.validates_length_of_password_confirmation_field_options =
195
+ validates_length_of_password_confirmation_field_options.merge(options)
144
196
  end
145
197
 
146
- # The class you want to use to encrypt and verify your encrypted passwords. See the Authlogic::CryptoProviders module for more info
147
- # on the available methods and how to create your own.
198
+ # The class you want to use to encrypt and verify your encrypted
199
+ # passwords. See the Authlogic::CryptoProviders module for more info on
200
+ # the available methods and how to create your own.
201
+ #
202
+ # The family of adaptive hash functions (BCrypt, SCrypt, PBKDF2) is the
203
+ # best choice for password storage today. We recommend SCrypt. Other
204
+ # one-way functions like SHA512 are inferior, but widely used.
205
+ # Reverisbile functions like AES256 are the worst choice.
206
+ #
207
+ # You can use the `transition_from_crypto_providers` option to gradually
208
+ # transition to a better crypto provider without causing your users any
209
+ # pain.
148
210
  #
149
211
  # * <tt>Default:</tt> CryptoProviders::SCrypt
150
212
  # * <tt>Accepts:</tt> Class
151
213
  def crypto_provider(value = nil)
214
+ CryptoProviders::Guidance.new(value).impart_wisdom
152
215
  rw_config(:crypto_provider, value, CryptoProviders::SCrypt)
153
216
  end
154
217
  alias_method :crypto_provider=, :crypto_provider
155
218
 
156
- # Let's say you originally encrypted your passwords with Sha1. Sha1 is starting to join the party with MD5 and you want to switch
157
- # to something stronger. No problem, just specify your new and improved algorithm with the crypt_provider option and then let
158
- # Authlogic know you are transitioning from Sha1 using this option. Authlogic will take care of everything, including transitioning
159
- # your users to the new algorithm. The next time a user logs in, they will be granted access using the old algorithm and their
160
- # password will be resaved with the new algorithm. All new users will obviously use the new algorithm as well.
219
+ # Let's say you originally encrypted your passwords with Sha1. Sha1 is
220
+ # starting to join the party with MD5 and you want to switch to
221
+ # something stronger. No problem, just specify your new and improved
222
+ # algorithm with the crypt_provider option and then let Authlogic know
223
+ # you are transitioning from Sha1 using this option. Authlogic will take
224
+ # care of everything, including transitioning your users to the new
225
+ # algorithm. The next time a user logs in, they will be granted access
226
+ # using the old algorithm and their password will be resaved with the
227
+ # new algorithm. All new users will obviously use the new algorithm as
228
+ # well.
161
229
  #
162
- # Lastly, if you want to transition again, you can pass an array of crypto providers. So you can transition from as many algorithms
163
- # as you want.
230
+ # Lastly, if you want to transition again, you can pass an array of
231
+ # crypto providers. So you can transition from as many algorithms as you
232
+ # want.
164
233
  #
165
234
  # * <tt>Default:</tt> nil
166
235
  # * <tt>Accepts:</tt> Class or Array
167
236
  def transition_from_crypto_providers(value = nil)
168
- rw_config(:transition_from_crypto_providers, (!value.nil? && [value].flatten.compact) || value, [])
237
+ rw_config(
238
+ :transition_from_crypto_providers,
239
+ (!value.nil? && [value].flatten.compact) || value,
240
+ []
241
+ )
169
242
  end
170
243
  alias_method :transition_from_crypto_providers=, :transition_from_crypto_providers
171
244
  end
172
245
 
173
246
  # Callbacks / hooks to allow other modules to modify the behavior of this module.
174
247
  module Callbacks
175
- METHODS = [
176
- "before_password_set", "after_password_set",
177
- "before_password_verification", "after_password_verification"
178
- ]
248
+ # Does the order of this array matter?
249
+ METHODS = %w[
250
+ before_password_set
251
+ after_password_set
252
+ before_password_verification
253
+ after_password_verification
254
+ ].freeze
179
255
 
180
256
  def self.included(klass)
181
257
  return if klass.crypted_password_field.nil?
182
- klass.define_callbacks *METHODS
258
+ klass.define_callbacks(*METHODS)
183
259
 
184
260
  # If Rails 3, support the new callback syntax
185
- if klass.send(klass.respond_to?(:singleton_class) ? :singleton_class : :metaclass).method_defined?(:set_callback)
261
+ if klass.singleton_class.method_defined?(:set_callback)
186
262
  METHODS.each do |method|
187
- klass.class_eval <<-"end_eval", __FILE__, __LINE__
263
+ klass.class_eval <<-EOS, __FILE__, __LINE__ + 1
188
264
  def self.#{method}(*methods, &block)
189
265
  set_callback :#{method}, *methods, &block
190
266
  end
191
- end_eval
267
+ EOS
192
268
  end
193
269
  end
194
270
  end
195
271
 
196
- private
197
- METHODS.each do |method|
198
- class_eval <<-"end_eval", __FILE__, __LINE__
199
- def #{method}
200
- run_callbacks(:#{method}) { |result, object| result == false }
201
- end
202
- end_eval
203
- end
272
+ # TODO: Ideally, once this module is included, the included copies of
273
+ # the following methods would be private. This cannot be accomplished
274
+ # by using calling `private` here in the module. Maybe we can set the
275
+ # privacy inside `included`?
276
+ METHODS.each do |method|
277
+ class_eval <<-EOS, __FILE__, __LINE__ + 1
278
+ def #{method}
279
+ run_callbacks(:#{method}) { |result, object| result == false }
280
+ end
281
+ EOS
282
+ end
204
283
  end
205
284
 
206
285
  # The methods related to the password field.
@@ -215,8 +294,14 @@ module Authlogic
215
294
  validates_length_of :password, validates_length_of_password_field_options
216
295
 
217
296
  if require_password_confirmation
218
- validates_confirmation_of :password, validates_confirmation_of_password_field_options
219
- validates_length_of :password_confirmation, validates_length_of_password_confirmation_field_options
297
+ validates_confirmation_of(
298
+ :password,
299
+ validates_confirmation_of_password_field_options
300
+ )
301
+ validates_length_of(
302
+ :password_confirmation,
303
+ validates_length_of_password_confirmation_field_options
304
+ )
220
305
  end
221
306
  end
222
307
 
@@ -224,43 +309,62 @@ module Authlogic
224
309
  end
225
310
  end
226
311
 
312
+ # :nodoc:
227
313
  module InstanceMethods
228
314
  # The password
229
315
  def password
316
+ return nil unless defined?(@password)
230
317
  @password
231
318
  end
232
319
 
233
- # This is a virtual method. Once a password is passed to it, it will create new password salt as well as encrypt
234
- # the password.
320
+ # This is a virtual method. Once a password is passed to it, it will
321
+ # create new password salt as well as encrypt the password.
235
322
  def password=(pass)
236
323
  return if ignore_blank_passwords? && pass.blank?
237
324
  before_password_set
238
325
  @password = pass
239
- send("#{password_salt_field}=", Authlogic::Random.friendly_token) if password_salt_field
240
- send("#{crypted_password_field}=", crypto_provider.encrypt(*encrypt_arguments(@password, false, act_like_restful_authentication? ? :restful_authentication : nil)))
326
+ if password_salt_field
327
+ send("#{password_salt_field}=", Authlogic::Random.friendly_token)
328
+ end
329
+ encryptor_args_type = act_like_restful_authentication? ? :restful_authentication : nil
330
+ send(
331
+ "#{crypted_password_field}=",
332
+ crypto_provider.encrypt(
333
+ *encrypt_arguments(@password, false, encryptor_args_type)
334
+ )
335
+ )
241
336
  @password_changed = true
242
337
  after_password_set
243
338
  end
244
339
 
245
- # Accepts a raw password to determine if it is the correct password or not. Notice the second argument. That defaults to the value of
246
- # check_passwords_against_database. See that method for more information, but basically it just tells Authlogic to check the password
247
- # against the value in the database or the value in the object.
248
- def valid_password?(attempted_password, check_against_database = check_passwords_against_database?)
249
- crypted = check_against_database && send("#{crypted_password_field}_changed?") ? send("#{crypted_password_field}_was") : send(crypted_password_field)
340
+ # Accepts a raw password to determine if it is the correct password.
341
+ #
342
+ # - attempted_password [String] - password entered by user
343
+ # - check_against_database [boolean] - Should we check the password
344
+ # against the value in the database or the value in the object?
345
+ # Default taken from config option check_passwords_against_database.
346
+ # See config method for more information.
347
+ def valid_password?(
348
+ attempted_password,
349
+ check_against_database = check_passwords_against_database?
350
+ )
351
+ crypted = crypted_password_to_validate_against(check_against_database)
250
352
  return false if attempted_password.blank? || crypted.blank?
251
353
  before_password_verification
252
354
 
253
355
  crypto_providers.each_with_index do |encryptor, index|
254
- # The arguments_type of for the transitioning from restful_authentication
255
- arguments_type = (act_like_restful_authentication? && index == 0) ||
256
- (transition_from_restful_authentication? && index > 0 && encryptor == Authlogic::CryptoProviders::Sha1) ?
257
- :restful_authentication : nil
258
-
259
- if encryptor.matches?(crypted, *encrypt_arguments(attempted_password, check_against_database, arguments_type))
260
- transition_password(attempted_password) if transition_password?(index, encryptor, crypted, check_against_database)
261
- after_password_verification
262
- return true
356
+ next unless encryptor_matches?(
357
+ crypted,
358
+ encryptor,
359
+ index,
360
+ attempted_password,
361
+ check_against_database
362
+ )
363
+ if transition_password?(index, encryptor, check_against_database)
364
+ transition_password(attempted_password)
263
365
  end
366
+ after_password_verification
367
+ return true
264
368
  end
265
369
 
266
370
  false
@@ -277,77 +381,130 @@ module Authlogic
277
381
  # Resets the password to a random friendly token and then saves the record.
278
382
  def reset_password!
279
383
  reset_password
280
- save_without_session_maintenance(:validate => false)
384
+ save_without_session_maintenance(validate: false)
281
385
  end
282
386
  alias_method :randomize_password!, :reset_password!
283
387
 
284
388
  private
285
- def check_passwords_against_database?
286
- self.class.check_passwords_against_database == true
287
- end
288
389
 
289
- def crypto_providers
290
- [crypto_provider] + transition_from_crypto_providers
390
+ def crypted_password_to_validate_against(check_against_database)
391
+ if check_against_database && send("#{crypted_password_field}_changed?")
392
+ send("#{crypted_password_field}_was")
393
+ else
394
+ send(crypted_password_field)
291
395
  end
396
+ end
292
397
 
293
- def encrypt_arguments(raw_password, check_against_database, arguments_type = nil)
294
- salt = nil
295
- salt = (check_against_database && send("#{password_salt_field}_changed?") ? send("#{password_salt_field}_was") : send(password_salt_field)) if password_salt_field
398
+ def check_passwords_against_database?
399
+ self.class.check_passwords_against_database == true
400
+ end
296
401
 
297
- case arguments_type
298
- when :restful_authentication
299
- [REST_AUTH_SITE_KEY, salt, raw_password, REST_AUTH_SITE_KEY].compact
300
- else
301
- [raw_password, salt].compact
302
- end
303
- end
402
+ def crypto_providers
403
+ [crypto_provider] + transition_from_crypto_providers
404
+ end
304
405
 
305
- # Determines if we need to tranisiton the password.
306
- # If the index > 0 then we are using an "transition from" crypto provider.
307
- # If the encryptor has a cost and the cost it outdated.
308
- # If we aren't using database values
309
- # If we are using database values, only if the password hasn't changed so we don't overwrite any changes
310
- def transition_password?(index, encryptor, crypted, check_against_database)
311
- (index > 0 || (encryptor.respond_to?(:cost_matches?) && !encryptor.cost_matches?(send(crypted_password_field)))) &&
312
- (!check_against_database || !send("#{crypted_password_field}_changed?"))
406
+ # Returns an array of arguments to be passed to a crypto provider, either its
407
+ # `matches?` or its `encrypt` method.
408
+ def encrypt_arguments(raw_password, check_against_database, arguments_type = nil)
409
+ salt = nil
410
+ if password_salt_field
411
+ salt =
412
+ if check_against_database && send("#{password_salt_field}_changed?")
413
+ send("#{password_salt_field}_was")
414
+ else
415
+ send(password_salt_field)
416
+ end
313
417
  end
314
418
 
315
- def transition_password(attempted_password)
316
- self.password = attempted_password
317
- save(:validate => false)
419
+ case arguments_type
420
+ when :restful_authentication
421
+ [REST_AUTH_SITE_KEY, salt, raw_password, REST_AUTH_SITE_KEY].compact
422
+ when nil
423
+ [raw_password, salt].compact
424
+ else
425
+ raise "Invalid encryptor arguments_type: #{arguments_type}"
318
426
  end
427
+ end
319
428
 
320
- def require_password?
321
- new_record? || password_changed? || send(crypted_password_field).blank?
322
- end
429
+ # Given `encryptor`, does `attempted_password` match the `crypted` password?
430
+ def encryptor_matches?(
431
+ crypted,
432
+ encryptor,
433
+ index,
434
+ attempted_password,
435
+ check_against_database
436
+ )
437
+ # The arguments_type for the transitioning from restful_authentication
438
+ acting_restful = act_like_restful_authentication? && index.zero?
439
+ transitioning = transition_from_restful_authentication? &&
440
+ index > 0 &&
441
+ encryptor == Authlogic::CryptoProviders::Sha1
442
+ restful = acting_restful || transitioning
443
+ arguments_type = restful ? :restful_authentication : nil
444
+ encryptor_args = encrypt_arguments(
445
+ attempted_password,
446
+ check_against_database,
447
+ arguments_type
448
+ )
449
+ encryptor.matches?(crypted, *encryptor_args)
450
+ end
323
451
 
324
- def ignore_blank_passwords?
325
- self.class.ignore_blank_passwords == true
326
- end
452
+ # Determines if we need to transition the password.
453
+ #
454
+ # - If the index > 0 then we are using an "transition from" crypto
455
+ # provider.
456
+ # - If the encryptor has a cost and the cost it outdated.
457
+ # - If we aren't using database values
458
+ # - If we are using database values, only if the password hasn't
459
+ # changed so we don't overwrite any changes
460
+ def transition_password?(index, encryptor, check_against_database)
461
+ (
462
+ index > 0 ||
463
+ (encryptor.respond_to?(:cost_matches?) &&
464
+ !encryptor.cost_matches?(send(crypted_password_field)))
465
+ ) &&
466
+ (
467
+ !check_against_database ||
468
+ !send("#{crypted_password_field}_changed?")
469
+ )
470
+ end
327
471
 
328
- def password_changed?
329
- @password_changed == true
330
- end
472
+ def transition_password(attempted_password)
473
+ self.password = attempted_password
474
+ save(validate: false)
475
+ end
331
476
 
332
- def reset_password_changed
333
- @password_changed = nil
334
- end
477
+ def require_password?
478
+ new_record? || password_changed? || send(crypted_password_field).blank?
479
+ end
335
480
 
336
- def crypted_password_field
337
- self.class.crypted_password_field
338
- end
481
+ def ignore_blank_passwords?
482
+ self.class.ignore_blank_passwords == true
483
+ end
339
484
 
340
- def password_salt_field
341
- self.class.password_salt_field
342
- end
485
+ def password_changed?
486
+ defined?(@password_changed) && @password_changed == true
487
+ end
343
488
 
344
- def crypto_provider
345
- self.class.crypto_provider
346
- end
489
+ def reset_password_changed
490
+ @password_changed = nil
491
+ end
347
492
 
348
- def transition_from_crypto_providers
349
- self.class.transition_from_crypto_providers
350
- end
493
+ def crypted_password_field
494
+ self.class.crypted_password_field
495
+ end
496
+
497
+ def password_salt_field
498
+ self.class.password_salt_field
499
+ end
500
+
501
+ def crypto_provider
502
+ self.class.crypto_provider
503
+ end
504
+
505
+ def transition_from_crypto_providers
506
+ self.class.transition_from_crypto_providers
507
+ end
351
508
  end
352
509
  end
353
510
  end