devise-security 0.14.1 → 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 +136 -61
- data/app/controllers/devise/paranoid_verification_code_controller.rb +26 -12
- data/app/controllers/devise/password_expired_controller.rb +32 -10
- data/app/views/devise/paranoid_verification_code/show.html.erb +3 -3
- data/app/views/devise/password_expired/show.html.erb +5 -5
- data/config/locales/bg.yml +42 -0
- data/config/locales/by.yml +50 -0
- data/config/locales/cs.yml +46 -0
- data/config/locales/de.yml +16 -2
- data/config/locales/en.yml +15 -2
- data/config/locales/es.yml +22 -9
- data/config/locales/fa.yml +42 -0
- data/config/locales/fr.yml +15 -2
- data/config/locales/hi.yml +43 -0
- data/config/locales/it.yml +36 -4
- data/config/locales/ja.yml +14 -1
- data/config/locales/nl.yml +42 -0
- data/config/locales/pt.yml +42 -0
- data/config/locales/ru.yml +50 -0
- data/config/locales/tr.yml +26 -1
- data/config/locales/uk.yml +50 -0
- data/config/locales/zh_CN.yml +42 -0
- data/config/locales/zh_TW.yml +42 -0
- data/lib/devise-security/controllers/helpers.rb +72 -51
- 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 +3 -3
- data/lib/devise-security/hooks/session_limitable.rb +29 -14
- data/lib/devise-security/models/compatibility/{active_record.rb → active_record_patch.rb} +14 -2
- data/lib/devise-security/models/compatibility/{mongoid.rb → mongoid_patch.rb} +12 -1
- data/lib/devise-security/models/compatibility.rb +2 -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/password_expirable.rb +5 -1
- data/lib/devise-security/models/secure_validatable.rb +62 -11
- data/lib/devise-security/models/session_limitable.rb +17 -2
- 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 -24
- 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/lib/generators/templates/{devise-security.rb → devise_security.rb} +9 -1
- data/test/controllers/test_paranoid_verification_code_controller.rb +133 -0
- data/test/controllers/test_password_expired_controller.rb +164 -0
- data/test/{test_security_question_controller.rb → controllers/test_security_question_controller.rb} +19 -37
- data/test/dummy/app/assets/config/manifest.js +3 -0
- 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 +9 -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 +10 -2
- 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 +6 -3
- data/test/dummy/config.ru +1 -1
- data/test/dummy/db/migrate/20120508165529_create_tables.rb +15 -6
- 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 +45240 -0
- 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 +53 -0
- data/test/integration/test_session_limitable_workflow.rb +69 -0
- data/test/orm/active_record.rb +7 -4
- data/test/orm/mongoid.rb +2 -1
- data/test/support/integration_helpers.rb +35 -0
- data/test/support/mongoid.yml +1 -1
- data/test/test_compatibility.rb +15 -0
- data/test/test_complexity_validator.rb +251 -29
- data/test/test_database_authenticatable_patch.rb +146 -0
- data/test/test_helper.rb +23 -8
- data/test/test_install_generator.rb +12 -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 +57 -0
- data/test/tmp/config/initializers/devise_security.rb +52 -0
- 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 +42 -0
- data/test/tmp/config/locales/devise.security_extension.en.yml +42 -0
- data/test/tmp/config/locales/devise.security_extension.es.yml +42 -0
- data/test/tmp/config/locales/devise.security_extension.fa.yml +42 -0
- data/test/tmp/config/locales/devise.security_extension.fr.yml +42 -0
- data/test/tmp/config/locales/devise.security_extension.hi.yml +43 -0
- data/test/tmp/config/locales/devise.security_extension.it.yml +42 -0
- data/test/tmp/config/locales/devise.security_extension.ja.yml +42 -0
- data/test/tmp/config/locales/devise.security_extension.nl.yml +42 -0
- data/test/tmp/config/locales/devise.security_extension.pt.yml +42 -0
- data/test/tmp/config/locales/devise.security_extension.ru.yml +50 -0
- data/test/tmp/config/locales/devise.security_extension.tr.yml +42 -0
- data/test/tmp/config/locales/devise.security_extension.uk.yml +50 -0
- data/test/tmp/config/locales/devise.security_extension.zh_CN.yml +42 -0
- data/test/tmp/config/locales/devise.security_extension.zh_TW.yml +42 -0
- metadata +202 -138
- data/.codeclimate.yml +0 -63
- data/.document +0 -5
- data/.gitignore +0 -43
- data/.mdlrc +0 -1
- data/.rubocop.yml +0 -64
- data/.ruby-version +0 -1
- data/.travis.yml +0 -39
- data/Appraisals +0 -35
- data/Gemfile +0 -10
- data/Rakefile +0 -27
- data/devise-security.gemspec +0 -50
- data/gemfiles/rails_4.2_stable.gemfile +0 -16
- data/gemfiles/rails_5.0_stable.gemfile +0 -15
- data/gemfiles/rails_5.1_stable.gemfile +0 -15
- data/gemfiles/rails_5.2_stable.gemfile +0 -15
- data/gemfiles/rails_6.0_beta.gemfile +0 -15
- 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/.gitkeep +0 -0
- data/test/dummy/app/models/secure_user.rb +0 -9
- data/test/dummy/lib/shared_user_without_email.rb +0 -28
- data/test/test_password_expired_controller.rb +0 -46
- /data/test/{test_captcha_controller.rb → controllers/test_captcha_controller.rb} +0 -0
@@ -0,0 +1,42 @@
|
|
1
|
+
zh_CN:
|
2
|
+
errors:
|
3
|
+
messages:
|
4
|
+
taken_in_past: '曾被使用过。'
|
5
|
+
equal_to_current_password: '必须与当前密码不同。'
|
6
|
+
equal_to_email: '必须与电子邮件地址不同。'
|
7
|
+
password_complexity:
|
8
|
+
digit:
|
9
|
+
one: 必须包含至少1个数字
|
10
|
+
other: 必须包含至少%{count}个数字
|
11
|
+
lower:
|
12
|
+
one: 必须包含至少1个小写字母
|
13
|
+
other: 必须包含至少%{count}个小写字母
|
14
|
+
symbol:
|
15
|
+
one: 必须包含至少1个特殊符号(!"#$%&'()*+,-./:;<=>?@[\]^_‘{|}~)
|
16
|
+
other: 必须包含至少%{count}个特殊符号(!"#$%&'()*+,-./:;<=>?@[\]^_‘{|}~)
|
17
|
+
upper:
|
18
|
+
one: 必须包含至少1个大写字母
|
19
|
+
other: 必须包含至少%{count}个大写字母
|
20
|
+
devise:
|
21
|
+
invalid_captcha: '验证码无效。'
|
22
|
+
invalid_security_question: '安全问题答案无效。'
|
23
|
+
paranoid_verify:
|
24
|
+
code_required: '请输入我们支持团队提供的代码'
|
25
|
+
paranoid_verification_code:
|
26
|
+
updated: 接受验证码
|
27
|
+
show:
|
28
|
+
submit_verification_code: 提交验证码
|
29
|
+
verification_code: 验证码
|
30
|
+
submit: 提交
|
31
|
+
password_expired:
|
32
|
+
updated: '你的新密码已保存。'
|
33
|
+
change_required: '你的密码已过期,请更新密码。'
|
34
|
+
show:
|
35
|
+
renew_your_password: 更新你的密码
|
36
|
+
current_password: 当前密码
|
37
|
+
new_password: 新密码
|
38
|
+
new_password_confirmation: 确认新密码
|
39
|
+
change_my_password: 更改密码
|
40
|
+
failure:
|
41
|
+
session_limited: '你的登录凭据已在另一个浏览器上被使用。请重新登录以在此浏览器继续。'
|
42
|
+
expired: '由于不活跃,你的账户已过期。请联系站点管理员。'
|
@@ -0,0 +1,42 @@
|
|
1
|
+
zh_TW:
|
2
|
+
errors:
|
3
|
+
messages:
|
4
|
+
taken_in_past: '曾被使用過。'
|
5
|
+
equal_to_current_password: '必須與目前密碼不同。'
|
6
|
+
equal_to_email: '必須與電子郵件地址不同。'
|
7
|
+
password_complexity:
|
8
|
+
digit:
|
9
|
+
one: 必須包含至少一個數字
|
10
|
+
other: 必須包含至少 %{count} 個數字
|
11
|
+
lower:
|
12
|
+
one: 必須包含至少一個小寫字母
|
13
|
+
other: 必須包含至少 %{count} 個小寫字母
|
14
|
+
symbol:
|
15
|
+
one: 必須包含至少一個特殊符號
|
16
|
+
other: 必須包含至少 %{count} 個特殊符號
|
17
|
+
upper:
|
18
|
+
one: 必須包含至少一個大寫字母
|
19
|
+
other: 必須包含至少 %{count} 個大寫字母
|
20
|
+
devise:
|
21
|
+
invalid_captcha: '輸入的驗證碼無效。'
|
22
|
+
invalid_security_question: '安全問題答案無效。'
|
23
|
+
paranoid_verify:
|
24
|
+
code_required: '請輸入由我們客服團隊提供的代碼'
|
25
|
+
paranoid_verification_code:
|
26
|
+
updated: 接受驗證碼
|
27
|
+
show:
|
28
|
+
submit_verification_code: 送出驗證碼
|
29
|
+
verification_code: 驗證碼
|
30
|
+
submit: 送出
|
31
|
+
password_expired:
|
32
|
+
updated: '你的新密碼已儲存'
|
33
|
+
change_required: '你的密碼已經過期,請更新密碼。'
|
34
|
+
show:
|
35
|
+
renew_your_password: 更新你的密碼
|
36
|
+
current_password: 目前密碼
|
37
|
+
new_password: 新密碼
|
38
|
+
new_password_confirmation: 確認新密碼
|
39
|
+
change_my_password: 更改我的密碼
|
40
|
+
failure:
|
41
|
+
session_limited: '你的登入憑證已在另一個瀏覽器上被使用,請重新登入以在此瀏覽器繼續使用。'
|
42
|
+
expired: '你的帳號因過久沒使用而已經過期,請洽網站管理員。'
|
@@ -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)
|
@@ -40,71 +40,92 @@ module DeviseSecurity
|
|
40
40
|
|
41
41
|
# controller instance methods
|
42
42
|
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
43
|
+
private
|
44
|
+
|
45
|
+
# Called as a `before_action` on all actions on any controller that uses
|
46
|
+
# this helper. If the user's session is marked as having an expired
|
47
|
+
# password we double check in case it has been changed by another process,
|
48
|
+
# then redirect to the password change url.
|
49
|
+
#
|
50
|
+
# @note `Warden::Manager.after_authentication` is run AFTER this method
|
51
|
+
#
|
52
|
+
# @note Once the warden session has `'password_expired'` set to `false`,
|
53
|
+
# it will **never** be checked again until the user re-logs in.
|
54
|
+
def handle_password_change
|
55
|
+
return if warden.nil?
|
56
|
+
|
57
|
+
if !devise_controller? &&
|
58
|
+
!ignore_password_expire? &&
|
59
|
+
!request.format.nil? &&
|
60
|
+
request.format.html?
|
61
|
+
Devise.mappings.keys.flatten.any? do |scope|
|
62
|
+
if signed_in?(scope) && warden.session(scope)['password_expired'] == true
|
63
|
+
if send(:"current_#{scope}").try(:need_change_password?)
|
64
|
+
store_location_for(scope, request.original_fullpath) if request.get?
|
65
|
+
redirect_for_password_change(scope)
|
66
|
+
else
|
67
|
+
warden.session(scope)['password_expired'] = false
|
60
68
|
end
|
61
69
|
end
|
62
70
|
end
|
63
71
|
end
|
72
|
+
end
|
64
73
|
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
74
|
+
# lookup if extra (paranoid) code verification is needed
|
75
|
+
def handle_paranoid_verification
|
76
|
+
return if warden.nil?
|
77
|
+
|
78
|
+
if !devise_controller? &&
|
79
|
+
!ignore_paranoid_verification_code? &&
|
80
|
+
!request.format.nil? &&
|
81
|
+
request.format.html?
|
82
|
+
Devise.mappings.keys.flatten.any? do |scope|
|
83
|
+
if signed_in?(scope) && warden.session(scope)['paranoid_verify'] == true
|
84
|
+
if send(:"current_#{scope}").try(:need_paranoid_verification?)
|
72
85
|
store_location_for(scope, request.original_fullpath) if request.get?
|
73
|
-
redirect_for_paranoid_verification
|
74
|
-
|
86
|
+
redirect_for_paranoid_verification(scope)
|
87
|
+
else
|
88
|
+
warden.session(scope)['paranoid_verify'] = false
|
75
89
|
end
|
76
90
|
end
|
77
91
|
end
|
78
92
|
end
|
93
|
+
end
|
79
94
|
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
95
|
+
# redirect for password update with alert message
|
96
|
+
def redirect_for_password_change(scope)
|
97
|
+
redirect_to change_password_required_path_for(scope), alert: I18n.t('change_required', scope: 'devise.password_expired')
|
98
|
+
end
|
84
99
|
|
85
|
-
|
86
|
-
|
87
|
-
|
100
|
+
def redirect_for_paranoid_verification(scope)
|
101
|
+
redirect_to paranoid_verification_code_path_for(scope), alert: I18n.t('code_required', scope: 'devise.paranoid_verify')
|
102
|
+
end
|
88
103
|
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
104
|
+
# path for change password
|
105
|
+
def change_password_required_path_for(resource_or_scope = nil)
|
106
|
+
scope = Devise::Mapping.find_scope!(resource_or_scope)
|
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")
|
110
|
+
end
|
95
111
|
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
112
|
+
def paranoid_verification_code_path_for(resource_or_scope = nil)
|
113
|
+
scope = Devise::Mapping.find_scope!(resource_or_scope)
|
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")
|
117
|
+
end
|
101
118
|
|
102
|
-
|
119
|
+
protected
|
103
120
|
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
121
|
+
# allow to overwrite for some special handlings
|
122
|
+
def ignore_password_expire?
|
123
|
+
false
|
124
|
+
end
|
125
|
+
|
126
|
+
def ignore_paranoid_verification_code?
|
127
|
+
false
|
128
|
+
end
|
108
129
|
end
|
109
130
|
end
|
110
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
|
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
# @note This happens after
|
4
|
+
# {DeviseSecurity::Controller::Helpers#handle_password_change}
|
3
5
|
Warden::Manager.after_authentication do |record, warden, options|
|
4
|
-
if record.respond_to?(:need_change_password?)
|
5
|
-
warden.session(options[:scope])['password_expired'] = record.need_change_password?
|
6
|
-
end
|
6
|
+
warden.session(options[:scope])['password_expired'] = record.need_change_password? if record.respond_to?(:need_change_password?)
|
7
7
|
end
|
@@ -1,26 +1,41 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
# After each sign in, update unique_session_id.
|
4
|
-
#
|
5
|
-
#
|
6
|
-
# not trigger it.
|
3
|
+
# After each sign in, update unique_session_id. This is only triggered when the
|
4
|
+
# user is explicitly set (with set_user) and on authentication. Retrieving the
|
5
|
+
# user from session (:fetch) does not trigger it.
|
7
6
|
Warden::Manager.after_set_user except: :fetch do |record, warden, options|
|
8
|
-
if record.
|
9
|
-
|
10
|
-
|
11
|
-
|
7
|
+
if record.devise_modules.include?(:session_limitable) &&
|
8
|
+
warden.authenticated?(options[:scope]) &&
|
9
|
+
!record.skip_session_limitable?
|
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
|
12
18
|
end
|
13
19
|
end
|
14
20
|
|
15
|
-
# Each time a record is fetched from session we check if a new session from
|
16
|
-
# browser was opened for the record or not, based on a unique session
|
17
|
-
# If so, the old account is logged out and redirected to the sign in
|
21
|
+
# Each time a record is fetched from session we check if a new session from
|
22
|
+
# another browser was opened for the record or not, based on a unique session
|
23
|
+
# identifier. If so, the old account is logged out and redirected to the sign in
|
24
|
+
# page on the next request.
|
18
25
|
Warden::Manager.after_set_user only: :fetch do |record, warden, options|
|
19
26
|
scope = options[:scope]
|
20
|
-
env = warden.request.env
|
21
27
|
|
22
|
-
if record.
|
23
|
-
|
28
|
+
if record.devise_modules.include?(:session_limitable) &&
|
29
|
+
warden.authenticated?(scope) &&
|
30
|
+
options[:store] != false
|
31
|
+
if record.unique_session_id != warden.session(scope)['unique_session_id'] &&
|
32
|
+
!record.skip_session_limitable? &&
|
33
|
+
!warden.session(scope)['devise.skip_session_limitable']
|
34
|
+
Rails.logger.warn do
|
35
|
+
'[devise-security][session_limitable] session id mismatch: '\
|
36
|
+
"expected=#{record.unique_session_id.inspect} "\
|
37
|
+
"actual=#{warden.session(scope)['unique_session_id'].inspect}"
|
38
|
+
end
|
24
39
|
warden.raw_session.clear
|
25
40
|
warden.logout(scope)
|
26
41
|
throw :warden, scope: scope, message: :session_limited
|
@@ -1,9 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Devise
|
2
4
|
module Models
|
3
5
|
module Compatibility
|
4
|
-
|
6
|
+
class NotPersistedError < ActiveRecord::ActiveRecordError; end
|
7
|
+
|
8
|
+
module ActiveRecordPatch
|
5
9
|
extend ActiveSupport::Concern
|
6
|
-
|
10
|
+
|
11
|
+
unless defined?(ActiveRecord) && ActiveRecord.gem_version >= Gem::Version.new("5.1.x")
|
7
12
|
# When the record was saved, was the +encrypted_password+ changed?
|
8
13
|
# @return [Boolean]
|
9
14
|
def saved_change_to_encrypted_password?
|
@@ -23,6 +28,13 @@ module Devise
|
|
23
28
|
changed_attributes['encrypted_password'].present?
|
24
29
|
end
|
25
30
|
end
|
31
|
+
|
32
|
+
# Updates the record with the value and does not trigger validations or callbacks
|
33
|
+
# @param name [Symbol] attribute to update
|
34
|
+
# @param value [String] value to set
|
35
|
+
def update_attribute_without_validatons_or_callbacks(name, value)
|
36
|
+
update_column(name, value)
|
37
|
+
end
|
26
38
|
end
|
27
39
|
end
|
28
40
|
end
|
@@ -1,7 +1,11 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Devise
|
2
4
|
module Models
|
3
5
|
module Compatibility
|
4
|
-
|
6
|
+
class NotPersistedError < Mongoid::Errors::MongoidError; end
|
7
|
+
|
8
|
+
module MongoidPatch
|
5
9
|
extend ActiveSupport::Concern
|
6
10
|
|
7
11
|
# Will saving this record change the +email+ attribute?
|
@@ -15,6 +19,13 @@ module Devise
|
|
15
19
|
def will_save_change_to_encrypted_password?
|
16
20
|
changed.include? 'encrypted_password'
|
17
21
|
end
|
22
|
+
|
23
|
+
# Updates the document with the value and does not trigger validations or callbacks
|
24
|
+
# @param name [Symbol] attribute to update
|
25
|
+
# @param value [String] value to set
|
26
|
+
def update_attribute_without_validatons_or_callbacks(name, value)
|
27
|
+
set(Hash[name, value])
|
28
|
+
end
|
18
29
|
end
|
19
30
|
end
|
20
31
|
end
|
@@ -1,6 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require_relative "compatibility/#{DEVISE_ORM}"
|
3
|
+
require_relative "compatibility/#{DEVISE_ORM}_patch"
|
4
4
|
|
5
5
|
module Devise
|
6
6
|
module Models
|
@@ -9,7 +9,7 @@ module Devise
|
|
9
9
|
# and/or older versions of ORMs.
|
10
10
|
module Compatibility
|
11
11
|
extend ActiveSupport::Concern
|
12
|
-
include "Devise::Models::Compatibility::#{DEVISE_ORM.to_s.classify}".constantize
|
12
|
+
include "Devise::Models::Compatibility::#{DEVISE_ORM.to_s.classify}Patch".constantize
|
13
13
|
end
|
14
14
|
end
|
15
15
|
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
|
@@ -92,7 +92,11 @@ module Devise::Models
|
|
92
92
|
# Update +password_changed_at+ for new records and changed passwords.
|
93
93
|
# @note called as a +before_save+ hook
|
94
94
|
def update_password_changed
|
95
|
-
|
95
|
+
if defined?(will_save_change_to_attribute?)
|
96
|
+
return unless (new_record? || will_save_change_to_encrypted_password?) && !will_save_change_to_password_changed_at?
|
97
|
+
else
|
98
|
+
return unless (new_record? || encrypted_password_changed?) && !password_changed_at_changed?
|
99
|
+
end
|
96
100
|
|
97
101
|
self.password_changed_at = Time.zone.now
|
98
102
|
end
|