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
data/config/locales/es.yml
CHANGED
@@ -3,7 +3,19 @@ es:
|
|
3
3
|
messages:
|
4
4
|
taken_in_past: 'la contraseña fue usada previamente, favor elegir otra.'
|
5
5
|
equal_to_current_password: 'tiene que ser diferente a la contraseña actual.'
|
6
|
-
|
6
|
+
password_complexity:
|
7
|
+
digit:
|
8
|
+
one: tiene que contener al menos un digito
|
9
|
+
other: tiene que contener al menos %{count} digitos
|
10
|
+
lower:
|
11
|
+
one: tiene que contener al menos un minúscula
|
12
|
+
other: tiene que contener al menos %{count} minúsculas
|
13
|
+
symbol:
|
14
|
+
one: tiene que contener al menos un signo de puntuación
|
15
|
+
other: tiene que contener al menos %{count} signos de puntuación
|
16
|
+
upper:
|
17
|
+
one: tiene que contener al menos un mayúscula
|
18
|
+
other: tiene que contener al menos %{count} mayúsculas
|
7
19
|
devise:
|
8
20
|
invalid_captcha: 'El captcha ingresado es inválido.'
|
9
21
|
invalid_security_question: 'La respuesta a la pregunta de suguridad fue incorrecta.'
|
@@ -0,0 +1,29 @@
|
|
1
|
+
fr:
|
2
|
+
errors:
|
3
|
+
messages:
|
4
|
+
taken_in_past: a été utilisé trop récemment - s'il vous plaît, choisissez un autre
|
5
|
+
equal_to_current_password: doit être différent du l'actuel
|
6
|
+
password_complexity:
|
7
|
+
digit:
|
8
|
+
one: doit containir en moins d'un chiffre
|
9
|
+
other: doit containir en moins de %{count} chiffres
|
10
|
+
lower:
|
11
|
+
one: doit containir en moins d'un miniscule
|
12
|
+
other: doit containir en moins de %{count} miniscules
|
13
|
+
symbol:
|
14
|
+
one: doit containir en moins d'un signe de ponctuation
|
15
|
+
other: doit containir en moins de %{count} signes de ponctuation
|
16
|
+
upper:
|
17
|
+
one: doit containir en moins d'un majuscule
|
18
|
+
other: doit containir en moins de %{count} majuscules
|
19
|
+
devise:
|
20
|
+
invalid_captcha: Le captcha n'est pas valide
|
21
|
+
invalid_security_question: La réponse à la question de sécurité était invalide
|
22
|
+
paranoid_verify:
|
23
|
+
code_required: Veuillez entrer le code fourni par notre équipe de support
|
24
|
+
password_expired:
|
25
|
+
updated: Votre nouveau mot de passe est enregistré
|
26
|
+
change_required: Votre mot de passe a expiré - s'il vous plaît, choisissez un autre
|
27
|
+
failure:
|
28
|
+
session_limited: Vos identifiants de connexion ont été utilisés dans un autre navigateur. Veuillez vous reconnecter pour continuer dans ce navigateur
|
29
|
+
expired: Votre compte a expiré en raison de l'inactivité. Veuillez contacter l'administrateur du site
|
@@ -0,0 +1,17 @@
|
|
1
|
+
tr:
|
2
|
+
errors:
|
3
|
+
messages:
|
4
|
+
taken_in_past: "daha önce kullanıldı."
|
5
|
+
equal_to_current_password: "mevcut paroladan farklı olmalı."
|
6
|
+
password_format: "büyük, küçük harfler ve sayılar içermeli."
|
7
|
+
devise:
|
8
|
+
invalid_captcha: "Captcha hatalı."
|
9
|
+
invalid_security_question: "Güvenlik sorusunun cevabı yanlış."
|
10
|
+
paranoid_verify:
|
11
|
+
code_required: "Destek ekibimizden aldığınız kodu girin."
|
12
|
+
password_expired:
|
13
|
+
updated: "Yeni parolanız kaydedildi."
|
14
|
+
change_required: "Parolanızın geçerlilik süresi dolmuş. Lütfen parolanızı yenileyin."
|
15
|
+
failure:
|
16
|
+
session_limited: 'Hesabınıza başka bir tarayıcıdan giriş yapılmış. Lütfen devam etmek için yeniden giriş yapın.'
|
17
|
+
expired: 'Hesabınız aktif olarak kullanılmadığı için artık geçerli değil. Lütfen yönetici ile irtibata geçin.'
|
data/devise-security.gemspec
CHANGED
@@ -20,25 +20,25 @@ Gem::Specification.new do |s|
|
|
20
20
|
s.files = `git ls-files`.split("\n")
|
21
21
|
s.test_files = `git ls-files -- test/*`.split("\n")
|
22
22
|
s.require_paths = ['lib']
|
23
|
-
s.required_ruby_version = '>= 2.
|
23
|
+
s.required_ruby_version = '>= 2.3.7'
|
24
24
|
|
25
25
|
if RUBY_VERSION >= '2.4'
|
26
|
-
s.add_runtime_dependency 'rails', '>= 4.
|
26
|
+
s.add_runtime_dependency 'rails', '>= 4.2.0', '< 6.0'
|
27
27
|
else
|
28
|
-
s.add_runtime_dependency 'railties', '>= 4.
|
28
|
+
s.add_runtime_dependency 'railties', '>= 4.2.0', '< 6.0'
|
29
29
|
end
|
30
30
|
s.add_runtime_dependency 'devise', '>= 4.2.0', '< 5.0'
|
31
31
|
|
32
32
|
s.add_development_dependency 'appraisal'
|
33
|
-
s.add_development_dependency 'bundler'
|
34
|
-
s.add_development_dependency 'coveralls'
|
35
|
-
s.add_development_dependency 'easy_captcha'
|
33
|
+
s.add_development_dependency 'bundler'
|
34
|
+
s.add_development_dependency 'coveralls'
|
35
|
+
s.add_development_dependency 'easy_captcha'
|
36
36
|
s.add_development_dependency 'm'
|
37
|
-
s.add_development_dependency 'minitest'
|
37
|
+
s.add_development_dependency 'minitest'
|
38
38
|
s.add_development_dependency 'pry-byebug'
|
39
39
|
s.add_development_dependency 'pry-rescue'
|
40
40
|
s.add_development_dependency 'pry'
|
41
|
-
s.add_development_dependency 'rails_email_validator'
|
42
|
-
s.add_development_dependency 'rubocop', '~> 0'
|
43
|
-
s.add_development_dependency 'sqlite3'
|
41
|
+
s.add_development_dependency 'rails_email_validator'
|
42
|
+
s.add_development_dependency 'rubocop', '~> 0.58.0'
|
43
|
+
s.add_development_dependency 'sqlite3'
|
44
44
|
end
|
data/lib/devise-security.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'active_record'
|
2
4
|
require 'active_support/core_ext/integer'
|
3
5
|
require 'active_support/ordered_hash'
|
@@ -6,13 +8,15 @@ require 'devise'
|
|
6
8
|
|
7
9
|
module Devise
|
8
10
|
|
9
|
-
#
|
11
|
+
# Number of seconds that passwords are valid (e.g 3.months)
|
12
|
+
# Disable pasword expiration with +false+
|
13
|
+
# Expire only on demand with +true+
|
10
14
|
mattr_accessor :expire_password_after
|
11
15
|
@@expire_password_after = 3.months
|
12
16
|
|
13
17
|
# Validate password for strongness
|
14
|
-
mattr_accessor :
|
15
|
-
@@
|
18
|
+
mattr_accessor :password_complexity
|
19
|
+
@@password_complexity = { digit: 1, lower: 1, symbol: 1, upper: 1 }
|
16
20
|
|
17
21
|
# Number of old passwords in archive
|
18
22
|
mattr_accessor :password_archiving_count
|
@@ -23,7 +27,7 @@ module Devise
|
|
23
27
|
@@deny_old_passwords = true
|
24
28
|
|
25
29
|
# enable email validation for :secure_validatable. (true, false, validation_options)
|
26
|
-
# dependency: need an email validator
|
30
|
+
# dependency: need an email validator, see https://github.com/devise-security/devise-security/blob/master/README.md#e-mail-validation
|
27
31
|
mattr_accessor :email_validation
|
28
32
|
@@email_validation = true
|
29
33
|
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
# Updates the last_activity_at fields from the record. Only when the user is active
|
2
4
|
# for authentication and authenticated.
|
3
5
|
# An expiry of the account is only checked on sign in OR on manually setting the
|
@@ -7,4 +9,4 @@ Warden::Manager.after_set_user do |record, warden, options|
|
|
7
9
|
warden.authenticated?(options[:scope]) && record.respond_to?(:update_last_activity!)
|
8
10
|
record.update_last_activity!
|
9
11
|
end
|
10
|
-
end
|
12
|
+
end
|
@@ -1,67 +1,113 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
4
|
-
module Models
|
3
|
+
require 'devise-security/hooks/password_expirable'
|
5
4
|
|
6
|
-
|
7
|
-
|
8
|
-
|
5
|
+
module Devise::Models
|
6
|
+
# PasswordExpirable makes passwords expire after a configurable amount of
|
7
|
+
# time, or on demand.
|
8
|
+
#
|
9
|
+
# == Configuration
|
10
|
+
# Set +expire_password_after+ to the number of seconds a password is valid for
|
11
|
+
# (example: +3.months+). Setting it to +true+ will allow passwords to be expired
|
12
|
+
# on-demand only, and +false+ disables this feature.
|
13
|
+
#
|
14
|
+
# == Expire On-Demand
|
15
|
+
# This is useful to force users to change passwords for complex business reasons.
|
16
|
+
# Call +need_change_password+ to indicate a record needs a new password.
|
17
|
+
module PasswordExpirable
|
18
|
+
extend ActiveSupport::Concern
|
9
19
|
|
10
|
-
|
11
|
-
|
12
|
-
|
20
|
+
included do
|
21
|
+
scope :with_password_change_requested, -> { where(password_changed_at: nil) }
|
22
|
+
scope :without_password_change_requested, -> { where.not(password_changed_at: nil) }
|
23
|
+
scope :with_expired_password, -> { where('password_changed_at is NULL OR password_changed_at < ?', expire_password_after.seconds.ago) }
|
24
|
+
scope :without_expired_password, -> { without_password_change_requested.where('password_changed_at >= ?', expire_password_after.seconds.ago) }
|
25
|
+
before_save :update_password_changed
|
26
|
+
end
|
13
27
|
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
end
|
28
|
+
# Is a password change required?
|
29
|
+
# @return [Boolean]
|
30
|
+
# @return [true] if +password_changed_at+ has not been set or if it is old
|
31
|
+
# enough based on +expire_password_after+ configuration.
|
32
|
+
def need_change_password?
|
33
|
+
password_change_requested? || password_too_old?
|
34
|
+
end
|
22
35
|
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
36
|
+
# Clear the +password_changed_at+ field so that the user will be required to
|
37
|
+
# update their password.
|
38
|
+
# @note Saves the record (without validations)
|
39
|
+
# @return [Boolean]
|
40
|
+
def need_change_password!
|
41
|
+
return unless password_expiration_enabled?
|
42
|
+
need_change_password
|
43
|
+
save(validate: false)
|
44
|
+
end
|
45
|
+
alias expire_password! need_change_password!
|
46
|
+
alias request_password_change! need_change_password!
|
30
47
|
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
48
|
+
# Clear the +password_changed_at+ field so that the user will be required to
|
49
|
+
# update their password.
|
50
|
+
# @note Does not save the record
|
51
|
+
# @return [void]
|
52
|
+
def need_change_password
|
53
|
+
return unless password_expiration_enabled?
|
54
|
+
self.password_changed_at = nil
|
55
|
+
end
|
56
|
+
alias expire_password need_change_password
|
57
|
+
alias request_password_change need_change_password
|
36
58
|
|
37
|
-
|
38
|
-
|
59
|
+
# @return [Integer] number of seconds passwords are valid for
|
60
|
+
# @return [true] passwords are expired 'on demand' only.
|
61
|
+
# @return [false] passwords never expire (this feature is disabled)
|
62
|
+
def expire_password_after
|
63
|
+
self.class.expire_password_after
|
64
|
+
end
|
39
65
|
|
40
|
-
|
41
|
-
|
66
|
+
# When +password_changed_at+ is set to +NULL+ in the database
|
67
|
+
# the user is required to change their password. This only happens
|
68
|
+
# on demand or when the column is first added to the table.
|
69
|
+
# @return [Boolean]
|
70
|
+
def password_change_requested?
|
71
|
+
return false unless password_expiration_enabled?
|
72
|
+
return false if new_record?
|
73
|
+
password_changed_at.nil?
|
74
|
+
end
|
42
75
|
|
43
|
-
|
44
|
-
|
45
|
-
|
76
|
+
# Is this password older than the configured expiration timeout?
|
77
|
+
# @return [Boolean]
|
78
|
+
def password_too_old?
|
79
|
+
return false if new_record?
|
80
|
+
return false unless password_expiration_enabled?
|
81
|
+
return false if expire_password_on_demand?
|
82
|
+
password_changed_at < expire_password_after.seconds.ago
|
83
|
+
end
|
84
|
+
alias password_expired? password_too_old?
|
46
85
|
|
47
|
-
|
86
|
+
private
|
48
87
|
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
88
|
+
# Update +password_changed_at+ for new records and changed passwords.
|
89
|
+
# @note called as a +before_save+ hook
|
90
|
+
def update_password_changed
|
91
|
+
return unless (new_record? || encrypted_password_changed?) && !password_changed_at_changed?
|
92
|
+
self.password_changed_at = Time.zone.now
|
93
|
+
end
|
53
94
|
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
95
|
+
# Enabled if configuration +expire_password_after+ is set to an {Integer},
|
96
|
+
# {Float}, or {true}
|
97
|
+
def password_expiration_enabled?
|
98
|
+
expire_password_after.is_a?(1.class) ||
|
99
|
+
expire_password_after.is_a?(Float) ||
|
100
|
+
expire_password_on_demand?
|
101
|
+
end
|
59
102
|
|
60
|
-
|
61
|
-
|
62
|
-
|
103
|
+
# When +expire_password_after+ is set to +true+ then only expire passwords
|
104
|
+
# on demand.
|
105
|
+
def expire_password_on_demand?
|
106
|
+
expire_password_after.present? && expire_password_after == true
|
63
107
|
end
|
64
108
|
|
109
|
+
module ClassMethods
|
110
|
+
::Devise::Models.config(self, :expire_password_after)
|
111
|
+
end
|
65
112
|
end
|
66
|
-
|
67
113
|
end
|
@@ -1,4 +1,7 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require_relative 'compatibility'
|
4
|
+
require_relative '../validators/password_complexity_validator'
|
2
5
|
|
3
6
|
module Devise
|
4
7
|
module Models
|
@@ -45,8 +48,10 @@ module Devise
|
|
45
48
|
end
|
46
49
|
|
47
50
|
# extra validations
|
48
|
-
validates :email, email: email_validation if email_validation #
|
49
|
-
validates :password,
|
51
|
+
validates :email, email: email_validation if email_validation # see https://github.com/devise-security/devise-security/blob/master/README.md#e-mail-validation
|
52
|
+
validates :password,
|
53
|
+
'devise_security/password_complexity': password_complexity,
|
54
|
+
if: :password_required?
|
50
55
|
|
51
56
|
# don't allow use same password
|
52
57
|
validate :current_equal_password_validation
|
@@ -79,9 +84,10 @@ module Devise
|
|
79
84
|
end
|
80
85
|
|
81
86
|
module ClassMethods
|
82
|
-
Devise::Models.config(self, :
|
87
|
+
Devise::Models.config(self, :password_complexity, :password_length, :email_validation)
|
88
|
+
|
89
|
+
private
|
83
90
|
|
84
|
-
private
|
85
91
|
def has_uniqueness_validation_of_login?
|
86
92
|
validators.any? do |validator|
|
87
93
|
validator.kind_of?(ActiveRecord::Validations::UniquenessValidator) &&
|