From 4c0e52e72fc2aaa6ab178cfaf5681f8790b322e7 Mon Sep 17 00:00:00 2001 From: "Eric D. Helms" Date: Mon, 27 Oct 2025 16:43:32 -0400 Subject: [PATCH] Fixes #38867: Clear browser data on logout Signed-off-by: Eric D. Helms --- .../concerns/foreman/controller/session.rb | 9 ++++ app/controllers/users_controller.rb | 1 + test/controllers/users_controller_test.rb | 7 ++++ test/integration/clear_site_data_test.rb | 41 +++++++++++++++++++ 4 files changed, 58 insertions(+) create mode 100644 test/integration/clear_site_data_test.rb diff --git a/app/controllers/concerns/foreman/controller/session.rb b/app/controllers/concerns/foreman/controller/session.rb index b860925e8be..c4d480b5f6a 100644 --- a/app/controllers/concerns/foreman/controller/session.rb +++ b/app/controllers/concerns/foreman/controller/session.rb @@ -1,6 +1,10 @@ module Foreman::Controller::Session extend ActiveSupport::Concern + ::SecureHeaders::Configuration.override(:clear_browser_cache) do |config| + config.clear_site_data = SecureHeaders::ClearSiteData::ALL_TYPES + end + def session_expiry return if ignore_api_request? if session[:expires_at].blank? || (Time.at(session[:expires_at]).utc - Time.now.utc).to_i < 0 @@ -31,6 +35,7 @@ def set_activity_time def expire_session logger.info "Session for #{User.current} is expired." + backup_session_content { reset_session } if api_request? render :plain => '', :status => :unauthorized @@ -51,4 +56,8 @@ def expire_session def ignore_api_request? api_request? && session[:expires_at].blank? end + + def clear_browser_cache + SecureHeaders.use_secure_headers_override(request, :clear_browser_cache) + end end diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index f2b5d81b75e..aae849c3495 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -12,6 +12,7 @@ class UsersController < ApplicationController before_action :require_admin, :only => :impersonate after_action :update_activity_time, :only => :login before_action :verify_active_session, :only => :login + after_action :clear_browser_cache, only: :logout def index @users = User.authorized(:view_users).except_hidden.search_for(params[:search], :order => params[:order]).includes(:auth_source, :cached_usergroups).paginate(:page => params[:page], :per_page => params[:per_page]) diff --git a/test/controllers/users_controller_test.rb b/test/controllers/users_controller_test.rb index 0faaaa406ee..2abc39e67be 100644 --- a/test/controllers/users_controller_test.rb +++ b/test/controllers/users_controller_test.rb @@ -583,6 +583,13 @@ class UsersControllerTest < ActionController::TestCase assert_response :success assert @response.body.include?("Are you") end + + test "logout sets Clear-Site-Data header" do + @controller.expects(:verify_authenticity_token).returns(true) + post :logout, session: set_session_user + assert_response :found + assert_equal '"cache", "cookies", "storage"', @response.headers['Clear-Site-Data'] + end end test "#login respects session original_uri" do diff --git a/test/integration/clear_site_data_test.rb b/test/integration/clear_site_data_test.rb new file mode 100644 index 00000000000..7c1e78b56d2 --- /dev/null +++ b/test/integration/clear_site_data_test.rb @@ -0,0 +1,41 @@ +require 'integration_test_helper' + +class ClearSiteDataTest < IntegrationTestWithJavascript + test 'logout clears browser storage' do + visit '/domains' + assert page.has_content?('Domains'), 'Should be logged in and on domains page' + + page.execute_script(<<~JAVASCRIPT) + localStorage.setItem('test-local-storage', 'should-be-cleared'); + sessionStorage.setItem('test-session-storage', 'should-be-cleared'); + document.cookie = 'test-logout-cookie=should-be-cleared; path=/'; + JAVASCRIPT + + assert_equal 'should-be-cleared', page.evaluate_script("localStorage.getItem('test-local-storage')") + assert_equal 'should-be-cleared', page.evaluate_script("sessionStorage.getItem('test-session-storage')") + + visit '/users/logout' + + assert page.has_selector?('input[name="login[password]"]'), 'Should be redirected to login page after logout' + + local_storage_after = page.evaluate_script("localStorage.getItem('test-local-storage')") + session_storage_after = page.evaluate_script("sessionStorage.getItem('test-session-storage')") + cookies_after = page.evaluate_script("document.cookie") + + assert_nil local_storage_after, 'localStorage should be cleared after logout' + assert_nil session_storage_after, 'sessionStorage should be cleared after logout' + refute_includes cookies_after, 'test-logout-cookie=should-be-cleared', 'Test cookie should be cleared after logout' + end + + test 'normal page navigation does not clear storage' do + visit '/domains' + assert page.has_content?('Domains'), 'Should be on Domains page' + + page.execute_script("localStorage.setItem('test-persist', 'should-remain')") + + visit '/users' + assert page.has_content?('Users'), 'Should be on users page' + + assert_equal 'should-remain', page.evaluate_script("localStorage.getItem('test-persist')") + end +end