devise-security 0.12.0 → 0.18.0

Sign up to get free protection for your applications and to get access to all the features.
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