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.
- checksums.yaml +4 -4
- data/README.md +186 -63
- data/app/controllers/devise/paranoid_verification_code_controller.rb +2 -0
- data/app/controllers/devise/password_expired_controller.rb +13 -6
- data/app/views/devise/paranoid_verification_code/show.html.erb +4 -4
- data/app/views/devise/password_expired/show.html.erb +6 -6
- data/config/locales/by.yml +49 -0
- data/config/locales/cs.yml +41 -0
- data/config/locales/de.yml +30 -7
- data/config/locales/en.yml +25 -1
- data/config/locales/es.yml +19 -6
- data/config/locales/fa.yml +41 -0
- data/config/locales/fr.yml +30 -0
- data/config/locales/hi.yml +42 -0
- data/config/locales/it.yml +35 -4
- data/config/locales/ja.yml +30 -0
- 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 +18 -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 +61 -50
- data/lib/devise-security/hooks/expirable.rb +3 -1
- data/lib/devise-security/hooks/paranoid_verification.rb +2 -0
- data/lib/devise-security/hooks/password_expirable.rb +4 -0
- data/lib/devise-security/hooks/session_limitable.rb +31 -14
- data/lib/devise-security/models/active_record/old_password.rb +5 -0
- data/lib/devise-security/models/compatibility/active_record_patch.rb +40 -0
- data/lib/devise-security/models/compatibility/mongoid_patch.rb +31 -0
- data/lib/devise-security/models/compatibility.rb +8 -15
- data/lib/devise-security/models/database_authenticatable_patch.rb +3 -1
- data/lib/devise-security/models/expirable.rb +8 -2
- data/lib/devise-security/models/mongoid/old_password.rb +21 -0
- data/lib/devise-security/models/paranoid_verification.rb +2 -0
- data/lib/devise-security/models/password_archivable.rb +18 -7
- data/lib/devise-security/models/password_expirable.rb +103 -48
- data/lib/devise-security/models/secure_validatable.rb +26 -6
- data/lib/devise-security/models/security_questionable.rb +2 -0
- data/lib/devise-security/models/session_limitable.rb +19 -2
- data/lib/devise-security/orm/mongoid.rb +7 -0
- data/lib/devise-security/patches/confirmations_controller_captcha.rb +2 -0
- data/lib/devise-security/patches/confirmations_controller_security_question.rb +2 -0
- data/lib/devise-security/patches/controller_captcha.rb +2 -0
- data/lib/devise-security/patches/controller_security_question.rb +3 -1
- data/lib/devise-security/patches/passwords_controller_captcha.rb +2 -0
- data/lib/devise-security/patches/passwords_controller_security_question.rb +2 -0
- data/lib/devise-security/patches/registrations_controller_captcha.rb +2 -0
- data/lib/devise-security/patches/sessions_controller_captcha.rb +2 -0
- data/lib/devise-security/patches/unlocks_controller_captcha.rb +2 -0
- data/lib/devise-security/patches/unlocks_controller_security_question.rb +2 -0
- data/lib/devise-security/patches.rb +2 -0
- data/lib/devise-security/rails.rb +2 -0
- data/lib/devise-security/routes.rb +2 -0
- data/lib/devise-security/validators/password_complexity_validator.rb +35 -0
- data/lib/devise-security/version.rb +3 -1
- data/lib/devise-security.rb +16 -10
- data/lib/generators/devise_security/install_generator.rb +5 -3
- data/lib/generators/templates/devise_security.rb +47 -0
- data/test/{test_captcha_controller.rb → controllers/test_captcha_controller.rb} +2 -0
- data/test/controllers/test_password_expired_controller.rb +110 -0
- data/test/controllers/test_security_question_controller.rb +60 -0
- data/test/dummy/Rakefile +3 -1
- data/test/dummy/app/assets/config/manifest.js +3 -0
- data/test/dummy/app/controllers/application_controller.rb +2 -0
- data/test/dummy/app/controllers/captcha/sessions_controller.rb +2 -0
- data/test/dummy/app/controllers/security_question/unlocks_controller.rb +2 -0
- data/test/dummy/app/controllers/widgets_controller.rb +6 -0
- data/test/dummy/app/models/application_record.rb +10 -2
- data/test/dummy/app/models/application_user_record.rb +11 -0
- data/test/dummy/app/models/captcha_user.rb +7 -2
- data/test/dummy/app/models/mongoid/confirmable_fields.rb +13 -0
- data/test/dummy/app/models/mongoid/database_authenticable_fields.rb +17 -0
- data/test/dummy/app/models/mongoid/expirable_fields.rb +11 -0
- data/test/dummy/app/models/mongoid/lockable_fields.rb +13 -0
- data/test/dummy/app/models/mongoid/mappings.rb +13 -0
- data/test/dummy/app/models/mongoid/omniauthable_fields.rb +11 -0
- data/test/dummy/app/models/mongoid/paranoid_verification_fields.rb +10 -0
- data/test/dummy/app/models/mongoid/password_archivable_fields.rb +9 -0
- data/test/dummy/app/models/mongoid/password_expirable_fields.rb +10 -0
- data/test/dummy/app/models/mongoid/recoverable_fields.rb +11 -0
- data/test/dummy/app/models/mongoid/registerable_fields.rb +19 -0
- data/test/dummy/app/models/mongoid/rememberable_fields.rb +10 -0
- data/test/dummy/app/models/mongoid/secure_validatable_fields.rb +11 -0
- data/test/dummy/app/models/mongoid/security_questionable_fields.rb +13 -0
- data/test/dummy/app/models/mongoid/session_limitable_fields.rb +10 -0
- data/test/dummy/app/models/mongoid/timeoutable_fields.rb +9 -0
- data/test/dummy/app/models/mongoid/trackable_fields.rb +14 -0
- data/test/dummy/app/models/mongoid/validatable_fields.rb +7 -0
- data/test/dummy/app/models/secure_user.rb +7 -1
- data/test/dummy/app/models/security_question_user.rb +9 -4
- data/test/dummy/app/models/user.rb +15 -0
- data/test/dummy/app/models/widget.rb +6 -0
- data/test/dummy/app/mongoid/admin.rb +31 -0
- data/test/dummy/app/mongoid/one_user.rb +58 -0
- data/test/dummy/app/mongoid/shim.rb +25 -0
- data/test/dummy/app/mongoid/user_on_engine.rb +41 -0
- data/test/dummy/app/mongoid/user_on_main_app.rb +41 -0
- data/test/dummy/app/mongoid/user_with_validations.rb +37 -0
- data/test/dummy/app/mongoid/user_without_email.rb +35 -0
- data/test/dummy/config/application.rb +13 -7
- data/test/dummy/config/boot.rb +2 -0
- data/test/dummy/config/environment.rb +2 -0
- data/test/dummy/config/environments/test.rb +5 -13
- data/test/dummy/config/initializers/devise.rb +10 -3
- data/test/dummy/config/initializers/migration_class.rb +3 -6
- data/test/dummy/config/mongoid.yml +6 -0
- data/test/dummy/config/routes.rb +6 -3
- data/test/dummy/config.ru +3 -1
- data/test/dummy/db/migrate/20120508165529_create_tables.rb +13 -2
- data/test/dummy/db/migrate/20150402165590_add_verification_columns.rb +2 -0
- data/test/dummy/db/migrate/20150407162345_add_verification_attempt_column.rb +2 -0
- data/test/dummy/db/migrate/20160320162345_add_security_questions_fields.rb +2 -0
- data/test/dummy/db/migrate/20180318103603_add_expireable_columns.rb +2 -0
- data/test/dummy/db/migrate/20180318105329_add_confirmable_columns.rb +2 -0
- data/test/dummy/db/migrate/20180318105732_add_rememberable_columns.rb +2 -0
- data/test/dummy/db/migrate/20180318111336_add_recoverable_columns.rb +2 -0
- data/test/dummy/db/migrate/20180319114023_add_widget.rb +2 -0
- data/test/dummy/lib/shared_expirable_columns.rb +14 -0
- data/test/dummy/lib/shared_security_questions_fields.rb +16 -0
- data/test/dummy/lib/shared_user.rb +32 -0
- data/test/dummy/lib/shared_user_with_password_verification.rb +13 -0
- data/test/dummy/lib/shared_user_without_email.rb +28 -0
- data/test/dummy/lib/shared_user_without_omniauth.rb +15 -0
- data/test/dummy/lib/shared_verification_fields.rb +15 -0
- data/test/dummy/log/development.log +883 -0
- data/test/dummy/log/test.log +21689 -0
- data/test/integration/test_password_expirable_workflow.rb +53 -0
- data/test/integration/test_session_limitable_workflow.rb +67 -0
- data/test/orm/active_record.rb +15 -0
- data/test/orm/mongoid.rb +13 -0
- data/test/support/integration_helpers.rb +29 -0
- data/test/support/mongoid.yml +6 -0
- data/test/test_compatibility.rb +13 -0
- data/test/test_complexity_validator.rb +72 -0
- data/test/test_helper.rb +42 -9
- data/test/test_install_generator.rb +19 -2
- data/test/test_paranoid_verification.rb +2 -0
- data/test/test_password_archivable.rb +8 -7
- data/test/test_password_expirable.rb +70 -7
- data/test/test_secure_validatable.rb +97 -21
- data/test/test_session_limitable.rb +57 -0
- data/{lib/generators/templates → test/tmp/config/initializers}/devise-security.rb +12 -3
- 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 +39 -0
- data/test/tmp/config/locales/devise.security_extension.en.yml +41 -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 +235 -110
- data/.circleci/config.yml +0 -41
- data/.document +0 -5
- data/.gitignore +0 -40
- data/.rubocop.yml +0 -63
- data/.ruby-version +0 -1
- data/.travis.yml +0 -25
- data/Appraisals +0 -19
- data/Gemfile +0 -3
- data/Rakefile +0 -28
- data/devise-security.gemspec +0 -44
- data/gemfiles/rails_4.1_stable.gemfile +0 -8
- data/gemfiles/rails_4.2_stable.gemfile +0 -8
- data/gemfiles/rails_5.0_stable.gemfile +0 -8
- data/gemfiles/rails_5.1_stable.gemfile +0 -8
- data/gemfiles/rails_5.2_rc1.gemfile +0 -8
- data/lib/devise-security/models/old_password.rb +0 -4
- data/lib/devise-security/orm/active_record.rb +0 -18
- data/lib/devise-security/schema.rb +0 -64
- data/test/dummy/app/models/.gitkeep +0 -0
- data/test/test_password_expired_controller.rb +0 -44
- 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
|
data/test/orm/mongoid.rb
ADDED
@@ -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,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
|
-
|
5
|
-
|
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
|
-
|
13
|
-
|
14
|
-
|
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
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
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/
|
13
|
-
assert_file 'config/locales/devise.security_extension.
|
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,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(
|
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
|
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(
|
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(
|
72
|
+
assert_raises(ORMInvalidRecordException) { set_password(user, 'Password2') }
|
72
73
|
|
73
|
-
assert_raises(
|
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.
|
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 '
|
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
|
-
|
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 '
|
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.
|
85
|
+
4.months
|
25
86
|
end
|
26
87
|
end
|
27
|
-
user.password_changed_at = Time.now.ago(3.
|
88
|
+
user.password_changed_at = Time.now.ago(3.months)
|
28
89
|
refute user.need_change_password?
|
29
|
-
user.
|
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
|