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.
Files changed (57) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +39 -0
  3. data/.rubocop.yml +38 -0
  4. data/Gemfile +1 -5
  5. data/Gemfile.lock +144 -141
  6. data/README.md +37 -11
  7. data/Rakefile +13 -29
  8. data/app/controllers/devise/paranoid_verification_code_controller.rb +42 -0
  9. data/app/controllers/devise/password_expired_controller.rb +16 -7
  10. data/app/views/devise/paranoid_verification_code/show.html.erb +10 -0
  11. data/config/locales/de.yml +2 -0
  12. data/config/locales/en.yml +6 -4
  13. data/config/locales/it.yml +10 -0
  14. data/devise_security_extension.gemspec +24 -104
  15. data/lib/devise_security_extension.rb +18 -8
  16. data/lib/devise_security_extension/controllers/helpers.rb +39 -6
  17. data/lib/devise_security_extension/hooks/paranoid_verification.rb +5 -0
  18. data/lib/devise_security_extension/hooks/session_limitable.rb +1 -0
  19. data/lib/devise_security_extension/models/paranoid_verification.rb +35 -0
  20. data/lib/devise_security_extension/models/password_archivable.rb +3 -7
  21. data/lib/devise_security_extension/models/password_expirable.rb +9 -5
  22. data/lib/devise_security_extension/patches/confirmations_controller_captcha.rb +3 -1
  23. data/lib/devise_security_extension/patches/confirmations_controller_security_question.rb +3 -1
  24. data/lib/devise_security_extension/patches/passwords_controller_captcha.rb +3 -1
  25. data/lib/devise_security_extension/patches/passwords_controller_security_question.rb +3 -1
  26. data/lib/devise_security_extension/patches/registrations_controller_captcha.rb +5 -3
  27. data/lib/devise_security_extension/patches/sessions_controller_captcha.rb +5 -3
  28. data/lib/devise_security_extension/patches/unlocks_controller_captcha.rb +3 -1
  29. data/lib/devise_security_extension/patches/unlocks_controller_security_question.rb +3 -1
  30. data/lib/devise_security_extension/routes.rb +4 -0
  31. data/lib/devise_security_extension/version.rb +3 -0
  32. data/lib/generators/devise_security_extension/install_generator.rb +16 -33
  33. data/lib/generators/templates/devise_security_extension.rb +38 -0
  34. data/test/dummy/Rakefile +6 -0
  35. data/test/dummy/app/controllers/application_controller.rb +2 -0
  36. data/test/dummy/app/controllers/foos_controller.rb +0 -0
  37. data/test/dummy/app/models/user.rb +2 -1
  38. data/test/dummy/app/views/foos/index.html.erb +0 -0
  39. data/test/dummy/config/application.rb +4 -2
  40. data/test/dummy/config/boot.rb +1 -1
  41. data/test/dummy/config/environments/test.rb +4 -2
  42. data/test/dummy/config/initializers/devise.rb +4 -4
  43. data/test/dummy/config/routes.rb +6 -0
  44. data/test/dummy/config/secrets.yml +3 -0
  45. data/test/dummy/db/migrate/20120508165529_create_tables.rb +4 -4
  46. data/test/dummy/db/migrate/20150402165590_add_verification_columns.rb +11 -0
  47. data/test/dummy/db/migrate/20150407162345_add_verification_attempt_column.rb +9 -0
  48. data/test/test_helper.rb +10 -0
  49. data/test/test_install_generator.rb +16 -0
  50. data/test/test_paranoid_verification.rb +124 -0
  51. data/test/test_password_archivable.rb +35 -21
  52. data/test/test_password_expired_controller.rb +24 -0
  53. metadata +104 -34
  54. data/VERSION +0 -1
  55. data/lib/devise_security_extension/models/security_question.rb +0 -3
  56. data/test/helper.rb +0 -22
  57. data/test/test_devise_security_extension.rb +0 -6
@@ -0,0 +1,5 @@
1
+ Warden::Manager.after_set_user do |record, warden, options|
2
+ if record.respond_to?(:need_paranoid_verification?)
3
+ warden.session(options[:scope])['paranoid_verify'] = record.need_paranoid_verification?
4
+ end
5
+ end
@@ -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
- salt_change = if self.respond_to?(:password_salt_change) and not self.password_salt_change.nil?
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.class.expire_password_after.is_a? Fixnum or self.class.expire_password_after.is_a? Float
17
- self.password_changed_at.nil? or self.password_changed_at < self.class.expire_password_after.ago
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.class.expire_password_after.is_a? Fixnum or self.class.expire_password_after.is_a? Float
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.class.expire_password_after.is_a? Fixnum or self.class.expire_password_after.is_a? Float
34
- self.password_changed_at = self.class.expire_password_after.ago
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 valid_captcha? params[:captcha]
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 valid_captcha? params[:captcha] or
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 valid_captcha? params[:captcha]
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 valid_captcha? params[:captcha] or
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 valid_captcha? params[:captcha]
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
- yield resource if block_given?
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 valid_captcha? params[:captcha]
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
- yield resource if block_given?
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 valid_captcha? params[:captcha]
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 valid_captcha? params[:captcha] or
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
 
@@ -0,0 +1,3 @@
1
+ module DeviseSecurityExtension
2
+ VERSION = "0.10.0".freeze
3
+ end
@@ -1,43 +1,26 @@
1
1
  module DeviseSecurityExtension
2
2
  module Generators
3
- # Install Generator
3
+ # Generator for Rails to create or append to a Devise initializer.
4
4
  class InstallGenerator < Rails::Generators::Base
5
- source_root File.expand_path("../../templates", __FILE__)
5
+ LOCALES = %w[ en de it ]
6
6
 
7
- desc "Install the devise security extension"
7
+ source_root File.expand_path('../../templates', __FILE__)
8
+ desc 'Install the devise security extension'
8
9
 
9
- def add_configs
10
- inject_into_file "config/initializers/devise.rb", "\n # ==> Security Extension\n # Configure security extension for devise\n\n" +
11
- " # Should the password expire (e.g 3.months)\n" +
12
- " # config.expire_password_after = false\n\n" +
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 copy_locale
38
- copy_file "../../../config/locales/en.yml", "config/locales/devise.security_extension.en.yml"
39
- copy_file "../../../config/locales/de.yml", "config/locales/devise.security_extension.de.yml"
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
@@ -0,0 +1,6 @@
1
+ # Add your own tasks in files placed in lib/tasks ending in .rake,
2
+ # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake.
3
+
4
+ require File.expand_path('../config/application', __FILE__)
5
+
6
+ Rails.application.load_tasks
@@ -0,0 +1,2 @@
1
+ class ApplicationController < ActionController::Base
2
+ end
@@ -1,3 +1,4 @@
1
1
  class User < ActiveRecord::Base
2
- devise :database_authenticatable, :password_archivable
2
+ devise :database_authenticatable, :password_archivable,
3
+ :paranoid_verification, :password_expirable
3
4
  end
@@ -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(:assets => %w(development test)))
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 = "utf-8"
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
@@ -3,4 +3,4 @@ require 'rubygems'
3
3
  # Set up gems listed in the Gemfile.
4
4
  ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__)
5
5
 
6
- require 'bundler/setup' if File.exists?(ENV['BUNDLE_GEMFILE'])
6
+ require 'bundler/setup' if File.exist?(ENV['BUNDLE_GEMFILE'])
@@ -2,8 +2,8 @@ RailsApp::Application.configure do
2
2
  config.cache_classes = true
3
3
  config.eager_load = false
4
4
 
5
- config.serve_static_assets = true
6
- config.static_cache_control = "public, max-age=3600"
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 = "please-change-me-at-config-initializers-devise@example.com"
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.case_insensitive_keys = [ :email ]
7
-
8
- config.strip_whitespace_keys = [ :email ]
8
+ config.strip_whitespace_keys = [:email]
9
9
  end
@@ -0,0 +1,6 @@
1
+ RailsApp::Application.routes.draw do
2
+ devise_for :users
3
+ resources :foos
4
+
5
+ root to: 'foos#index'
6
+ end