diff --git a/typo3/sysext/core/Classes/Controller/ErrorPageController.php b/typo3/sysext/core/Classes/Controller/ErrorPageController.php index 698a33aac1bc95638befe859f18a43728756a6ed..b250de8cd6fb66ade1768ad5ad1d332f1f1ea31c 100644 --- a/typo3/sysext/core/Classes/Controller/ErrorPageController.php +++ b/typo3/sysext/core/Classes/Controller/ErrorPageController.php @@ -17,6 +17,7 @@ declare(strict_types=1); namespace TYPO3\CMS\Core\Controller; +use TYPO3\CMS\Core\Core\RequestId; use TYPO3\CMS\Core\Information\Typo3Information; use TYPO3\CMS\Core\Utility\GeneralUtility; use TYPO3Fluid\Fluid\View\TemplateView; @@ -68,6 +69,7 @@ class ErrorPageController $this->view->assign('errorCodeUrlPrefix', Typo3Information::URL_EXCEPTION); $this->view->assign('donationUrl', Typo3Information::URL_DONATE); $this->view->assign('errorCode', $errorCode); + $this->view->assign('requestId', GeneralUtility::makeInstance(RequestId::class)); $this->view->assign('copyrightYear', GeneralUtility::makeInstance(Typo3Information::class)->getCopyrightYear()); return $this->view->render(); } diff --git a/typo3/sysext/core/Documentation/Changelog/13.1/Feature-103441-RequestIdAsPublicVisibleErrorReferenceInErrorHandlersOutput.rst b/typo3/sysext/core/Documentation/Changelog/13.1/Feature-103441-RequestIdAsPublicVisibleErrorReferenceInErrorHandlersOutput.rst new file mode 100644 index 0000000000000000000000000000000000000000..cf9b7b3a139b47c92ca1ff483c32e816366f5890 --- /dev/null +++ b/typo3/sysext/core/Documentation/Changelog/13.1/Feature-103441-RequestIdAsPublicVisibleErrorReferenceInErrorHandlersOutput.rst @@ -0,0 +1,28 @@ +.. include:: /Includes.rst.txt + +.. _feature-103441-1710969809: + +======================================================================================== +Feature: #103441 - Request id as public visible error reference in error handlers output +======================================================================================== + +See :issue:`103441` + +Description +=========== + +The ProductionExceptionHandler in EXT:core outputs error details, but not for everyone. As a normal visitor you don't see any tracable error information. + +The ProductionExceptionHandler in EXT:frontend outputs "Oops, an error occurred!" followed by a timestamp + a hash. This is part of log messages. + +Whenever an error/exception is logged, the log message contains the request id. + +With this the request id is also shown in web output of error/exception handlers as public visible error reference. + + +Impact +====== + +Everyone sees a request id as tracable error information. + +.. index:: Frontend, ext:core \ No newline at end of file diff --git a/typo3/sysext/core/Resources/Private/Templates/ErrorPage/Error.html b/typo3/sysext/core/Resources/Private/Templates/ErrorPage/Error.html index 528489e0b41484e9485665cbe31aeb4714cce929..27fcb499e3f29d37845b6812c3ae66cc63a612e0 100644 --- a/typo3/sysext/core/Resources/Private/Templates/ErrorPage/Error.html +++ b/typo3/sysext/core/Resources/Private/Templates/ErrorPage/Error.html @@ -84,6 +84,7 @@ <f:if condition="{errorCode} > 0"> <p class="typo3-error-page-message-help">More information regarding this error might be available <a href="{errorCodeUrlPrefix}{errorCode}" target="_blank" rel="noreferrer">online</a>.</p> </f:if> + <p class="typo3-error-page-requestid">Request: {requestId}</p> <div class="typo3-error-page-footer"> <svg class="typo3-error-page-logo" alt="TYPO3 logo" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 484 130"><path fill="#FF8700" d="M96.1 88.8c-1.9.6-3.4.8-5.4.8-16.2 0-39.9-56.5-39.9-75.3 0-6.9 1.6-9.2 4-11.2C35 5.3 11.2 12.6 3.6 21.8 2 24.1 1 27.7 1 32.4c0 29.4 31.3 96 53.4 96 10.3 0 27.5-16.9 41.7-39.6M85.8 1c20.4 0 40.9 3.3 40.9 14.8 0 23.4-14.9 51.8-22.4 51.8-13.5 0-30.4-37.6-30.4-56.4C73.9 2.7 77.2 1 85.8 1"/><path d="M195.3 42.5v86h-12v-86H162V32.1h54.4v10.5l-21.1-.1zm63.1 41.2v44.8H246V83.7L221.3 32h13.2l18.2 40.1L270.9 32h12.5l-25 51.7zm63.1 8.4h-11.6v36.4h-12V32.1s11.8-1 23.3-1c20.9 0 27 13 27 29.9 0 20.9-7.2 31.1-26.7 31.1zm1.4-51.1c-7.6 0-13 .8-13 .8V82h13c7.7 0 13.1-5.1 13.1-20.1-.1-13.7-3.4-20.9-13.1-20.9zm70 88.5c-22.7 0-28.8-16.5-28.8-50.3 0-32.4 6.1-48.1 28.8-48.1 22.7 0 28.8 15.7 28.8 48.1 0 33.7-6.1 50.3-28.8 50.3zm0-88.4c-12.4 0-16.5 8.4-16.5 38.7 0 29.6 4.1 39.5 16.5 39.5s16.5-9.9 16.5-39.5c0-30.2-4.1-38.7-16.5-38.7zm65.8 88.4c-6.7 0-16.8-2.1-18.1-2.3v-10.3c3.3.7 11.9 2.2 17.9 2.2 7 0 11.6-5.8 11.6-16.4 0-12.8-2.1-19.2-11.9-19.2H447V73.4h9.9c11.2 0 11.7-11.4 11.7-16.9 0-10.9-3.4-15.3-10.3-15.3-6.1 0-13 1.5-17 2.3V33.4c1.5-.3 9.6-2.3 16.7-2.3 14.2 0 22.3 6.1 22.3 26.6 0 9.4-2.9 17.6-10.5 20 9 1.2 12.1 10.1 12.1 23.6.1 20.5-7.9 28.2-23.2 28.2z"/></svg> <div class="typo3-error-page-copyright"> diff --git a/typo3/sysext/frontend/Classes/ContentObject/Exception/ProductionExceptionHandler.php b/typo3/sysext/frontend/Classes/ContentObject/Exception/ProductionExceptionHandler.php index 6fb2172105fd998556e7ec1fd2d96e40825a3eaf..ed8e344f7ce8fc9c311b8e2384d88f1b7152e5b7 100644 --- a/typo3/sysext/frontend/Classes/ContentObject/Exception/ProductionExceptionHandler.php +++ b/typo3/sysext/frontend/Classes/ContentObject/Exception/ProductionExceptionHandler.php @@ -19,6 +19,7 @@ namespace TYPO3\CMS\Frontend\ContentObject\Exception; use Psr\Log\LoggerInterface; use TYPO3\CMS\Core\Context\Context; +use TYPO3\CMS\Core\Core\RequestId; use TYPO3\CMS\Core\Crypto\Random; use TYPO3\CMS\Core\Error\AbstractExceptionHandler; use TYPO3\CMS\Core\Http\ImmediateResponseException; @@ -32,16 +33,12 @@ class ProductionExceptionHandler implements ExceptionHandlerInterface { protected array $configuration = []; - protected Context $context; - protected Random $random; - protected LoggerInterface $logger; - - public function __construct(Context $context, Random $random, LoggerInterface $logger) - { - $this->context = $context; - $this->random = $random; - $this->logger = $logger; - } + public function __construct( + protected Context $context, + protected Random $random, + protected LoggerInterface $logger, + protected RequestId $requestId + ) {} public function setConfiguration(array $configuration): void { @@ -71,18 +68,18 @@ class ProductionExceptionHandler implements ExceptionHandlerInterface throw $exception; } - $errorMessage = $this->configuration['errorMessage'] ?? 'Oops, an error occurred! Code: {code}'; - $code = $this->context->getAspect('date')->getDateTime()->format('YmdHis') . $this->random->generateRandomHexString(8); + $errorMessage = $this->configuration['errorMessage'] ?? 'Oops, an error occurred! Request: {requestId}'; - // "%s" has to be replaced by {code} for b/w compatibility + // $code and it's placeholder %s for b/w compatibility + $code = $this->context->getAspect('date')->getDateTime()->format('YmdHis') . $this->random->generateRandomHexString(8); $errorMessage = str_replace('%s', '{code}', $errorMessage); // Log exception except HMAC validation exceptions caused by potentially forged requests if (!in_array($exception->getCode(), AbstractExceptionHandler::IGNORED_HMAC_EXCEPTION_CODES, true)) { - $this->logger->alert($errorMessage, ['exception' => $exception, 'code' => $code]); + $this->logger->alert($errorMessage, ['exception' => $exception, 'code' => $code, 'requestId' => $this->requestId]); } - // Return error message by replacing {code} with the actual code, generated above - return str_replace('{code}', $code, $errorMessage); + // Return interpolated error message + return str_replace(['{code}', '{requestId}'], [$code, $this->requestId], $errorMessage); } } diff --git a/typo3/sysext/frontend/Tests/Unit/ContentObject/ContentObjectRendererTest.php b/typo3/sysext/frontend/Tests/Unit/ContentObject/ContentObjectRendererTest.php index 745fe16cc51fef58c22b7c26ee862d6d1fbc4997..b260f22c64d1bc02fede51fd5f57c6e8f85164fe 100644 --- a/typo3/sysext/frontend/Tests/Unit/ContentObject/ContentObjectRendererTest.php +++ b/typo3/sysext/frontend/Tests/Unit/ContentObject/ContentObjectRendererTest.php @@ -34,6 +34,7 @@ use TYPO3\CMS\Core\Context\UserAspect; use TYPO3\CMS\Core\Context\WorkspaceAspect; use TYPO3\CMS\Core\Core\ApplicationContext; use TYPO3\CMS\Core\Core\Environment; +use TYPO3\CMS\Core\Core\RequestId; use TYPO3\CMS\Core\Crypto\Random; use TYPO3\CMS\Core\Domain\Repository\PageRepository; use TYPO3\CMS\Core\EventDispatcher\EventDispatcher; @@ -2030,7 +2031,7 @@ final class ContentObjectRendererTest extends UnitTestCase if ($addProductionExceptionHandlerInstance) { GeneralUtility::addInstance( ProductionExceptionHandler::class, - new ProductionExceptionHandler(new Context(), new Random(), new NullLogger()) + new ProductionExceptionHandler(new Context(), new Random(), new NullLogger(), new RequestId()) ); } return $contentObjectFixture; diff --git a/typo3/sysext/frontend/Tests/Unit/ContentObject/Exception/ProductionExceptionHandlerTest.php b/typo3/sysext/frontend/Tests/Unit/ContentObject/Exception/ProductionExceptionHandlerTest.php index 0fc14273b9bbfb42798832f41ceaa4c13d9af967..9e0389a67fb97ed1f021efd763937918bf87fbc8 100644 --- a/typo3/sysext/frontend/Tests/Unit/ContentObject/Exception/ProductionExceptionHandlerTest.php +++ b/typo3/sysext/frontend/Tests/Unit/ContentObject/Exception/ProductionExceptionHandlerTest.php @@ -21,6 +21,7 @@ use PHPUnit\Framework\Attributes\Test; use Psr\Log\NullLogger; use TYPO3\CMS\Core\Context\Context; use TYPO3\CMS\Core\Context\DateTimeAspect; +use TYPO3\CMS\Core\Core\RequestId; use TYPO3\CMS\Core\Crypto\Random; use TYPO3\CMS\Core\Http\HtmlResponse; use TYPO3\CMS\Core\Http\ImmediateResponseException; @@ -36,7 +37,7 @@ final class ProductionExceptionHandlerTest extends UnitTestCase $exception = new PropagateResponseException(new HtmlResponse(''), 1607328584); $this->expectException(PropagateResponseException::class); $this->expectExceptionCode(1607328584); - $subject = new ProductionExceptionHandler(new Context(), new Random(), new NullLogger()); + $subject = new ProductionExceptionHandler(new Context(), new Random(), new NullLogger(), new RequestId()); $subject->handle($exception); } @@ -46,25 +47,18 @@ final class ProductionExceptionHandlerTest extends UnitTestCase $exception = new ImmediateResponseException(new HtmlResponse(''), 1533939251); $this->expectException(ImmediateResponseException::class); $this->expectExceptionCode(1533939251); - $subject = new ProductionExceptionHandler(new Context(), new Random(), new NullLogger()); + $subject = new ProductionExceptionHandler(new Context(), new Random(), new NullLogger(), new RequestId()); $subject->handle($exception); } #[Test] - public function handleReturnsMessageWithResolvedErrorCode(): void + public function handleReturnsMessageWithRequestId(): void { - $currentTimestamp = 1629993829; - $random = '029cca07'; - - $randomMock = $this->createMock(Random::class); - $randomMock->method('generateRandomHexString')->with(8)->willReturn($random); - - $context = new Context(); - $context->setAspect('date', new DateTimeAspect(new \DateTimeImmutable('@' . $currentTimestamp))); - $exceptionHandler = new ProductionExceptionHandler($context, $randomMock, new NullLogger()); + $requestId = new RequestId(); + $exceptionHandler = new ProductionExceptionHandler(new Context(), new Random(), new NullLogger(), $requestId); self::assertEquals( - 'Oops, an error occurred! Code: ' . date('YmdHis', $currentTimestamp) . $random, + 'Oops, an error occurred! Request: ' . $requestId, $exceptionHandler->handle(new \Exception('Some exception', 1629996089)) ); } @@ -80,7 +74,7 @@ final class ProductionExceptionHandlerTest extends UnitTestCase $context = new Context(); $context->setAspect('date', new DateTimeAspect(new \DateTimeImmutable('@' . $currentTimestamp))); - $exceptionHandler = new ProductionExceptionHandler($context, $randomMock, new NullLogger()); + $exceptionHandler = new ProductionExceptionHandler($context, $randomMock, new NullLogger(), new RequestId()); $exceptionHandler->setConfiguration([ 'errorMessage' => 'Custom error message: {code}', ]); @@ -105,7 +99,8 @@ final class ProductionExceptionHandlerTest extends UnitTestCase $exceptionHandler = new ProductionExceptionHandler( $context, $randomMock, - new NullLogger() + new NullLogger(), + new RequestId() ); $exceptionHandler->setConfiguration([ 'errorMessage' => 'Custom error message: %s',