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.
- checksums.yaml +4 -4
- data/LICENSE.txt +3 -1
- data/README.md +43 -24
- data/app/controllers/devise/paranoid_verification_code_controller.rb +26 -12
- data/app/controllers/devise/password_expired_controller.rb +23 -10
- data/config/locales/bg.yml +42 -0
- data/config/locales/by.yml +2 -0
- data/config/locales/cs.yml +6 -0
- data/config/locales/de.yml +4 -0
- data/config/locales/en.yml +3 -1
- data/config/locales/es.yml +13 -0
- data/config/locales/fa.yml +2 -0
- data/config/locales/fr.yml +15 -2
- data/config/locales/hi.yml +22 -20
- data/config/locales/it.yml +2 -0
- data/config/locales/ja.yml +13 -0
- data/config/locales/nl.yml +2 -0
- data/config/locales/pt.yml +2 -0
- data/config/locales/ru.yml +2 -0
- data/config/locales/tr.yml +26 -1
- data/config/locales/uk.yml +2 -0
- data/config/locales/zh_CN.yml +2 -0
- data/config/locales/zh_TW.yml +2 -0
- data/lib/devise-security/controllers/helpers.rb +25 -13
- data/lib/devise-security/hooks/expirable.rb +3 -3
- data/lib/devise-security/hooks/paranoid_verification.rb +1 -3
- data/lib/devise-security/hooks/password_expirable.rb +1 -3
- data/lib/devise-security/hooks/session_limitable.rb +10 -6
- data/lib/devise-security/models/compatibility/active_record_patch.rb +4 -3
- data/lib/devise-security/models/compatibility/mongoid_patch.rb +3 -2
- data/lib/devise-security/models/database_authenticatable_patch.rb +18 -10
- data/lib/devise-security/models/expirable.rb +6 -5
- data/lib/devise-security/models/paranoid_verification.rb +2 -2
- data/lib/devise-security/models/password_archivable.rb +3 -3
- data/lib/devise-security/models/secure_validatable.rb +62 -11
- data/lib/devise-security/orm/mongoid.rb +1 -1
- data/lib/devise-security/patches.rb +14 -8
- data/lib/devise-security/routes.rb +2 -3
- data/lib/devise-security/validators/password_complexity_validator.rb +53 -26
- data/lib/devise-security/version.rb +1 -1
- data/lib/devise-security.rb +15 -6
- data/lib/generators/devise_security/install_generator.rb +4 -6
- data/{test/tmp/config/initializers/devise-security.rb → lib/generators/templates/devise_security.rb} +9 -1
- data/test/controllers/test_paranoid_verification_code_controller.rb +133 -0
- data/test/controllers/test_password_expired_controller.rb +122 -99
- data/test/controllers/test_security_question_controller.rb +19 -37
- data/test/dummy/app/controllers/overrides/paranoid_verification_code_controller.rb +7 -0
- data/test/dummy/app/controllers/overrides/password_expired_controller.rb +17 -0
- data/test/dummy/app/controllers/widgets_controller.rb +3 -0
- data/test/dummy/app/models/application_user_record.rb +2 -1
- data/test/dummy/app/models/mongoid/confirmable_fields.rb +2 -0
- data/test/dummy/app/models/mongoid/database_authenticable_fields.rb +4 -3
- data/test/dummy/app/models/mongoid/expirable_fields.rb +2 -0
- data/test/dummy/app/models/mongoid/lockable_fields.rb +2 -0
- data/test/dummy/app/models/mongoid/mappings.rb +4 -2
- data/test/dummy/app/models/mongoid/omniauthable_fields.rb +2 -0
- data/test/dummy/app/models/mongoid/paranoid_verification_fields.rb +2 -0
- data/test/dummy/app/models/mongoid/password_archivable_fields.rb +2 -0
- data/test/dummy/app/models/mongoid/password_expirable_fields.rb +2 -0
- data/test/dummy/app/models/mongoid/recoverable_fields.rb +2 -0
- data/test/dummy/app/models/mongoid/registerable_fields.rb +4 -2
- data/test/dummy/app/models/mongoid/rememberable_fields.rb +2 -0
- data/test/dummy/app/models/mongoid/secure_validatable_fields.rb +2 -0
- data/test/dummy/app/models/mongoid/security_questionable_fields.rb +2 -0
- data/test/dummy/app/models/mongoid/session_limitable_fields.rb +2 -0
- data/test/dummy/app/models/mongoid/timeoutable_fields.rb +2 -0
- data/test/dummy/app/models/mongoid/trackable_fields.rb +2 -0
- data/test/dummy/app/models/mongoid/validatable_fields.rb +2 -0
- data/test/dummy/app/models/paranoid_verification_user.rb +26 -0
- data/test/dummy/app/models/password_expired_user.rb +26 -0
- data/test/dummy/app/models/user.rb +5 -5
- data/test/dummy/app/models/widget.rb +1 -3
- data/test/dummy/app/mongoid/one_user.rb +5 -5
- data/test/dummy/app/mongoid/user_on_engine.rb +2 -2
- data/test/dummy/app/mongoid/user_on_main_app.rb +2 -2
- data/test/dummy/app/mongoid/user_with_validations.rb +3 -3
- data/test/dummy/app/mongoid/user_without_email.rb +7 -4
- data/test/dummy/config/application.rb +3 -7
- data/test/dummy/config/boot.rb +1 -1
- data/test/dummy/config/environment.rb +1 -1
- data/test/dummy/config/environments/test.rb +4 -13
- data/test/dummy/config/initializers/devise.rb +1 -5
- data/test/dummy/config/initializers/migration_class.rb +1 -8
- data/test/dummy/config/locales/en.yml +10 -0
- data/test/dummy/config/mongoid.yml +1 -1
- data/test/dummy/config/routes.rb +3 -1
- data/test/dummy/config.ru +1 -1
- data/test/dummy/db/migrate/20120508165529_create_tables.rb +5 -5
- data/test/dummy/lib/shared_expirable_columns.rb +1 -0
- data/test/dummy/lib/shared_security_questions_fields.rb +1 -0
- data/test/dummy/lib/shared_user.rb +17 -6
- data/test/dummy/lib/shared_user_without_omniauth.rb +12 -3
- data/test/dummy/lib/shared_verification_fields.rb +1 -0
- data/test/dummy/log/test.log +44592 -1151
- data/test/i18n_test.rb +22 -0
- data/test/integration/test_paranoid_verification_code_workflow.rb +53 -0
- data/test/integration/test_password_expirable_workflow.rb +2 -6
- data/test/integration/test_session_limitable_workflow.rb +5 -3
- data/test/orm/active_record.rb +7 -7
- data/test/orm/mongoid.rb +2 -1
- data/test/support/integration_helpers.rb +10 -22
- data/test/support/mongoid.yml +1 -1
- data/test/test_compatibility.rb +2 -0
- data/test/test_complexity_validator.rb +247 -37
- data/test/test_database_authenticatable_patch.rb +146 -0
- data/test/test_helper.rb +11 -12
- data/test/test_install_generator.rb +2 -2
- data/test/test_paranoid_verification.rb +8 -9
- data/test/test_password_archivable.rb +34 -11
- data/test/test_password_expirable.rb +27 -27
- data/test/test_secure_validatable.rb +284 -50
- data/test/test_secure_validatable_overrides.rb +185 -0
- data/test/test_session_limitable.rb +9 -9
- data/{lib/generators/templates/devise-security.rb → test/tmp/config/initializers/devise_security.rb} +9 -1
- data/test/tmp/config/locales/devise.security_extension.by.yml +50 -0
- data/test/tmp/config/locales/devise.security_extension.cs.yml +46 -0
- data/test/tmp/config/locales/devise.security_extension.de.yml +4 -0
- data/test/tmp/config/locales/devise.security_extension.en.yml +3 -1
- data/test/tmp/config/locales/devise.security_extension.es.yml +22 -9
- data/test/tmp/config/locales/devise.security_extension.fa.yml +2 -0
- data/test/tmp/config/locales/devise.security_extension.fr.yml +15 -2
- data/test/tmp/config/locales/devise.security_extension.hi.yml +43 -0
- data/test/tmp/config/locales/devise.security_extension.it.yml +2 -0
- data/test/tmp/config/locales/devise.security_extension.ja.yml +13 -0
- data/test/tmp/config/locales/devise.security_extension.nl.yml +2 -0
- data/test/tmp/config/locales/devise.security_extension.pt.yml +2 -0
- data/test/tmp/config/locales/devise.security_extension.ru.yml +2 -0
- data/test/tmp/config/locales/devise.security_extension.tr.yml +26 -1
- data/test/tmp/config/locales/devise.security_extension.uk.yml +2 -0
- data/test/tmp/config/locales/devise.security_extension.zh_CN.yml +2 -0
- data/test/tmp/config/locales/devise.security_extension.zh_TW.yml +42 -0
- metadata +65 -45
- data/lib/devise-security/orm/active_record.rb +0 -20
- data/lib/devise-security/patches/confirmations_controller_captcha.rb +0 -23
- data/lib/devise-security/patches/confirmations_controller_security_question.rb +0 -26
- data/lib/devise-security/patches/passwords_controller_captcha.rb +0 -22
- data/lib/devise-security/patches/passwords_controller_security_question.rb +0 -25
- data/lib/devise-security/patches/registrations_controller_captcha.rb +0 -35
- data/lib/devise-security/patches/sessions_controller_captcha.rb +0 -26
- data/lib/devise-security/patches/unlocks_controller_captcha.rb +0 -22
- data/lib/devise-security/patches/unlocks_controller_security_question.rb +0 -25
- data/lib/devise-security/schema.rb +0 -66
- data/test/dummy/app/controllers/foos_controller.rb +0 -0
- data/test/dummy/app/models/secure_user.rb +0 -9
- data/test/dummy/lib/shared_user_without_email.rb +0 -28
data/config/locales/ru.yml
CHANGED
@@ -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: Код подверждения
|
data/config/locales/tr.yml
CHANGED
@@ -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
|
-
|
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.'
|
data/config/locales/uk.yml
CHANGED
@@ -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: Код підтвердження
|
data/config/locales/zh_CN.yml
CHANGED
@@ -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: 验证码
|
data/config/locales/zh_TW.yml
CHANGED
@@ -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? &&
|
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
|
-
|
82
|
-
|
83
|
-
|
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',
|
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',
|
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
|
-
|
102
|
-
send(
|
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
|
-
|
108
|
-
send(
|
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
|
-
|
11
|
-
|
12
|
-
|
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
|
-
!
|
29
|
-
!
|
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
|
-
|
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[
|
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
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
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
|
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
|
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 && !
|
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
|
-
!
|
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
|
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.
|
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.
|
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
|
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
|
-
|
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
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
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
|
-
|
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(
|
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
|
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.
|
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
|
-
|
154
|
+
ancestors.map(&:to_s).include? 'Devise::Models::Validatable'
|
104
155
|
end
|
105
156
|
end
|
106
157
|
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.
|
11
|
-
Devise::UnlocksController.
|
12
|
-
Devise::ConfirmationsController.
|
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.
|
15
|
-
Devise::UnlocksController.
|
16
|
-
Devise::ConfirmationsController.
|
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.
|
19
|
-
Devise::SessionsController.
|
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: [
|
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: [
|
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
|