devise-security 0.12.0 → 0.13.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/.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
         |