devise-security 0.16.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.
- checksums.yaml +4 -4
- data/LICENSE.txt +3 -1
- data/README.md +18 -7
- data/app/controllers/devise/paranoid_verification_code_controller.rb +26 -12
- data/app/controllers/devise/password_expired_controller.rb +22 -5
- data/config/locales/bg.yml +42 -0
- data/config/locales/by.yml +1 -0
- data/config/locales/cs.yml +5 -0
- data/config/locales/de.yml +3 -0
- data/config/locales/en.yml +2 -1
- data/config/locales/es.yml +12 -0
- data/config/locales/fa.yml +1 -0
- data/config/locales/fr.yml +14 -2
- data/config/locales/hi.yml +1 -0
- data/config/locales/it.yml +1 -0
- data/config/locales/ja.yml +12 -0
- data/config/locales/nl.yml +1 -0
- data/config/locales/pt.yml +1 -0
- data/config/locales/ru.yml +1 -0
- data/config/locales/tr.yml +25 -1
- data/config/locales/uk.yml +1 -0
- data/config/locales/zh_CN.yml +1 -0
- data/config/locales/zh_TW.yml +1 -0
- data/lib/devise-security/controllers/helpers.rb +23 -11
- data/lib/devise-security/hooks/expirable.rb +3 -3
- data/lib/devise-security/hooks/paranoid_verification.rb +1 -3
- data/lib/devise-security/hooks/password_expirable.rb +1 -3
- data/lib/devise-security/hooks/session_limitable.rb +4 -4
- data/lib/devise-security/models/compatibility/active_record_patch.rb +4 -3
- data/lib/devise-security/models/compatibility/mongoid_patch.rb +3 -2
- data/lib/devise-security/models/database_authenticatable_patch.rb +18 -10
- data/lib/devise-security/models/expirable.rb +6 -5
- data/lib/devise-security/models/paranoid_verification.rb +2 -2
- data/lib/devise-security/models/password_archivable.rb +3 -3
- data/lib/devise-security/models/secure_validatable.rb +57 -20
- data/lib/devise-security/orm/mongoid.rb +1 -1
- data/lib/devise-security/patches.rb +14 -8
- data/lib/devise-security/routes.rb +2 -3
- data/lib/devise-security/validators/password_complexity_validator.rb +53 -26
- data/lib/devise-security/version.rb +1 -1
- data/lib/devise-security.rb +9 -3
- data/lib/generators/devise_security/install_generator.rb +3 -5
- data/lib/generators/templates/devise_security.rb +6 -1
- data/test/controllers/test_paranoid_verification_code_controller.rb +133 -0
- data/test/controllers/test_password_expired_controller.rb +87 -33
- data/test/controllers/test_security_question_controller.rb +25 -19
- data/test/dummy/app/controllers/overrides/paranoid_verification_code_controller.rb +7 -0
- data/test/dummy/app/controllers/overrides/password_expired_controller.rb +17 -0
- data/test/dummy/app/controllers/widgets_controller.rb +3 -0
- data/test/dummy/app/models/application_user_record.rb +2 -1
- data/test/dummy/app/models/mongoid/confirmable_fields.rb +2 -0
- data/test/dummy/app/models/mongoid/database_authenticable_fields.rb +4 -3
- data/test/dummy/app/models/mongoid/expirable_fields.rb +2 -0
- data/test/dummy/app/models/mongoid/lockable_fields.rb +2 -0
- data/test/dummy/app/models/mongoid/mappings.rb +4 -2
- data/test/dummy/app/models/mongoid/omniauthable_fields.rb +2 -0
- data/test/dummy/app/models/mongoid/paranoid_verification_fields.rb +2 -0
- data/test/dummy/app/models/mongoid/password_archivable_fields.rb +2 -0
- data/test/dummy/app/models/mongoid/password_expirable_fields.rb +2 -0
- data/test/dummy/app/models/mongoid/recoverable_fields.rb +2 -0
- data/test/dummy/app/models/mongoid/registerable_fields.rb +4 -2
- data/test/dummy/app/models/mongoid/rememberable_fields.rb +2 -0
- data/test/dummy/app/models/mongoid/secure_validatable_fields.rb +2 -0
- data/test/dummy/app/models/mongoid/security_questionable_fields.rb +2 -0
- data/test/dummy/app/models/mongoid/session_limitable_fields.rb +2 -0
- data/test/dummy/app/models/mongoid/timeoutable_fields.rb +2 -0
- data/test/dummy/app/models/mongoid/trackable_fields.rb +2 -0
- data/test/dummy/app/models/mongoid/validatable_fields.rb +2 -0
- data/test/dummy/app/models/paranoid_verification_user.rb +26 -0
- data/test/dummy/app/models/password_expired_user.rb +26 -0
- data/test/dummy/app/models/user.rb +5 -5
- data/test/dummy/app/models/widget.rb +1 -3
- data/test/dummy/app/mongoid/one_user.rb +5 -5
- data/test/dummy/app/mongoid/user_on_engine.rb +2 -2
- data/test/dummy/app/mongoid/user_on_main_app.rb +2 -2
- data/test/dummy/app/mongoid/user_with_validations.rb +3 -3
- data/test/dummy/app/mongoid/user_without_email.rb +7 -4
- data/test/dummy/config/application.rb +3 -7
- data/test/dummy/config/boot.rb +1 -1
- data/test/dummy/config/environment.rb +1 -1
- data/test/dummy/config/environments/test.rb +1 -0
- data/test/dummy/config/initializers/devise.rb +1 -5
- data/test/dummy/config/locales/en.yml +10 -0
- data/test/dummy/config/routes.rb +3 -1
- data/test/dummy/config.ru +1 -1
- data/test/dummy/db/migrate/20120508165529_create_tables.rb +5 -5
- data/test/dummy/lib/shared_expirable_columns.rb +1 -0
- data/test/dummy/lib/shared_security_questions_fields.rb +1 -0
- data/test/dummy/lib/shared_user.rb +17 -6
- data/test/dummy/lib/shared_user_without_omniauth.rb +12 -3
- data/test/dummy/lib/shared_verification_fields.rb +1 -0
- data/test/dummy/log/test.log +39637 -16086
- data/test/i18n_test.rb +22 -0
- data/test/integration/test_paranoid_verification_code_workflow.rb +53 -0
- data/test/integration/test_password_expirable_workflow.rb +2 -2
- data/test/integration/test_session_limitable_workflow.rb +5 -3
- data/test/orm/active_record.rb +7 -7
- data/test/support/integration_helpers.rb +18 -12
- data/test/test_compatibility.rb +2 -0
- data/test/test_complexity_validator.rb +247 -37
- data/test/test_database_authenticatable_patch.rb +146 -0
- data/test/test_helper.rb +7 -8
- data/test/test_install_generator.rb +1 -1
- data/test/test_paranoid_verification.rb +8 -9
- data/test/test_password_archivable.rb +34 -11
- data/test/test_password_expirable.rb +27 -27
- data/test/test_secure_validatable.rb +265 -107
- data/test/test_secure_validatable_overrides.rb +185 -0
- data/test/test_session_limitable.rb +9 -9
- data/test/tmp/config/initializers/{devise-security.rb → devise_security.rb} +6 -1
- data/test/tmp/config/locales/devise.security_extension.by.yml +1 -0
- data/test/tmp/config/locales/devise.security_extension.cs.yml +5 -0
- data/test/tmp/config/locales/devise.security_extension.de.yml +3 -0
- data/test/tmp/config/locales/devise.security_extension.en.yml +2 -1
- data/test/tmp/config/locales/devise.security_extension.es.yml +12 -0
- data/test/tmp/config/locales/devise.security_extension.fa.yml +1 -0
- data/test/tmp/config/locales/devise.security_extension.fr.yml +14 -2
- data/test/tmp/config/locales/devise.security_extension.hi.yml +21 -20
- data/test/tmp/config/locales/devise.security_extension.it.yml +1 -0
- data/test/tmp/config/locales/devise.security_extension.ja.yml +12 -0
- data/test/tmp/config/locales/devise.security_extension.nl.yml +1 -0
- data/test/tmp/config/locales/devise.security_extension.pt.yml +1 -0
- data/test/tmp/config/locales/devise.security_extension.ru.yml +1 -0
- data/test/tmp/config/locales/devise.security_extension.tr.yml +25 -1
- data/test/tmp/config/locales/devise.security_extension.uk.yml +1 -0
- data/test/tmp/config/locales/devise.security_extension.zh_CN.yml +1 -0
- data/test/tmp/config/locales/devise.security_extension.zh_TW.yml +1 -0
- metadata +82 -41
- data/lib/devise-security/patches/confirmations_controller_captcha.rb +0 -23
- data/lib/devise-security/patches/confirmations_controller_security_question.rb +0 -26
- data/lib/devise-security/patches/passwords_controller_captcha.rb +0 -22
- data/lib/devise-security/patches/passwords_controller_security_question.rb +0 -25
- data/lib/devise-security/patches/registrations_controller_captcha.rb +0 -35
- data/lib/devise-security/patches/sessions_controller_captcha.rb +0 -26
- data/lib/devise-security/patches/unlocks_controller_captcha.rb +0 -22
- data/lib/devise-security/patches/unlocks_controller_security_question.rb +0 -25
- data/test/dummy/app/controllers/foos_controller.rb +0 -0
- data/test/dummy/app/models/secure_user.rb +0 -9
- data/test/dummy/lib/shared_user_without_email.rb +0 -28
- data/test/dummy/log/development.log +0 -883
data/test/i18n_test.rb
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'i18n/tasks'
|
4
|
+
|
5
|
+
class I18nTest < ActiveSupport::TestCase
|
6
|
+
def setup
|
7
|
+
@i18n = I18n::Tasks::BaseTask.new
|
8
|
+
@missing_keys = @i18n.missing_keys
|
9
|
+
end
|
10
|
+
|
11
|
+
def test_no_missing_keys
|
12
|
+
assert_empty @missing_keys,
|
13
|
+
"Missing #{@missing_keys.leaves.count} i18n keys, run `i18n-tasks missing' to show them"
|
14
|
+
end
|
15
|
+
|
16
|
+
def test_no_inconsistent_interpolations
|
17
|
+
inconsistent_interpolations = @i18n.inconsistent_interpolations
|
18
|
+
error_message = "#{inconsistent_interpolations.leaves.count} i18n keys have inconsistent interpolations.\n" \
|
19
|
+
"Please run `i18n-tasks check-consistent-interpolations' to show them"
|
20
|
+
assert_empty inconsistent_interpolations, error_message
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'test_helper'
|
4
|
+
|
5
|
+
class TestParanoidVerificationCodeWorkflow < ActionDispatch::IntegrationTest
|
6
|
+
include IntegrationHelpers
|
7
|
+
|
8
|
+
setup do
|
9
|
+
@user = User.create!(
|
10
|
+
password: 'passWord1',
|
11
|
+
password_confirmation: 'passWord1',
|
12
|
+
email: 'bob@microsoft.com',
|
13
|
+
paranoid_verification_code: 'cookies'
|
14
|
+
) # the default verification code is nil
|
15
|
+
@user.confirm
|
16
|
+
|
17
|
+
assert @user.valid?
|
18
|
+
assert @user.need_paranoid_verification?
|
19
|
+
end
|
20
|
+
|
21
|
+
test 'sign in and check paranoid verification code' do
|
22
|
+
sign_in(@user)
|
23
|
+
assert_redirected_to(root_path)
|
24
|
+
follow_redirect!
|
25
|
+
assert_redirected_to(user_paranoid_verification_code_path)
|
26
|
+
# @note This is not the same controller used by Devise for password changes
|
27
|
+
patch '/users/verification_code', params: {
|
28
|
+
user: {
|
29
|
+
paranoid_verification_code: 'cookies'
|
30
|
+
}
|
31
|
+
}
|
32
|
+
assert_redirected_to(root_path)
|
33
|
+
@user.reload
|
34
|
+
assert_not @user.need_paranoid_verification?
|
35
|
+
end
|
36
|
+
|
37
|
+
test 'sign in and paranoid verification code is checked before redirect completes' do
|
38
|
+
sign_in(@user)
|
39
|
+
assert_redirected_to(root_path)
|
40
|
+
|
41
|
+
# simulates an external process verifying the paranoid verification code
|
42
|
+
@user.update(paranoid_verification_code: nil)
|
43
|
+
assert_not @user.need_paranoid_verification?
|
44
|
+
|
45
|
+
follow_redirect!
|
46
|
+
assert_response :success
|
47
|
+
|
48
|
+
# if the paranoid verification code is not empty/nil at this point they will be redirected to the
|
49
|
+
# paranoid verification code change controller.
|
50
|
+
get root_path
|
51
|
+
assert_response :success
|
52
|
+
end
|
53
|
+
end
|
@@ -26,8 +26,8 @@ class TestPasswordExpirableWorkflow < ActionDispatch::IntegrationTest
|
|
26
26
|
user: {
|
27
27
|
current_password: 'passWord1',
|
28
28
|
password: 'Password12345!',
|
29
|
-
password_confirmation: 'Password12345!'
|
30
|
-
}
|
29
|
+
password_confirmation: 'Password12345!'
|
30
|
+
}
|
31
31
|
}
|
32
32
|
assert_redirected_to(root_path)
|
33
33
|
@user.reload
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'test_helper'
|
2
4
|
|
3
5
|
class TestSessionLimitableWorkflow < ActionDispatch::IntegrationTest
|
@@ -29,7 +31,7 @@ class TestSessionLimitableWorkflow < ActionDispatch::IntegrationTest
|
|
29
31
|
session.assert_redirected_to '/'
|
30
32
|
session.get widgets_path
|
31
33
|
session.assert_response(:success)
|
32
|
-
assert_equal session.response.body
|
34
|
+
assert_equal('success', session.response.body)
|
33
35
|
assert_not_nil @user.reload.unique_session_id
|
34
36
|
end
|
35
37
|
end
|
@@ -44,7 +46,7 @@ class TestSessionLimitableWorkflow < ActionDispatch::IntegrationTest
|
|
44
46
|
session.assert_redirected_to '/'
|
45
47
|
session.get widgets_path
|
46
48
|
session.assert_response(:success)
|
47
|
-
assert_equal session.response.body
|
49
|
+
assert_equal('success', session.response.body)
|
48
50
|
unique_session_id = @user.reload.unique_session_id
|
49
51
|
assert_not_nil unique_session_id
|
50
52
|
end
|
@@ -54,7 +56,7 @@ class TestSessionLimitableWorkflow < ActionDispatch::IntegrationTest
|
|
54
56
|
session.assert_redirected_to '/'
|
55
57
|
session.get widgets_path
|
56
58
|
session.assert_response(:success)
|
57
|
-
assert_equal session.response.body
|
59
|
+
assert_equal('success', session.response.body)
|
58
60
|
assert_not_equal unique_session_id, @user.reload.unique_session_id
|
59
61
|
end
|
60
62
|
|
data/test/orm/active_record.rb
CHANGED
@@ -1,14 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'active_record'
|
2
4
|
|
3
5
|
ActiveRecord::Migration.verbose = false
|
4
6
|
ActiveRecord::Base.logger = Logger.new(nil)
|
5
|
-
|
6
|
-
|
7
|
-
ActiveRecord::MigrationContext.new(File.expand_path('
|
8
|
-
|
9
|
-
ActiveRecord::MigrationContext.new(File.expand_path('
|
10
|
-
else
|
11
|
-
ActiveRecord::Migrator.migrate(File.expand_path('../../dummy/db/migrate', __FILE__))
|
7
|
+
|
8
|
+
if Rails.gem_version >= Gem::Version.new('6.0.0')
|
9
|
+
ActiveRecord::MigrationContext.new(File.expand_path('../dummy/db/migrate', __dir__), ActiveRecord::SchemaMigration).migrate
|
10
|
+
elsif Rails.gem_version >= Gem::Version.new('5.2.0')
|
11
|
+
ActiveRecord::MigrationContext.new(File.expand_path('../dummy/db/migrate', __dir__)).migrate
|
12
12
|
end
|
13
13
|
|
14
14
|
DatabaseCleaner[:active_record].strategy = :transaction
|
@@ -6,12 +6,15 @@ module IntegrationHelpers
|
|
6
6
|
# @param session [ActionDispatch::Integration::Session]
|
7
7
|
# @return [void]
|
8
8
|
def sign_in(user, session = integration_session)
|
9
|
-
session.post
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
9
|
+
session.post(
|
10
|
+
new_user_session_path,
|
11
|
+
params: {
|
12
|
+
user: {
|
13
|
+
email: user.email,
|
14
|
+
password: user.password
|
15
|
+
}
|
16
|
+
}
|
17
|
+
)
|
15
18
|
end
|
16
19
|
|
17
20
|
# attempt to login the user with a bad password. This will exercise all the Warden Hooks
|
@@ -19,11 +22,14 @@ module IntegrationHelpers
|
|
19
22
|
# @param session [ActionDispatch::Integration::Session]
|
20
23
|
# @return [void]
|
21
24
|
def failed_sign_in(user, session)
|
22
|
-
session.post
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
25
|
+
session.post(
|
26
|
+
new_user_session_path,
|
27
|
+
params: {
|
28
|
+
user: {
|
29
|
+
email: user.email,
|
30
|
+
password: 'bad-password'
|
31
|
+
}
|
32
|
+
}
|
33
|
+
)
|
28
34
|
end
|
29
35
|
end
|
data/test/test_compatibility.rb
CHANGED
@@ -1,6 +1,8 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'test_helper'
|
2
4
|
|
3
|
-
class PasswordComplexityValidatorTest <
|
5
|
+
class PasswordComplexityValidatorTest < ActiveSupport::TestCase
|
4
6
|
class ModelWithPassword
|
5
7
|
include ActiveModel::Validations
|
6
8
|
|
@@ -15,58 +17,266 @@ class PasswordComplexityValidatorTest < Minitest::Test
|
|
15
17
|
ModelWithPassword.clear_validators!
|
16
18
|
end
|
17
19
|
|
20
|
+
def create_model(password, opts = {})
|
21
|
+
ModelWithPassword.validates(
|
22
|
+
:password, 'devise_security/password_complexity': opts
|
23
|
+
)
|
24
|
+
ModelWithPassword.new(password)
|
25
|
+
end
|
26
|
+
|
18
27
|
def test_with_no_rules_anything_goes
|
19
|
-
assert(
|
28
|
+
assert(create_model('aaaa').valid?)
|
29
|
+
end
|
30
|
+
|
31
|
+
def test_allows_blank
|
32
|
+
assert(create_model('', { upper: 1 }).valid?)
|
33
|
+
end
|
34
|
+
|
35
|
+
def test_enforces_uppercase_invalid
|
36
|
+
model = create_model('aaaa', { upper: 1 })
|
37
|
+
|
38
|
+
assert_not(model.valid?)
|
39
|
+
assert_equal(
|
40
|
+
{ password: ['must contain at least one upper-case letter'] },
|
41
|
+
model.errors.messages
|
42
|
+
)
|
43
|
+
end
|
44
|
+
|
45
|
+
def test_enforces_uppercase_valid
|
46
|
+
assert(create_model('Aaaa', { upper: 1 }).valid?)
|
47
|
+
end
|
48
|
+
|
49
|
+
def test_enforces_uppercase_count_invalid
|
50
|
+
model = create_model('Aaaa', { upper: 2 })
|
51
|
+
|
52
|
+
assert_not(model.valid?)
|
53
|
+
assert_equal(
|
54
|
+
{ password: ['must contain at least 2 upper-case letters'] },
|
55
|
+
model.errors.messages
|
56
|
+
)
|
57
|
+
end
|
58
|
+
|
59
|
+
def test_enforces_uppercase_count_valid
|
60
|
+
assert(create_model('AAaa', { upper: 2 }).valid?)
|
61
|
+
end
|
62
|
+
|
63
|
+
def test_enforces_digit_invalid
|
64
|
+
model = create_model('aaaa', { digit: 1 })
|
65
|
+
|
66
|
+
assert_not(model.valid?)
|
67
|
+
assert_equal(
|
68
|
+
{ password: ['must contain at least one digit'] }, model.errors.messages
|
69
|
+
)
|
20
70
|
end
|
21
71
|
|
22
|
-
def
|
23
|
-
|
24
|
-
refute(ModelWithPassword.new('aaaa').valid?)
|
25
|
-
assert(ModelWithPassword.new('Aaaa').valid?)
|
72
|
+
def test_enforces_digit_valid
|
73
|
+
assert(create_model('1aaa', { digit: 1 }).valid?)
|
26
74
|
end
|
27
75
|
|
28
|
-
def
|
29
|
-
|
30
|
-
|
31
|
-
|
76
|
+
def test_enforces_digit_count_invalid
|
77
|
+
model = create_model('1aaa', { digit: 2 })
|
78
|
+
|
79
|
+
assert_not(model.valid?)
|
80
|
+
assert_equal(
|
81
|
+
{ password: ['must contain at least 2 digits'] }, model.errors.messages
|
82
|
+
)
|
32
83
|
end
|
33
84
|
|
34
|
-
def
|
35
|
-
|
36
|
-
refute(ModelWithPassword.new('aaaa').valid?)
|
37
|
-
assert(ModelWithPassword.new('aaa1').valid?)
|
85
|
+
def test_enforces_digit_count_valid
|
86
|
+
assert(create_model('11aa', { digit: 2 }).valid?)
|
38
87
|
end
|
39
88
|
|
40
|
-
def
|
41
|
-
|
42
|
-
|
43
|
-
|
89
|
+
def test_enforces_digits_invalid
|
90
|
+
model = create_model('aaaa', { digits: 1 })
|
91
|
+
|
92
|
+
assert_not(model.valid?)
|
93
|
+
assert_equal(
|
94
|
+
{ password: ['must contain at least one digit'] }, model.errors.messages
|
95
|
+
)
|
44
96
|
end
|
45
97
|
|
46
|
-
def
|
47
|
-
|
48
|
-
refute(ModelWithPassword.new('AAAA').valid?)
|
49
|
-
assert(ModelWithPassword.new('AAAa').valid?)
|
98
|
+
def test_enforces_digits_valid
|
99
|
+
assert(create_model('1aaa', { digits: 1 }).valid?)
|
50
100
|
end
|
51
101
|
|
52
|
-
def
|
53
|
-
|
54
|
-
|
55
|
-
|
102
|
+
def test_enforces_digits_count_invalid
|
103
|
+
model = create_model('1aaa', { digits: 2 })
|
104
|
+
|
105
|
+
assert_not(model.valid?)
|
106
|
+
assert_equal(
|
107
|
+
{ password: ['must contain at least 2 digits'] }, model.errors.messages
|
108
|
+
)
|
109
|
+
end
|
110
|
+
|
111
|
+
def test_enforces_digits_count_valid
|
112
|
+
assert(create_model('11aa', { digits: 2 }).valid?)
|
113
|
+
end
|
114
|
+
|
115
|
+
def test_enforces_lower_invalid
|
116
|
+
model = create_model('AAAA', { lower: 1 })
|
117
|
+
|
118
|
+
assert_not(model.valid?)
|
119
|
+
assert_equal(
|
120
|
+
{ password: ['must contain at least one lower-case letter'] },
|
121
|
+
model.errors.messages
|
122
|
+
)
|
56
123
|
end
|
57
124
|
|
58
|
-
def
|
59
|
-
|
60
|
-
|
61
|
-
|
125
|
+
def test_enforces_lower_valid
|
126
|
+
assert(create_model('aAAA', { lower: 1 }).valid?)
|
127
|
+
end
|
128
|
+
|
129
|
+
def test_enforces_lower_count_invalid
|
130
|
+
model = create_model('aAAA', { lower: 2 })
|
131
|
+
|
132
|
+
assert_not(model.valid?)
|
133
|
+
assert_equal(
|
134
|
+
{ password: ['must contain at least 2 lower-case letters'] },
|
135
|
+
model.errors.messages
|
136
|
+
)
|
137
|
+
end
|
138
|
+
|
139
|
+
def test_enforces_lower_count_valid
|
140
|
+
assert(create_model('aaAA', { lower: 2 }).valid?)
|
141
|
+
end
|
142
|
+
|
143
|
+
def test_enforces_symbol_invalid
|
144
|
+
model = create_model('aaaa', { symbol: 1 })
|
145
|
+
|
146
|
+
assert_not(model.valid?)
|
147
|
+
assert_equal(
|
148
|
+
{ password: ['must contain at least one punctuation mark or symbol'] },
|
149
|
+
model.errors.messages
|
150
|
+
)
|
62
151
|
end
|
63
152
|
|
64
|
-
def
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
153
|
+
def test_enforces_symbol_valid
|
154
|
+
assert(create_model('!aaa', { symbol: 1 }).valid?)
|
155
|
+
end
|
156
|
+
|
157
|
+
def test_enforces_symbol_count_invalid
|
158
|
+
model = create_model('!aaa', { symbol: 2 })
|
159
|
+
|
160
|
+
assert_not(model.valid?)
|
161
|
+
assert_equal(
|
162
|
+
{ password: ['must contain at least 2 punctuation marks or symbols'] },
|
163
|
+
model.errors.messages
|
164
|
+
)
|
165
|
+
end
|
166
|
+
|
167
|
+
def test_enforces_symbol_count_valid
|
168
|
+
assert(create_model('!!aa', { symbol: 2 }).valid?)
|
169
|
+
end
|
170
|
+
|
171
|
+
def test_enforces_symbols_invalid
|
172
|
+
model = create_model('aaaa', { symbols: 1 })
|
173
|
+
|
174
|
+
assert_not(model.valid?)
|
175
|
+
assert_equal(
|
176
|
+
{ password: ['must contain at least one punctuation mark or symbol'] },
|
177
|
+
model.errors.messages
|
178
|
+
)
|
179
|
+
end
|
180
|
+
|
181
|
+
def test_enforces_symbols_valid
|
182
|
+
assert(create_model('!aaa', { symbols: 1 }).valid?)
|
183
|
+
end
|
184
|
+
|
185
|
+
def test_enforces_symbols_count_invalid
|
186
|
+
model = create_model('!aaa', { symbols: 2 })
|
187
|
+
|
188
|
+
assert_not(model.valid?)
|
189
|
+
assert_equal(
|
190
|
+
{ password: ['must contain at least 2 punctuation marks or symbols'] },
|
191
|
+
model.errors.messages
|
192
|
+
)
|
193
|
+
end
|
194
|
+
|
195
|
+
def test_enforces_symbols_count_valid
|
196
|
+
assert(create_model('!!aa', { symbols: 2 }).valid?)
|
197
|
+
end
|
198
|
+
|
199
|
+
def test_enforces_combination_only_lower_invalid
|
200
|
+
model = create_model('aaaa', { lower: 1, upper: 1, digit: 1, symbol: 1 })
|
201
|
+
|
202
|
+
assert_not(model.valid?)
|
203
|
+
assert_equal(
|
204
|
+
{
|
205
|
+
password:
|
206
|
+
[
|
207
|
+
'must contain at least one digit',
|
208
|
+
'must contain at least one punctuation mark or symbol',
|
209
|
+
'must contain at least one upper-case letter'
|
210
|
+
]
|
211
|
+
},
|
212
|
+
model.errors.messages
|
213
|
+
)
|
214
|
+
end
|
215
|
+
|
216
|
+
def test_enforces_combination_only_upper_invalid
|
217
|
+
model = create_model('AAAA', { lower: 1, upper: 1, digit: 1, symbol: 1 })
|
218
|
+
|
219
|
+
assert_not(model.valid?)
|
220
|
+
assert_equal(
|
221
|
+
{
|
222
|
+
password:
|
223
|
+
[
|
224
|
+
'must contain at least one digit',
|
225
|
+
'must contain at least one lower-case letter',
|
226
|
+
'must contain at least one punctuation mark or symbol'
|
227
|
+
]
|
228
|
+
},
|
229
|
+
model.errors.messages
|
230
|
+
)
|
231
|
+
end
|
232
|
+
|
233
|
+
def test_enforces_combination_only_digit_invalid
|
234
|
+
model = create_model('1111', { lower: 1, upper: 1, digit: 1, symbol: 1 })
|
235
|
+
|
236
|
+
assert_not(model.valid?)
|
237
|
+
assert_equal(
|
238
|
+
{
|
239
|
+
password:
|
240
|
+
[
|
241
|
+
'must contain at least one lower-case letter',
|
242
|
+
'must contain at least one punctuation mark or symbol',
|
243
|
+
'must contain at least one upper-case letter'
|
244
|
+
]
|
245
|
+
},
|
246
|
+
model.errors.messages
|
247
|
+
)
|
248
|
+
end
|
249
|
+
|
250
|
+
def test_enforces_combination_only_symbol_invalid
|
251
|
+
model = create_model('!!!!', { lower: 1, upper: 1, digit: 1, symbol: 1 })
|
252
|
+
|
253
|
+
assert_not(model.valid?)
|
254
|
+
assert_equal(
|
255
|
+
{
|
256
|
+
password:
|
257
|
+
[
|
258
|
+
'must contain at least one digit',
|
259
|
+
'must contain at least one lower-case letter',
|
260
|
+
'must contain at least one upper-case letter'
|
261
|
+
]
|
262
|
+
},
|
263
|
+
model.errors.messages
|
264
|
+
)
|
265
|
+
end
|
266
|
+
|
267
|
+
def test_enforces_combination_some_but_not_all_invalid
|
268
|
+
model = create_model('aAa!', { lower: 1, upper: 1, digit: 1, symbol: 1 })
|
269
|
+
|
270
|
+
assert_not(model.valid?)
|
271
|
+
assert_equal(
|
272
|
+
{ password: ['must contain at least one digit'] },
|
273
|
+
model.errors.messages
|
274
|
+
)
|
275
|
+
end
|
276
|
+
|
277
|
+
def test_enforces_combination_all_valid
|
278
|
+
model = create_model('aA1!', { lower: 1, upper: 1, digit: 1, symbol: 1 })
|
279
|
+
|
280
|
+
assert(model.valid?)
|
71
281
|
end
|
72
282
|
end
|
@@ -0,0 +1,146 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'test_helper'
|
4
|
+
|
5
|
+
class TestDatabaseAuthenticatablePatch < ActiveSupport::TestCase
|
6
|
+
def create_user
|
7
|
+
User.create(
|
8
|
+
email: 'bob@microsoft.com',
|
9
|
+
password: 'Password1!',
|
10
|
+
password_confirmation: 'Password1!'
|
11
|
+
) do |user|
|
12
|
+
user.extend(Devise::Models::DatabaseAuthenticatablePatch)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
test 'updates if all params are present and valid' do
|
17
|
+
user = create_user
|
18
|
+
|
19
|
+
assert(
|
20
|
+
user.update_with_password(
|
21
|
+
{
|
22
|
+
current_password: 'Password1!',
|
23
|
+
password: 'Password2!',
|
24
|
+
password_confirmation: 'Password2!'
|
25
|
+
}
|
26
|
+
)
|
27
|
+
)
|
28
|
+
end
|
29
|
+
|
30
|
+
test 'does not update if current_password is missing' do
|
31
|
+
user = create_user
|
32
|
+
|
33
|
+
user.update_with_password(
|
34
|
+
{
|
35
|
+
password: 'Password2!',
|
36
|
+
password_confirmation: 'Password2!'
|
37
|
+
}
|
38
|
+
)
|
39
|
+
|
40
|
+
assert_equal(["Current password can't be blank"], user.errors.full_messages)
|
41
|
+
end
|
42
|
+
|
43
|
+
test 'does not update if current_password is incorrect' do
|
44
|
+
user = create_user
|
45
|
+
|
46
|
+
user.update_with_password(
|
47
|
+
{
|
48
|
+
current_password: 'Password2!',
|
49
|
+
password: 'Password2!',
|
50
|
+
password_confirmation: 'Password2!'
|
51
|
+
}
|
52
|
+
)
|
53
|
+
|
54
|
+
assert_equal(['Current password is invalid'], user.errors.full_messages)
|
55
|
+
end
|
56
|
+
|
57
|
+
test 'does not update if password is missing' do
|
58
|
+
user = create_user
|
59
|
+
|
60
|
+
user.update_with_password(
|
61
|
+
{
|
62
|
+
current_password: 'Password1!',
|
63
|
+
password: '',
|
64
|
+
password_confirmation: 'Password2!'
|
65
|
+
}
|
66
|
+
)
|
67
|
+
|
68
|
+
assert_equal(["Password can't be blank"], user.errors.full_messages)
|
69
|
+
end
|
70
|
+
|
71
|
+
test 'does not update if password is invalid and mismatches confirmation' do
|
72
|
+
user = create_user
|
73
|
+
|
74
|
+
user.update_with_password(
|
75
|
+
{
|
76
|
+
current_password: 'Password1!',
|
77
|
+
password: 'f',
|
78
|
+
password_confirmation: 'Password2!'
|
79
|
+
}
|
80
|
+
)
|
81
|
+
|
82
|
+
assert_equal(
|
83
|
+
[
|
84
|
+
"Password confirmation doesn't match Password",
|
85
|
+
'Password is too short (minimum is 7 characters)',
|
86
|
+
'Password must contain at least one digit',
|
87
|
+
'Password must contain at least one upper-case letter'
|
88
|
+
],
|
89
|
+
user.errors.full_messages
|
90
|
+
)
|
91
|
+
end
|
92
|
+
|
93
|
+
test 'does not update if password is invalid and matches confirmation' do
|
94
|
+
user = create_user
|
95
|
+
|
96
|
+
user.update_with_password(
|
97
|
+
{
|
98
|
+
current_password: 'Password1!',
|
99
|
+
password: 'f',
|
100
|
+
password_confirmation: 'f'
|
101
|
+
}
|
102
|
+
)
|
103
|
+
|
104
|
+
assert_equal(
|
105
|
+
[
|
106
|
+
'Password is too short (minimum is 7 characters)',
|
107
|
+
'Password must contain at least one digit',
|
108
|
+
'Password must contain at least one upper-case letter'
|
109
|
+
],
|
110
|
+
user.errors.full_messages
|
111
|
+
)
|
112
|
+
end
|
113
|
+
|
114
|
+
test 'does not update if password_confirmation is missing' do
|
115
|
+
user = create_user
|
116
|
+
|
117
|
+
user.update_with_password(
|
118
|
+
{
|
119
|
+
current_password: 'Password1!',
|
120
|
+
password: 'Password2!',
|
121
|
+
password_confirmation: ''
|
122
|
+
}
|
123
|
+
)
|
124
|
+
|
125
|
+
assert_equal(
|
126
|
+
["Password confirmation can't be blank"], user.errors.full_messages
|
127
|
+
)
|
128
|
+
end
|
129
|
+
|
130
|
+
test 'does not update if password_confirmation is mismatched' do
|
131
|
+
user = create_user
|
132
|
+
|
133
|
+
user.update_with_password(
|
134
|
+
{
|
135
|
+
current_password: 'Password1!',
|
136
|
+
password: 'Password2!',
|
137
|
+
password_confirmation: 'Password3!'
|
138
|
+
}
|
139
|
+
)
|
140
|
+
|
141
|
+
assert_equal(
|
142
|
+
["Password confirmation doesn't match Password"],
|
143
|
+
user.errors.full_messages
|
144
|
+
)
|
145
|
+
end
|
146
|
+
end
|
data/test/test_helper.rb
CHANGED
@@ -3,6 +3,13 @@
|
|
3
3
|
ENV['RAILS_ENV'] ||= 'test'
|
4
4
|
|
5
5
|
require 'simplecov'
|
6
|
+
|
7
|
+
if ENV['CI']
|
8
|
+
require 'simplecov-lcov'
|
9
|
+
SimpleCov.formatter = SimpleCov::Formatter::LcovFormatter
|
10
|
+
SimpleCov::Formatter::LcovFormatter.config.report_with_single_file = true
|
11
|
+
end
|
12
|
+
|
6
13
|
SimpleCov.start do
|
7
14
|
add_filter 'gemfiles'
|
8
15
|
add_filter 'test/dummy/db'
|
@@ -18,14 +25,6 @@ SimpleCov.start do
|
|
18
25
|
add_group 'Tests', 'test'
|
19
26
|
end
|
20
27
|
|
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
|
28
|
-
|
29
28
|
require 'pry'
|
30
29
|
require 'dummy/config/environment'
|
31
30
|
require 'minitest/autorun'
|
@@ -6,7 +6,7 @@ require 'generators/devise_security/install_generator'
|
|
6
6
|
|
7
7
|
class TestInstallGenerator < Rails::Generators::TestCase
|
8
8
|
tests DeviseSecurity::Generators::InstallGenerator
|
9
|
-
destination File.expand_path('
|
9
|
+
destination File.expand_path('tmp', __dir__)
|
10
10
|
setup :prepare_destination
|
11
11
|
|
12
12
|
test 'Assert all files are properly created' do
|