devise-security 0.15.0 → 0.18.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 (145) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE.txt +3 -1
  3. data/README.md +43 -24
  4. data/app/controllers/devise/paranoid_verification_code_controller.rb +26 -12
  5. data/app/controllers/devise/password_expired_controller.rb +23 -10
  6. data/config/locales/bg.yml +42 -0
  7. data/config/locales/by.yml +2 -0
  8. data/config/locales/cs.yml +6 -0
  9. data/config/locales/de.yml +4 -0
  10. data/config/locales/en.yml +3 -1
  11. data/config/locales/es.yml +13 -0
  12. data/config/locales/fa.yml +2 -0
  13. data/config/locales/fr.yml +15 -2
  14. data/config/locales/hi.yml +22 -20
  15. data/config/locales/it.yml +2 -0
  16. data/config/locales/ja.yml +13 -0
  17. data/config/locales/nl.yml +2 -0
  18. data/config/locales/pt.yml +2 -0
  19. data/config/locales/ru.yml +2 -0
  20. data/config/locales/tr.yml +26 -1
  21. data/config/locales/uk.yml +2 -0
  22. data/config/locales/zh_CN.yml +2 -0
  23. data/config/locales/zh_TW.yml +2 -0
  24. data/lib/devise-security/controllers/helpers.rb +25 -13
  25. data/lib/devise-security/hooks/expirable.rb +3 -3
  26. data/lib/devise-security/hooks/paranoid_verification.rb +1 -3
  27. data/lib/devise-security/hooks/password_expirable.rb +1 -3
  28. data/lib/devise-security/hooks/session_limitable.rb +10 -6
  29. data/lib/devise-security/models/compatibility/active_record_patch.rb +4 -3
  30. data/lib/devise-security/models/compatibility/mongoid_patch.rb +3 -2
  31. data/lib/devise-security/models/database_authenticatable_patch.rb +18 -10
  32. data/lib/devise-security/models/expirable.rb +6 -5
  33. data/lib/devise-security/models/paranoid_verification.rb +2 -2
  34. data/lib/devise-security/models/password_archivable.rb +3 -3
  35. data/lib/devise-security/models/secure_validatable.rb +62 -11
  36. data/lib/devise-security/orm/mongoid.rb +1 -1
  37. data/lib/devise-security/patches.rb +14 -8
  38. data/lib/devise-security/routes.rb +2 -3
  39. data/lib/devise-security/validators/password_complexity_validator.rb +53 -26
  40. data/lib/devise-security/version.rb +1 -1
  41. data/lib/devise-security.rb +15 -6
  42. data/lib/generators/devise_security/install_generator.rb +4 -6
  43. data/{test/tmp/config/initializers/devise-security.rb → lib/generators/templates/devise_security.rb} +9 -1
  44. data/test/controllers/test_paranoid_verification_code_controller.rb +133 -0
  45. data/test/controllers/test_password_expired_controller.rb +122 -99
  46. data/test/controllers/test_security_question_controller.rb +19 -37
  47. data/test/dummy/app/controllers/overrides/paranoid_verification_code_controller.rb +7 -0
  48. data/test/dummy/app/controllers/overrides/password_expired_controller.rb +17 -0
  49. data/test/dummy/app/controllers/widgets_controller.rb +3 -0
  50. data/test/dummy/app/models/application_user_record.rb +2 -1
  51. data/test/dummy/app/models/mongoid/confirmable_fields.rb +2 -0
  52. data/test/dummy/app/models/mongoid/database_authenticable_fields.rb +4 -3
  53. data/test/dummy/app/models/mongoid/expirable_fields.rb +2 -0
  54. data/test/dummy/app/models/mongoid/lockable_fields.rb +2 -0
  55. data/test/dummy/app/models/mongoid/mappings.rb +4 -2
  56. data/test/dummy/app/models/mongoid/omniauthable_fields.rb +2 -0
  57. data/test/dummy/app/models/mongoid/paranoid_verification_fields.rb +2 -0
  58. data/test/dummy/app/models/mongoid/password_archivable_fields.rb +2 -0
  59. data/test/dummy/app/models/mongoid/password_expirable_fields.rb +2 -0
  60. data/test/dummy/app/models/mongoid/recoverable_fields.rb +2 -0
  61. data/test/dummy/app/models/mongoid/registerable_fields.rb +4 -2
  62. data/test/dummy/app/models/mongoid/rememberable_fields.rb +2 -0
  63. data/test/dummy/app/models/mongoid/secure_validatable_fields.rb +2 -0
  64. data/test/dummy/app/models/mongoid/security_questionable_fields.rb +2 -0
  65. data/test/dummy/app/models/mongoid/session_limitable_fields.rb +2 -0
  66. data/test/dummy/app/models/mongoid/timeoutable_fields.rb +2 -0
  67. data/test/dummy/app/models/mongoid/trackable_fields.rb +2 -0
  68. data/test/dummy/app/models/mongoid/validatable_fields.rb +2 -0
  69. data/test/dummy/app/models/paranoid_verification_user.rb +26 -0
  70. data/test/dummy/app/models/password_expired_user.rb +26 -0
  71. data/test/dummy/app/models/user.rb +5 -5
  72. data/test/dummy/app/models/widget.rb +1 -3
  73. data/test/dummy/app/mongoid/one_user.rb +5 -5
  74. data/test/dummy/app/mongoid/user_on_engine.rb +2 -2
  75. data/test/dummy/app/mongoid/user_on_main_app.rb +2 -2
  76. data/test/dummy/app/mongoid/user_with_validations.rb +3 -3
  77. data/test/dummy/app/mongoid/user_without_email.rb +7 -4
  78. data/test/dummy/config/application.rb +3 -7
  79. data/test/dummy/config/boot.rb +1 -1
  80. data/test/dummy/config/environment.rb +1 -1
  81. data/test/dummy/config/environments/test.rb +4 -13
  82. data/test/dummy/config/initializers/devise.rb +1 -5
  83. data/test/dummy/config/initializers/migration_class.rb +1 -8
  84. data/test/dummy/config/locales/en.yml +10 -0
  85. data/test/dummy/config/mongoid.yml +1 -1
  86. data/test/dummy/config/routes.rb +3 -1
  87. data/test/dummy/config.ru +1 -1
  88. data/test/dummy/db/migrate/20120508165529_create_tables.rb +5 -5
  89. data/test/dummy/lib/shared_expirable_columns.rb +1 -0
  90. data/test/dummy/lib/shared_security_questions_fields.rb +1 -0
  91. data/test/dummy/lib/shared_user.rb +17 -6
  92. data/test/dummy/lib/shared_user_without_omniauth.rb +12 -3
  93. data/test/dummy/lib/shared_verification_fields.rb +1 -0
  94. data/test/dummy/log/test.log +44592 -1151
  95. data/test/i18n_test.rb +22 -0
  96. data/test/integration/test_paranoid_verification_code_workflow.rb +53 -0
  97. data/test/integration/test_password_expirable_workflow.rb +2 -6
  98. data/test/integration/test_session_limitable_workflow.rb +5 -3
  99. data/test/orm/active_record.rb +7 -7
  100. data/test/orm/mongoid.rb +2 -1
  101. data/test/support/integration_helpers.rb +10 -22
  102. data/test/support/mongoid.yml +1 -1
  103. data/test/test_compatibility.rb +2 -0
  104. data/test/test_complexity_validator.rb +247 -37
  105. data/test/test_database_authenticatable_patch.rb +146 -0
  106. data/test/test_helper.rb +11 -12
  107. data/test/test_install_generator.rb +2 -2
  108. data/test/test_paranoid_verification.rb +8 -9
  109. data/test/test_password_archivable.rb +34 -11
  110. data/test/test_password_expirable.rb +27 -27
  111. data/test/test_secure_validatable.rb +284 -50
  112. data/test/test_secure_validatable_overrides.rb +185 -0
  113. data/test/test_session_limitable.rb +9 -9
  114. data/{lib/generators/templates/devise-security.rb → test/tmp/config/initializers/devise_security.rb} +9 -1
  115. data/test/tmp/config/locales/devise.security_extension.by.yml +50 -0
  116. data/test/tmp/config/locales/devise.security_extension.cs.yml +46 -0
  117. data/test/tmp/config/locales/devise.security_extension.de.yml +4 -0
  118. data/test/tmp/config/locales/devise.security_extension.en.yml +3 -1
  119. data/test/tmp/config/locales/devise.security_extension.es.yml +22 -9
  120. data/test/tmp/config/locales/devise.security_extension.fa.yml +2 -0
  121. data/test/tmp/config/locales/devise.security_extension.fr.yml +15 -2
  122. data/test/tmp/config/locales/devise.security_extension.hi.yml +43 -0
  123. data/test/tmp/config/locales/devise.security_extension.it.yml +2 -0
  124. data/test/tmp/config/locales/devise.security_extension.ja.yml +13 -0
  125. data/test/tmp/config/locales/devise.security_extension.nl.yml +2 -0
  126. data/test/tmp/config/locales/devise.security_extension.pt.yml +2 -0
  127. data/test/tmp/config/locales/devise.security_extension.ru.yml +2 -0
  128. data/test/tmp/config/locales/devise.security_extension.tr.yml +26 -1
  129. data/test/tmp/config/locales/devise.security_extension.uk.yml +2 -0
  130. data/test/tmp/config/locales/devise.security_extension.zh_CN.yml +2 -0
  131. data/test/tmp/config/locales/devise.security_extension.zh_TW.yml +42 -0
  132. metadata +65 -45
  133. data/lib/devise-security/orm/active_record.rb +0 -20
  134. data/lib/devise-security/patches/confirmations_controller_captcha.rb +0 -23
  135. data/lib/devise-security/patches/confirmations_controller_security_question.rb +0 -26
  136. data/lib/devise-security/patches/passwords_controller_captcha.rb +0 -22
  137. data/lib/devise-security/patches/passwords_controller_security_question.rb +0 -25
  138. data/lib/devise-security/patches/registrations_controller_captcha.rb +0 -35
  139. data/lib/devise-security/patches/sessions_controller_captcha.rb +0 -26
  140. data/lib/devise-security/patches/unlocks_controller_captcha.rb +0 -22
  141. data/lib/devise-security/patches/unlocks_controller_security_question.rb +0 -25
  142. data/lib/devise-security/schema.rb +0 -66
  143. data/test/dummy/app/controllers/foos_controller.rb +0 -0
  144. data/test/dummy/app/models/secure_user.rb +0 -9
  145. data/test/dummy/lib/shared_user_without_email.rb +0 -28
@@ -3,6 +3,7 @@ ru:
3
3
  messages:
4
4
  taken_in_past: 'уже ранее использовался.'
5
5
  equal_to_current_password: 'должен отличаться от текущего пароля.'
6
+ equal_to_email: 'должно отличаться от адреса электронной почты.'
6
7
  password_complexity:
7
8
  digit:
8
9
  one: 'должен содержать хотя бы одну цифру'
@@ -30,6 +31,7 @@ ru:
30
31
  paranoid_verify:
31
32
  code_required: 'Пожалуйста введите код, полученный от нашей команды поддержки'
32
33
  paranoid_verification_code:
34
+ updated: Код подтверждения принят
33
35
  show:
34
36
  submit_verification_code: Ввод кода подтверждения
35
37
  verification_code: Код подверждения
@@ -3,15 +3,40 @@ tr:
3
3
  messages:
4
4
  taken_in_past: "daha önce kullanıldı."
5
5
  equal_to_current_password: "mevcut paroladan farklı olmalı."
6
- password_format: "büyük, küçük harfler ve sayılar içermeli."
6
+ equal_to_email: "e-postadan farklı olmalı."
7
+ password_complexity:
8
+ digit:
9
+ one: en az bir rakam içermelidir
10
+ other: en az %{count} basamak içermelidir
11
+ lower:
12
+ one: en az bir küçük harf içermelidir
13
+ other: en az %{count} küçük harf içermelidir
14
+ symbol:
15
+ one: en az bir noktalama işareti veya sembolü içermelidir
16
+ other: en az %{count} noktalama işareti veya sembolü içermelidir
17
+ upper:
18
+ one: en az bir büyük harf içermelidir
19
+ other: en az %{count} büyük harf içermelidir
7
20
  devise:
8
21
  invalid_captcha: "Captcha hatalı."
9
22
  invalid_security_question: "Güvenlik sorusunun cevabı yanlış."
10
23
  paranoid_verify:
11
24
  code_required: "Destek ekibimizden aldığınız kodu girin."
25
+ paranoid_verification_code:
26
+ updated: Doğrulama kodu kabul edildi
27
+ show:
28
+ submit_verification_code: Doğrulama kodunu gönder
29
+ verification_code: Doğrulama kodu
30
+ submit: Gönder
12
31
  password_expired:
13
32
  updated: "Yeni parolanız kaydedildi."
14
33
  change_required: "Parolanızın geçerlilik süresi dolmuş. Lütfen parolanızı yenileyin."
34
+ show:
35
+ renew_your_password: Şifrenizi yenileyin
36
+ current_password: Mevcut Şifre
37
+ new_password: Yeni Şifre
38
+ new_password_confirmation: Yeni şifreyi onayla
39
+ change_my_password: Şifremi Değiştir
15
40
  failure:
16
41
  session_limited: 'Hesabınıza başka bir tarayıcıdan giriş yapılmış. Lütfen devam etmek için yeniden giriş yapın.'
17
42
  expired: 'Hesabınız aktif olarak kullanılmadığı için artık geçerli değil. Lütfen yönetici ile irtibata geçin.'
@@ -3,6 +3,7 @@ uk:
3
3
  messages:
4
4
  taken_in_past: 'раніше використовувався.'
5
5
  equal_to_current_password: 'має відрізнятися від поточного паролю.'
6
+ equal_to_email: 'має відрізнятися від електронної пошти.'
6
7
  password_complexity:
7
8
  digit:
8
9
  one: 'повинен включати хоча б одну цифру'
@@ -30,6 +31,7 @@ uk:
30
31
  paranoid_verify:
31
32
  code_required: 'Введіть, будь ласка, код від нашої команди підтримки'
32
33
  paranoid_verification_code:
34
+ updated: Код підтвердження прийнято
33
35
  show:
34
36
  submit_verification_code: Відправити код підтвердження
35
37
  verification_code: Код підтвердження
@@ -3,6 +3,7 @@ zh_CN:
3
3
  messages:
4
4
  taken_in_past: '曾被使用过。'
5
5
  equal_to_current_password: '必须与当前密码不同。'
6
+ equal_to_email: '必须与电子邮件地址不同。'
6
7
  password_complexity:
7
8
  digit:
8
9
  one: 必须包含至少1个数字
@@ -22,6 +23,7 @@ zh_CN:
22
23
  paranoid_verify:
23
24
  code_required: '请输入我们支持团队提供的代码'
24
25
  paranoid_verification_code:
26
+ updated: 接受验证码
25
27
  show:
26
28
  submit_verification_code: 提交验证码
27
29
  verification_code: 验证码
@@ -3,6 +3,7 @@ zh_TW:
3
3
  messages:
4
4
  taken_in_past: '曾被使用過。'
5
5
  equal_to_current_password: '必須與目前密碼不同。'
6
+ equal_to_email: '必須與電子郵件地址不同。'
6
7
  password_complexity:
7
8
  digit:
8
9
  one: 必須包含至少一個數字
@@ -22,6 +23,7 @@ zh_TW:
22
23
  paranoid_verify:
23
24
  code_required: '請輸入由我們客服團隊提供的代碼'
24
25
  paranoid_verification_code:
26
+ updated: 接受驗證碼
25
27
  show:
26
28
  submit_verification_code: 送出驗證碼
27
29
  verification_code: 驗證碼
@@ -29,8 +29,8 @@ module DeviseSecurity
29
29
  end
30
30
 
31
31
  def valid_captcha_if_defined?(captcha)
32
- defined?(verify_recaptcha) && verify_recaptcha ||
33
- defined?(valid_captcha?) && valid_captcha?(captcha)
32
+ (defined?(verify_recaptcha) && verify_recaptcha) ||
33
+ (defined?(valid_captcha?) && valid_captcha?(captcha))
34
34
  end
35
35
 
36
36
  def valid_security_question_answer?(resource, answer)
@@ -75,12 +75,18 @@ module DeviseSecurity
75
75
  def handle_paranoid_verification
76
76
  return if warden.nil?
77
77
 
78
- if !devise_controller? && !request.format.nil? && request.format.html?
78
+ if !devise_controller? &&
79
+ !ignore_paranoid_verification_code? &&
80
+ !request.format.nil? &&
81
+ request.format.html?
79
82
  Devise.mappings.keys.flatten.any? do |scope|
80
- if signed_in?(scope) && warden.session(scope)['paranoid_verify']
81
- store_location_for(scope, request.original_fullpath) if request.get?
82
- redirect_for_paranoid_verification scope
83
- return
83
+ if signed_in?(scope) && warden.session(scope)['paranoid_verify'] == true
84
+ if send(:"current_#{scope}").try(:need_paranoid_verification?)
85
+ store_location_for(scope, request.original_fullpath) if request.get?
86
+ redirect_for_paranoid_verification(scope)
87
+ else
88
+ warden.session(scope)['paranoid_verify'] = false
89
+ end
84
90
  end
85
91
  end
86
92
  end
@@ -88,24 +94,26 @@ module DeviseSecurity
88
94
 
89
95
  # redirect for password update with alert message
90
96
  def redirect_for_password_change(scope)
91
- redirect_to change_password_required_path_for(scope), alert: I18n.t('change_required', { scope: 'devise.password_expired' })
97
+ redirect_to change_password_required_path_for(scope), alert: I18n.t('change_required', scope: 'devise.password_expired')
92
98
  end
93
99
 
94
100
  def redirect_for_paranoid_verification(scope)
95
- redirect_to paranoid_verification_code_path_for(scope), alert: I18n.t('code_required', { scope: 'devise.paranoid_verify' })
101
+ redirect_to paranoid_verification_code_path_for(scope), alert: I18n.t('code_required', scope: 'devise.paranoid_verify')
96
102
  end
97
103
 
98
104
  # path for change password
99
105
  def change_password_required_path_for(resource_or_scope = nil)
100
106
  scope = Devise::Mapping.find_scope!(resource_or_scope)
101
- change_path = "#{scope}_password_expired_path"
102
- send(change_path)
107
+ router_name = Devise.mappings[scope].router_name
108
+ context = router_name ? send(router_name) : _devise_route_context
109
+ context.send("#{scope}_password_expired_path")
103
110
  end
104
111
 
105
112
  def paranoid_verification_code_path_for(resource_or_scope = nil)
106
113
  scope = Devise::Mapping.find_scope!(resource_or_scope)
107
- change_path = "#{scope}_paranoid_verification_code_path"
108
- send(change_path)
114
+ router_name = Devise.mappings[scope].router_name
115
+ context = router_name ? send(router_name) : _devise_route_context
116
+ context.send("#{scope}_paranoid_verification_code_path")
109
117
  end
110
118
 
111
119
  protected
@@ -114,6 +122,10 @@ module DeviseSecurity
114
122
  def ignore_password_expire?
115
123
  false
116
124
  end
125
+
126
+ def ignore_paranoid_verification_code?
127
+ false
128
+ end
117
129
  end
118
130
  end
119
131
  end
@@ -1,11 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # Updates the last_activity_at fields from the record. Only when the user is active
3
+ # Updates the last_activity_at fields from the record. Only when the user is active
4
4
  # for authentication and authenticated.
5
- # An expiry of the account is only checked on sign in OR on manually setting the
5
+ # An expiry of the account is only checked on sign in OR on manually setting the
6
6
  # expired_at to the past (see Devise::Models::Expirable for this)
7
7
  Warden::Manager.after_set_user do |record, warden, options|
8
- if record && record.respond_to?(:active_for_authentication?) && record.active_for_authentication? &&
8
+ if record && record.respond_to?(:active_for_authentication?) && record.active_for_authentication? &&
9
9
  warden.authenticated?(options[:scope]) && record.respond_to?(:update_last_activity!)
10
10
  record.update_last_activity!
11
11
  end
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  Warden::Manager.after_set_user do |record, warden, options|
4
- if record.respond_to?(:need_paranoid_verification?)
5
- warden.session(options[:scope])['paranoid_verify'] = record.need_paranoid_verification?
6
- end
4
+ warden.session(options[:scope])['paranoid_verify'] = record.need_paranoid_verification? if record.respond_to?(:need_paranoid_verification?)
7
5
  end
@@ -3,7 +3,5 @@
3
3
  # @note This happens after
4
4
  # {DeviseSecurity::Controller::Helpers#handle_password_change}
5
5
  Warden::Manager.after_authentication do |record, warden, options|
6
- if record.respond_to?(:need_change_password?)
7
- warden.session(options[:scope])['password_expired'] = record.need_change_password?
8
- end
6
+ warden.session(options[:scope])['password_expired'] = record.need_change_password? if record.respond_to?(:need_change_password?)
9
7
  end
@@ -7,9 +7,14 @@ Warden::Manager.after_set_user except: :fetch do |record, warden, options|
7
7
  if record.devise_modules.include?(:session_limitable) &&
8
8
  warden.authenticated?(options[:scope]) &&
9
9
  !record.skip_session_limitable?
10
- unique_session_id = Devise.friendly_token
11
- warden.session(options[:scope])['unique_session_id'] = unique_session_id
12
- record.update_unique_session_id!(unique_session_id)
10
+
11
+ if !options[:skip_session_limitable]
12
+ unique_session_id = Devise.friendly_token
13
+ warden.session(options[:scope])['unique_session_id'] = unique_session_id
14
+ record.update_unique_session_id!(unique_session_id)
15
+ else
16
+ warden.session(options[:scope])['devise.skip_session_limitable'] = true
17
+ end
13
18
  end
14
19
  end
15
20
 
@@ -19,14 +24,13 @@ end
19
24
  # page on the next request.
20
25
  Warden::Manager.after_set_user only: :fetch do |record, warden, options|
21
26
  scope = options[:scope]
22
- env = warden.request.env
23
27
 
24
28
  if record.devise_modules.include?(:session_limitable) &&
25
29
  warden.authenticated?(scope) &&
26
30
  options[:store] != false
27
31
  if record.unique_session_id != warden.session(scope)['unique_session_id'] &&
28
- !env['devise.skip_session_limitable'] &&
29
- !record.skip_session_limitable?
32
+ !record.skip_session_limitable? &&
33
+ !warden.session(scope)['devise.skip_session_limitable']
30
34
  Rails.logger.warn do
31
35
  '[devise-security][session_limitable] session id mismatch: '\
32
36
  "expected=#{record.unique_session_id.inspect} "\
@@ -1,12 +1,14 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Devise
2
4
  module Models
3
5
  module Compatibility
4
-
5
6
  class NotPersistedError < ActiveRecord::ActiveRecordError; end
6
7
 
7
8
  module ActiveRecordPatch
8
9
  extend ActiveSupport::Concern
9
- unless Devise.activerecord51?
10
+
11
+ unless defined?(ActiveRecord) && ActiveRecord.gem_version >= Gem::Version.new("5.1.x")
10
12
  # When the record was saved, was the +encrypted_password+ changed?
11
13
  # @return [Boolean]
12
14
  def saved_change_to_encrypted_password?
@@ -33,7 +35,6 @@ module Devise
33
35
  def update_attribute_without_validatons_or_callbacks(name, value)
34
36
  update_column(name, value)
35
37
  end
36
-
37
38
  end
38
39
  end
39
40
  end
@@ -1,7 +1,8 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Devise
2
4
  module Models
3
5
  module Compatibility
4
-
5
6
  class NotPersistedError < Mongoid::Errors::MongoidError; end
6
7
 
7
8
  module MongoidPatch
@@ -23,7 +24,7 @@ module Devise
23
24
  # @param name [Symbol] attribute to update
24
25
  # @param value [String] value to set
25
26
  def update_attribute_without_validatons_or_callbacks(name, value)
26
- set(Hash[*[name, value]])
27
+ set(Hash[name, value])
27
28
  end
28
29
  end
29
30
  end
@@ -5,20 +5,28 @@ module Devise
5
5
  module DatabaseAuthenticatablePatch
6
6
  def update_with_password(params, *options)
7
7
  current_password = params.delete(:current_password)
8
+ valid_password = valid_password?(current_password)
8
9
 
9
10
  new_password = params[:password]
10
11
  new_password_confirmation = params[:password_confirmation]
11
12
 
12
- result = if valid_password?(current_password) && new_password.present? && new_password_confirmation.present?
13
- update(params, *options)
14
- else
15
- self.assign_attributes(params, *options)
16
- self.valid?
17
- self.errors.add(:current_password, current_password.blank? ? :blank : :invalid)
18
- self.errors.add(:password, new_password.blank? ? :blank : :invalid)
19
- self.errors.add(:password_confirmation, new_password_confirmation.blank? ? :blank : :invalid)
20
- false
21
- end
13
+ result = if valid_password && new_password.present? && new_password_confirmation.present?
14
+ update(params, *options)
15
+ else
16
+ assign_attributes(params, *options)
17
+
18
+ if current_password.blank?
19
+ errors.add(:current_password, :blank)
20
+ elsif !valid_password
21
+ errors.add(:current_password, :invalid)
22
+ end
23
+
24
+ errors.add(:password, :blank) if new_password.blank?
25
+
26
+ errors.add(:password_confirmation, :blank) if new_password_confirmation.blank?
27
+
28
+ false
29
+ end
22
30
 
23
31
  clean_up_passwords
24
32
  result
@@ -34,9 +34,11 @@ module Devise
34
34
  # @return [bool]
35
35
  def expired?
36
36
  # expired_at set (manually, via cron, etc.)
37
- return self.expired_at < Time.now.utc unless self.expired_at.nil?
37
+ return expired_at < Time.now.utc unless expired_at.nil?
38
+
38
39
  # if it is not set, check the last activity against configured expire_after time range
39
- return self.last_activity_at < self.class.expire_after.ago unless self.last_activity_at.nil?
40
+ return last_activity_at < self.class.expire_after.ago unless last_activity_at.nil?
41
+
40
42
  # if last_activity_at is nil as well, the user has to be 'fresh' and is therefore not expired
41
43
  false
42
44
  end
@@ -58,13 +60,13 @@ module Devise
58
60
  #
59
61
  # @return [bool]
60
62
  def active_for_authentication?
61
- super && !self.expired?
63
+ super && !expired?
62
64
  end
63
65
 
64
66
  # The message sym, if {#active_for_authentication?} returns +false+. E.g. needed
65
67
  # for i18n.
66
68
  def inactive_message
67
- !self.expired? ? super : :expired
69
+ !expired? ? super : :expired
68
70
  end
69
71
 
70
72
  module ClassMethods
@@ -80,7 +82,6 @@ module Devise
80
82
  all.each do |u|
81
83
  u.expire! if u.expired? && u.expired_at.nil?
82
84
  end
83
- return
84
85
  end
85
86
 
86
87
  # Scope method to collect all expired users since +time+ ago
@@ -20,7 +20,7 @@ module Devise
20
20
  elsif code == paranoid_verification_code
21
21
  attempt = 0
22
22
  update_without_password paranoid_verification_code: nil,
23
- paranoid_verified_at: Time.now,
23
+ paranoid_verified_at: Time.zone.now,
24
24
  paranoid_verification_attempt: attempt
25
25
  else
26
26
  update_without_password paranoid_verification_attempt: attempt
@@ -32,7 +32,7 @@ module Devise
32
32
  end
33
33
 
34
34
  def generate_paranoid_code
35
- update_without_password paranoid_verification_code: Devise.verification_code_generator.call(),
35
+ update_without_password paranoid_verification_code: Devise.verification_code_generator.call,
36
36
  paranoid_verification_attempt: 0
37
37
  end
38
38
  end
@@ -35,13 +35,13 @@ module Devise
35
35
  end
36
36
  end
37
37
 
38
- # validate is the password used in the past
38
+ # validate if the password was used in the past
39
39
  # @return [true] if current password was used previously
40
40
  # @return [false] if disabled or not previously used
41
41
  def password_archive_included?
42
42
  return false unless max_old_passwords.positive?
43
43
 
44
- old_passwords_including_cur_change = old_passwords.order(created_at: :desc).limit(max_old_passwords).pluck(:encrypted_password)
44
+ old_passwords_including_cur_change = old_passwords.reorder(created_at: :desc).limit(max_old_passwords).pluck(:encrypted_password)
45
45
  old_passwords_including_cur_change << encrypted_password_was # include most recent change in list, but don't save it yet!
46
46
  old_passwords_including_cur_change.any? do |old_password|
47
47
  # NOTE: we deliberately do not do mass assignment here so that users that
@@ -73,7 +73,7 @@ module Devise
73
73
  return true if old_passwords.where(encrypted_password: encrypted_password_was).exists?
74
74
 
75
75
  old_passwords.create!(encrypted_password: encrypted_password_was) if encrypted_password_was.present?
76
- old_passwords.order(created_at: :desc).offset(max_old_passwords).destroy_all
76
+ old_passwords.reorder(created_at: :desc).offset(max_old_passwords).destroy_all
77
77
  else
78
78
  old_passwords.destroy_all
79
79
  end
@@ -26,7 +26,7 @@ module Devise
26
26
  already_validated_email = false
27
27
 
28
28
  # validate login in a strict way if not yet validated
29
- unless has_uniqueness_validation_of_login?
29
+ unless uniqueness_validation_of_login?
30
30
  validation_condition = "#{login_attribute}_changed?".to_sym
31
31
 
32
32
  validates login_attribute, uniqueness: {
@@ -44,17 +44,39 @@ module Devise
44
44
  validates :email, uniqueness: true, allow_blank: true, if: :email_changed? # check uniq for email ever
45
45
  end
46
46
 
47
- validates :password, presence: true, length: password_length, confirmation: true, if: :password_required?
47
+ validates_presence_of :password, if: :password_required?
48
+ validates_confirmation_of :password, if: :password_required?
49
+
50
+ validate if: :password_required? do |record|
51
+ validates_with ActiveModel::Validations::LengthValidator,
52
+ attributes: :password,
53
+ allow_blank: true,
54
+ in: record.password_length
55
+ end
48
56
  end
49
57
 
50
58
  # extra validations
51
- validates :email, email: email_validation if email_validation # see https://github.com/devise-security/devise-security/blob/master/README.md#e-mail-validation
52
- validates :password,
53
- 'devise_security/password_complexity': password_complexity,
54
- if: :password_required?
59
+ # see https://github.com/devise-security/devise-security/blob/master/README.md#e-mail-validation
60
+ validate do |record|
61
+ if email_validation
62
+ validates_with(
63
+ EmailValidator, { attributes: :email }
64
+ )
65
+ end
66
+ end
67
+
68
+ validate if: :password_required? do |record|
69
+ validates_with(
70
+ record.password_complexity_validator.is_a?(Class) ? record.password_complexity_validator : record.password_complexity_validator.classify.constantize,
71
+ { attributes: :password }.merge(record.password_complexity)
72
+ )
73
+ end
55
74
 
56
75
  # don't allow use same password
57
76
  validate :current_equal_password_validation
77
+
78
+ # don't allow email to equal password
79
+ validate :email_not_equal_password_validation
58
80
  end
59
81
  end
60
82
 
@@ -64,10 +86,21 @@ module Devise
64
86
 
65
87
  def current_equal_password_validation
66
88
  return if new_record? || !will_save_change_to_encrypted_password? || password.blank?
89
+
67
90
  dummy = self.class.new(encrypted_password: encrypted_password_was).tap do |user|
68
91
  user.password_salt = password_salt_was if respond_to?(:password_salt)
69
92
  end
70
- self.errors.add(:password, :equal_to_current_password) if dummy.valid_password?(password)
93
+ errors.add(:password, :equal_to_current_password) if dummy.valid_password?(password)
94
+ end
95
+
96
+ def email_not_equal_password_validation
97
+ return if allow_passwords_equal_to_email
98
+
99
+ return if password.blank? || email.blank? || (!new_record? && !will_save_change_to_encrypted_password?)
100
+
101
+ return unless Devise.secure_compare(password.downcase.strip, email.downcase.strip)
102
+
103
+ errors.add(:password, :equal_to_email)
71
104
  end
72
105
 
73
106
  protected
@@ -75,6 +108,8 @@ module Devise
75
108
  # Checks whether a password is needed or not. For validations only.
76
109
  # Passwords are always required if it's a new record, or if the password
77
110
  # or confirmation are being set somewhere.
111
+ #
112
+ # @return [Boolean]
78
113
  def password_required?
79
114
  !persisted? || !password.nil? || !password_confirmation.nil?
80
115
  end
@@ -83,15 +118,31 @@ module Devise
83
118
  true
84
119
  end
85
120
 
121
+ delegate(
122
+ :allow_passwords_equal_to_email,
123
+ :email_validation,
124
+ :password_complexity,
125
+ :password_complexity_validator,
126
+ :password_length,
127
+ to: :class
128
+ )
129
+
86
130
  module ClassMethods
87
- Devise::Models.config(self, :password_complexity, :password_length, :email_validation)
131
+ Devise::Models.config(
132
+ self,
133
+ :allow_passwords_equal_to_email,
134
+ :email_validation,
135
+ :password_complexity,
136
+ :password_complexity_validator,
137
+ :password_length
138
+ )
88
139
 
89
140
  private
90
141
 
91
- def has_uniqueness_validation_of_login?
142
+ def uniqueness_validation_of_login?
92
143
  validators.any? do |validator|
93
144
  validator_orm_klass = DEVISE_ORM == :active_record ? ActiveRecord::Validations::UniquenessValidator : ::Mongoid::Validatable::UniquenessValidator
94
- validator.kind_of?(validator_orm_klass) && validator.attributes.include?(login_attribute)
145
+ validator.is_a?(validator_orm_klass) && validator.attributes.include?(login_attribute)
95
146
  end
96
147
  end
97
148
 
@@ -100,7 +151,7 @@ module Devise
100
151
  end
101
152
 
102
153
  def devise_validation_enabled?
103
- self.ancestors.map(&:to_s).include? 'Devise::Models::Validatable'
154
+ ancestors.map(&:to_s).include? 'Devise::Models::Validatable'
104
155
  end
105
156
  end
106
157
  end
@@ -3,5 +3,5 @@
3
3
  ActiveSupport.on_load(:mongoid) do
4
4
  require 'orm_adapter/adapters/mongoid'
5
5
 
6
- Mongoid::Document::ClassMethods.send :include, Devise::Models
6
+ Mongoid::Document::ClassMethods.include(Devise::Models)
7
7
  end
@@ -6,18 +6,24 @@ module DeviseSecurity
6
6
  autoload :ControllerSecurityQuestion, 'devise-security/patches/controller_security_question'
7
7
 
8
8
  class << self
9
+ # rubocop:disable Metrics/AbcSize
10
+ # rubocop:disable Metrics/CyclomaticComplexity
11
+ # rubocop:disable Metrics/PerceivedComplexity
9
12
  def apply
10
- Devise::PasswordsController.send(:include, Patches::ControllerCaptcha) if Devise.captcha_for_recover || Devise.security_question_for_recover
11
- Devise::UnlocksController.send(:include, Patches::ControllerCaptcha) if Devise.captcha_for_unlock || Devise.security_question_for_unlock
12
- Devise::ConfirmationsController.send(:include, Patches::ControllerCaptcha) if Devise.captcha_for_confirmation
13
+ Devise::PasswordsController.include(Patches::ControllerCaptcha) if Devise.captcha_for_recover || Devise.security_question_for_recover
14
+ Devise::UnlocksController.include(Patches::ControllerCaptcha) if Devise.captcha_for_unlock || Devise.security_question_for_unlock
15
+ Devise::ConfirmationsController.include(Patches::ControllerCaptcha) if Devise.captcha_for_confirmation
13
16
 
14
- Devise::PasswordsController.send(:include, Patches::ControllerSecurityQuestion) if Devise.security_question_for_recover
15
- Devise::UnlocksController.send(:include, Patches::ControllerSecurityQuestion) if Devise.security_question_for_unlock
16
- Devise::ConfirmationsController.send(:include, Patches::ControllerSecurityQuestion) if Devise.security_question_for_confirmation
17
+ Devise::PasswordsController.include(Patches::ControllerSecurityQuestion) if Devise.security_question_for_recover
18
+ Devise::UnlocksController.include(Patches::ControllerSecurityQuestion) if Devise.security_question_for_unlock
19
+ Devise::ConfirmationsController.include(Patches::ControllerSecurityQuestion) if Devise.security_question_for_confirmation
17
20
 
18
- Devise::RegistrationsController.send(:include, Patches::ControllerCaptcha) if Devise.captcha_for_sign_up
19
- Devise::SessionsController.send(:include, Patches::ControllerCaptcha) if Devise.captcha_for_sign_in
21
+ Devise::RegistrationsController.include(Patches::ControllerCaptcha) if Devise.captcha_for_sign_up
22
+ Devise::SessionsController.include(Patches::ControllerCaptcha) if Devise.captcha_for_sign_in
20
23
  end
24
+ # rubocop:enable Metrics/AbcSize
25
+ # rubocop:enable Metrics/CyclomaticComplexity
26
+ # rubocop:enable Metrics/PerceivedComplexity
21
27
  end
22
28
  end
23
29
  end
@@ -2,17 +2,16 @@
2
2
 
3
3
  module ActionDispatch::Routing
4
4
  class Mapper
5
-
6
5
  protected
7
6
 
8
7
  # route for handle expired passwords
9
8
  def devise_password_expired(mapping, controllers)
10
- resource :password_expired, only: [:show, :update], path: mapping.path_names[:password_expired], controller: controllers[:password_expired]
9
+ resource :password_expired, only: %i[show update], path: mapping.path_names[:password_expired], controller: controllers[:password_expired]
11
10
  end
12
11
 
13
12
  # route for handle paranoid verification
14
13
  def devise_verification_code(mapping, controllers)
15
- resource :paranoid_verification_code, only: [:show, :update], path: mapping.path_names[:verification_code], controller: controllers[:paranoid_verification_code]
14
+ resource :paranoid_verification_code, only: %i[show update], path: mapping.path_names[:verification_code], controller: controllers[:paranoid_verification_code]
16
15
  end
17
16
  end
18
17
  end