From 2de4b7378125e0ff4e0224c0f0ce597c2be7a9b2 Mon Sep 17 00:00:00 2001 From: f2cmb <2480194+f2cmb@users.noreply.github.com> Date: Wed, 22 Oct 2025 14:19:56 +0200 Subject: [PATCH 1/2] fix: return 401 instead of 500 for AJAX requests with expired session --- .../ExceptionListener/AccessErrorListener.php | 50 +++++++++++++------ 1 file changed, 34 insertions(+), 16 deletions(-) diff --git a/src/Glpi/Kernel/Listener/ExceptionListener/AccessErrorListener.php b/src/Glpi/Kernel/Listener/ExceptionListener/AccessErrorListener.php index b9ae5bbe7e9..1e0aa4711e1 100644 --- a/src/Glpi/Kernel/Listener/ExceptionListener/AccessErrorListener.php +++ b/src/Glpi/Kernel/Listener/ExceptionListener/AccessErrorListener.php @@ -41,6 +41,7 @@ use Glpi\Http\RedirectResponse; use Session; use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpKernel\Event\ExceptionEvent; use Symfony\Component\HttpKernel\KernelEvents; @@ -62,12 +63,6 @@ public function onKernelException(ExceptionEvent $event): void } $request = $event->getRequest(); - - if ($request->isXmlHttpRequest() || $request->getPreferredFormat() !== 'html') { - // Do not redirect AJAX requests nor requests that expect the response to be something else than HTML. - return; - } - $throwable = $event->getThrowable(); $response = null; @@ -79,18 +74,39 @@ public function onKernelException(ExceptionEvent $event): void ->getBoolean('_redirected_from_profile_selector') ; + // Handle SessionExpiredException BEFORE checking if request is AJAX + // This ensures that expired sessions return proper HTTP 401 response for AJAX requests if ($throwable instanceof SessionExpiredException) { Session::destroy(); // destroy the session to prevent pesistence of unexpected data - $response = new RedirectResponse( - sprintf( - '%s/?redirect=%s&error=3', - $request->getBasePath(), - \rawurlencode($request->getPathInfo() . '?' . $request->getQueryString()) - ) - ); - } elseif ( - $throwable instanceof AccessDeniedHttpException + if ($request->isXmlHttpRequest() || $request->getPreferredFormat() !== 'html') { + // For AJAX/JSON requests, return a JSON response with 401 status + $response = new JsonResponse([ + 'error' => __('Your session has expired'), + 'message' => __('Please log in again'), + 'code' => 'ERROR_SESSION_EXPIRED', + ], 401); + } else { + // For HTML requests, redirect to login page (existing behavior) + $response = new RedirectResponse( + sprintf( + '%s/?redirect=%s&error=3', + $request->getBasePath(), + \rawurlencode($request->getPathInfo() . '?' . $request->getQueryString()) + ) + ); + } + } + + // Skip AJAX requests for OTHER exceptions (not SessionExpiredException) + if ($response === null && ($request->isXmlHttpRequest() || $request->getPreferredFormat() !== 'html')) { + // Do not redirect AJAX requests nor requests that expect the response to be something else than HTML. + return; + } + + if ( + $response === null + && $throwable instanceof AccessDeniedHttpException && $redirect_to_home_on_error ) { $request = $event->getRequest(); @@ -100,7 +116,9 @@ public function onKernelException(ExceptionEvent $event): void $request->getBasePath() ) ); - } elseif ($throwable instanceof AuthenticationFailedException) { + } + + if ($response === null && $throwable instanceof AuthenticationFailedException) { $login_url = sprintf( '%s/front/logout.php?noAUTO=1', $request->getBasePath() From 2d6d33bf1ced6c3fbfdb01780bb8a1da95d508c1 Mon Sep 17 00:00:00 2001 From: f2cmb <2480194+f2cmb@users.noreply.github.com> Date: Tue, 28 Oct 2025 09:11:28 +0100 Subject: [PATCH 2/2] use Http Exception instead of JsonResponse --- .../ExceptionListener/AccessErrorListener.php | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/Glpi/Kernel/Listener/ExceptionListener/AccessErrorListener.php b/src/Glpi/Kernel/Listener/ExceptionListener/AccessErrorListener.php index 1e0aa4711e1..0c93afabe3e 100644 --- a/src/Glpi/Kernel/Listener/ExceptionListener/AccessErrorListener.php +++ b/src/Glpi/Kernel/Listener/ExceptionListener/AccessErrorListener.php @@ -37,11 +37,11 @@ use Glpi\Application\View\TemplateRenderer; use Glpi\Exception\AuthenticationFailedException; use Glpi\Exception\Http\AccessDeniedHttpException; +use Glpi\Exception\Http\HttpException; use Glpi\Exception\SessionExpiredException; use Glpi\Http\RedirectResponse; use Session; use Symfony\Component\EventDispatcher\EventSubscriberInterface; -use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpKernel\Event\ExceptionEvent; use Symfony\Component\HttpKernel\KernelEvents; @@ -80,12 +80,10 @@ public function onKernelException(ExceptionEvent $event): void Session::destroy(); // destroy the session to prevent pesistence of unexpected data if ($request->isXmlHttpRequest() || $request->getPreferredFormat() !== 'html') { - // For AJAX/JSON requests, return a JSON response with 401 status - $response = new JsonResponse([ - 'error' => __('Your session has expired'), - 'message' => __('Please log in again'), - 'code' => 'ERROR_SESSION_EXPIRED', - ], 401); + // For AJAX/JSON requests, convert the error into a HttpException + $http_exception = new HttpException(401, 'Session expired.'); + $http_exception->setMessageToDisplay(__('Your session has expired. Please log in again.')); + throw $http_exception; } else { // For HTML requests, redirect to login page (existing behavior) $response = new RedirectResponse(