devise_security_extension 0.9.2 → 0.10.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|