]> source.dussan.org Git - redmine.git/commitdiff
Expire other sessions on password change (#17796).
authorJean-Baptiste Barth <jeanbaptiste.barth@gmail.com>
Tue, 16 Sep 2014 21:38:54 +0000 (21:38 +0000)
committerJean-Baptiste Barth <jeanbaptiste.barth@gmail.com>
Tue, 16 Sep 2014 21:38:54 +0000 (21:38 +0000)
Contributed by Jan Schulz-Hofen.

git-svn-id: http://svn.redmine.org/redmine/trunk@13412 e93f8b46-1217-0410-a6f0-8f06a7374b81

app/controllers/application_controller.rb
app/controllers/my_controller.rb
app/models/user.rb
db/migrate/20140903143914_add_password_changed_at_to_user.rb [new file with mode: 0644]
test/functional/my_controller_test.rb

index e70f1602cef0e20764a1ca6d3fdaf1449987998b..e8f3565ee1c847eaa432555e73aa1f4617a91c9b 100644 (file)
@@ -49,7 +49,7 @@ class ApplicationController < ActionController::Base
     end
   end
 
-  before_filter :session_expiration, :user_setup, :check_if_login_required, :check_password_change, :set_localization
+  before_filter :session_expiration, :user_setup, :force_logout_if_password_changed, :check_if_login_required, :check_password_change, :set_localization
 
   rescue_from ::Unauthorized, :with => :deny_access
   rescue_from ::ActionView::MissingTemplate, :with => :missing_template
@@ -145,6 +145,18 @@ class ApplicationController < ActionController::Base
     user
   end
 
+  def force_logout_if_password_changed
+    passwd_changed_on = User.current.passwd_changed_on || Time.at(0)
+    # Make sure we force logout only for web browser sessions, not API calls
+    # if the password was changed after the session creation.
+    if session[:user_id] && passwd_changed_on.utc.to_i > session[:ctime].to_i
+      reset_session
+      set_localization
+      flash[:error] = l(:error_session_expired)
+      redirect_to signin_url
+    end
+  end
+
   def autologin_cookie_name
     Redmine::Configuration['autologin_cookie_name'].presence || 'autologin'
   end
index 59d1d2d0fe5e1db0412bf304554f98d927fb0b6e..714b238570e1f0dd84613682181c145c6afa5529 100644 (file)
@@ -100,6 +100,9 @@ class MyController < ApplicationController
         @user.password, @user.password_confirmation = params[:new_password], params[:new_password_confirmation]
         @user.must_change_passwd = false
         if @user.save
+          # Reset the session creation time to not log out this session on next
+          # request due to ApplicationController#force_logout_if_password_changed
+          session[:ctime] = Time.now.utc.to_i
           flash[:notice] = l(:notice_account_password_updated)
           redirect_to my_account_path
         end
index 8405fa4b6d579667e86eefae837043228ee6b7df..c79c674a9f0d9ec3a180d73bb552f05bdf824427 100644 (file)
@@ -279,6 +279,7 @@ class User < Principal
   def salt_password(clear_password)
     self.salt = User.generate_salt
     self.hashed_password = User.hash_password("#{salt}#{User.hash_password clear_password}")
+    self.passwd_changed_on = Time.now
   end
 
   # Does the backend storage allow this user to change their password?
diff --git a/db/migrate/20140903143914_add_password_changed_at_to_user.rb b/db/migrate/20140903143914_add_password_changed_at_to_user.rb
new file mode 100644 (file)
index 0000000..508b9d8
--- /dev/null
@@ -0,0 +1,5 @@
+class AddPasswordChangedAtToUser < ActiveRecord::Migration
+  def change
+    add_column :users, :passwd_changed_on, :datetime
+  end
+end
index 6577aee6a0049b69f57819fa5c0d071239d64444..53c73341af8b0ed9c87223352145bee4b945e4b9 100644 (file)
@@ -185,6 +185,18 @@ class MyControllerTest < ActionController::TestCase
     assert User.try_to_login('jsmith', 'secret123')
   end
 
+  def test_change_password_kills_other_sessions
+    @request.session[:ctime] = (Time.now - 30.minutes).utc.to_i
+
+    jsmith = User.find(2)
+    jsmith.passwd_changed_on = Time.now
+    jsmith.save!
+
+    get 'account'
+    assert_response 302
+    assert flash[:error].match(/Your session has expired/)
+  end
+
   def test_change_password_should_redirect_if_user_cannot_change_its_password
     User.find(2).update_attribute(:auth_source_id, 1)