devise-security 0.12.0 → 0.13.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.codeclimate.yml +63 -0
- data/.gitignore +2 -0
- data/.mdlrc +1 -0
- data/.rubocop.yml +2 -1
- data/.ruby-version +1 -1
- data/.travis.yml +9 -11
- data/Appraisals +2 -2
- data/README.md +72 -53
- data/app/controllers/devise/paranoid_verification_code_controller.rb +2 -0
- data/app/controllers/devise/password_expired_controller.rb +2 -0
- data/config/locales/de.yml +13 -1
- data/config/locales/en.yml +13 -1
- data/config/locales/es.yml +13 -1
- data/config/locales/fr.yml +29 -0
- data/config/locales/tr.yml +17 -0
- data/devise-security.gemspec +10 -10
- data/gemfiles/{rails_4.1_stable.gemfile → rails_5.2.0.gemfile} +1 -1
- data/lib/devise-security.rb +8 -4
- data/lib/devise-security/controllers/helpers.rb +2 -0
- 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 +2 -0
- data/lib/devise-security/hooks/session_limitable.rb +2 -0
- data/lib/devise-security/models/compatibility.rb +2 -0
- data/lib/devise-security/models/database_authenticatable_patch.rb +2 -0
- data/lib/devise-security/models/expirable.rb +2 -0
- data/lib/devise-security/models/old_password.rb +2 -0
- data/lib/devise-security/models/paranoid_verification.rb +2 -0
- data/lib/devise-security/models/password_archivable.rb +2 -0
- data/lib/devise-security/models/password_expirable.rb +96 -50
- data/lib/devise-security/models/secure_validatable.rb +10 -4
- data/lib/devise-security/models/security_questionable.rb +2 -0
- data/lib/devise-security/models/session_limitable.rb +2 -0
- data/lib/devise-security/orm/active_record.rb +2 -0
- data/lib/devise-security/patches.rb +2 -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 +2 -0
- 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/rails.rb +2 -0
- data/lib/devise-security/routes.rb +2 -0
- data/lib/devise-security/schema.rb +2 -0
- data/lib/devise-security/validators/password_complexity_validator.rb +33 -0
- data/lib/devise-security/version.rb +3 -1
- data/lib/generators/devise_security/install_generator.rb +3 -1
- data/lib/generators/templates/devise-security.rb +9 -3
- data/test/dummy/Rakefile +3 -1
- 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/models/application_record.rb +2 -0
- data/test/dummy/app/models/captcha_user.rb +3 -1
- data/test/dummy/app/models/secure_user.rb +3 -1
- data/test/dummy/app/models/security_question_user.rb +3 -1
- data/test/dummy/app/models/user.rb +2 -0
- data/test/dummy/app/models/widget.rb +2 -0
- data/test/dummy/config.ru +3 -1
- data/test/dummy/config/application.rb +2 -0
- data/test/dummy/config/boot.rb +2 -0
- data/test/dummy/config/environment.rb +2 -0
- data/test/dummy/config/environments/test.rb +2 -0
- data/test/dummy/config/initializers/devise.rb +8 -0
- data/test/dummy/config/initializers/migration_class.rb +2 -0
- data/test/dummy/config/routes.rb +2 -0
- data/test/dummy/db/migrate/20120508165529_create_tables.rb +2 -0
- 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/test_captcha_controller.rb +2 -0
- data/test/test_complexity_validator.rb +60 -0
- data/test/test_helper.rb +19 -8
- data/test/test_install_generator.rb +7 -1
- data/test/test_paranoid_verification.rb +2 -0
- data/test/test_password_archivable.rb +2 -0
- data/test/test_password_expirable.rb +68 -7
- data/test/test_password_expired_controller.rb +2 -0
- data/test/test_secure_validatable.rb +10 -11
- data/test/test_security_question_controller.rb +2 -0
- metadata +32 -39
- data/.circleci/config.yml +0 -41
- data/gemfiles/rails_5.2_rc1.gemfile +0 -8
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Password complexity validator
|
4
|
+
# Options:
|
5
|
+
# - digit: minimum number of digits in the validated string
|
6
|
+
# - lower: minimum number of lower-case letters in the validated string
|
7
|
+
# - symbol: minimum number of punctuation characters or symbols in the validated string
|
8
|
+
# - upper: minimum number of upper-case letters in the validated string
|
9
|
+
class DeviseSecurity::PasswordComplexityValidator < ActiveModel::EachValidator
|
10
|
+
PATTERNS = {
|
11
|
+
digit: /\p{Digit}/,
|
12
|
+
digits: /\p{Digit}/,
|
13
|
+
lower: /\p{Lower}/,
|
14
|
+
upper: /\p{Upper}/,
|
15
|
+
symbol: /\p{Punct}|\p{S}/,
|
16
|
+
symbols: /\p{Punct}|\p{S}/
|
17
|
+
}.freeze
|
18
|
+
|
19
|
+
def validate_each(record, attribute, value)
|
20
|
+
active_pattern_keys.each do |key|
|
21
|
+
minimum = [0, options[key].to_i].max
|
22
|
+
pattern = Regexp.new PATTERNS[key]
|
23
|
+
|
24
|
+
unless (value || '').scan(pattern).size >= minimum
|
25
|
+
record.errors.add attribute, :"password_complexity.#{key}", count: minimum
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def active_pattern_keys
|
31
|
+
options.keys & PATTERNS.keys
|
32
|
+
end
|
33
|
+
end
|
@@ -1,8 +1,10 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module DeviseSecurity
|
2
4
|
module Generators
|
3
5
|
# Generator for Rails to create or append to a Devise initializer.
|
4
6
|
class InstallGenerator < Rails::Generators::Base
|
5
|
-
LOCALES = %w[
|
7
|
+
LOCALES = %w[en es de fr it tr].freeze
|
6
8
|
|
7
9
|
source_root File.expand_path('../../templates', __FILE__)
|
8
10
|
desc 'Install the devise security extension'
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
Devise.setup do |config|
|
2
4
|
# ==> Security Extension
|
3
5
|
# Configure security extension for devise
|
@@ -6,16 +8,20 @@ Devise.setup do |config|
|
|
6
8
|
# config.expire_password_after = false
|
7
9
|
|
8
10
|
# Need 1 char of A-Z, a-z and 0-9
|
9
|
-
# config.
|
11
|
+
# config.password_complexity = { digit: 1, lower: 1, symbol: 1, upper: 1 }
|
10
12
|
|
11
13
|
# How many passwords to keep in archive
|
12
14
|
# config.password_archiving_count = 5
|
13
15
|
|
14
|
-
# Deny old
|
16
|
+
# Deny old passwords (true, false, number_of_old_passwords_to_check)
|
17
|
+
# Examples:
|
18
|
+
# config.deny_old_passwords = false # allow old passwords
|
19
|
+
# config.deny_old_passwords = true # will deny all the old passwords
|
20
|
+
# config.deny_old_passwords = 3 # will deny new passwords that matches with the last 3 passwords
|
15
21
|
# config.deny_old_passwords = true
|
16
22
|
|
17
23
|
# enable email validation for :secure_validatable. (true, false, validation_options)
|
18
|
-
# dependency:
|
24
|
+
# dependency: see https://github.com/devise-security/devise-security/blob/master/README.md#e-mail-validation
|
19
25
|
# config.email_validation = true
|
20
26
|
|
21
27
|
# captcha integration for recover form
|
data/test/dummy/Rakefile
CHANGED
@@ -1,6 +1,8 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
# Add your own tasks in files placed in lib/tasks ending in .rake,
|
2
4
|
# for example lib/tasks/capistrano.rake, and they will automatically be available to Rake.
|
3
5
|
|
4
|
-
require File.expand_path('
|
6
|
+
require File.expand_path('config/application', __dir__)
|
5
7
|
|
6
8
|
Rails.application.load_tasks
|
@@ -1,4 +1,6 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class SecurityQuestionUser < ApplicationRecord
|
2
4
|
self.table_name = 'users'
|
3
5
|
devise :database_authenticatable, :password_archivable, :lockable,
|
4
6
|
:paranoid_verification, :password_expirable, :security_questionable
|
data/test/dummy/config.ru
CHANGED
data/test/dummy/config/boot.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'rails_email_validator'
|
2
4
|
Devise.setup do |config|
|
3
5
|
config.mailer_sender = 'please-change-me-at-config-initializers-devise@example.com'
|
@@ -7,4 +9,10 @@ Devise.setup do |config|
|
|
7
9
|
config.case_insensitive_keys = [:email]
|
8
10
|
|
9
11
|
config.strip_whitespace_keys = [:email]
|
12
|
+
|
13
|
+
config.password_complexity = {
|
14
|
+
digit: 1,
|
15
|
+
lower: 1,
|
16
|
+
upper: 1,
|
17
|
+
}
|
10
18
|
end
|
data/test/dummy/config/routes.rb
CHANGED
@@ -0,0 +1,60 @@
|
|
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_lower
|
41
|
+
ModelWithPassword.validates :password, 'devise_security/password_complexity': { lower: 1 }
|
42
|
+
refute(ModelWithPassword.new('AAAA').valid?)
|
43
|
+
assert(ModelWithPassword.new('AAAa').valid?)
|
44
|
+
end
|
45
|
+
|
46
|
+
def test_enforces_symbol
|
47
|
+
ModelWithPassword.validates :password, 'devise_security/password_complexity': { symbol: 1 }
|
48
|
+
refute(ModelWithPassword.new('aaaa').valid?)
|
49
|
+
assert(ModelWithPassword.new('aaa!').valid?)
|
50
|
+
end
|
51
|
+
|
52
|
+
def test_enforces_combination
|
53
|
+
ModelWithPassword.validates :password, 'devise_security/password_complexity': { lower: 1, upper: 1, digit: 1, symbol: 1 }
|
54
|
+
refute(ModelWithPassword.new('abcd').valid?)
|
55
|
+
refute(ModelWithPassword.new('ABCD').valid?)
|
56
|
+
refute(ModelWithPassword.new('1234').valid?)
|
57
|
+
refute(ModelWithPassword.new('$!,*').valid?)
|
58
|
+
assert(ModelWithPassword.new('aB3*').valid?)
|
59
|
+
end
|
60
|
+
end
|