devise-security 0.12.0 → 0.16.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 (183) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +186 -63
  3. data/app/controllers/devise/paranoid_verification_code_controller.rb +2 -0
  4. data/app/controllers/devise/password_expired_controller.rb +13 -6
  5. data/app/views/devise/paranoid_verification_code/show.html.erb +4 -4
  6. data/app/views/devise/password_expired/show.html.erb +6 -6
  7. data/config/locales/by.yml +49 -0
  8. data/config/locales/cs.yml +41 -0
  9. data/config/locales/de.yml +30 -7
  10. data/config/locales/en.yml +25 -1
  11. data/config/locales/es.yml +19 -6
  12. data/config/locales/fa.yml +41 -0
  13. data/config/locales/fr.yml +30 -0
  14. data/config/locales/hi.yml +42 -0
  15. data/config/locales/it.yml +35 -4
  16. data/config/locales/ja.yml +30 -0
  17. data/config/locales/nl.yml +41 -0
  18. data/config/locales/pt.yml +41 -0
  19. data/config/locales/ru.yml +49 -0
  20. data/config/locales/tr.yml +18 -0
  21. data/config/locales/uk.yml +49 -0
  22. data/config/locales/zh_CN.yml +41 -0
  23. data/config/locales/zh_TW.yml +41 -0
  24. data/lib/devise-security/controllers/helpers.rb +61 -50
  25. data/lib/devise-security/hooks/expirable.rb +3 -1
  26. data/lib/devise-security/hooks/paranoid_verification.rb +2 -0
  27. data/lib/devise-security/hooks/password_expirable.rb +4 -0
  28. data/lib/devise-security/hooks/session_limitable.rb +31 -14
  29. data/lib/devise-security/models/active_record/old_password.rb +5 -0
  30. data/lib/devise-security/models/compatibility/active_record_patch.rb +40 -0
  31. data/lib/devise-security/models/compatibility/mongoid_patch.rb +31 -0
  32. data/lib/devise-security/models/compatibility.rb +8 -15
  33. data/lib/devise-security/models/database_authenticatable_patch.rb +3 -1
  34. data/lib/devise-security/models/expirable.rb +8 -2
  35. data/lib/devise-security/models/mongoid/old_password.rb +21 -0
  36. data/lib/devise-security/models/paranoid_verification.rb +2 -0
  37. data/lib/devise-security/models/password_archivable.rb +18 -7
  38. data/lib/devise-security/models/password_expirable.rb +103 -48
  39. data/lib/devise-security/models/secure_validatable.rb +26 -6
  40. data/lib/devise-security/models/security_questionable.rb +2 -0
  41. data/lib/devise-security/models/session_limitable.rb +19 -2
  42. data/lib/devise-security/orm/mongoid.rb +7 -0
  43. data/lib/devise-security/patches/confirmations_controller_captcha.rb +2 -0
  44. data/lib/devise-security/patches/confirmations_controller_security_question.rb +2 -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/passwords_controller_captcha.rb +2 -0
  48. data/lib/devise-security/patches/passwords_controller_security_question.rb +2 -0
  49. data/lib/devise-security/patches/registrations_controller_captcha.rb +2 -0
  50. data/lib/devise-security/patches/sessions_controller_captcha.rb +2 -0
  51. data/lib/devise-security/patches/unlocks_controller_captcha.rb +2 -0
  52. data/lib/devise-security/patches/unlocks_controller_security_question.rb +2 -0
  53. data/lib/devise-security/patches.rb +2 -0
  54. data/lib/devise-security/rails.rb +2 -0
  55. data/lib/devise-security/routes.rb +2 -0
  56. data/lib/devise-security/validators/password_complexity_validator.rb +35 -0
  57. data/lib/devise-security/version.rb +3 -1
  58. data/lib/devise-security.rb +16 -10
  59. data/lib/generators/devise_security/install_generator.rb +5 -3
  60. data/lib/generators/templates/devise_security.rb +47 -0
  61. data/test/{test_captcha_controller.rb → controllers/test_captcha_controller.rb} +2 -0
  62. data/test/controllers/test_password_expired_controller.rb +110 -0
  63. data/test/controllers/test_security_question_controller.rb +60 -0
  64. data/test/dummy/Rakefile +3 -1
  65. data/test/dummy/app/assets/config/manifest.js +3 -0
  66. data/test/dummy/app/controllers/application_controller.rb +2 -0
  67. data/test/dummy/app/controllers/captcha/sessions_controller.rb +2 -0
  68. data/test/dummy/app/controllers/security_question/unlocks_controller.rb +2 -0
  69. data/test/dummy/app/controllers/widgets_controller.rb +6 -0
  70. data/test/dummy/app/models/application_record.rb +10 -2
  71. data/test/dummy/app/models/application_user_record.rb +11 -0
  72. data/test/dummy/app/models/captcha_user.rb +7 -2
  73. data/test/dummy/app/models/mongoid/confirmable_fields.rb +13 -0
  74. data/test/dummy/app/models/mongoid/database_authenticable_fields.rb +17 -0
  75. data/test/dummy/app/models/mongoid/expirable_fields.rb +11 -0
  76. data/test/dummy/app/models/mongoid/lockable_fields.rb +13 -0
  77. data/test/dummy/app/models/mongoid/mappings.rb +13 -0
  78. data/test/dummy/app/models/mongoid/omniauthable_fields.rb +11 -0
  79. data/test/dummy/app/models/mongoid/paranoid_verification_fields.rb +10 -0
  80. data/test/dummy/app/models/mongoid/password_archivable_fields.rb +9 -0
  81. data/test/dummy/app/models/mongoid/password_expirable_fields.rb +10 -0
  82. data/test/dummy/app/models/mongoid/recoverable_fields.rb +11 -0
  83. data/test/dummy/app/models/mongoid/registerable_fields.rb +19 -0
  84. data/test/dummy/app/models/mongoid/rememberable_fields.rb +10 -0
  85. data/test/dummy/app/models/mongoid/secure_validatable_fields.rb +11 -0
  86. data/test/dummy/app/models/mongoid/security_questionable_fields.rb +13 -0
  87. data/test/dummy/app/models/mongoid/session_limitable_fields.rb +10 -0
  88. data/test/dummy/app/models/mongoid/timeoutable_fields.rb +9 -0
  89. data/test/dummy/app/models/mongoid/trackable_fields.rb +14 -0
  90. data/test/dummy/app/models/mongoid/validatable_fields.rb +7 -0
  91. data/test/dummy/app/models/secure_user.rb +7 -1
  92. data/test/dummy/app/models/security_question_user.rb +9 -4
  93. data/test/dummy/app/models/user.rb +15 -0
  94. data/test/dummy/app/models/widget.rb +6 -0
  95. data/test/dummy/app/mongoid/admin.rb +31 -0
  96. data/test/dummy/app/mongoid/one_user.rb +58 -0
  97. data/test/dummy/app/mongoid/shim.rb +25 -0
  98. data/test/dummy/app/mongoid/user_on_engine.rb +41 -0
  99. data/test/dummy/app/mongoid/user_on_main_app.rb +41 -0
  100. data/test/dummy/app/mongoid/user_with_validations.rb +37 -0
  101. data/test/dummy/app/mongoid/user_without_email.rb +35 -0
  102. data/test/dummy/config/application.rb +13 -7
  103. data/test/dummy/config/boot.rb +2 -0
  104. data/test/dummy/config/environment.rb +2 -0
  105. data/test/dummy/config/environments/test.rb +5 -13
  106. data/test/dummy/config/initializers/devise.rb +10 -3
  107. data/test/dummy/config/initializers/migration_class.rb +3 -6
  108. data/test/dummy/config/mongoid.yml +6 -0
  109. data/test/dummy/config/routes.rb +6 -3
  110. data/test/dummy/config.ru +3 -1
  111. data/test/dummy/db/migrate/20120508165529_create_tables.rb +13 -2
  112. data/test/dummy/db/migrate/20150402165590_add_verification_columns.rb +2 -0
  113. data/test/dummy/db/migrate/20150407162345_add_verification_attempt_column.rb +2 -0
  114. data/test/dummy/db/migrate/20160320162345_add_security_questions_fields.rb +2 -0
  115. data/test/dummy/db/migrate/20180318103603_add_expireable_columns.rb +2 -0
  116. data/test/dummy/db/migrate/20180318105329_add_confirmable_columns.rb +2 -0
  117. data/test/dummy/db/migrate/20180318105732_add_rememberable_columns.rb +2 -0
  118. data/test/dummy/db/migrate/20180318111336_add_recoverable_columns.rb +2 -0
  119. data/test/dummy/db/migrate/20180319114023_add_widget.rb +2 -0
  120. data/test/dummy/lib/shared_expirable_columns.rb +14 -0
  121. data/test/dummy/lib/shared_security_questions_fields.rb +16 -0
  122. data/test/dummy/lib/shared_user.rb +32 -0
  123. data/test/dummy/lib/shared_user_with_password_verification.rb +13 -0
  124. data/test/dummy/lib/shared_user_without_email.rb +28 -0
  125. data/test/dummy/lib/shared_user_without_omniauth.rb +15 -0
  126. data/test/dummy/lib/shared_verification_fields.rb +15 -0
  127. data/test/dummy/log/development.log +883 -0
  128. data/test/dummy/log/test.log +21689 -0
  129. data/test/integration/test_password_expirable_workflow.rb +53 -0
  130. data/test/integration/test_session_limitable_workflow.rb +67 -0
  131. data/test/orm/active_record.rb +15 -0
  132. data/test/orm/mongoid.rb +13 -0
  133. data/test/support/integration_helpers.rb +29 -0
  134. data/test/support/mongoid.yml +6 -0
  135. data/test/test_compatibility.rb +13 -0
  136. data/test/test_complexity_validator.rb +72 -0
  137. data/test/test_helper.rb +42 -9
  138. data/test/test_install_generator.rb +19 -2
  139. data/test/test_paranoid_verification.rb +2 -0
  140. data/test/test_password_archivable.rb +8 -7
  141. data/test/test_password_expirable.rb +70 -7
  142. data/test/test_secure_validatable.rb +97 -21
  143. data/test/test_session_limitable.rb +57 -0
  144. data/{lib/generators/templates → test/tmp/config/initializers}/devise-security.rb +12 -3
  145. data/test/tmp/config/locales/devise.security_extension.by.yml +49 -0
  146. data/test/tmp/config/locales/devise.security_extension.cs.yml +41 -0
  147. data/test/tmp/config/locales/devise.security_extension.de.yml +39 -0
  148. data/test/tmp/config/locales/devise.security_extension.en.yml +41 -0
  149. data/test/tmp/config/locales/devise.security_extension.es.yml +30 -0
  150. data/test/tmp/config/locales/devise.security_extension.fa.yml +41 -0
  151. data/test/tmp/config/locales/devise.security_extension.fr.yml +30 -0
  152. data/test/tmp/config/locales/devise.security_extension.hi.yml +42 -0
  153. data/test/tmp/config/locales/devise.security_extension.it.yml +41 -0
  154. data/test/tmp/config/locales/devise.security_extension.ja.yml +30 -0
  155. data/test/tmp/config/locales/devise.security_extension.nl.yml +41 -0
  156. data/test/tmp/config/locales/devise.security_extension.pt.yml +41 -0
  157. data/test/tmp/config/locales/devise.security_extension.ru.yml +49 -0
  158. data/test/tmp/config/locales/devise.security_extension.tr.yml +18 -0
  159. data/test/tmp/config/locales/devise.security_extension.uk.yml +49 -0
  160. data/test/tmp/config/locales/devise.security_extension.zh_CN.yml +41 -0
  161. data/test/tmp/config/locales/devise.security_extension.zh_TW.yml +41 -0
  162. metadata +235 -110
  163. data/.circleci/config.yml +0 -41
  164. data/.document +0 -5
  165. data/.gitignore +0 -40
  166. data/.rubocop.yml +0 -63
  167. data/.ruby-version +0 -1
  168. data/.travis.yml +0 -25
  169. data/Appraisals +0 -19
  170. data/Gemfile +0 -3
  171. data/Rakefile +0 -28
  172. data/devise-security.gemspec +0 -44
  173. data/gemfiles/rails_4.1_stable.gemfile +0 -8
  174. data/gemfiles/rails_4.2_stable.gemfile +0 -8
  175. data/gemfiles/rails_5.0_stable.gemfile +0 -8
  176. data/gemfiles/rails_5.1_stable.gemfile +0 -8
  177. data/gemfiles/rails_5.2_rc1.gemfile +0 -8
  178. data/lib/devise-security/models/old_password.rb +0 -4
  179. data/lib/devise-security/orm/active_record.rb +0 -18
  180. data/lib/devise-security/schema.rb +0 -64
  181. data/test/dummy/app/models/.gitkeep +0 -0
  182. data/test/test_password_expired_controller.rb +0 -44
  183. data/test/test_security_question_controller.rb +0 -84
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'test_helper'
4
+
5
+ class TestPasswordExpirableWorkflow < ActionDispatch::IntegrationTest
6
+ include IntegrationHelpers
7
+
8
+ setup do
9
+ @user = User.create!(password: 'passWord1',
10
+ password_confirmation: 'passWord1',
11
+ email: 'bob@microsoft.com',
12
+ password_changed_at: 4.months.ago) # the default expiration time is 3.months.ago
13
+ @user.confirm
14
+
15
+ assert @user.valid?
16
+ assert @user.need_change_password?
17
+ end
18
+
19
+ test 'sign in and change expired password' do
20
+ sign_in(@user)
21
+ assert_redirected_to(root_path)
22
+ follow_redirect!
23
+ assert_redirected_to(user_password_expired_path)
24
+ # @note This is not the same controller used by Devise for password changes
25
+ put '/users/password_expired', params: {
26
+ user: {
27
+ current_password: 'passWord1',
28
+ password: 'Password12345!',
29
+ password_confirmation: 'Password12345!',
30
+ },
31
+ }
32
+ assert_redirected_to(root_path)
33
+ @user.reload
34
+ assert_not @user.need_change_password?
35
+ end
36
+
37
+ test 'sign in and password is updated before redirect completes' do
38
+ sign_in(@user)
39
+ assert_redirected_to(root_path)
40
+
41
+ # simulates an external process updating the password
42
+ @user.update(password_changed_at: Time.zone.now)
43
+ assert_not @user.need_change_password?
44
+
45
+ follow_redirect!
46
+ assert_response :success
47
+
48
+ # if the password is expired at this point they will be redirected to the
49
+ # password change controller.
50
+ get root_path
51
+ assert_response :success
52
+ end
53
+ end
@@ -0,0 +1,67 @@
1
+ require 'test_helper'
2
+
3
+ class TestSessionLimitableWorkflow < ActionDispatch::IntegrationTest
4
+ include IntegrationHelpers
5
+
6
+ setup do
7
+ @user = User.create!(password: 'passWord1',
8
+ password_confirmation: 'passWord1',
9
+ email: 'bob@microsoft.com')
10
+ @user.confirm
11
+ end
12
+
13
+ test 'failed login' do
14
+ assert_nil @user.unique_session_id
15
+
16
+ open_session do |session|
17
+ failed_sign_in(@user, session)
18
+ session.assert_response(:success)
19
+ assert_equal session.flash[:alert], I18n.t('devise.failure.invalid', authentication_keys: 'Email')
20
+ assert_nil @user.reload.unique_session_id
21
+ end
22
+ end
23
+
24
+ test 'successful login' do
25
+ assert_nil @user.unique_session_id
26
+
27
+ open_session do |session|
28
+ sign_in(@user, session)
29
+ session.assert_redirected_to '/'
30
+ session.get widgets_path
31
+ session.assert_response(:success)
32
+ assert_equal session.response.body, 'success'
33
+ assert_not_nil @user.reload.unique_session_id
34
+ end
35
+ end
36
+
37
+ test 'session is logged out when another session is created' do
38
+ first_session = open_session
39
+ second_session = open_session
40
+ unique_session_id = nil
41
+
42
+ first_session.tap do |session|
43
+ sign_in(@user, session)
44
+ session.assert_redirected_to '/'
45
+ session.get widgets_path
46
+ session.assert_response(:success)
47
+ assert_equal session.response.body, 'success'
48
+ unique_session_id = @user.reload.unique_session_id
49
+ assert_not_nil unique_session_id
50
+ end
51
+
52
+ second_session.tap do |session|
53
+ sign_in(@user, session)
54
+ session.assert_redirected_to '/'
55
+ session.get widgets_path
56
+ session.assert_response(:success)
57
+ assert_equal session.response.body, 'success'
58
+ assert_not_equal unique_session_id, @user.reload.unique_session_id
59
+ end
60
+
61
+ first_session.tap do |session|
62
+ session.get widgets_path
63
+ session.assert_redirected_to new_user_session_path
64
+ assert_equal session.flash[:alert], I18n.t('devise.failure.session_limited')
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,15 @@
1
+ require 'active_record'
2
+
3
+ ActiveRecord::Migration.verbose = false
4
+ ActiveRecord::Base.logger = Logger.new(nil)
5
+ case
6
+ when Rails.gem_version >= Gem::Version.new('6.0.0')
7
+ ActiveRecord::MigrationContext.new(File.expand_path('../../dummy/db/migrate', __FILE__), ActiveRecord::SchemaMigration).migrate
8
+ when Rails.gem_version >= Gem::Version.new('5.2.0')
9
+ ActiveRecord::MigrationContext.new(File.expand_path('../../dummy/db/migrate', __FILE__)).migrate
10
+ else
11
+ ActiveRecord::Migrator.migrate(File.expand_path('../../dummy/db/migrate', __FILE__))
12
+ end
13
+
14
+ DatabaseCleaner[:active_record].strategy = :transaction
15
+ ORMInvalidRecordException = ActiveRecord::RecordInvalid
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'mongoid/version'
4
+ require 'database_cleaner-mongoid'
5
+
6
+ Mongoid.configure do |config|
7
+ config.load!('test/support/mongoid.yml', Rails.env)
8
+ config.use_utc = true
9
+ config.include_root_in_json = true
10
+ end
11
+
12
+ DatabaseCleaner[:mongoid].strategy = :deletion
13
+ ORMInvalidRecordException = Mongoid::Errors::Validations
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module IntegrationHelpers
4
+ # login the user. This will exercise all the Warden Hooks
5
+ # @param user [User]
6
+ # @param session [ActionDispatch::Integration::Session]
7
+ # @return [void]
8
+ def sign_in(user, session = integration_session)
9
+ session.post new_user_session_path, params: {
10
+ user: {
11
+ email: user.email,
12
+ password: user.password,
13
+ },
14
+ }
15
+ end
16
+
17
+ # attempt to login the user with a bad password. This will exercise all the Warden Hooks
18
+ # @param user [User]
19
+ # @param session [ActionDispatch::Integration::Session]
20
+ # @return [void]
21
+ def failed_sign_in(user, session)
22
+ session.post new_user_session_path, params: {
23
+ user: {
24
+ email: user.email,
25
+ password: 'bad-password',
26
+ },
27
+ }
28
+ end
29
+ end
@@ -0,0 +1,6 @@
1
+ test:
2
+ clients:
3
+ default:
4
+ database: devise-test-suite
5
+ hosts:
6
+ - localhost:<%= ENV.fetch('MONGODB_PORT', '27017') %>
@@ -0,0 +1,13 @@
1
+ require 'test_helper'
2
+
3
+ class TestCompatibility < ActiveSupport::TestCase
4
+ test 'can access ActiveRecord namespace' do
5
+ skip unless DEVISE_ORM == :active_record
6
+ assert_nothing_raised { User.new.some_method_calling_active_record }
7
+ end
8
+
9
+ test 'can access Mongoid namespace' do
10
+ skip unless DEVISE_ORM == :mongoid
11
+ assert_nothing_raised { User.new.some_method_calling_mongoid }
12
+ end
13
+ end
@@ -0,0 +1,72 @@
1
+ require 'test_helper'
2
+
3
+ class PasswordComplexityValidatorTest < Minitest::Test
4
+ class ModelWithPassword
5
+ include ActiveModel::Validations
6
+
7
+ attr_reader :password
8
+
9
+ def initialize(password)
10
+ @password = password
11
+ end
12
+ end
13
+
14
+ def setup
15
+ ModelWithPassword.clear_validators!
16
+ end
17
+
18
+ def test_with_no_rules_anything_goes
19
+ assert(ModelWithPassword.new('aaaa').valid?)
20
+ end
21
+
22
+ def test_enforces_uppercase
23
+ ModelWithPassword.validates :password, 'devise_security/password_complexity': { upper: 1 }
24
+ refute(ModelWithPassword.new('aaaa').valid?)
25
+ assert(ModelWithPassword.new('Aaaa').valid?)
26
+ end
27
+
28
+ def test_enforces_count
29
+ ModelWithPassword.validates :password, 'devise_security/password_complexity': { upper: 2 }
30
+ refute(ModelWithPassword.new('Aaaa').valid?)
31
+ assert(ModelWithPassword.new('AAaa').valid?)
32
+ end
33
+
34
+ def test_enforces_digit
35
+ ModelWithPassword.validates :password, 'devise_security/password_complexity': { digit: 1 }
36
+ refute(ModelWithPassword.new('aaaa').valid?)
37
+ assert(ModelWithPassword.new('aaa1').valid?)
38
+ end
39
+
40
+ def test_enforces_digits
41
+ ModelWithPassword.validates :password, 'devise_security/password_complexity': { digits: 2 }
42
+ refute(ModelWithPassword.new('aaa1').valid?)
43
+ assert(ModelWithPassword.new('aa12').valid?)
44
+ end
45
+
46
+ def test_enforces_lower
47
+ ModelWithPassword.validates :password, 'devise_security/password_complexity': { lower: 1 }
48
+ refute(ModelWithPassword.new('AAAA').valid?)
49
+ assert(ModelWithPassword.new('AAAa').valid?)
50
+ end
51
+
52
+ def test_enforces_symbol
53
+ ModelWithPassword.validates :password, 'devise_security/password_complexity': { symbol: 1 }
54
+ refute(ModelWithPassword.new('aaaa').valid?)
55
+ assert(ModelWithPassword.new('aaa!').valid?)
56
+ end
57
+
58
+ def test_enforces_symbols
59
+ ModelWithPassword.validates :password, 'devise_security/password_complexity': { symbols: 2 }
60
+ refute(ModelWithPassword.new('aaa!').valid?)
61
+ assert(ModelWithPassword.new('aa!?').valid?)
62
+ end
63
+
64
+ def test_enforces_combination
65
+ ModelWithPassword.validates :password, 'devise_security/password_complexity': { lower: 1, upper: 1, digit: 1, symbol: 1 }
66
+ refute(ModelWithPassword.new('abcd').valid?)
67
+ refute(ModelWithPassword.new('ABCD').valid?)
68
+ refute(ModelWithPassword.new('1234').valid?)
69
+ refute(ModelWithPassword.new('$!,*').valid?)
70
+ assert(ModelWithPassword.new('aB3*').valid?)
71
+ end
72
+ end
data/test/test_helper.rb CHANGED
@@ -1,20 +1,53 @@
1
+ # frozen_string_literal: true
2
+
1
3
  ENV['RAILS_ENV'] ||= 'test'
2
4
 
3
5
  require 'simplecov'
4
- require 'coveralls'
5
- Coveralls.wear!
6
+ SimpleCov.start do
7
+ add_filter 'gemfiles'
8
+ add_filter 'test/dummy/db'
9
+ add_group 'ActiveRecord', 'active_record'
10
+ add_group 'Expirable', /(?<!password_)expirable/
11
+ add_group 'Mongoid', 'mongoid'
12
+ add_group 'Paranoid Verifiable', 'paranoid_verification'
13
+ add_group 'Password Archivable', /password_archivable|old_password/
14
+ add_group 'Password Expirable', /password_expirable|password_expired/
15
+ add_group 'Secure Validateable', 'secure_validatable'
16
+ add_group 'Security Questionable', 'security_question'
17
+ add_group 'Session Limitable', 'session_limitable'
18
+ add_group 'Tests', 'test'
19
+ end
20
+
21
+ if ENV['CI']
22
+ require 'simplecov'
23
+ require 'simplecov-lcov'
24
+ SimpleCov.formatter = SimpleCov::Formatter::LcovFormatter
25
+ SimpleCov::Formatter::LcovFormatter.config.report_with_single_file = true
26
+ SimpleCov.start
27
+ end
6
28
 
29
+ require 'pry'
7
30
  require 'dummy/config/environment'
8
31
  require 'minitest/autorun'
9
32
  require 'rails/test_help'
10
33
  require 'devise-security'
34
+ require 'database_cleaner'
35
+ require "orm/#{DEVISE_ORM}"
11
36
 
12
- ActiveRecord::Migration.verbose = false
13
- ActiveRecord::Base.logger = Logger.new(nil)
14
- ActiveRecord::Migrator.migrate(File.expand_path('../dummy/db/migrate', __FILE__))
37
+ # Controller testing is the way that Devise itself tests the functionality of
38
+ # controller, even though it has been deprecated in favor of request tests.
39
+ require 'rails-controller-testing'
40
+ Rails::Controller::Testing.install
41
+ require 'support/integration_helpers'
15
42
 
16
- SimpleCov.formatter = Coveralls::SimpleCov::Formatter
17
- SimpleCov.start do
18
- add_filter 'test/dummy'
19
- add_filter 'gemfiles'
43
+ class Minitest::Test
44
+ def before_setup
45
+ DatabaseCleaner.start
46
+ end
47
+
48
+ def after_teardown
49
+ DatabaseCleaner.clean
50
+ end
20
51
  end
52
+
53
+ DatabaseCleaner.clean
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'test_helper'
2
4
  require 'rails/generators/test_case'
3
5
  require 'generators/devise_security/install_generator'
@@ -9,8 +11,23 @@ class TestInstallGenerator < Rails::Generators::TestCase
9
11
 
10
12
  test 'Assert all files are properly created' do
11
13
  run_generator
12
- assert_file 'config/initializers/devise-security.rb'
13
- assert_file 'config/locales/devise.security_extension.en.yml'
14
+ assert_file 'config/initializers/devise_security.rb'
15
+ assert_file 'config/locales/devise.security_extension.by.yml'
16
+ assert_file 'config/locales/devise.security_extension.cs.yml'
14
17
  assert_file 'config/locales/devise.security_extension.de.yml'
18
+ assert_file 'config/locales/devise.security_extension.en.yml'
19
+ assert_file 'config/locales/devise.security_extension.es.yml'
20
+ assert_file 'config/locales/devise.security_extension.fa.yml'
21
+ assert_file 'config/locales/devise.security_extension.fr.yml'
22
+ assert_file 'config/locales/devise.security_extension.hi.yml'
23
+ assert_file 'config/locales/devise.security_extension.it.yml'
24
+ assert_file 'config/locales/devise.security_extension.ja.yml'
25
+ assert_file 'config/locales/devise.security_extension.nl.yml'
26
+ assert_file 'config/locales/devise.security_extension.pt.yml'
27
+ assert_file 'config/locales/devise.security_extension.ru.yml'
28
+ assert_file 'config/locales/devise.security_extension.tr.yml'
29
+ assert_file 'config/locales/devise.security_extension.uk.yml'
30
+ assert_file 'config/locales/devise.security_extension.zh_CN.yml'
31
+ assert_file 'config/locales/devise.security_extension.zh_TW.yml'
15
32
  end
16
33
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'test_helper'
2
4
 
3
5
  class TestParanoidVerification < ActiveSupport::TestCase
@@ -1,6 +1,9 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'test_helper'
2
4
 
3
5
  class TestPasswordArchivable < ActiveSupport::TestCase
6
+
4
7
  setup do
5
8
  Devise.password_archiving_count = 2
6
9
  end
@@ -17,7 +20,7 @@ class TestPasswordArchivable < ActiveSupport::TestCase
17
20
 
18
21
  test 'cannot use same password' do
19
22
  user = User.create email: 'bob@microsoft.com', password: 'Password1', password_confirmation: 'Password1'
20
- assert_raises(ActiveRecord::RecordInvalid) { set_password(user, 'Password1') }
23
+ assert_raises(ORMInvalidRecordException) { set_password(user, 'Password1') }
21
24
  end
22
25
 
23
26
  test 'indirectly saving associated user does not cause deprecation warning' do
@@ -35,17 +38,15 @@ class TestPasswordArchivable < ActiveSupport::TestCase
35
38
  assert_equal 0, OldPassword.count
36
39
  end
37
40
 
38
- test 'cannot use archived passwords' do
41
+ test 'cannot reuse archived passwords' do
39
42
  assert_equal 2, Devise.password_archiving_count
40
43
 
41
44
  user = User.create! email: 'bob@microsoft.com', password: 'Password1', password_confirmation: 'Password1'
42
45
  assert_equal 0, OldPassword.count
43
-
44
46
  set_password(user, 'Password2')
45
47
  assert_equal 1, OldPassword.count
46
48
 
47
- assert_raises(ActiveRecord::RecordInvalid) { set_password(user, 'Password1') }
48
-
49
+ assert_raises(ORMInvalidRecordException) { set_password(user, 'Password1') }
49
50
  set_password(user, 'Password3')
50
51
  assert_equal 2, OldPassword.count
51
52
 
@@ -68,8 +69,8 @@ class TestPasswordArchivable < ActiveSupport::TestCase
68
69
 
69
70
  assert set_password(user, 'Password2')
70
71
 
71
- assert_raises(ActiveRecord::RecordInvalid) { set_password(user, 'Password2') }
72
+ assert_raises(ORMInvalidRecordException) { set_password(user, 'Password2') }
72
73
 
73
- assert_raises(ActiveRecord::RecordInvalid) { set_password(user, 'Password1') }
74
+ assert_raises(ORMInvalidRecordException) { set_password(user, 'Password1') }
74
75
  end
75
76
  end
@@ -1,32 +1,95 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'test_helper'
2
4
 
3
5
  class TestPasswordArchivable < ActiveSupport::TestCase
4
6
  setup do
5
- Devise.expire_password_after = 2.month
7
+ Devise.expire_password_after = 2.months
6
8
  end
7
9
 
8
10
  teardown do
9
11
  Devise.expire_password_after = 90.days
10
12
  end
11
13
 
12
- test 'password expires' do
14
+ test 'does nothing if disabled' do
15
+ Devise.expire_password_after = false
13
16
  user = User.create email: 'bob@microsoft.com', password: 'Password1', password_confirmation: 'Password1'
14
17
  refute user.need_change_password?
18
+ refute user.password_expired?
19
+ user.need_change_password!
20
+ refute user.need_change_password?
21
+ refute user.password_expired?
22
+ end
15
23
 
16
- user.update(password_changed_at: Time.now.ago(3.month))
24
+ test 'password change can be requested' do
25
+ Devise.expire_password_after = true
26
+ user = User.create email: 'bob@microsoft.com', password: 'Password1', password_confirmation: 'Password1'
27
+ refute user.need_change_password?
28
+ refute user.password_expired?
29
+ refute user.password_change_requested?
30
+ user.need_change_password!
17
31
  assert user.need_change_password?
32
+ refute user.password_expired? # it's not too old because it's not set at all
33
+ assert user.password_change_requested?
18
34
  end
19
35
 
20
- test 'override expire after at runtime' do
36
+ test 'password expires' do
37
+ user = User.create email: 'bob@microsoft.com', password: 'Password1', password_confirmation: 'Password1'
38
+ refute user.need_change_password?
39
+ refute user.password_expired?
40
+ refute user.password_too_old?
41
+ user.update(password_changed_at: Time.now.ago(3.months))
42
+ assert user.password_too_old?
43
+ assert user.need_change_password?
44
+ assert user.password_expired?
45
+ refute user.password_change_requested?
46
+ end
47
+
48
+ test 'saving a record records the time the password was changed' do
21
49
  user = User.new email: 'bob@microsoft.com', password: 'Password1', password_confirmation: 'Password1'
50
+ assert user.password_changed_at.nil?
51
+ refute user.password_change_requested?
52
+ refute user.password_expired?
53
+ user.save
54
+ assert user.password_changed_at.present?
55
+ refute user.password_change_requested?
56
+ refute user.password_expired?
57
+ end
58
+
59
+ test 'updating a record updates the time the password was changed if the password is changed' do
60
+ user = User.create email: 'bob@microsoft.com', password: 'Password1', password_confirmation: 'Password1'
61
+ user.update(password_changed_at: Time.now.ago(3.months))
62
+ original_password_changed_at = user.password_changed_at
63
+ user.expire_password!
64
+ assert user.password_change_requested?
65
+ user.password = "NewPassword1"
66
+ user.password_confirmation = "NewPassword1"
67
+ user.save
68
+ assert user.password_changed_at > original_password_changed_at
69
+ refute user.password_change_requested?
70
+ end
71
+
72
+ test 'updating a record does not updates the time the password was changed if the password was not changed' do
73
+ user = User.create email: 'bob@microsoft.com', password: 'Password1', password_confirmation: 'Password1'
74
+ user.expire_password!
75
+ assert user.password_change_requested?
76
+ user.save
77
+ refute user.previous_changes.key?(:password_changed_at)
78
+ assert user.password_change_requested?
79
+ end
80
+
81
+ test 'override expire after at runtime' do
82
+ user = User.create email: 'bob@microsoft.com', password: 'Password1', password_confirmation: 'Password1'
22
83
  user.instance_eval do
23
84
  def expire_password_after
24
- 4.month
85
+ 4.months
25
86
  end
26
87
  end
27
- user.password_changed_at = Time.now.ago(3.month)
88
+ user.password_changed_at = Time.now.ago(3.months)
28
89
  refute user.need_change_password?
29
- user.password_changed_at = Time.now.ago(5.month)
90
+ refute user.password_expired?
91
+ user.password_changed_at = Time.now.ago(5.months)
30
92
  assert user.need_change_password?
93
+ assert user.password_expired?
31
94
  end
32
95
  end