devise_security_extension 0.7.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 (68) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +39 -0
  3. data/.rubocop.yml +38 -0
  4. data/Gemfile +2 -15
  5. data/Gemfile.lock +157 -112
  6. data/README.md +264 -0
  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 +20 -7
  10. data/app/views/devise/paranoid_verification_code/show.html.erb +10 -0
  11. data/config/locales/de.yml +3 -0
  12. data/config/locales/en.yml +7 -4
  13. data/config/locales/it.yml +10 -0
  14. data/devise_security_extension.gemspec +24 -88
  15. data/lib/devise_security_extension/controllers/helpers.rb +40 -7
  16. data/lib/devise_security_extension/hooks/paranoid_verification.rb +5 -0
  17. data/lib/devise_security_extension/hooks/password_expirable.rb +1 -1
  18. data/lib/devise_security_extension/hooks/session_limitable.rb +3 -2
  19. data/lib/devise_security_extension/models/database_authenticatable_patch.rb +26 -0
  20. data/lib/devise_security_extension/models/expirable.rb +1 -2
  21. data/lib/devise_security_extension/models/old_password.rb +1 -2
  22. data/lib/devise_security_extension/models/paranoid_verification.rb +35 -0
  23. data/lib/devise_security_extension/models/password_archivable.rb +11 -11
  24. data/lib/devise_security_extension/models/password_expirable.rb +9 -5
  25. data/lib/devise_security_extension/models/secure_validatable.rb +35 -9
  26. data/lib/devise_security_extension/models/security_questionable.rb +4 -1
  27. data/lib/devise_security_extension/patches/confirmations_controller_captcha.rb +3 -1
  28. data/lib/devise_security_extension/patches/confirmations_controller_security_question.rb +3 -1
  29. data/lib/devise_security_extension/patches/passwords_controller_captcha.rb +3 -1
  30. data/lib/devise_security_extension/patches/passwords_controller_security_question.rb +3 -1
  31. data/lib/devise_security_extension/patches/registrations_controller_captcha.rb +11 -8
  32. data/lib/devise_security_extension/patches/sessions_controller_captcha.rb +8 -5
  33. data/lib/devise_security_extension/patches/unlocks_controller_captcha.rb +3 -1
  34. data/lib/devise_security_extension/patches/unlocks_controller_security_question.rb +3 -1
  35. data/lib/devise_security_extension/routes.rb +4 -0
  36. data/lib/devise_security_extension/version.rb +3 -0
  37. data/lib/devise_security_extension.rb +20 -10
  38. data/lib/generators/devise_security_extension/install_generator.rb +16 -33
  39. data/lib/generators/templates/devise_security_extension.rb +38 -0
  40. data/test/dummy/Rakefile +6 -0
  41. data/test/dummy/app/controllers/application_controller.rb +2 -0
  42. data/test/dummy/app/controllers/foos_controller.rb +0 -0
  43. data/test/dummy/app/models/.gitkeep +0 -0
  44. data/test/dummy/app/models/user.rb +4 -0
  45. data/test/dummy/app/views/foos/index.html.erb +0 -0
  46. data/test/dummy/config/application.rb +24 -0
  47. data/test/dummy/config/boot.rb +6 -0
  48. data/test/dummy/config/database.yml +7 -0
  49. data/test/dummy/config/environment.rb +5 -0
  50. data/test/dummy/config/environments/test.rb +21 -0
  51. data/test/dummy/config/initializers/devise.rb +9 -0
  52. data/test/dummy/config/routes.rb +6 -0
  53. data/test/dummy/config/secrets.yml +3 -0
  54. data/test/dummy/config.ru +4 -0
  55. data/test/dummy/db/migrate/20120508165529_create_tables.rb +26 -0
  56. data/test/dummy/db/migrate/20150402165590_add_verification_columns.rb +11 -0
  57. data/test/dummy/db/migrate/20150407162345_add_verification_attempt_column.rb +9 -0
  58. data/test/test_helper.rb +10 -0
  59. data/test/test_install_generator.rb +16 -0
  60. data/test/test_paranoid_verification.rb +124 -0
  61. data/test/test_password_archivable.rb +61 -0
  62. data/test/test_password_expired_controller.rb +24 -0
  63. metadata +142 -62
  64. data/README.rdoc +0 -193
  65. data/VERSION +0 -1
  66. data/lib/devise_security_extension/models/security_question.rb +0 -3
  67. data/test/helper.rb +0 -17
  68. data/test/test_devise_security_extension.rb +0 -7
data/Rakefile CHANGED
@@ -1,39 +1,23 @@
1
+ $:.unshift File.join(File.dirname(__FILE__), 'lib')
1
2
  require 'rubygems'
2
3
  require 'bundler'
3
- begin
4
- Bundler.setup(:default, :development)
5
- rescue Bundler::BundlerError => e
6
- $stderr.puts e.message
7
- $stderr.puts "Run `bundle install` to install missing gems"
8
- exit e.status_code
9
- end
10
- require 'rake'
4
+ require 'rake/testtask'
5
+ require 'rdoc/task'
6
+ require 'devise_security_extension/version'
11
7
 
12
- require 'jeweler'
13
- Jeweler::Tasks.new do |gem|
14
- # gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
15
- gem.name = "devise_security_extension"
16
- gem.homepage = "http://github.com/phatworx/devise_security_extension"
17
- gem.license = "MIT"
18
- gem.summary = %Q{Security extension for devise}
19
- gem.description = %Q{An enterprise security extension for devise, trying to meet industrial standard security demands for web applications.}
20
- gem.email = "team@phatworx.de"
21
- gem.authors = ["Marco Scholl", "Alexander Dreher"]
22
- end
23
- Jeweler::RubygemsDotOrgTasks.new
8
+ desc 'Default: Run DeviseSecurityExtension unit tests'
9
+ task default: :test
24
10
 
25
- require 'rake/testtask'
26
- Rake::TestTask.new(:test) do |test|
27
- test.libs << 'lib' << 'test'
28
- test.pattern = 'test/**/test_*.rb'
29
- test.verbose = true
11
+ Rake::TestTask.new(:test) do |t|
12
+ t.libs << 'lib'
13
+ t.libs << 'test'
14
+ t.test_files = FileList['test/*test*.rb']
15
+ t.verbose = true
16
+ t.warning = false
30
17
  end
31
18
 
32
- task :default => :test
33
-
34
- require 'rdoc/task'
35
19
  Rake::RDocTask.new do |rdoc|
36
- version = File.exist?('VERSION') ? File.read('VERSION') : ""
20
+ version = DeviseSecurityExtension::VERSION.dup
37
21
 
38
22
  rdoc.rdoc_dir = 'rdoc'
39
23
  rdoc.title = "devise_security_extension #{version}"
@@ -0,0 +1,42 @@
1
+ class Devise::ParanoidVerificationCodeController < DeviseController
2
+ skip_before_filter :handle_paranoid_verification
3
+ prepend_before_filter :authenticate_scope!, :only => [:show, :update]
4
+
5
+ def show
6
+ if !resource.nil? && resource.need_paranoid_verification?
7
+ respond_with(resource)
8
+ else
9
+ redirect_to :root
10
+ end
11
+ end
12
+
13
+ def update
14
+ if resource.verify_code(resource_params[:paranoid_verification_code])
15
+ warden.session(scope)['paranoid_verify'] = false
16
+ set_flash_message :notice, :updated
17
+ sign_in scope, resource, :bypass => true
18
+ redirect_to stored_location_for(scope) || :root
19
+ else
20
+ respond_with(resource, action: :show)
21
+ end
22
+ end
23
+
24
+ private
25
+
26
+ def resource_params
27
+ if params.respond_to?(:permit)
28
+ params.require(resource_name).permit(:paranoid_verification_code)
29
+ else
30
+ params[scope].slice(:paranoid_verification_code)
31
+ end
32
+ end
33
+
34
+ def scope
35
+ resource_name.to_sym
36
+ end
37
+
38
+ def authenticate_scope!
39
+ send(:"authenticate_#{resource_name}!")
40
+ self.resource = send("current_#{resource_name}")
41
+ end
42
+ end
@@ -1,18 +1,16 @@
1
1
  class Devise::PasswordExpiredController < DeviseController
2
2
  skip_before_filter :handle_password_change
3
+ before_filter :skip_password_change, only: [:show, :update]
3
4
  prepend_before_filter :authenticate_scope!, :only => [:show, :update]
4
5
 
5
6
  def show
6
- if not resource.nil? and resource.need_change_password?
7
- respond_with(resource)
8
- else
9
- redirect_to :root
10
- end
7
+ respond_with(resource)
11
8
  end
12
9
 
13
10
  def update
14
- if resource.update_with_password(params[resource_name])
15
- warden.session(scope)[:password_expired] = false
11
+ resource.extend(Devise::Models::DatabaseAuthenticatablePatch)
12
+ if resource.update_with_password(resource_params)
13
+ warden.session(scope)['password_expired'] = false
16
14
  set_flash_message :notice, :updated
17
15
  sign_in scope, resource, :bypass => true
18
16
  redirect_to stored_location_for(scope) || :root
@@ -24,6 +22,21 @@ class Devise::PasswordExpiredController < DeviseController
24
22
 
25
23
  private
26
24
 
25
+ def skip_password_change
26
+ return if !resource.nil? && resource.need_change_password?
27
+ redirect_to :root
28
+ end
29
+
30
+ def resource_params
31
+ permitted_params = [:current_password, :password, :password_confirmation]
32
+
33
+ if params.respond_to?(:permit)
34
+ params.require(resource_name).permit(*permitted_params)
35
+ else
36
+ params[scope].slice(*permitted_params)
37
+ end
38
+ end
39
+
27
40
  def scope
28
41
  resource_name.to_sym
29
42
  end
@@ -0,0 +1,10 @@
1
+ <h2>Submit verification code</h2>
2
+
3
+ <%= form_for(resource, :as => resource_name, :url => [resource_name, :paranoid_verification_code], :html => { :method => :put }) do |f| %>
4
+ <%= devise_error_messages! %>
5
+
6
+ <p><%= f.label :paranoid_verification_code, 'Verification code' %><br />
7
+ <%= f.text_field :paranoid_verification_code, value: '' %></p>
8
+
9
+ <p><%= f.submit "Submit" %></p>
10
+ <% end %>
@@ -3,8 +3,11 @@ de:
3
3
  messages:
4
4
  taken_in_past: "wurde bereits in der Vergangenheit verwendet!"
5
5
  equal_to_current_password: "darf nicht dem aktuellen Passwort entsprechen!"
6
+ password_format: "müssen große, kleine Buchstaben und Ziffern enthalten"
6
7
  devise:
7
8
  invalid_captcha: "Die Captchaeingabe ist nicht gültig!"
9
+ paranoid_verify:
10
+ code_required: "Bitte geben Sie den Code unser Support-Team zur Verfügung gestellt"
8
11
  password_expired:
9
12
  updated: "Das neue Passwort wurde übernommen."
10
13
  change_required: "Ihr Passwort ist abgelaufen. Bitte vergeben sie ein neues Passwort!"
@@ -1,13 +1,16 @@
1
1
  en:
2
2
  errors:
3
3
  messages:
4
- taken_in_past: "was already taken in the past!"
5
- equal_to_current_password: "must be different to the current password!"
4
+ taken_in_past: "was used previously."
5
+ equal_to_current_password: "must be different than the current password."
6
+ password_format: "must contain big, small letters and digits"
6
7
  devise:
7
- invalid_captcha: "The captcha input is not valid!"
8
+ invalid_captcha: "The captcha input was invalid."
9
+ paranoid_verify:
10
+ code_required: "Please enter the code our support team provided"
8
11
  password_expired:
9
12
  updated: "Your new password is saved."
10
- change_required: "Your password is expired. Please renew your password!"
13
+ change_required: "Your password is expired. Please renew your password."
11
14
  failure:
12
15
  session_limited: 'Your login credentials were used in another browser. Please sign in again to continue in this browser.'
13
16
  expired: 'Your account has expired due to inactivity. Please contact the site administrator.'
@@ -0,0 +1,10 @@
1
+ it:
2
+ errors:
3
+ messages:
4
+ taken_in_past: "e' stata gia' utilizzata in passato!"
5
+ equal_to_current_password: " deve essere differente dalla password corrente!"
6
+ devise:
7
+ invalid_captcha: "Il captcha inserito non e' valido!"
8
+ password_expired:
9
+ updated: "La tua nuova password e' stata salvata."
10
+ change_required: "La tua password e' scaduta. Si prega di rinnovarla!"
@@ -1,95 +1,31 @@
1
- # Generated by jeweler
2
- # DO NOT EDIT THIS FILE DIRECTLY
3
- # Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
4
1
  # -*- encoding: utf-8 -*-
2
+ $LOAD_PATH.unshift(File.expand_path('../lib', __FILE__))
3
+ require 'devise_security_extension/version'
5
4
 
6
5
  Gem::Specification.new do |s|
7
- s.name = "devise_security_extension"
8
- s.version = "0.7.2"
6
+ s.name = 'devise_security_extension'
7
+ s.version = DeviseSecurityExtension::VERSION.dup
8
+ s.platform = Gem::Platform::RUBY
9
+ s.licenses = ['MIT']
10
+ s.summary = 'Security extension for devise'
11
+ s.email = 'team@phatworx.de'
12
+ s.homepage = 'https://github.com/phatworx/devise_security_extension'
13
+ s.description = 'An enterprise security extension for devise, trying to meet industrial standard security demands for web applications.'
14
+ s.authors = ['Marco Scholl', 'Alexander Dreher']
9
15
 
10
- s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
- s.authors = ["Marco Scholl", "Alexander Dreher"]
12
- s.date = "2012-11-22"
13
- s.description = "An enterprise security extension for devise, trying to meet industrial standard security demands for web applications."
14
- s.email = "team@phatworx.de"
15
- s.extra_rdoc_files = [
16
- "LICENSE.txt",
17
- "README.rdoc"
18
- ]
19
- s.files = [
20
- ".document",
21
- "Gemfile",
22
- "Gemfile.lock",
23
- "LICENSE.txt",
24
- "README.rdoc",
25
- "Rakefile",
26
- "VERSION",
27
- "app/controllers/devise/password_expired_controller.rb",
28
- "app/views/devise/password_expired/show.html.erb",
29
- "config/locales/de.yml",
30
- "config/locales/en.yml",
31
- "devise_security_extension.gemspec",
32
- "lib/devise_security_extension.rb",
33
- "lib/devise_security_extension/controllers/helpers.rb",
34
- "lib/devise_security_extension/hooks/expirable.rb",
35
- "lib/devise_security_extension/hooks/password_expirable.rb",
36
- "lib/devise_security_extension/hooks/session_limitable.rb",
37
- "lib/devise_security_extension/models/expirable.rb",
38
- "lib/devise_security_extension/models/old_password.rb",
39
- "lib/devise_security_extension/models/password_archivable.rb",
40
- "lib/devise_security_extension/models/password_expirable.rb",
41
- "lib/devise_security_extension/models/secure_validatable.rb",
42
- "lib/devise_security_extension/models/security_question.rb",
43
- "lib/devise_security_extension/models/security_questionable.rb",
44
- "lib/devise_security_extension/models/session_limitable.rb",
45
- "lib/devise_security_extension/orm/active_record.rb",
46
- "lib/devise_security_extension/patches.rb",
47
- "lib/devise_security_extension/patches/confirmations_controller_captcha.rb",
48
- "lib/devise_security_extension/patches/confirmations_controller_security_question.rb",
49
- "lib/devise_security_extension/patches/passwords_controller_captcha.rb",
50
- "lib/devise_security_extension/patches/passwords_controller_security_question.rb",
51
- "lib/devise_security_extension/patches/registrations_controller_captcha.rb",
52
- "lib/devise_security_extension/patches/sessions_controller_captcha.rb",
53
- "lib/devise_security_extension/patches/unlocks_controller_captcha.rb",
54
- "lib/devise_security_extension/patches/unlocks_controller_security_question.rb",
55
- "lib/devise_security_extension/rails.rb",
56
- "lib/devise_security_extension/routes.rb",
57
- "lib/devise_security_extension/schema.rb",
58
- "lib/generators/devise_security_extension/install_generator.rb",
59
- "test/helper.rb",
60
- "test/test_devise_security_extension.rb"
61
- ]
62
- s.homepage = "http://github.com/phatworx/devise_security_extension"
63
- s.licenses = ["MIT"]
64
- s.require_paths = ["lib"]
65
- s.rubygems_version = "1.8.24"
66
- s.summary = "Security extension for devise"
16
+ s.rubyforge_project = 'devise_security_extension'
67
17
 
68
- if s.respond_to? :specification_version then
69
- s.specification_version = 3
18
+ s.files = `git ls-files`.split("\n")
19
+ s.test_files = `git ls-files -- test/*`.split("\n")
20
+ s.require_paths = ['lib']
21
+ s.required_ruby_version = '>= 1.9.3'
70
22
 
71
- if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
72
- s.add_runtime_dependency(%q<rails>, [">= 3.1.1"])
73
- s.add_runtime_dependency(%q<devise>, [">= 2.0.0"])
74
- s.add_development_dependency(%q<rails_email_validator>, [">= 0"])
75
- s.add_development_dependency(%q<easy_captcha>, [">= 0"])
76
- s.add_development_dependency(%q<bundler>, [">= 1.0.0"])
77
- s.add_development_dependency(%q<jeweler>, ["~> 1.8.4"])
78
- else
79
- s.add_dependency(%q<rails>, [">= 3.1.1"])
80
- s.add_dependency(%q<devise>, [">= 2.0.0"])
81
- s.add_dependency(%q<rails_email_validator>, [">= 0"])
82
- s.add_dependency(%q<easy_captcha>, [">= 0"])
83
- s.add_dependency(%q<bundler>, [">= 1.0.0"])
84
- s.add_dependency(%q<jeweler>, ["~> 1.8.4"])
85
- end
86
- else
87
- s.add_dependency(%q<rails>, [">= 3.1.1"])
88
- s.add_dependency(%q<devise>, [">= 2.0.0"])
89
- s.add_dependency(%q<rails_email_validator>, [">= 0"])
90
- s.add_dependency(%q<easy_captcha>, [">= 0"])
91
- s.add_dependency(%q<bundler>, [">= 1.0.0"])
92
- s.add_dependency(%q<jeweler>, ["~> 1.8.4"])
93
- end
23
+ s.add_runtime_dependency 'railties', '>= 3.2.6', '< 5.0'
24
+ s.add_runtime_dependency 'devise', '>= 3.0.0', '< 4.0'
25
+ s.add_development_dependency 'bundler', '>= 1.3.0', '< 2.0'
26
+ s.add_development_dependency 'sqlite3', '~> 1.3.10'
27
+ s.add_development_dependency 'rubocop', '~> 0'
28
+ s.add_development_dependency 'minitest'
29
+ s.add_development_dependency 'easy_captcha', '~> 0'
30
+ s.add_development_dependency 'rails_email_validator', '~> 0'
94
31
  end
95
-
@@ -5,13 +5,14 @@ module DeviseSecurityExtension
5
5
 
6
6
  included do
7
7
  before_filter :handle_password_change
8
+ before_filter :handle_paranoid_verification
8
9
  end
9
-
10
+
10
11
  module ClassMethods
11
12
  # helper for captcha
12
13
  def init_recover_password_captcha
13
14
  include RecoverPasswordCaptcha
14
- end
15
+ end
15
16
  end
16
17
 
17
18
  module RecoverPasswordCaptcha
@@ -26,11 +27,33 @@ module DeviseSecurityExtension
26
27
 
27
28
  # lookup if an password change needed
28
29
  def handle_password_change
30
+ return if warden.nil?
31
+
29
32
  if not devise_controller? and not ignore_password_expire? and not request.format.nil? and request.format.html?
30
33
  Devise.mappings.keys.flatten.any? do |scope|
31
- if signed_in?(scope) and warden.session(scope)[:password_expired]
32
- session["#{scope}_return_to"] = request.path if request.get?
33
- redirect_for_password_change scope
34
+ if signed_in?(scope) and warden.session(scope)['password_expired']
35
+ # re-check to avoid infinite loop if date changed after login attempt
36
+ if send(:"current_#{scope}").try(:need_change_password?)
37
+ session["#{scope}_return_to"] = request.original_fullpath if request.get?
38
+ redirect_for_password_change scope
39
+ return
40
+ else
41
+ warden.session(scope)[:password_expired] = false
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
47
+
48
+ # lookup if extra (paranoid) code verification is needed
49
+ def handle_paranoid_verification
50
+ return if warden.nil?
51
+
52
+ if !devise_controller? && !request.format.nil? && request.format.html?
53
+ Devise.mappings.keys.flatten.any? do |scope|
54
+ if signed_in?(scope) && warden.session(scope)['paranoid_verify']
55
+ session["#{scope}_return_to"] = request.original_fullpath if request.get?
56
+ redirect_for_paranoid_verification scope
34
57
  return
35
58
  end
36
59
  end
@@ -42,15 +65,25 @@ module DeviseSecurityExtension
42
65
  redirect_to change_password_required_path_for(scope), :alert => I18n.t('change_required', {:scope => 'devise.password_expired'})
43
66
  end
44
67
 
68
+ def redirect_for_paranoid_verification(scope)
69
+ redirect_to paranoid_verification_code_path_for(scope), :alert => I18n.t('code_required', {:scope => 'devise.paranoid_verify'})
70
+ end
71
+
45
72
  # path for change password
46
73
  def change_password_required_path_for(resource_or_scope = nil)
47
74
  scope = Devise::Mapping.find_scope!(resource_or_scope)
48
75
  change_path = "#{scope}_password_expired_path"
49
76
  send(change_path)
50
77
  end
51
-
78
+
79
+ def paranoid_verification_code_path_for(resource_or_scope = nil)
80
+ scope = Devise::Mapping.find_scope!(resource_or_scope)
81
+ change_path = "#{scope}_paranoid_verification_code_path"
82
+ send(change_path)
83
+ end
84
+
52
85
  protected
53
-
86
+
54
87
  # allow to overwrite for some special handlings
55
88
  def ignore_password_expire?
56
89
  false
@@ -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
@@ -1,5 +1,5 @@
1
1
  Warden::Manager.after_authentication do |record, warden, options|
2
2
  if record.respond_to?(:need_change_password?)
3
- warden.session(options[:scope])[:password_expired] = record.need_change_password?
3
+ warden.session(options[:scope])['password_expired'] = record.need_change_password?
4
4
  end
5
5
  end
@@ -17,10 +17,11 @@ Warden::Manager.after_set_user :only => :fetch do |record, warden, options|
17
17
  scope = options[:scope]
18
18
  env = warden.request.env
19
19
 
20
- if warden.authenticated?(scope) && options[:store] != false
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
25
26
  end
26
- end
27
+ end
@@ -0,0 +1,26 @@
1
+ module Devise
2
+ module Models
3
+ module DatabaseAuthenticatablePatch
4
+ def update_with_password(params, *options)
5
+ current_password = params.delete(:current_password)
6
+
7
+ new_password = params[:password]
8
+ new_password_confirmation = params[:password_confirmation]
9
+
10
+ result = if valid_password?(current_password) && new_password.present? && new_password_confirmation.present?
11
+ update_attributes(params, *options)
12
+ else
13
+ self.assign_attributes(params, *options)
14
+ self.valid?
15
+ self.errors.add(:current_password, current_password.blank? ? :blank : :invalid)
16
+ self.errors.add(:password, new_password.blank? ? :blank : :invalid)
17
+ self.errors.add(:password_confirmation, new_password_confirmation.blank? ? :blank : :invalid)
18
+ false
19
+ end
20
+
21
+ clean_up_passwords
22
+ result
23
+ end
24
+ end
25
+ end
26
+ end
@@ -20,8 +20,7 @@ module Devise
20
20
 
21
21
  # Updates +last_activity_at+, called from a Warden::Manager.after_set_user hook.
22
22
  def update_last_activity!
23
- self.last_activity_at = Time.now.utc
24
- save(:validate => false)
23
+ self.update_column(:last_activity_at, Time.now.utc)
25
24
  end
26
25
 
27
26
  # Tells if the account has expired
@@ -1,5 +1,4 @@
1
+ require 'active_record'
1
2
  class OldPassword < ActiveRecord::Base
2
3
  belongs_to :password_archivable, :polymorphic => true
3
-
4
- attr_accessible :encrypted_password, :password_salt
5
4
  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
@@ -18,8 +18,8 @@ module Devise
18
18
  # validate is the password used in the past
19
19
  def password_archive_included?
20
20
  unless self.class.deny_old_passwords.is_a? Fixnum
21
- if self.class.deny_old_passwords.is_a? TrueClass and self.class.password_archiving_count > 0
22
- self.class.deny_old_passwords = self.class.password_archiving_count
21
+ if self.class.deny_old_passwords.is_a? TrueClass and archive_count > 0
22
+ self.class.deny_old_passwords = archive_count
23
23
  else
24
24
  self.class.deny_old_passwords = 0
25
25
  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
@@ -46,23 +45,24 @@ module Devise
46
45
 
47
46
  private
48
47
 
48
+ def archive_count
49
+ self.class.password_archiving_count
50
+ end
51
+
49
52
  # archive the last password before save and delete all to old passwords from archive
50
53
  def archive_password
51
54
  if self.encrypted_password_changed?
52
- if self.class.password_archiving_count.to_i > 0
55
+ if archive_count.to_i > 0
53
56
  self.old_passwords.create! old_password_params
54
- self.old_passwords.order(:id).reverse_order.offset(self.class.password_archiving_count).destroy_all
57
+ self.old_passwords.order(:id).reverse_order.offset(archive_count).destroy_all
55
58
  else
56
59
  self.old_passwords.destroy_all
57
60
  end
58
61
  end
59
62
  end
60
-
63
+
61
64
  def old_password_params
62
- salt_change = if self.respond_to?(:password_salt_change) and not self.password_salt_change.nil?
63
- self.password_salt_change.first
64
- end
65
- { :encrypted_password => self.encrypted_password_change.first, :password_salt => salt_change }
65
+ { encrypted_password: self.encrypted_password_change.first }
66
66
  end
67
67
 
68
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