devise-security 0.12.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 (195) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE.txt +3 -1
  3. data/README.md +199 -65
  4. data/app/controllers/devise/paranoid_verification_code_controller.rb +28 -12
  5. data/app/controllers/devise/password_expired_controller.rb +34 -10
  6. data/app/views/devise/paranoid_verification_code/show.html.erb +4 -4
  7. data/app/views/devise/password_expired/show.html.erb +6 -6
  8. data/config/locales/bg.yml +42 -0
  9. data/config/locales/by.yml +50 -0
  10. data/config/locales/cs.yml +46 -0
  11. data/config/locales/de.yml +33 -7
  12. data/config/locales/en.yml +26 -1
  13. data/config/locales/es.yml +31 -6
  14. data/config/locales/fa.yml +42 -0
  15. data/config/locales/fr.yml +42 -0
  16. data/config/locales/hi.yml +43 -0
  17. data/config/locales/it.yml +36 -4
  18. data/config/locales/ja.yml +42 -0
  19. data/config/locales/nl.yml +42 -0
  20. data/config/locales/pt.yml +42 -0
  21. data/config/locales/ru.yml +50 -0
  22. data/config/locales/tr.yml +42 -0
  23. data/config/locales/uk.yml +50 -0
  24. data/config/locales/zh_CN.yml +42 -0
  25. data/config/locales/zh_TW.yml +42 -0
  26. data/lib/devise-security/controllers/helpers.rb +74 -51
  27. data/lib/devise-security/hooks/expirable.rb +6 -4
  28. data/lib/devise-security/hooks/paranoid_verification.rb +3 -3
  29. data/lib/devise-security/hooks/password_expirable.rb +5 -3
  30. data/lib/devise-security/hooks/session_limitable.rb +31 -14
  31. data/lib/devise-security/models/active_record/old_password.rb +5 -0
  32. data/lib/devise-security/models/compatibility/active_record_patch.rb +41 -0
  33. data/lib/devise-security/models/compatibility/mongoid_patch.rb +32 -0
  34. data/lib/devise-security/models/compatibility.rb +8 -15
  35. data/lib/devise-security/models/database_authenticatable_patch.rb +20 -10
  36. data/lib/devise-security/models/expirable.rb +14 -7
  37. data/lib/devise-security/models/mongoid/old_password.rb +21 -0
  38. data/lib/devise-security/models/paranoid_verification.rb +4 -2
  39. data/lib/devise-security/models/password_archivable.rb +19 -8
  40. data/lib/devise-security/models/password_expirable.rb +103 -48
  41. data/lib/devise-security/models/secure_validatable.rb +69 -12
  42. data/lib/devise-security/models/security_questionable.rb +2 -0
  43. data/lib/devise-security/models/session_limitable.rb +19 -2
  44. data/lib/devise-security/orm/mongoid.rb +7 -0
  45. data/lib/devise-security/patches/controller_captcha.rb +2 -0
  46. data/lib/devise-security/patches/controller_security_question.rb +3 -1
  47. data/lib/devise-security/patches.rb +16 -8
  48. data/lib/devise-security/rails.rb +2 -0
  49. data/lib/devise-security/routes.rb +4 -3
  50. data/lib/devise-security/validators/password_complexity_validator.rb +62 -0
  51. data/lib/devise-security/version.rb +3 -1
  52. data/lib/devise-security.rb +23 -11
  53. data/lib/generators/devise_security/install_generator.rb +6 -6
  54. data/lib/generators/templates/devise_security.rb +52 -0
  55. data/test/{test_captcha_controller.rb → controllers/test_captcha_controller.rb} +2 -0
  56. data/test/controllers/test_paranoid_verification_code_controller.rb +133 -0
  57. data/test/controllers/test_password_expired_controller.rb +164 -0
  58. data/test/controllers/test_security_question_controller.rb +66 -0
  59. data/test/dummy/Rakefile +3 -1
  60. data/test/dummy/app/assets/config/manifest.js +3 -0
  61. data/test/dummy/app/controllers/application_controller.rb +2 -0
  62. data/test/dummy/app/controllers/captcha/sessions_controller.rb +2 -0
  63. data/test/dummy/app/controllers/overrides/paranoid_verification_code_controller.rb +7 -0
  64. data/test/dummy/app/controllers/overrides/password_expired_controller.rb +17 -0
  65. data/test/dummy/app/controllers/security_question/unlocks_controller.rb +2 -0
  66. data/test/dummy/app/controllers/widgets_controller.rb +9 -0
  67. data/test/dummy/app/models/application_record.rb +10 -2
  68. data/test/dummy/app/models/application_user_record.rb +12 -0
  69. data/test/dummy/app/models/captcha_user.rb +7 -2
  70. data/test/dummy/app/models/mongoid/confirmable_fields.rb +15 -0
  71. data/test/dummy/app/models/mongoid/database_authenticable_fields.rb +18 -0
  72. data/test/dummy/app/models/mongoid/expirable_fields.rb +13 -0
  73. data/test/dummy/app/models/mongoid/lockable_fields.rb +15 -0
  74. data/test/dummy/app/models/mongoid/mappings.rb +15 -0
  75. data/test/dummy/app/models/mongoid/omniauthable_fields.rb +13 -0
  76. data/test/dummy/app/models/mongoid/paranoid_verification_fields.rb +12 -0
  77. data/test/dummy/app/models/mongoid/password_archivable_fields.rb +11 -0
  78. data/test/dummy/app/models/mongoid/password_expirable_fields.rb +12 -0
  79. data/test/dummy/app/models/mongoid/recoverable_fields.rb +13 -0
  80. data/test/dummy/app/models/mongoid/registerable_fields.rb +21 -0
  81. data/test/dummy/app/models/mongoid/rememberable_fields.rb +12 -0
  82. data/test/dummy/app/models/mongoid/secure_validatable_fields.rb +13 -0
  83. data/test/dummy/app/models/mongoid/security_questionable_fields.rb +15 -0
  84. data/test/dummy/app/models/mongoid/session_limitable_fields.rb +12 -0
  85. data/test/dummy/app/models/mongoid/timeoutable_fields.rb +11 -0
  86. data/test/dummy/app/models/mongoid/trackable_fields.rb +16 -0
  87. data/test/dummy/app/models/mongoid/validatable_fields.rb +9 -0
  88. data/test/dummy/app/models/paranoid_verification_user.rb +26 -0
  89. data/test/dummy/app/models/password_expired_user.rb +26 -0
  90. data/test/dummy/app/models/security_question_user.rb +9 -4
  91. data/test/dummy/app/models/user.rb +16 -1
  92. data/test/dummy/app/models/widget.rb +4 -0
  93. data/test/dummy/app/mongoid/admin.rb +31 -0
  94. data/test/dummy/app/mongoid/one_user.rb +58 -0
  95. data/test/dummy/app/mongoid/shim.rb +25 -0
  96. data/test/dummy/app/mongoid/user_on_engine.rb +41 -0
  97. data/test/dummy/app/mongoid/user_on_main_app.rb +41 -0
  98. data/test/dummy/app/mongoid/user_with_validations.rb +37 -0
  99. data/test/dummy/app/mongoid/user_without_email.rb +38 -0
  100. data/test/dummy/config/application.rb +13 -11
  101. data/test/dummy/config/boot.rb +3 -1
  102. data/test/dummy/config/environment.rb +3 -1
  103. data/test/dummy/config/environments/test.rb +6 -13
  104. data/test/dummy/config/initializers/devise.rb +6 -3
  105. data/test/dummy/config/initializers/migration_class.rb +3 -6
  106. data/test/dummy/config/locales/en.yml +10 -0
  107. data/test/dummy/config/mongoid.yml +6 -0
  108. data/test/dummy/config/routes.rb +8 -3
  109. data/test/dummy/config.ru +3 -1
  110. data/test/dummy/db/migrate/20120508165529_create_tables.rb +17 -6
  111. data/test/dummy/db/migrate/20150402165590_add_verification_columns.rb +2 -0
  112. data/test/dummy/db/migrate/20150407162345_add_verification_attempt_column.rb +2 -0
  113. data/test/dummy/db/migrate/20160320162345_add_security_questions_fields.rb +2 -0
  114. data/test/dummy/db/migrate/20180318103603_add_expireable_columns.rb +2 -0
  115. data/test/dummy/db/migrate/20180318105329_add_confirmable_columns.rb +2 -0
  116. data/test/dummy/db/migrate/20180318105732_add_rememberable_columns.rb +2 -0
  117. data/test/dummy/db/migrate/20180318111336_add_recoverable_columns.rb +2 -0
  118. data/test/dummy/db/migrate/20180319114023_add_widget.rb +2 -0
  119. data/test/dummy/lib/shared_expirable_columns.rb +15 -0
  120. data/test/dummy/lib/shared_security_questions_fields.rb +17 -0
  121. data/test/dummy/lib/shared_user.rb +43 -0
  122. data/test/dummy/lib/shared_user_with_password_verification.rb +13 -0
  123. data/test/dummy/lib/shared_user_without_omniauth.rb +24 -0
  124. data/test/dummy/lib/shared_verification_fields.rb +16 -0
  125. data/test/dummy/log/test.log +45240 -0
  126. data/test/i18n_test.rb +22 -0
  127. data/test/integration/test_paranoid_verification_code_workflow.rb +53 -0
  128. data/test/integration/test_password_expirable_workflow.rb +53 -0
  129. data/test/integration/test_session_limitable_workflow.rb +69 -0
  130. data/test/orm/active_record.rb +15 -0
  131. data/test/orm/mongoid.rb +13 -0
  132. data/test/support/integration_helpers.rb +35 -0
  133. data/test/support/mongoid.yml +6 -0
  134. data/test/test_compatibility.rb +15 -0
  135. data/test/test_complexity_validator.rb +282 -0
  136. data/test/test_database_authenticatable_patch.rb +146 -0
  137. data/test/test_helper.rb +41 -9
  138. data/test/test_install_generator.rb +20 -3
  139. data/test/test_paranoid_verification.rb +10 -9
  140. data/test/test_password_archivable.rb +37 -13
  141. data/test/test_password_expirable.rb +72 -9
  142. data/test/test_secure_validatable.rb +289 -55
  143. data/test/test_secure_validatable_overrides.rb +185 -0
  144. data/test/test_session_limitable.rb +57 -0
  145. data/test/tmp/config/initializers/devise_security.rb +52 -0
  146. data/test/tmp/config/locales/devise.security_extension.by.yml +50 -0
  147. data/test/tmp/config/locales/devise.security_extension.cs.yml +46 -0
  148. data/test/tmp/config/locales/devise.security_extension.de.yml +42 -0
  149. data/test/tmp/config/locales/devise.security_extension.en.yml +42 -0
  150. data/test/tmp/config/locales/devise.security_extension.es.yml +42 -0
  151. data/test/tmp/config/locales/devise.security_extension.fa.yml +42 -0
  152. data/test/tmp/config/locales/devise.security_extension.fr.yml +42 -0
  153. data/test/tmp/config/locales/devise.security_extension.hi.yml +43 -0
  154. data/test/tmp/config/locales/devise.security_extension.it.yml +42 -0
  155. data/test/tmp/config/locales/devise.security_extension.ja.yml +42 -0
  156. data/test/tmp/config/locales/devise.security_extension.nl.yml +42 -0
  157. data/test/tmp/config/locales/devise.security_extension.pt.yml +42 -0
  158. data/test/tmp/config/locales/devise.security_extension.ru.yml +50 -0
  159. data/test/tmp/config/locales/devise.security_extension.tr.yml +42 -0
  160. data/test/tmp/config/locales/devise.security_extension.uk.yml +50 -0
  161. data/test/tmp/config/locales/devise.security_extension.zh_CN.yml +42 -0
  162. data/test/tmp/config/locales/devise.security_extension.zh_TW.yml +42 -0
  163. metadata +290 -124
  164. data/.circleci/config.yml +0 -41
  165. data/.document +0 -5
  166. data/.gitignore +0 -40
  167. data/.rubocop.yml +0 -63
  168. data/.ruby-version +0 -1
  169. data/.travis.yml +0 -25
  170. data/Appraisals +0 -19
  171. data/Gemfile +0 -3
  172. data/Rakefile +0 -28
  173. data/devise-security.gemspec +0 -44
  174. data/gemfiles/rails_4.1_stable.gemfile +0 -8
  175. data/gemfiles/rails_4.2_stable.gemfile +0 -8
  176. data/gemfiles/rails_5.0_stable.gemfile +0 -8
  177. data/gemfiles/rails_5.1_stable.gemfile +0 -8
  178. data/gemfiles/rails_5.2_rc1.gemfile +0 -8
  179. data/lib/devise-security/models/old_password.rb +0 -4
  180. data/lib/devise-security/orm/active_record.rb +0 -18
  181. data/lib/devise-security/patches/confirmations_controller_captcha.rb +0 -21
  182. data/lib/devise-security/patches/confirmations_controller_security_question.rb +0 -24
  183. data/lib/devise-security/patches/passwords_controller_captcha.rb +0 -20
  184. data/lib/devise-security/patches/passwords_controller_security_question.rb +0 -23
  185. data/lib/devise-security/patches/registrations_controller_captcha.rb +0 -33
  186. data/lib/devise-security/patches/sessions_controller_captcha.rb +0 -24
  187. data/lib/devise-security/patches/unlocks_controller_captcha.rb +0 -20
  188. data/lib/devise-security/patches/unlocks_controller_security_question.rb +0 -23
  189. data/lib/devise-security/schema.rb +0 -64
  190. data/lib/generators/templates/devise-security.rb +0 -38
  191. data/test/dummy/app/controllers/foos_controller.rb +0 -0
  192. data/test/dummy/app/models/.gitkeep +0 -0
  193. data/test/dummy/app/models/secure_user.rb +0 -3
  194. data/test/test_password_expired_controller.rb +0 -44
  195. data/test/test_security_question_controller.rb +0 -84
@@ -0,0 +1,50 @@
1
+ uk:
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
+ few: 'повинен включати хоча б %{count} цифри'
11
+ many: 'повинен включати хоча б %{count} цифр'
12
+ other: 'повинен включати хоча б %{count} цифри'
13
+ lower:
14
+ one: 'повинен включати хоча б одну малу букву'
15
+ few: 'повинен включати хоча б %{count} малі букви'
16
+ many: 'повинен включати хоча б %{count} малих букв'
17
+ other: 'повинен включати хоча б %{count} малі букви'
18
+ symbol:
19
+ one: 'повинен включати хоча б один розділовий знак або символ'
20
+ few: 'повинен включати хоча б %{count} розділові знаки або символи'
21
+ many: 'повинен включати хоча б %{count} розділових знаків або символів'
22
+ other: 'повинен включати хоча б %{count} розділові знаки або символи'
23
+ upper:
24
+ one: 'повинен включати хоча б одну прописну букву'
25
+ few: 'повинен включати хоча б %{count} прописні букви'
26
+ many: 'повинен включати хоча б %{count} прописних букв'
27
+ other: 'повинен включати хоча б %{count} прописні букви'
28
+ devise:
29
+ invalid_captcha: 'Введення капчі недійсне.'
30
+ invalid_security_question: 'Неправильна відповідь на секретне питання.'
31
+ paranoid_verify:
32
+ code_required: 'Введіть, будь ласка, код від нашої команди підтримки'
33
+ paranoid_verification_code:
34
+ updated: Код підтвердження прийнято
35
+ show:
36
+ submit_verification_code: Відправити код підтвердження
37
+ verification_code: Код підтвердження
38
+ submit: Відправити
39
+ password_expired:
40
+ updated: 'Ваш новий пароль збережено.'
41
+ change_required: 'Ваш пароль застарілий. Будь ласка, оновіть пароль.'
42
+ show:
43
+ renew_your_password: Оновити свій пароль
44
+ current_password: Теперішній пароль
45
+ new_password: Новий пароль
46
+ new_password_confirmation: Підтвердити новий пароль
47
+ change_my_password: Змінити пароль
48
+ failure:
49
+ session_limited: 'Ваші облікові дані були використані в іншому браузері. Будь ласка, авторизуйтесь знову щоб продовжити в цьому браузері.'
50
+ expired: "Ваш аккаунт застарів через неактивність. Будь ласка, зв'яжіться з адміністратором."
@@ -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: '你的帳號因過久沒使用而已經過期,請洽網站管理員。'
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module DeviseSecurity
2
4
  module Controllers
3
5
  module Helpers
@@ -27,8 +29,8 @@ module DeviseSecurity
27
29
  end
28
30
 
29
31
  def valid_captcha_if_defined?(captcha)
30
- defined?(verify_recaptcha) && verify_recaptcha ||
31
- defined?(valid_captcha?) && valid_captcha?(captcha)
32
+ (defined?(verify_recaptcha) && verify_recaptcha) ||
33
+ (defined?(valid_captcha?) && valid_captcha?(captcha))
32
34
  end
33
35
 
34
36
  def valid_security_question_answer?(resource, answer)
@@ -38,71 +40,92 @@ module DeviseSecurity
38
40
 
39
41
  # controller instance methods
40
42
 
41
- private
42
-
43
- # lookup if an password change needed
44
- def handle_password_change
45
- return if warden.nil?
46
-
47
- if !devise_controller? && !ignore_password_expire? && !request.format.nil? && request.format.html?
48
- Devise.mappings.keys.flatten.any? do |scope|
49
- if signed_in?(scope) && warden.session(scope)['password_expired']
50
- # re-check to avoid infinite loop if date changed after login attempt
51
- if send(:"current_#{scope}").try(:need_change_password?)
52
- store_location_for(scope, request.original_fullpath) if request.get?
53
- redirect_for_password_change scope
54
- return
55
- else
56
- warden.session(scope)[:password_expired] = false
57
- end
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
58
68
  end
59
69
  end
60
70
  end
61
71
  end
72
+ end
62
73
 
63
- # lookup if extra (paranoid) code verification is needed
64
- def handle_paranoid_verification
65
- return if warden.nil?
66
-
67
- if !devise_controller? && !request.format.nil? && request.format.html?
68
- Devise.mappings.keys.flatten.any? do |scope|
69
- if signed_in?(scope) && warden.session(scope)['paranoid_verify']
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?)
70
85
  store_location_for(scope, request.original_fullpath) if request.get?
71
- redirect_for_paranoid_verification scope
72
- return
86
+ redirect_for_paranoid_verification(scope)
87
+ else
88
+ warden.session(scope)['paranoid_verify'] = false
73
89
  end
74
90
  end
75
91
  end
76
92
  end
93
+ end
77
94
 
78
- # redirect for password update with alert message
79
- def redirect_for_password_change(scope)
80
- redirect_to change_password_required_path_for(scope), alert: I18n.t('change_required', {scope: 'devise.password_expired'})
81
- end
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
82
99
 
83
- def redirect_for_paranoid_verification(scope)
84
- redirect_to paranoid_verification_code_path_for(scope), alert: I18n.t('code_required', {scope: 'devise.paranoid_verify'})
85
- end
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
86
103
 
87
- # path for change password
88
- def change_password_required_path_for(resource_or_scope = nil)
89
- scope = Devise::Mapping.find_scope!(resource_or_scope)
90
- change_path = "#{scope}_password_expired_path"
91
- send(change_path)
92
- end
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
93
111
 
94
- def paranoid_verification_code_path_for(resource_or_scope = nil)
95
- scope = Devise::Mapping.find_scope!(resource_or_scope)
96
- change_path = "#{scope}_paranoid_verification_code_path"
97
- send(change_path)
98
- end
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
99
118
 
100
- protected
119
+ protected
101
120
 
102
- # allow to overwrite for some special handlings
103
- def ignore_password_expire?
104
- false
105
- end
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
106
129
  end
107
130
  end
108
131
  end
@@ -1,10 +1,12 @@
1
- # Updates the last_activity_at fields from the record. Only when the user is active
1
+ # frozen_string_literal: true
2
+
3
+ # Updates the last_activity_at fields from the record. Only when the user is active
2
4
  # for authentication and authenticated.
3
- # 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
4
6
  # expired_at to the past (see Devise::Models::Expirable for this)
5
7
  Warden::Manager.after_set_user do |record, warden, options|
6
- 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? &&
7
9
  warden.authenticated?(options[:scope]) && record.respond_to?(:update_last_activity!)
8
10
  record.update_last_activity!
9
11
  end
10
- end
12
+ end
@@ -1,5 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  Warden::Manager.after_set_user do |record, warden, options|
2
- if record.respond_to?(:need_paranoid_verification?)
3
- warden.session(options[:scope])['paranoid_verify'] = record.need_paranoid_verification?
4
- end
4
+ warden.session(options[:scope])['paranoid_verify'] = record.need_paranoid_verification? if record.respond_to?(:need_paranoid_verification?)
5
5
  end
@@ -1,5 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ # @note This happens after
4
+ # {DeviseSecurity::Controller::Helpers#handle_password_change}
1
5
  Warden::Manager.after_authentication do |record, warden, options|
2
- if record.respond_to?(:need_change_password?)
3
- warden.session(options[:scope])['password_expired'] = record.need_change_password?
4
- end
6
+ warden.session(options[:scope])['password_expired'] = record.need_change_password? if record.respond_to?(:need_change_password?)
5
7
  end
@@ -1,24 +1,41 @@
1
- # After each sign in, update unique_session_id.
2
- # This is only triggered when the user is explicitly set (with set_user)
3
- # and on authentication. Retrieving the user from session (:fetch) does
4
- # not trigger it.
1
+ # frozen_string_literal: true
2
+
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.
5
6
  Warden::Manager.after_set_user except: :fetch do |record, warden, options|
6
- if record.respond_to?(:update_unique_session_id!) && warden.authenticated?(options[:scope])
7
- unique_session_id = Devise.friendly_token
8
- warden.session(options[:scope])['unique_session_id'] = unique_session_id
9
- record.update_unique_session_id!(unique_session_id)
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
10
18
  end
11
19
  end
12
20
 
13
- # Each time a record is fetched from session we check if a new session from another
14
- # browser was opened for the record or not, based on a unique session identifier.
15
- # If so, the old account is logged out and redirected to the sign in page on the next request.
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.
16
25
  Warden::Manager.after_set_user only: :fetch do |record, warden, options|
17
26
  scope = options[:scope]
18
- env = warden.request.env
19
27
 
20
- if record.respond_to?(:unique_session_id) && warden.authenticated?(scope) && options[:store] != false
21
- if record.unique_session_id != warden.session(scope)['unique_session_id'] && !env['devise.skip_session_limitable']
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
22
39
  warden.raw_session.clear
23
40
  warden.logout(scope)
24
41
  throw :warden, scope: scope, message: :session_limited
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ class OldPassword < ApplicationRecord
4
+ belongs_to :password_archivable, polymorphic: true
5
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Devise
4
+ module Models
5
+ module Compatibility
6
+ class NotPersistedError < ActiveRecord::ActiveRecordError; end
7
+
8
+ module ActiveRecordPatch
9
+ extend ActiveSupport::Concern
10
+
11
+ unless defined?(ActiveRecord) && ActiveRecord.gem_version >= Gem::Version.new("5.1.x")
12
+ # When the record was saved, was the +encrypted_password+ changed?
13
+ # @return [Boolean]
14
+ def saved_change_to_encrypted_password?
15
+ encrypted_password_changed?
16
+ end
17
+
18
+ # The encrypted password that existed before the record was saved
19
+ # @return [String]
20
+ # @return [nil] if an +encrypted_password+ had not been set
21
+ def encrypted_password_before_last_save
22
+ previous_changes['encrypted_password'].try(:first)
23
+ end
24
+
25
+ # When the record is saved, will the +encrypted_password+ be changed?
26
+ # @return [Boolean]
27
+ def will_save_change_to_encrypted_password?
28
+ changed_attributes['encrypted_password'].present?
29
+ end
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
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Devise
4
+ module Models
5
+ module Compatibility
6
+ class NotPersistedError < Mongoid::Errors::MongoidError; end
7
+
8
+ module MongoidPatch
9
+ extend ActiveSupport::Concern
10
+
11
+ # Will saving this record change the +email+ attribute?
12
+ # @return [Boolean]
13
+ def will_save_change_to_email?
14
+ changed.include? 'email'
15
+ end
16
+
17
+ # Will saving this record change the +encrypted_password+ attribute?
18
+ # @return [Boolean]
19
+ def will_save_change_to_encrypted_password?
20
+ changed.include? 'encrypted_password'
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
29
+ end
30
+ end
31
+ end
32
+ end
@@ -1,22 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "compatibility/#{DEVISE_ORM}_patch"
4
+
1
5
  module Devise
2
6
  module Models
7
+ # These compatibility modules define methods used by devise-security
8
+ # that may need to be defined or re-defined for compatibility between ORMs
9
+ # and/or older versions of ORMs.
3
10
  module Compatibility
4
11
  extend ActiveSupport::Concern
5
-
6
- # for backwards compatibility with Rails < 5.1.x
7
- unless Devise.activerecord51?
8
- def saved_change_to_encrypted_password?
9
- encrypted_password_changed?
10
- end
11
-
12
- def encrypted_password_before_last_save
13
- previous_changes['encrypted_password'].try(:first)
14
- end
15
-
16
- def will_save_change_to_encrypted_password?
17
- changed_attributes['encrypted_password'].present?
18
- end
19
- end
12
+ include "Devise::Models::Compatibility::#{DEVISE_ORM.to_s.classify}Patch".constantize
20
13
  end
21
14
  end
22
15
  end
@@ -1,22 +1,32 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Devise
2
4
  module Models
3
5
  module DatabaseAuthenticatablePatch
4
6
  def update_with_password(params, *options)
5
7
  current_password = params.delete(:current_password)
8
+ valid_password = valid_password?(current_password)
6
9
 
7
10
  new_password = params[:password]
8
11
  new_password_confirmation = params[:password_confirmation]
9
12
 
10
- result = if valid_password?(current_password) && new_password.present? && new_password_confirmation.present?
11
- update_attributes(params, *options)
12
- else
13
- self.assign_attributes(params, *options)
14
- self.valid?
15
- self.errors.add(:current_password, current_password.blank? ? :blank : :invalid)
16
- self.errors.add(:password, new_password.blank? ? :blank : :invalid)
17
- self.errors.add(:password_confirmation, new_password_confirmation.blank? ? :blank : :invalid)
18
- false
19
- 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
20
30
 
21
31
  clean_up_passwords
22
32
  result
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'devise-security/hooks/expirable'
2
4
 
3
5
  module Devise
@@ -20,7 +22,11 @@ module Devise
20
22
 
21
23
  # Updates +last_activity_at+, called from a Warden::Manager.after_set_user hook.
22
24
  def update_last_activity!
23
- self.update_column(:last_activity_at, Time.now.utc)
25
+ if respond_to?(:update_column)
26
+ self.update_column(:last_activity_at, Time.now.utc)
27
+ elsif defined? Mongoid
28
+ self.update_attribute(:last_activity_at, Time.now.utc)
29
+ end
24
30
  end
25
31
 
26
32
  # Tells if the account has expired
@@ -28,9 +34,11 @@ module Devise
28
34
  # @return [bool]
29
35
  def expired?
30
36
  # expired_at set (manually, via cron, etc.)
31
- return self.expired_at < Time.now.utc unless self.expired_at.nil?
37
+ return expired_at < Time.now.utc unless expired_at.nil?
38
+
32
39
  # if it is not set, check the last activity against configured expire_after time range
33
- 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
+
34
42
  # if last_activity_at is nil as well, the user has to be 'fresh' and is therefore not expired
35
43
  false
36
44
  end
@@ -52,13 +60,13 @@ module Devise
52
60
  #
53
61
  # @return [bool]
54
62
  def active_for_authentication?
55
- super && !self.expired?
63
+ super && !expired?
56
64
  end
57
65
 
58
66
  # The message sym, if {#active_for_authentication?} returns +false+. E.g. needed
59
67
  # for i18n.
60
68
  def inactive_message
61
- !self.expired? ? super : :expired
69
+ !expired? ? super : :expired
62
70
  end
63
71
 
64
72
  module ClassMethods
@@ -74,7 +82,6 @@ module Devise
74
82
  all.each do |u|
75
83
  u.expire! if u.expired? && u.expired_at.nil?
76
84
  end
77
- return
78
85
  end
79
86
 
80
87
  # Scope method to collect all expired users since +time+ ago
@@ -101,7 +108,7 @@ module Devise
101
108
  # @example Overwritten version to blank out the object.
102
109
  # def self.delete_all_expired_for(time = 90.days)
103
110
  # expired_for(time).each do |u|
104
- # u.update_attributes first_name: nil, last_name: nil
111
+ # u.update first_name: nil, last_name: nil
105
112
  # end
106
113
  # end
107
114
  def delete_all_expired_for(time)
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ class OldPassword
4
+ include Mongoid::Document
5
+
6
+ ## Database authenticatable
7
+ field :encrypted_password, type: String
8
+ validates_presence_of :encrypted_password
9
+ field :password_salt, type: String
10
+
11
+ field :password_archivable_type, type: String
12
+ validates_presence_of :password_archivable_type
13
+
14
+ field :password_archivable_id, type: String
15
+ validates_presence_of :password_archivable_id
16
+ index({ password_archivable_type: 1, password_archivable_id: 1 }, name: 'index_password_archivable')
17
+
18
+ include Mongoid::Timestamps
19
+
20
+ belongs_to :password_archivable, polymorphic: true
21
+ end