devise-security 0.14.2 → 0.17.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/README.md +125 -59
- data/app/controllers/devise/paranoid_verification_code_controller.rb +13 -1
- data/app/controllers/devise/password_expired_controller.rb +24 -6
- data/app/views/devise/paranoid_verification_code/show.html.erb +3 -3
- data/app/views/devise/password_expired/show.html.erb +5 -5
- data/config/locales/bg.yml +41 -0
- data/config/locales/by.yml +49 -0
- data/config/locales/cs.yml +41 -0
- data/config/locales/de.yml +15 -2
- data/config/locales/en.yml +15 -2
- data/config/locales/es.yml +10 -9
- data/config/locales/fa.yml +41 -0
- data/config/locales/fr.yml +1 -0
- data/config/locales/hi.yml +42 -0
- data/config/locales/it.yml +35 -4
- data/config/locales/ja.yml +2 -1
- data/config/locales/nl.yml +41 -0
- data/config/locales/pt.yml +41 -0
- data/config/locales/ru.yml +49 -0
- data/config/locales/tr.yml +1 -0
- data/config/locales/uk.yml +49 -0
- data/config/locales/zh_CN.yml +41 -0
- data/config/locales/zh_TW.yml +41 -0
- data/lib/devise-security/controllers/helpers.rb +59 -50
- data/lib/devise-security/hooks/password_expirable.rb +2 -0
- data/lib/devise-security/hooks/session_limitable.rb +21 -11
- data/lib/devise-security/models/database_authenticatable_patch.rb +15 -5
- data/lib/devise-security/models/password_archivable.rb +2 -2
- data/lib/devise-security/models/password_expirable.rb +5 -1
- data/lib/devise-security/models/secure_validatable.rb +56 -6
- data/lib/devise-security/models/session_limitable.rb +10 -1
- data/lib/devise-security/validators/password_complexity_validator.rb +53 -24
- data/lib/devise-security/version.rb +1 -1
- data/lib/devise-security.rb +13 -5
- data/lib/generators/devise_security/install_generator.rb +3 -3
- data/lib/generators/templates/{devise-security.rb → devise_security.rb} +6 -1
- data/test/controllers/test_paranoid_verification_code_controller.rb +68 -0
- data/test/controllers/test_password_expired_controller.rb +121 -19
- data/test/controllers/test_security_question_controller.rb +16 -40
- data/test/dummy/app/assets/config/manifest.js +3 -0
- data/test/dummy/app/controllers/overrides/paranoid_verification_code_controller.rb +7 -0
- data/test/dummy/app/controllers/overrides/password_expired_controller.rb +7 -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 +1 -2
- 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 +3 -3
- data/test/dummy/config/application.rb +4 -4
- data/test/dummy/config/boot.rb +1 -1
- data/test/dummy/config/environment.rb +1 -1
- data/test/dummy/config/environments/test.rb +3 -13
- data/test/dummy/config/initializers/migration_class.rb +1 -8
- data/test/dummy/config/locales/en.yml +10 -0
- data/test/dummy/config/mongoid.yml +1 -1
- data/test/dummy/config/routes.rb +5 -3
- data/test/dummy/db/migrate/20120508165529_create_tables.rb +3 -3
- 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_email.rb +2 -1
- data/test/dummy/lib/shared_user_without_omniauth.rb +12 -3
- data/test/dummy/lib/shared_verification_fields.rb +1 -0
- data/test/dummy/{app/models/.gitkeep → log/development.log} +0 -0
- data/test/dummy/log/test.log +101533 -0
- data/test/integration/test_password_expirable_workflow.rb +53 -0
- data/test/integration/test_session_limitable_workflow.rb +2 -0
- data/test/orm/active_record.rb +7 -4
- data/test/orm/mongoid.rb +2 -1
- data/test/support/integration_helpers.rb +15 -33
- data/test/support/mongoid.yml +1 -1
- data/test/test_compatibility.rb +2 -0
- data/test/test_complexity_validator.rb +250 -29
- data/test/test_database_authenticatable_patch.rb +146 -0
- data/test/test_helper.rb +12 -6
- data/test/test_install_generator.rb +12 -2
- data/test/test_paranoid_verification.rb +0 -1
- data/test/test_password_archivable.rb +34 -11
- data/test/test_password_expirable.rb +26 -26
- data/test/test_secure_validatable.rb +292 -50
- data/test/test_secure_validatable_overrides.rb +185 -0
- data/test/test_session_limitable.rb +27 -1
- data/test/tmp/config/initializers/devise_security.rb +49 -0
- data/test/tmp/config/locales/devise.security_extension.by.yml +49 -0
- data/test/tmp/config/locales/devise.security_extension.cs.yml +41 -0
- data/test/tmp/config/locales/devise.security_extension.de.yml +41 -0
- data/test/tmp/config/locales/devise.security_extension.en.yml +42 -0
- data/test/tmp/config/locales/devise.security_extension.es.yml +30 -0
- data/test/tmp/config/locales/devise.security_extension.fa.yml +41 -0
- data/test/tmp/config/locales/devise.security_extension.fr.yml +30 -0
- data/test/tmp/config/locales/devise.security_extension.hi.yml +42 -0
- data/test/tmp/config/locales/devise.security_extension.it.yml +41 -0
- data/test/tmp/config/locales/devise.security_extension.ja.yml +30 -0
- data/test/tmp/config/locales/devise.security_extension.nl.yml +41 -0
- data/test/tmp/config/locales/devise.security_extension.pt.yml +41 -0
- data/test/tmp/config/locales/devise.security_extension.ru.yml +49 -0
- data/test/tmp/config/locales/devise.security_extension.tr.yml +18 -0
- data/test/tmp/config/locales/devise.security_extension.uk.yml +49 -0
- data/test/tmp/config/locales/devise.security_extension.zh_CN.yml +41 -0
- data/test/tmp/config/locales/devise.security_extension.zh_TW.yml +41 -0
- metadata +168 -132
- data/.codeclimate.yml +0 -63
- data/.document +0 -5
- data/.gitignore +0 -43
- data/.mdlrc +0 -1
- data/.rubocop.yml +0 -64
- data/.ruby-version +0 -1
- data/.travis.yml +0 -39
- data/Appraisals +0 -35
- data/Gemfile +0 -10
- data/Rakefile +0 -27
- data/devise-security.gemspec +0 -50
- data/gemfiles/rails_4.2_stable.gemfile +0 -16
- data/gemfiles/rails_5.0_stable.gemfile +0 -15
- data/gemfiles/rails_5.1_stable.gemfile +0 -15
- data/gemfiles/rails_5.2_stable.gemfile +0 -15
- data/gemfiles/rails_6.0_beta.gemfile +0 -15
- data/lib/devise-security/orm/active_record.rb +0 -20
- data/lib/devise-security/schema.rb +0 -66
- data/test/dummy/app/models/secure_user.rb +0 -9
@@ -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
|
data/test/orm/active_record.rb
CHANGED
@@ -1,11 +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
|
-
|
8
|
-
|
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
|
9
12
|
end
|
10
13
|
|
11
14
|
DatabaseCleaner[:active_record].strategy = :transaction
|
data/test/orm/mongoid.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require 'mongoid/version'
|
4
|
+
require 'database_cleaner-mongoid'
|
4
5
|
|
5
6
|
Mongoid.configure do |config|
|
6
7
|
config.load!('test/support/mongoid.yml', Rails.env)
|
@@ -8,5 +9,5 @@ Mongoid.configure do |config|
|
|
8
9
|
config.include_root_in_json = true
|
9
10
|
end
|
10
11
|
|
11
|
-
DatabaseCleaner[:mongoid].strategy = :
|
12
|
+
DatabaseCleaner[:mongoid].strategy = :deletion
|
12
13
|
ORMInvalidRecordException = Mongoid::Errors::Validations
|
@@ -1,47 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module IntegrationHelpers
|
2
4
|
# login the user. This will exercise all the Warden Hooks
|
3
5
|
# @param user [User]
|
4
6
|
# @param session [ActionDispatch::Integration::Session]
|
5
7
|
# @return [void]
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
}
|
14
|
-
}
|
15
|
-
else
|
16
|
-
session.post new_user_session_path, {
|
17
|
-
user: {
|
18
|
-
email: user.email,
|
19
|
-
password: user.password
|
20
|
-
}
|
21
|
-
}
|
22
|
-
end
|
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
|
+
}
|
23
15
|
end
|
24
16
|
|
25
17
|
# attempt to login the user with a bad password. This will exercise all the Warden Hooks
|
26
18
|
# @param user [User]
|
27
19
|
# @param session [ActionDispatch::Integration::Session]
|
28
20
|
# @return [void]
|
29
|
-
# @note accounts for differences in the integration test API between rails versions
|
30
21
|
def failed_sign_in(user, session)
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
}
|
38
|
-
else
|
39
|
-
session.post new_user_session_path, {
|
40
|
-
user: {
|
41
|
-
email: user.email,
|
42
|
-
password: 'bad-password'
|
43
|
-
}
|
44
|
-
}
|
45
|
-
end
|
22
|
+
session.post new_user_session_path, params: {
|
23
|
+
user: {
|
24
|
+
email: user.email,
|
25
|
+
password: 'bad-password',
|
26
|
+
},
|
27
|
+
}
|
46
28
|
end
|
47
29
|
end
|
data/test/support/mongoid.yml
CHANGED
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,46 +17,265 @@ 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
|
+
model.errors.messages,
|
41
|
+
{ password: ["must contain at least one upper-case letter"] }
|
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
|
+
model.errors.messages,
|
55
|
+
{ password: ["must contain at least 2 upper-case letters"] }
|
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
|
+
model.errors.messages, { password: ["must contain at least one digit"] }
|
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
|
+
model.errors.messages, { password: ["must contain at least 2 digits"] }
|
82
|
+
)
|
83
|
+
end
|
84
|
+
|
85
|
+
def test_enforces_digit_count_valid
|
86
|
+
assert(create_model('11aa', { digit: 2 }).valid?)
|
87
|
+
end
|
88
|
+
|
89
|
+
def test_enforces_digits_invalid
|
90
|
+
model = create_model('aaaa', { digits: 1 })
|
91
|
+
|
92
|
+
assert_not(model.valid?)
|
93
|
+
assert_equal(
|
94
|
+
model.errors.messages, { password: ["must contain at least one digit"] }
|
95
|
+
)
|
96
|
+
end
|
97
|
+
|
98
|
+
def test_enforces_digits_valid
|
99
|
+
assert(create_model('1aaa', { digits: 1 }).valid?)
|
100
|
+
end
|
101
|
+
|
102
|
+
def test_enforces_digits_count_invalid
|
103
|
+
model = create_model('1aaa', { digits: 2 })
|
104
|
+
|
105
|
+
assert_not(model.valid?)
|
106
|
+
assert_equal(
|
107
|
+
model.errors.messages, { password: ["must contain at least 2 digits"] }
|
108
|
+
)
|
32
109
|
end
|
33
110
|
|
34
|
-
def
|
35
|
-
|
36
|
-
refute(ModelWithPassword.new('aaaa').valid?)
|
37
|
-
assert(ModelWithPassword.new('aaa1').valid?)
|
111
|
+
def test_enforces_digits_count_valid
|
112
|
+
assert(create_model('11aa', { digits: 2 }).valid?)
|
38
113
|
end
|
39
114
|
|
40
|
-
def
|
41
|
-
|
42
|
-
|
43
|
-
|
115
|
+
def test_enforces_lower_invalid
|
116
|
+
model = create_model('AAAA', { lower: 1 })
|
117
|
+
|
118
|
+
assert_not(model.valid?)
|
119
|
+
assert_equal(
|
120
|
+
model.errors.messages,
|
121
|
+
{ password: ["must contain at least one lower-case letter"] }
|
122
|
+
)
|
44
123
|
end
|
45
124
|
|
46
|
-
def
|
47
|
-
|
48
|
-
refute(ModelWithPassword.new('aaaa').valid?)
|
49
|
-
assert(ModelWithPassword.new('aaa!').valid?)
|
125
|
+
def test_enforces_lower_valid
|
126
|
+
assert(create_model('aAAA', { lower: 1 }).valid?)
|
50
127
|
end
|
51
128
|
|
52
|
-
def
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
129
|
+
def test_enforces_lower_count_invalid
|
130
|
+
model = create_model('aAAA', { lower: 2 })
|
131
|
+
|
132
|
+
assert_not(model.valid?)
|
133
|
+
assert_equal(
|
134
|
+
model.errors.messages,
|
135
|
+
{ password: ["must contain at least 2 lower-case letters"] }
|
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
|
+
model.errors.messages,
|
149
|
+
{ password: ["must contain at least one punctuation mark or symbol"] }
|
150
|
+
)
|
151
|
+
end
|
152
|
+
|
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
|
+
model.errors.messages,
|
163
|
+
{ password: ["must contain at least 2 punctuation marks or symbols"] }
|
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
|
+
model.errors.messages,
|
177
|
+
{ password: ["must contain at least one punctuation mark or symbol"] }
|
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
|
+
model.errors.messages,
|
191
|
+
{ password: ["must contain at least 2 punctuation marks or symbols"] }
|
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
|
+
model.errors.messages,
|
205
|
+
{
|
206
|
+
password:
|
207
|
+
[
|
208
|
+
"must contain at least one digit",
|
209
|
+
"must contain at least one punctuation mark or symbol",
|
210
|
+
"must contain at least one upper-case letter"
|
211
|
+
]
|
212
|
+
}
|
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
|
+
model.errors.messages,
|
222
|
+
{
|
223
|
+
password:
|
224
|
+
[
|
225
|
+
"must contain at least one digit",
|
226
|
+
"must contain at least one lower-case letter",
|
227
|
+
"must contain at least one punctuation mark or symbol"
|
228
|
+
]
|
229
|
+
}
|
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
|
+
model.errors.messages,
|
239
|
+
{
|
240
|
+
password:
|
241
|
+
[
|
242
|
+
"must contain at least one lower-case letter",
|
243
|
+
"must contain at least one punctuation mark or symbol",
|
244
|
+
"must contain at least one upper-case letter"
|
245
|
+
]
|
246
|
+
}
|
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
|
+
model.errors.messages,
|
256
|
+
{
|
257
|
+
password:
|
258
|
+
[
|
259
|
+
"must contain at least one digit",
|
260
|
+
"must contain at least one lower-case letter",
|
261
|
+
"must contain at least one upper-case letter"
|
262
|
+
]
|
263
|
+
}
|
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
|
+
model.errors.messages, { password: ["must contain at least one digit"] }
|
273
|
+
)
|
274
|
+
end
|
275
|
+
|
276
|
+
def test_enforces_combination_all_valid
|
277
|
+
model = create_model('aA1!', { lower: 1, upper: 1, digit: 1, symbol: 1 })
|
278
|
+
|
279
|
+
assert(model.valid?)
|
59
280
|
end
|
60
281
|
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
|
+
result = 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
|
+
result = 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,12 +25,6 @@ SimpleCov.start do
|
|
18
25
|
add_group 'Tests', 'test'
|
19
26
|
end
|
20
27
|
|
21
|
-
if ENV['CI']
|
22
|
-
require 'coveralls'
|
23
|
-
SimpleCov.formatter = Coveralls::SimpleCov::Formatter
|
24
|
-
Coveralls.wear!
|
25
|
-
end
|
26
|
-
|
27
28
|
require 'pry'
|
28
29
|
require 'dummy/config/environment'
|
29
30
|
require 'minitest/autorun'
|
@@ -31,6 +32,11 @@ require 'rails/test_help'
|
|
31
32
|
require 'devise-security'
|
32
33
|
require 'database_cleaner'
|
33
34
|
require "orm/#{DEVISE_ORM}"
|
35
|
+
|
36
|
+
# Controller testing is the way that Devise itself tests the functionality of
|
37
|
+
# controller, even though it has been deprecated in favor of request tests.
|
38
|
+
require 'rails-controller-testing'
|
39
|
+
Rails::Controller::Testing.install
|
34
40
|
require 'support/integration_helpers'
|
35
41
|
|
36
42
|
class Minitest::Test
|
@@ -6,18 +6,28 @@ 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
|
13
13
|
run_generator
|
14
|
-
assert_file 'config/initializers/
|
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'
|
15
17
|
assert_file 'config/locales/devise.security_extension.de.yml'
|
16
18
|
assert_file 'config/locales/devise.security_extension.en.yml'
|
17
19
|
assert_file 'config/locales/devise.security_extension.es.yml'
|
20
|
+
assert_file 'config/locales/devise.security_extension.fa.yml'
|
18
21
|
assert_file 'config/locales/devise.security_extension.fr.yml'
|
22
|
+
assert_file 'config/locales/devise.security_extension.hi.yml'
|
19
23
|
assert_file 'config/locales/devise.security_extension.it.yml'
|
20
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'
|
21
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'
|
22
32
|
end
|
23
33
|
end
|