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
@@ -1,18 +1,28 @@
1
- require 'active_record'
1
+ # frozen_string_literal: true
2
+
3
+ DEVISE_ORM = ENV.fetch('DEVISE_ORM', 'active_record').to_sym unless defined?(DEVISE_ORM)
4
+
5
+ require DEVISE_ORM.to_s if DEVISE_ORM.in? %i[active_record mongoid]
2
6
  require 'active_support/core_ext/integer'
3
7
  require 'active_support/ordered_hash'
4
8
  require 'active_support/concern'
5
9
  require 'devise'
6
10
 
7
11
  module Devise
8
-
9
- # Should the password expire (e.g 3.months)
12
+ # Number of seconds that passwords are valid (e.g 3.months)
13
+ # Disable password expiration with +false+
14
+ # Expire only on demand with +true+
10
15
  mattr_accessor :expire_password_after
11
16
  @@expire_password_after = 3.months
12
17
 
13
- # Validate password for strongness
14
- mattr_accessor :password_regex
15
- @@password_regex = /(?=.*\d)(?=.*[a-z])(?=.*[A-Z])/
18
+ # Validate password complexity
19
+ mattr_accessor :password_complexity
20
+ @@password_complexity = { digit: 1, lower: 1, symbol: 1, upper: 1 }
21
+
22
+ # Define the class used to validate password complexity. Set to a Class or a
23
+ # string which will be used to determine which class to use.
24
+ mattr_accessor :password_complexity_validator
25
+ @@password_complexity_validator = 'devise_security/password_complexity_validator'
16
26
 
17
27
  # Number of old passwords in archive
18
28
  mattr_accessor :password_archiving_count
@@ -23,7 +33,7 @@ module Devise
23
33
  @@deny_old_passwords = true
24
34
 
25
35
  # enable email validation for :secure_validatable. (true, false, validation_options)
26
- # dependency: need an email validator like rails_email_validator
36
+ # dependency: need an email validator, see https://github.com/devise-security/devise-security/blob/master/README.md#e-mail-validation
27
37
  mattr_accessor :email_validation
28
38
  @@email_validation = true
29
39
 
@@ -75,11 +85,14 @@ module Devise
75
85
  # paranoid_verification will regenerate verifacation code after faild attempt
76
86
  mattr_accessor :paranoid_code_regenerate_after_attempt
77
87
  @@paranoid_code_regenerate_after_attempt = 10
88
+
89
+ # Whether to allow passwords that are equal (case insensitive) to the email
90
+ mattr_accessor :allow_passwords_equal_to_email
91
+ @@allow_passwords_equal_to_email = false
78
92
  end
79
93
 
80
- # an security extension for devise
94
+ # a security extension for devise
81
95
  module DeviseSecurity
82
- autoload :Schema, 'devise-security/schema'
83
96
  autoload :Patches, 'devise-security/patches'
84
97
 
85
98
  module Controllers
@@ -100,7 +113,6 @@ Devise.add_module :paranoid_verification, controller: :paranoid_verification_cod
100
113
  # requires
101
114
  require 'devise-security/routes'
102
115
  require 'devise-security/rails'
103
- require 'devise-security/orm/active_record'
104
- require 'devise-security/models/old_password'
116
+ require "devise-security/orm/#{DEVISE_ORM}" if DEVISE_ORM == :mongoid
105
117
  require 'devise-security/models/database_authenticatable_patch'
106
118
  require 'devise-security/models/paranoid_verification'
@@ -1,23 +1,23 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module DeviseSecurity
2
4
  module Generators
3
5
  # Generator for Rails to create or append to a Devise initializer.
4
6
  class InstallGenerator < Rails::Generators::Base
5
- LOCALES = %w[ en de it ]
7
+ LOCALES = %w[by cs de en es fa fr hi it ja nl pt ru tr uk zh_CN zh_TW].freeze
6
8
 
7
- source_root File.expand_path('../../templates', __FILE__)
9
+ source_root File.expand_path('../templates', __dir__)
8
10
  desc 'Install the devise security extension'
9
11
 
10
12
  def copy_initializer
11
- template('devise-security.rb',
12
- 'config/initializers/devise-security.rb',
13
- )
13
+ template('devise_security.rb', 'config/initializers/devise_security.rb')
14
14
  end
15
15
 
16
16
  def copy_locales
17
17
  LOCALES.each do |locale|
18
18
  copy_file(
19
19
  "../../../config/locales/#{locale}.yml",
20
- "config/locales/devise.security_extension.#{locale}.yml",
20
+ "config/locales/devise.security_extension.#{locale}.yml"
21
21
  )
22
22
  end
23
23
  end
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ Devise.setup do |config|
4
+ # ==> Security Extension
5
+ # Configure security extension for devise
6
+
7
+ # Should the password expire (e.g 3.months)
8
+ # config.expire_password_after = false
9
+
10
+ # Need 1 char each of: A-Z, a-z, 0-9, and a punctuation mark or symbol
11
+ # You may use "digits" in place of "digit" and "symbols" in place of
12
+ # "symbol" based on your preference
13
+ # config.password_complexity = { digit: 1, lower: 1, symbol: 1, upper: 1 }
14
+
15
+ # How many passwords to keep in archive
16
+ # config.password_archiving_count = 5
17
+
18
+ # Deny old passwords (true, false, number_of_old_passwords_to_check)
19
+ # Examples:
20
+ # config.deny_old_passwords = false # allow old passwords
21
+ # config.deny_old_passwords = true # will deny all the old passwords
22
+ # config.deny_old_passwords = 3 # will deny new passwords that matches with the last 3 passwords
23
+ # config.deny_old_passwords = true
24
+
25
+ # enable email validation for :secure_validatable. (true, false, validation_options)
26
+ # dependency: see https://github.com/devise-security/devise-security/blob/master/README.md#e-mail-validation
27
+ # config.email_validation = true
28
+
29
+ # captcha integration for recover form
30
+ # config.captcha_for_recover = true
31
+
32
+ # captcha integration for sign up form
33
+ # config.captcha_for_sign_up = true
34
+
35
+ # captcha integration for sign in form
36
+ # config.captcha_for_sign_in = true
37
+
38
+ # captcha integration for unlock form
39
+ # config.captcha_for_unlock = true
40
+
41
+ # captcha integration for confirmation form
42
+ # config.captcha_for_confirmation = true
43
+
44
+ # Time period for account expiry from last_activity_at
45
+ # config.expire_after = 90.days
46
+
47
+ # Allow password to equal the email
48
+ # config.allow_passwords_equal_to_email = false
49
+
50
+ # paranoid_verification will regenerate verification code after failed attempt
51
+ # config.paranoid_code_regenerate_after_attempt = 10
52
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'test_helper'
2
4
 
3
5
  class TestWithCaptcha < ActionController::TestCase
@@ -0,0 +1,133 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'test_helper'
4
+
5
+ class Devise::ParanoidVerificationCodeControllerTest < ActionController::TestCase
6
+ include Devise::Test::ControllerHelpers
7
+
8
+ setup do
9
+ @controller.class.respond_to :json, :xml
10
+ @request.env['devise.mapping'] = Devise.mappings[:user]
11
+ @user = User.create!(
12
+ username: 'hello',
13
+ email: 'hello@path.travel',
14
+ password: 'Password4',
15
+ confirmed_at: 5.months.ago,
16
+ paranoid_verification_code: 'cookies'
17
+ )
18
+ assert @user.valid?
19
+ assert @user.need_paranoid_verification?
20
+
21
+ sign_in(@user)
22
+ end
23
+
24
+ test 'redirects to root on show if user not logged in' do
25
+ sign_out(@user)
26
+ get :show
27
+ assert_redirected_to :root
28
+ end
29
+
30
+ test "redirects to root on show if user doesn't need paranoid verification" do
31
+ @user.update(paranoid_verification_code: nil)
32
+ get :show
33
+ assert_redirected_to :root
34
+ end
35
+
36
+ test 'renders show on show if user needs paranoid verification' do
37
+ @user.update(paranoid_verification_code: 'cookies')
38
+ get :show
39
+ assert_template :show
40
+ end
41
+
42
+ test 'redirects on update if user not logged in' do
43
+ sign_out(@user)
44
+ patch :update
45
+ assert_redirected_to :root
46
+ end
47
+
48
+ test 'redirects on update if user does not need paranoid verification' do
49
+ @user.update(paranoid_verification_code: nil)
50
+ patch :update
51
+ assert_redirected_to :root
52
+ end
53
+
54
+ test 'update paranoid_verification_code with default format' do
55
+ patch(
56
+ :update,
57
+ params: {
58
+ user: {
59
+ paranoid_verification_code: 'cookies'
60
+ }
61
+ }
62
+ )
63
+ assert_redirected_to root_path
64
+ assert_equal 'Verification code accepted', flash[:notice]
65
+ assert_equal('text/html', response.media_type)
66
+ end
67
+
68
+ test 'update paranoid_verification_code using JSON format' do
69
+ patch(
70
+ :update,
71
+ format: :json,
72
+ params: {
73
+ user: {
74
+ paranoid_verification_code: 'cookies'
75
+ }
76
+ }
77
+ )
78
+
79
+ assert_response 204
80
+ assert_equal root_url, response.location
81
+ assert_nil response.media_type, 'No Content-Type header should be set for No Content response'
82
+ end
83
+
84
+ test 'update paranoid_verification_code using XML format' do
85
+ patch(
86
+ :update,
87
+ format: :xml,
88
+ params: {
89
+ user: {
90
+ paranoid_verification_code: 'cookies'
91
+ }
92
+ }
93
+ )
94
+ assert_response 204
95
+ assert_equal root_url, response.location
96
+ assert_nil response.media_type, 'No Content-Type header should be set for No Content response'
97
+ end
98
+ end
99
+
100
+ class ParanoidVerificationCodeCustomRedirectTest < ActionController::TestCase
101
+ include Devise::Test::ControllerHelpers
102
+ tests Overrides::ParanoidVerificationCodeController
103
+
104
+ setup do
105
+ @controller.class.respond_to :json, :xml
106
+ @request.env['devise.mapping'] = Devise.mappings[:paranoid_verification_user]
107
+ @user = ParanoidVerificationUser.create!(
108
+ username: 'hello',
109
+ email: 'hello@path.travel',
110
+ password: 'Password4',
111
+ confirmed_at: 5.months.ago,
112
+ paranoid_verification_code: 'cookies'
113
+ )
114
+ assert @user.valid?
115
+ assert @user.need_paranoid_verification?
116
+
117
+ sign_in(@user)
118
+ end
119
+
120
+ test 'redirects to custom redirect route on update' do
121
+ patch(
122
+ :update,
123
+ params: {
124
+ paranoid_verification_user: {
125
+ paranoid_verification_code: 'cookies'
126
+ }
127
+ }
128
+ )
129
+
130
+ assert_redirected_to '/cats'
131
+ assert_equal 'Verification code accepted', flash[:notice]
132
+ end
133
+ end
@@ -0,0 +1,164 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'test_helper'
4
+
5
+ class Devise::PasswordExpiredControllerTest < ActionController::TestCase
6
+ include Devise::Test::ControllerHelpers
7
+
8
+ setup do
9
+ @controller.class.respond_to :json, :xml
10
+ @request.env['devise.mapping'] = Devise.mappings[:user]
11
+ @user = User.create!(
12
+ username: 'hello',
13
+ email: 'hello@path.travel',
14
+ password: 'Password4',
15
+ password_changed_at: 4.months.ago,
16
+ confirmed_at: 5.months.ago
17
+ )
18
+ assert @user.valid?
19
+ assert @user.need_change_password?
20
+
21
+ sign_in(@user)
22
+ end
23
+
24
+ test 'redirects on show if user not logged in' do
25
+ sign_out(@user)
26
+ get :show
27
+ assert_redirected_to :root
28
+ end
29
+
30
+ test 'redirects on show if user does not need password change' do
31
+ @user.update(password_changed_at: Time.zone.now)
32
+ get :show
33
+ assert_redirected_to :root
34
+ end
35
+
36
+ test 'should render show' do
37
+ get :show
38
+ assert_includes @response.body, 'Renew your password'
39
+ end
40
+
41
+ test 'redirects on update if user not logged in' do
42
+ sign_out(@user)
43
+ put :update
44
+ assert_redirected_to :root
45
+ end
46
+
47
+ test 'redirects on update if user does not need password change' do
48
+ @user.update(password_changed_at: Time.zone.now)
49
+ put :update
50
+ assert_redirected_to :root
51
+ end
52
+
53
+ test 'update password with default format' do
54
+ put(
55
+ :update,
56
+ params: {
57
+ user: {
58
+ current_password: 'Password4',
59
+ password: 'Password5',
60
+ password_confirmation: 'Password5'
61
+ }
62
+ }
63
+ )
64
+ assert_redirected_to root_path
65
+ assert_equal('text/html', response.media_type)
66
+ end
67
+
68
+ test 'password confirmation does not match' do
69
+ put(
70
+ :update,
71
+ params: {
72
+ user: {
73
+ current_password: 'Password4',
74
+ password: 'Password5',
75
+ password_confirmation: 'Password6'
76
+ }
77
+ }
78
+ )
79
+
80
+ assert_response :success
81
+ assert_template :show
82
+ assert_equal('text/html', response.media_type)
83
+ assert_includes(
84
+ response.body,
85
+ 'Password confirmation doesn&#39;t match Password'
86
+ )
87
+ end
88
+
89
+ test 'update password using JSON format' do
90
+ put(
91
+ :update,
92
+ format: :json,
93
+ params: {
94
+ user: {
95
+ current_password: 'Password4',
96
+ password: 'Password5',
97
+ password_confirmation: 'Password5'
98
+ }
99
+ }
100
+ )
101
+
102
+ assert_response 204
103
+ assert_equal root_url, response.location
104
+ assert_nil response.media_type, 'No Content-Type header should be set for No Content response'
105
+ end
106
+
107
+ test 'update password using XML format' do
108
+ put(
109
+ :update,
110
+ format: :xml,
111
+ params: {
112
+ user: {
113
+ current_password: 'Password4',
114
+ password: 'Password5',
115
+ password_confirmation: 'Password5'
116
+ }
117
+ }
118
+ )
119
+ assert_response 204
120
+ assert_equal root_url, response.location
121
+ assert_nil response.media_type, 'No Content-Type header should be set for No Content response'
122
+ end
123
+ end
124
+
125
+ class PasswordExpiredCustomRedirectTest < ActionController::TestCase
126
+ include Devise::Test::ControllerHelpers
127
+ tests Overrides::PasswordExpiredController
128
+
129
+ setup do
130
+ @controller.class.respond_to :json, :xml
131
+ @request.env['devise.mapping'] = Devise.mappings[:password_expired_user]
132
+ @user = PasswordExpiredUser.create!(
133
+ username: 'hello',
134
+ email: 'hello@path.travel',
135
+ password: 'Password4',
136
+ password_changed_at: 4.months.ago,
137
+ confirmed_at: 5.months.ago
138
+ )
139
+ assert @user.valid?
140
+ assert @user.need_change_password?
141
+
142
+ sign_in(@user)
143
+ end
144
+
145
+ test 'update password with custom redirect route' do
146
+ put(
147
+ :update,
148
+ params: {
149
+ password_expired_user: {
150
+ current_password: 'Password4',
151
+ password: 'Password5',
152
+ password_confirmation: 'Password5'
153
+ }
154
+ }
155
+ )
156
+
157
+ assert_redirected_to '/cookies'
158
+ end
159
+
160
+ test 'yield resource to block on update' do
161
+ put(:update, params: { password_expired_user: { current_password: '123' } })
162
+ assert @controller.update_block_called?, 'Update failed to yield resource to provided block'
163
+ end
164
+ end
@@ -0,0 +1,66 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'test_helper'
4
+
5
+ class TestWithSecurityQuestion < ActionController::TestCase
6
+ include Devise::Test::ControllerHelpers
7
+ tests SecurityQuestion::UnlocksController
8
+
9
+ setup do
10
+ @user = SecurityQuestionUser.create!(
11
+ username: 'hello', email: 'hello@microsoft.com', password: 'A1234567z!', security_question_answer: 'Right Answer'
12
+ )
13
+ @user.lock_access!
14
+ assert @user.locked_at.present?
15
+ @request.env['devise.mapping'] = Devise.mappings[:security_question_user]
16
+ end
17
+
18
+ test 'When security question is enabled, it is inserted correctly' do
19
+ post(
20
+ :create,
21
+ params: {
22
+ security_question_answer: 'wrong answer',
23
+ security_question_user: {
24
+ email: @user.email
25
+ }
26
+ }
27
+ )
28
+ assert_equal I18n.t('devise.invalid_security_question'), flash[:alert]
29
+ assert_redirected_to new_security_question_user_unlock_path
30
+ end
31
+
32
+ test 'When security_question is valid, it runs as normal' do
33
+ post(
34
+ :create,
35
+ params: {
36
+ security_question_answer: @user.security_question_answer,
37
+ security_question_user: {
38
+ email: @user.email
39
+ }
40
+ }
41
+ )
42
+
43
+ assert_equal I18n.t('devise.unlocks.send_instructions'), flash[:notice]
44
+ assert_redirected_to new_security_question_user_session_path
45
+ end
46
+ end
47
+
48
+ class TestWithoutSecurityQuestion < ActionController::TestCase
49
+ include Devise::Test::ControllerHelpers
50
+ tests Devise::UnlocksController
51
+
52
+ setup do
53
+ @user = User.create(
54
+ username: 'hello', email: 'hello@path.travel', password: '1234', security_question_answer: 'Right Answer'
55
+ )
56
+ @user.lock_access!
57
+ @request.env['devise.mapping'] = Devise.mappings[:user]
58
+ end
59
+
60
+ test 'When security question is not enabled it is not inserted' do
61
+ post :create, params: { user: { email: @user.email } }
62
+
63
+ assert_equal I18n.t('devise.unlocks.send_instructions'), flash[:notice]
64
+ assert_redirected_to new_user_session_path
65
+ end
66
+ end
data/test/dummy/Rakefile CHANGED
@@ -1,6 +1,8 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # Add your own tasks in files placed in lib/tasks ending in .rake,
2
4
  # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake.
3
5
 
4
- require File.expand_path('../config/application', __FILE__)
6
+ require File.expand_path('config/application', __dir__)
5
7
 
6
8
  Rails.application.load_tasks
@@ -0,0 +1,3 @@
1
+ // = link_tree ../images
2
+ // = link_directory ../javascripts .js
3
+ // = link_directory ../stylesheets .css
@@ -1,2 +1,4 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class ApplicationController < ActionController::Base
2
4
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class Captcha::SessionsController < Devise::SessionsController
2
4
  include DeviseSecurity::Patches::ControllerCaptcha
3
5
  end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Overrides::ParanoidVerificationCodeController < Devise::ParanoidVerificationCodeController
4
+ def after_paranoid_verification_code_update_path_for(_)
5
+ '/cats'
6
+ end
7
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Overrides::PasswordExpiredController < Devise::PasswordExpiredController
4
+ def update
5
+ super do |resource|
6
+ @update_block_called = true
7
+ end
8
+ end
9
+
10
+ def after_password_expired_update_path_for(_)
11
+ '/cookies'
12
+ end
13
+
14
+ def update_block_called?
15
+ @update_block_called == true
16
+ end
17
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class SecurityQuestion::UnlocksController < Devise::UnlocksController
2
4
  include DeviseSecurity::Patches::ControllerSecurityQuestion
3
5
  end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ class WidgetsController < ApplicationController
4
+ before_action :authenticate_user!
5
+
6
+ def show
7
+ render plain: 'success'
8
+ end
9
+ end
@@ -1,3 +1,11 @@
1
- class ApplicationRecord < ActiveRecord::Base
2
- self.abstract_class = true
1
+ # frozen_string_literal: true
2
+
3
+ if DEVISE_ORM == :active_record
4
+ class ApplicationRecord < ActiveRecord::Base
5
+ self.abstract_class = true
6
+ end
7
+ else
8
+ class ApplicationRecord
9
+ include Mongoid::Document
10
+ end
3
11
  end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ if DEVISE_ORM == :active_record
4
+ class ApplicationUserRecord < ApplicationRecord
5
+ self.table_name = 'users'
6
+ end
7
+ else
8
+ class ApplicationUserRecord
9
+ include Mongoid::Document
10
+ store_in collection: 'users'
11
+ end
12
+ end
@@ -1,5 +1,10 @@
1
- class CaptchaUser < ActiveRecord::Base
2
- self.table_name = 'users'
1
+ # frozen_string_literal: true
2
+
3
+ class CaptchaUser < ApplicationUserRecord
3
4
  devise :database_authenticatable, :password_archivable,
4
5
  :paranoid_verification, :password_expirable
6
+ if DEVISE_ORM == :mongoid
7
+ require './test/dummy/app/models/mongoid/mappings'
8
+ include ::Mongoid::Mappings
9
+ end
5
10
  end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ConfirmableFields
4
+ extend ::ActiveSupport::Concern
5
+
6
+ included do
7
+ include Mongoid::Document
8
+
9
+ ## Confirmable
10
+ field :confirmation_token, type: String
11
+ field :confirmed_at, type: Time
12
+ field :confirmation_sent_at, type: Time
13
+ field :unconfirmed_email, type: String # Only if using reconfirmable
14
+ end
15
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DatabaseAuthenticatableFields
4
+ extend ::ActiveSupport::Concern
5
+
6
+ included do
7
+ include Mongoid::Document
8
+
9
+ ## Database authenticatable
10
+ field :username, type: String
11
+ field :email, type: String, default: ''
12
+
13
+ field :encrypted_password, type: String, default: ''
14
+ validates_presence_of :encrypted_password
15
+
16
+ include Mongoid::Timestamps
17
+ end
18
+ end