devise_security_extension 0.9.2 → 0.10.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/.gitignore +39 -0
- data/.rubocop.yml +38 -0
- data/Gemfile +1 -5
- data/Gemfile.lock +144 -141
- data/README.md +37 -11
- data/Rakefile +13 -29
- data/app/controllers/devise/paranoid_verification_code_controller.rb +42 -0
- data/app/controllers/devise/password_expired_controller.rb +16 -7
- data/app/views/devise/paranoid_verification_code/show.html.erb +10 -0
- data/config/locales/de.yml +2 -0
- data/config/locales/en.yml +6 -4
- data/config/locales/it.yml +10 -0
- data/devise_security_extension.gemspec +24 -104
- data/lib/devise_security_extension.rb +18 -8
- data/lib/devise_security_extension/controllers/helpers.rb +39 -6
- data/lib/devise_security_extension/hooks/paranoid_verification.rb +5 -0
- data/lib/devise_security_extension/hooks/session_limitable.rb +1 -0
- data/lib/devise_security_extension/models/paranoid_verification.rb +35 -0
- data/lib/devise_security_extension/models/password_archivable.rb +3 -7
- data/lib/devise_security_extension/models/password_expirable.rb +9 -5
- data/lib/devise_security_extension/patches/confirmations_controller_captcha.rb +3 -1
- data/lib/devise_security_extension/patches/confirmations_controller_security_question.rb +3 -1
- data/lib/devise_security_extension/patches/passwords_controller_captcha.rb +3 -1
- data/lib/devise_security_extension/patches/passwords_controller_security_question.rb +3 -1
- data/lib/devise_security_extension/patches/registrations_controller_captcha.rb +5 -3
- data/lib/devise_security_extension/patches/sessions_controller_captcha.rb +5 -3
- data/lib/devise_security_extension/patches/unlocks_controller_captcha.rb +3 -1
- data/lib/devise_security_extension/patches/unlocks_controller_security_question.rb +3 -1
- data/lib/devise_security_extension/routes.rb +4 -0
- data/lib/devise_security_extension/version.rb +3 -0
- data/lib/generators/devise_security_extension/install_generator.rb +16 -33
- data/lib/generators/templates/devise_security_extension.rb +38 -0
- data/test/dummy/Rakefile +6 -0
- data/test/dummy/app/controllers/application_controller.rb +2 -0
- data/test/dummy/app/controllers/foos_controller.rb +0 -0
- data/test/dummy/app/models/user.rb +2 -1
- data/test/dummy/app/views/foos/index.html.erb +0 -0
- data/test/dummy/config/application.rb +4 -2
- data/test/dummy/config/boot.rb +1 -1
- data/test/dummy/config/environments/test.rb +4 -2
- data/test/dummy/config/initializers/devise.rb +4 -4
- data/test/dummy/config/routes.rb +6 -0
- data/test/dummy/config/secrets.yml +3 -0
- data/test/dummy/db/migrate/20120508165529_create_tables.rb +4 -4
- data/test/dummy/db/migrate/20150402165590_add_verification_columns.rb +11 -0
- data/test/dummy/db/migrate/20150407162345_add_verification_attempt_column.rb +9 -0
- data/test/test_helper.rb +10 -0
- data/test/test_install_generator.rb +16 -0
- data/test/test_paranoid_verification.rb +124 -0
- data/test/test_password_archivable.rb +35 -21
- data/test/test_password_expired_controller.rb +24 -0
- metadata +104 -34
- data/VERSION +0 -1
- data/lib/devise_security_extension/models/security_question.rb +0 -3
- data/test/helper.rb +0 -22
- data/test/test_devise_security_extension.rb +0 -6
@@ -19,6 +19,7 @@ Warden::Manager.after_set_user :only => :fetch do |record, warden, options|
|
|
19
19
|
|
20
20
|
if record.respond_to?(:unique_session_id) && warden.authenticated?(scope) && options[:store] != false
|
21
21
|
if record.unique_session_id != warden.session(scope)['unique_session_id'] && !env['devise.skip_session_limitable']
|
22
|
+
warden.raw_session.clear
|
22
23
|
warden.logout(scope)
|
23
24
|
throw :warden, :scope => scope, :message => :session_limited
|
24
25
|
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'devise_security_extension/hooks/paranoid_verification'
|
2
|
+
|
3
|
+
module Devise
|
4
|
+
module Models
|
5
|
+
# PasswordExpirable takes care of change password after
|
6
|
+
module ParanoidVerification
|
7
|
+
extend ActiveSupport::Concern
|
8
|
+
|
9
|
+
def need_paranoid_verification?
|
10
|
+
!!paranoid_verification_code
|
11
|
+
end
|
12
|
+
|
13
|
+
def verify_code(code)
|
14
|
+
attempt = paranoid_verification_attempt
|
15
|
+
|
16
|
+
if (attempt += 1) >= Devise.paranoid_code_regenerate_after_attempt
|
17
|
+
generate_paranoid_code
|
18
|
+
elsif code == paranoid_verification_code
|
19
|
+
attempt = 0
|
20
|
+
update_without_password paranoid_verification_code: nil, paranoid_verified_at: Time.now, paranoid_verification_attempt: attempt
|
21
|
+
else
|
22
|
+
update_without_password paranoid_verification_attempt: attempt
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def paranoid_attempts_remaining
|
27
|
+
Devise.paranoid_code_regenerate_after_attempt - paranoid_verification_attempt
|
28
|
+
end
|
29
|
+
|
30
|
+
def generate_paranoid_code
|
31
|
+
update_without_password paranoid_verification_code: Devise.verification_code_generator.call(), paranoid_verification_attempt: 0
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -31,14 +31,13 @@ module Devise
|
|
31
31
|
old_passwords_including_cur_change.each do |old_password|
|
32
32
|
dummy = self.class.new
|
33
33
|
dummy.encrypted_password = old_password.encrypted_password
|
34
|
-
dummy.password_salt = old_password.password_salt if dummy.respond_to?(:password_salt)
|
35
34
|
return true if dummy.valid_password?(self.password)
|
36
35
|
end
|
37
36
|
end
|
38
37
|
|
39
38
|
false
|
40
39
|
end
|
41
|
-
|
40
|
+
|
42
41
|
def password_changed_to_same?
|
43
42
|
pass_change = encrypted_password_change
|
44
43
|
pass_change && pass_change.first == pass_change.last
|
@@ -61,12 +60,9 @@ module Devise
|
|
61
60
|
end
|
62
61
|
end
|
63
62
|
end
|
64
|
-
|
63
|
+
|
65
64
|
def old_password_params
|
66
|
-
|
67
|
-
self.password_salt_change.first
|
68
|
-
end
|
69
|
-
{ :encrypted_password => self.encrypted_password_change.first, :password_salt => salt_change }
|
65
|
+
{ encrypted_password: self.encrypted_password_change.first }
|
70
66
|
end
|
71
67
|
|
72
68
|
module ClassMethods
|
@@ -13,8 +13,8 @@ module Devise
|
|
13
13
|
|
14
14
|
# is an password change required?
|
15
15
|
def need_change_password?
|
16
|
-
if self.
|
17
|
-
self.password_changed_at.nil? or self.password_changed_at < self.
|
16
|
+
if self.expire_password_after.is_a? Fixnum or self.expire_password_after.is_a? Float
|
17
|
+
self.password_changed_at.nil? or self.password_changed_at < self.expire_password_after.ago
|
18
18
|
else
|
19
19
|
false
|
20
20
|
end
|
@@ -22,7 +22,7 @@ module Devise
|
|
22
22
|
|
23
23
|
# set a fake datetime so a password change is needed and save the record
|
24
24
|
def need_change_password!
|
25
|
-
if self.
|
25
|
+
if self.expire_password_after.is_a? Fixnum or self.expire_password_after.is_a? Float
|
26
26
|
need_change_password
|
27
27
|
self.save(:validate => false)
|
28
28
|
end
|
@@ -30,8 +30,8 @@ module Devise
|
|
30
30
|
|
31
31
|
# set a fake datetime so a password change is needed
|
32
32
|
def need_change_password
|
33
|
-
if self.
|
34
|
-
self.password_changed_at = self.
|
33
|
+
if self.expire_password_after.is_a? Fixnum or self.expire_password_after.is_a? Float
|
34
|
+
self.password_changed_at = self.expire_password_after.ago
|
35
35
|
end
|
36
36
|
|
37
37
|
# is date not set it will set default to need set new password next login
|
@@ -39,6 +39,10 @@ module Devise
|
|
39
39
|
|
40
40
|
self.password_changed_at
|
41
41
|
end
|
42
|
+
|
43
|
+
def expire_password_after
|
44
|
+
self.class.expire_password_after
|
45
|
+
end
|
42
46
|
|
43
47
|
private
|
44
48
|
|
@@ -3,7 +3,9 @@ module DeviseSecurityExtension::Patches
|
|
3
3
|
extend ActiveSupport::Concern
|
4
4
|
included do
|
5
5
|
define_method :create do
|
6
|
-
if
|
6
|
+
if ((defined? verify_recaptcha) && (verify_recaptcha
|
7
|
+
params[:captcha])) or ((defined? valid_captcha?) && (valid_captcha?
|
8
|
+
params[:captcha]))
|
7
9
|
self.resource = resource_class.send_confirmation_instructions(params[resource_name])
|
8
10
|
|
9
11
|
if successfully_sent?(resource)
|
@@ -6,7 +6,9 @@ module DeviseSecurityExtension::Patches
|
|
6
6
|
# only find via email, not login
|
7
7
|
resource = resource_class.find_or_initialize_with_error_by(:email, params[resource_name][:email], :not_found)
|
8
8
|
|
9
|
-
if
|
9
|
+
if ((defined? verify_recaptcha) && (verify_recaptcha
|
10
|
+
params[:captcha])) or ((defined? valid_captcha?) && (valid_captcha?
|
11
|
+
params[:captcha])) or
|
10
12
|
(resource.security_question_answer.present? and resource.security_question_answer == params[:security_question_answer])
|
11
13
|
self.resource = resource_class.send_confirmation_instructions(params[resource_name])
|
12
14
|
|
@@ -3,7 +3,9 @@ module DeviseSecurityExtension::Patches
|
|
3
3
|
extend ActiveSupport::Concern
|
4
4
|
included do
|
5
5
|
define_method :create do
|
6
|
-
if
|
6
|
+
if ((defined? verify_recaptcha) && (verify_recaptcha
|
7
|
+
params[:captcha])) or ((defined? valid_captcha?) && (valid_captcha?
|
8
|
+
params[:captcha]))
|
7
9
|
self.resource = resource_class.send_reset_password_instructions(params[resource_name])
|
8
10
|
if successfully_sent?(resource)
|
9
11
|
respond_with({}, :location => new_session_path(resource_name))
|
@@ -6,7 +6,9 @@ module DeviseSecurityExtension::Patches
|
|
6
6
|
# only find via email, not login
|
7
7
|
resource = resource_class.find_or_initialize_with_error_by(:email, params[resource_name][:email], :not_found)
|
8
8
|
|
9
|
-
if
|
9
|
+
if ((defined? verify_recaptcha) && (verify_recaptcha
|
10
|
+
params[:captcha])) or ((defined? valid_captcha?) && (valid_captcha?
|
11
|
+
params[:captcha]))
|
10
12
|
(resource.security_question_answer.present? and resource.security_question_answer == params[:security_question_answer])
|
11
13
|
self.resource = resource_class.send_reset_password_instructions(params[resource_name])
|
12
14
|
if successfully_sent?(resource)
|
@@ -2,13 +2,15 @@ module DeviseSecurityExtension::Patches
|
|
2
2
|
module RegistrationsControllerCaptcha
|
3
3
|
extend ActiveSupport::Concern
|
4
4
|
included do
|
5
|
-
define_method :create do
|
5
|
+
define_method :create do |&block|
|
6
6
|
build_resource(sign_up_params)
|
7
7
|
|
8
|
-
if
|
8
|
+
if ((defined? verify_recaptcha) && (verify_recaptcha
|
9
|
+
params[:captcha])) or ((defined? valid_captcha?) && (valid_captcha?
|
10
|
+
params[:captcha]))
|
9
11
|
|
10
12
|
if resource.save
|
11
|
-
|
13
|
+
block.call(resource) if block
|
12
14
|
if resource.active_for_authentication?
|
13
15
|
set_flash_message :notice, :signed_up if is_flashing_format?
|
14
16
|
sign_up(resource_name, resource)
|
@@ -2,12 +2,14 @@ module DeviseSecurityExtension::Patches
|
|
2
2
|
module SessionsControllerCaptcha
|
3
3
|
extend ActiveSupport::Concern
|
4
4
|
included do
|
5
|
-
define_method :create do
|
6
|
-
if
|
5
|
+
define_method :create do |&block|
|
6
|
+
if ((defined? verify_recaptcha) && (verify_recaptcha
|
7
|
+
params[:captcha])) or ((defined? valid_captcha?) && (valid_captcha?
|
8
|
+
params[:captcha]))
|
7
9
|
self.resource = warden.authenticate!(auth_options)
|
8
10
|
set_flash_message(:notice, :signed_in) if is_flashing_format?
|
9
11
|
sign_in(resource_name, resource)
|
10
|
-
|
12
|
+
block.call(resource) if block
|
11
13
|
respond_with resource, :location => after_sign_in_path_for(resource)
|
12
14
|
else
|
13
15
|
flash[:alert] = t('devise.invalid_captcha') if is_flashing_format?
|
@@ -3,7 +3,9 @@ module DeviseSecurityExtension::Patches
|
|
3
3
|
extend ActiveSupport::Concern
|
4
4
|
included do
|
5
5
|
define_method :create do
|
6
|
-
if
|
6
|
+
if ((defined? verify_recaptcha) && (verify_recaptcha
|
7
|
+
params[:captcha])) or ((defined? valid_captcha?) && (valid_captcha?
|
8
|
+
params[:captcha]))
|
7
9
|
self.resource = resource_class.send_unlock_instructions(params[resource_name])
|
8
10
|
if successfully_sent?(resource)
|
9
11
|
respond_with({}, :location => new_session_path(resource_name))
|
@@ -6,7 +6,9 @@ module DeviseSecurityExtension::Patches
|
|
6
6
|
# only find via email, not login
|
7
7
|
resource = resource_class.find_or_initialize_with_error_by(:email, params[resource_name][:email], :not_found)
|
8
8
|
|
9
|
-
if
|
9
|
+
if ((defined? verify_recaptcha) && (verify_recaptcha
|
10
|
+
params[:captcha])) or ((defined? valid_captcha?) && (valid_captcha?
|
11
|
+
params[:captcha]))
|
10
12
|
(resource.security_question_answer.present? and resource.security_question_answer == params[:security_question_answer])
|
11
13
|
self.resource = resource_class.send_unlock_instructions(params[resource_name])
|
12
14
|
if successfully_sent?(resource)
|
@@ -8,6 +8,10 @@ module ActionDispatch::Routing
|
|
8
8
|
resource :password_expired, :only => [:show, :update], :path => mapping.path_names[:password_expired], :controller => controllers[:password_expired]
|
9
9
|
end
|
10
10
|
|
11
|
+
# route for handle paranoid verification
|
12
|
+
def devise_verification_code(mapping, controllers)
|
13
|
+
resource :paranoid_verification_code, :only => [:show, :update], :path => mapping.path_names[:verification_code], :controller => controllers[:paranoid_verification_code]
|
14
|
+
end
|
11
15
|
end
|
12
16
|
end
|
13
17
|
|
@@ -1,43 +1,26 @@
|
|
1
1
|
module DeviseSecurityExtension
|
2
2
|
module Generators
|
3
|
-
#
|
3
|
+
# Generator for Rails to create or append to a Devise initializer.
|
4
4
|
class InstallGenerator < Rails::Generators::Base
|
5
|
-
|
5
|
+
LOCALES = %w[ en de it ]
|
6
6
|
|
7
|
-
|
7
|
+
source_root File.expand_path('../../templates', __FILE__)
|
8
|
+
desc 'Install the devise security extension'
|
8
9
|
|
9
|
-
def
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
" # Need 1 char of A-Z, a-z and 0-9\n" +
|
14
|
-
" # config.password_regex = /(?=.*\\d)(?=.*[a-z])(?=.*[A-Z])/\n\n" +
|
15
|
-
" # How many passwords to keep in archive\n" +
|
16
|
-
" # config.password_archiving_count = 5\n\n" +
|
17
|
-
" # Deny old password (true, false, count)\n" +
|
18
|
-
" # config.deny_old_passwords = true\n\n" +
|
19
|
-
" # enable email validation for :secure_validatable. (true, false, validation_options)\n" +
|
20
|
-
" # dependency: need an email validator like rails_email_validator\n" +
|
21
|
-
" # config.email_validation = true\n\n" +
|
22
|
-
" # captcha integration for recover form\n" +
|
23
|
-
" # config.captcha_for_recover = true\n\n" +
|
24
|
-
" # captcha integration for sign up form\n" +
|
25
|
-
" # config.captcha_for_sign_up = true\n\n" +
|
26
|
-
" # captcha integration for sign in form\n" +
|
27
|
-
" # config.captcha_for_sign_in = true\n\n" +
|
28
|
-
" # captcha integration for unlock form\n" +
|
29
|
-
" # config.captcha_for_unlock = true\n\n" +
|
30
|
-
" # captcha integration for confirmation form\n" +
|
31
|
-
" # config.captcha_for_confirmation = true\n\n" +
|
32
|
-
" # Time period for account expiry from last_activity_at\n" +
|
33
|
-
" # config.expire_after = 90.days\n\n" +
|
34
|
-
"", :before => /end[ |\n|]+\Z/
|
10
|
+
def copy_initializer
|
11
|
+
template('devise_security_extension.rb',
|
12
|
+
'config/initializers/devise_security_extension.rb',
|
13
|
+
)
|
35
14
|
end
|
36
15
|
|
37
|
-
def
|
38
|
-
|
39
|
-
|
16
|
+
def copy_locales
|
17
|
+
LOCALES.each do |locale|
|
18
|
+
copy_file(
|
19
|
+
"../../../config/locales/#{locale}.yml",
|
20
|
+
"config/locales/devise.security_extension.#{locale}.yml",
|
21
|
+
)
|
22
|
+
end
|
40
23
|
end
|
41
24
|
end
|
42
25
|
end
|
43
|
-
end
|
26
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
Devise.setup do |config|
|
2
|
+
# ==> Security Extension
|
3
|
+
# Configure security extension for devise
|
4
|
+
|
5
|
+
# Should the password expire (e.g 3.months)
|
6
|
+
# config.expire_password_after = false
|
7
|
+
|
8
|
+
# Need 1 char of A-Z, a-z and 0-9
|
9
|
+
# config.password_regex = /(?=.*\\d)(?=.*[a-z])(?=.*[A-Z])/
|
10
|
+
|
11
|
+
# How many passwords to keep in archive
|
12
|
+
# config.password_archiving_count = 5
|
13
|
+
|
14
|
+
# Deny old password (true, false, count)
|
15
|
+
# config.deny_old_passwords = true
|
16
|
+
|
17
|
+
# enable email validation for :secure_validatable. (true, false, validation_options)
|
18
|
+
# dependency: need an email validator like rails_email_validator
|
19
|
+
# config.email_validation = true
|
20
|
+
|
21
|
+
# captcha integration for recover form
|
22
|
+
# config.captcha_for_recover = true
|
23
|
+
|
24
|
+
# captcha integration for sign up form
|
25
|
+
# config.captcha_for_sign_up = true
|
26
|
+
|
27
|
+
# captcha integration for sign in form
|
28
|
+
# config.captcha_for_sign_in = true
|
29
|
+
|
30
|
+
# captcha integration for unlock form
|
31
|
+
# config.captcha_for_unlock = true
|
32
|
+
|
33
|
+
# captcha integration for confirmation form
|
34
|
+
# config.captcha_for_confirmation = true
|
35
|
+
|
36
|
+
# Time period for account expiry from last_activity_at
|
37
|
+
# config.expire_after = 90.days
|
38
|
+
end
|
data/test/dummy/Rakefile
ADDED
File without changes
|
File without changes
|
@@ -1,22 +1,24 @@
|
|
1
1
|
require File.expand_path('../boot', __FILE__)
|
2
2
|
|
3
3
|
require 'rails/all'
|
4
|
+
require 'devise_security_extension'
|
4
5
|
|
5
6
|
if defined?(Bundler)
|
6
7
|
# If you precompile assets before deploying to production, use this line
|
7
|
-
Bundler.require(*Rails.groups(:
|
8
|
+
Bundler.require(*Rails.groups(assets: %w[development test]))
|
8
9
|
# If you want your assets lazily compiled in production, use this line
|
9
10
|
# Bundler.require(:default, :assets, Rails.env)
|
10
11
|
end
|
11
12
|
|
12
13
|
module RailsApp
|
13
14
|
class Application < Rails::Application
|
14
|
-
config.encoding =
|
15
|
+
config.encoding = 'utf-8'
|
15
16
|
|
16
17
|
config.filter_parameters += [:password]
|
17
18
|
|
18
19
|
config.assets.enabled = true
|
19
20
|
|
20
21
|
config.assets.version = '1.0'
|
22
|
+
config.secret_key_base = 'fuuuuuuuuuuu'
|
21
23
|
end
|
22
24
|
end
|
data/test/dummy/config/boot.rb
CHANGED
@@ -2,8 +2,8 @@ RailsApp::Application.configure do
|
|
2
2
|
config.cache_classes = true
|
3
3
|
config.eager_load = false
|
4
4
|
|
5
|
-
config.
|
6
|
-
config.static_cache_control =
|
5
|
+
config.serve_static_files = true
|
6
|
+
config.static_cache_control = 'public, max-age=3600'
|
7
7
|
|
8
8
|
config.consider_all_requests_local = true
|
9
9
|
config.action_controller.perform_caching = false
|
@@ -16,4 +16,6 @@ RailsApp::Application.configure do
|
|
16
16
|
|
17
17
|
config.active_support.deprecation = :stderr
|
18
18
|
I18n.enforce_available_locales = false
|
19
|
+
|
20
|
+
config.active_support.test_order = :sorted
|
19
21
|
end
|
@@ -1,9 +1,9 @@
|
|
1
1
|
Devise.setup do |config|
|
2
|
-
config.mailer_sender =
|
2
|
+
config.mailer_sender = 'please-change-me-at-config-initializers-devise@example.com'
|
3
3
|
|
4
4
|
require 'devise/orm/active_record'
|
5
|
+
config.secret_key = 'f08cf11a38906f531d2dfc9a2c2d671aa0021be806c21255d4'
|
6
|
+
config.case_insensitive_keys = [:email]
|
5
7
|
|
6
|
-
config.
|
7
|
-
|
8
|
-
config.strip_whitespace_keys = [ :email ]
|
8
|
+
config.strip_whitespace_keys = [:email]
|
9
9
|
end
|