Skip to content

Commit b9b7c6a

Browse files
derhansengeorgringer
authored andcommitted
[FEATURE] Introduce ErrorHandler for 403 errors with redirect option
When TYPO3 is configured to create links to access protected pages using `typolinkLinkAccessRestrictedPages = NONE` (which is default), a 403 response is returned, if the current frontend request does not fulfill configured access permissions. This change introduces a new Site errorHandler, which can be used to handle 403 responses for access restricted pages and which redirects the user to a configured page and adds a configurable GET parameter (`return_url` or `redirect_url`) containing the original URL. The configurable GET parameter can be used by 3rd party extensions to redirect the user back to the original URL after a successful login. The TYPO3 extensions ext:felogin and ext:oidc both support the configurable redirect parameter introduced in the new Site errorHandler feature. Resolves: #101252 Releases: main Signed-off-by: Torben Hansen <[email protected]> Change-Id: I06d8e384c5519975efdc8803c98c0a92a56a7653 Reviewed-on: https://review.typo3.org/c/Packages/TYPO3.CMS/+/81945 Tested-by: core-ci <[email protected]> Reviewed-by: Garvin Hicking <[email protected]> Tested-by: Garvin Hicking <[email protected]> Reviewed-by: Georg Ringer <[email protected]> Reviewed-by: Markus Klein <[email protected]> Tested-by: Georg Ringer <[email protected]>
1 parent e9f467d commit b9b7c6a

File tree

5 files changed

+244
-0
lines changed

5 files changed

+244
-0
lines changed

typo3/sysext/backend/Configuration/SiteConfiguration/site_errorhandling.php

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
'Fluid' => 'mimetypes-text-html',
1313
'Page' => 'apps-pagetree-page-content-from-page',
1414
'PHP' => 'mimetypes-text-php',
15+
'LoginRedirect' => 'content-elements-login',
1516
],
1617
],
1718
'columns' => [
@@ -50,6 +51,7 @@
5051
['label' => 'LLL:EXT:backend/Resources/Private/Language/locallang_siteconfiguration_tca.xlf:site_errorhandling.errorHandler.fluid', 'value' => 'Fluid'],
5152
['label' => 'LLL:EXT:backend/Resources/Private/Language/locallang_siteconfiguration_tca.xlf:site_errorhandling.errorHandler.page', 'value' => 'Page'],
5253
['label' => 'LLL:EXT:backend/Resources/Private/Language/locallang_siteconfiguration_tca.xlf:site_errorhandling.errorHandler.php', 'value' => 'PHP'],
54+
['label' => 'LLL:EXT:backend/Resources/Private/Language/locallang_siteconfiguration_tca.xlf:site_errorhandling.errorHandler.loginRedirect', 'value' => 'LoginRedirect'],
5355
],
5456
],
5557
],
@@ -102,6 +104,26 @@
102104
'placeholder' => 'LLL:EXT:backend/Resources/Private/Language/locallang_siteconfiguration_tca.xlf:site_errorhandling.errorPhpClassFQCN.placeholder',
103105
],
104106
],
107+
'loginRedirectTarget' => [
108+
'label' => 'LLL:EXT:backend/Resources/Private/Language/locallang_siteconfiguration_tca.xlf:site_errorhandling.loginRedirectTarget',
109+
'config' => [
110+
'type' => 'link',
111+
'required' => true,
112+
'allowedTypes' => ['page'],
113+
],
114+
],
115+
'loginRedirectParameter' => [
116+
'label' => 'LLL:EXT:backend/Resources/Private/Language/locallang_siteconfiguration_tca.xlf:site_errorhandling.loginRedirectParameter',
117+
'config' => [
118+
'type' => 'select',
119+
'renderType' => 'selectSingle',
120+
'required' => true,
121+
'items' => [
122+
['label' => 'LLL:EXT:backend/Resources/Private/Language/locallang_siteconfiguration_tca.xlf:site_errorhandling.loginRedirectParameter.return_url', 'value' => 'return_url'],
123+
['label' => 'LLL:EXT:backend/Resources/Private/Language/locallang_siteconfiguration_tca.xlf:site_errorhandling.loginRedirectParameter.redirect_url', 'value' => 'redirect_url'],
124+
],
125+
],
126+
],
105127
],
106128
'types' => [
107129
'1' => [
@@ -118,6 +140,9 @@
118140
'PHP' => [
119141
'showitem' => '--palette--;;general, errorPhpClassFQCN',
120142
],
143+
'LoginRedirect' => [
144+
'showitem' => '--palette--;;general, loginRedirectTarget, loginRedirectParameter',
145+
],
121146
],
122147
'palettes' => [
123148
'general' => [

typo3/sysext/backend/Resources/Private/Language/locallang_siteconfiguration_tca.xlf

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,9 @@
180180
<trans-unit id="site_errorhandling.errorHandler.php" resname="site_errorhandling.errorHandler.php">
181181
<source>PHP Class (must implement the PageErrorHandlerInterface)</source>
182182
</trans-unit>
183+
<trans-unit id="site_errorhandling.errorHandler.loginRedirect" resname="site_errorhandling.errorHandler.loginRedirect">
184+
<source>Redirect to login page</source>
185+
</trans-unit>
183186
<trans-unit id="site_errorhandling.errorFluidTemplate" resname="site_errorhandling.errorFluidTemplate">
184187
<source>Fluid Template File</source>
185188
</trans-unit>
@@ -201,6 +204,18 @@
201204
<trans-unit id="site_errorhandling.errorPhpClassFQCN" resname="site_errorhandling.errorPhpClassFQCN">
202205
<source>ErrorHandler Class Target (FQCN)</source>
203206
</trans-unit>
207+
<trans-unit id="site_errorhandling.loginRedirectTarget" resname="site_errorhandling.loginRedirectTarget">
208+
<source>Target page where login process is handled</source>
209+
</trans-unit>
210+
<trans-unit id="site_errorhandling.loginRedirectParameter" resname="site_errorhandling.loginRedirectParameter">
211+
<source>URL parameter for redirect</source>
212+
</trans-unit>
213+
<trans-unit id="site_errorhandling.loginRedirectParameter.return_url" resname="site_errorhandling.loginRedirectParameter.return_url">
214+
<source>return_url (used by e.g. ext:felogin)</source>
215+
</trans-unit>
216+
<trans-unit id="site_errorhandling.loginRedirectParameter.redirect_url" resname="site_errorhandling.loginRedirectParameter.redirect_url">
217+
<source>redirect_url (used by e.g. ext:oidc or ext:felogin)</source>
218+
</trans-unit>
204219
<trans-unit id="site_errorhandling.errorPhpClassFQCN.placeholder" resname="site_errorhandling.errorPhpClassFQCN.placeholder">
205220
<source>Vendor\ExtensionName\ErrorHandlers\GenericErrorhandler</source>
206221
</trans-unit>
Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/*
6+
* This file is part of the TYPO3 CMS project.
7+
*
8+
* It is free software; you can redistribute it and/or modify it under
9+
* the terms of the GNU General Public License, either version 2
10+
* of the License, or any later version.
11+
*
12+
* For the full copyright and license information, please read the
13+
* LICENSE.txt file that was distributed with this source code.
14+
*
15+
* The TYPO3 project - inspiring people to share!
16+
*/
17+
18+
namespace TYPO3\CMS\Core\Error\PageErrorHandler;
19+
20+
use Psr\Http\Message\ResponseInterface;
21+
use Psr\Http\Message\ServerRequestInterface;
22+
use TYPO3\CMS\Core\Context\Context;
23+
use TYPO3\CMS\Core\Controller\ErrorPageController;
24+
use TYPO3\CMS\Core\Http\HtmlResponse;
25+
use TYPO3\CMS\Core\Http\RedirectResponse;
26+
use TYPO3\CMS\Core\LinkHandling\LinkService;
27+
use TYPO3\CMS\Core\Site\Entity\Site;
28+
use TYPO3\CMS\Core\Utility\GeneralUtility;
29+
use TYPO3\CMS\Frontend\Page\PageAccessFailureReasons;
30+
31+
/**
32+
* An error handler that redirects to a configured page, where the login process is handled. Passes a configurable
33+
* url parameter (`return_url` or `redirect_url`) to the target page.
34+
*/
35+
class RedirectLoginErrorHandler implements PageErrorHandlerInterface
36+
{
37+
protected int $loginRedirectPid = 0;
38+
protected int $statusCode = 0;
39+
protected string $loginRedirectParameter = '';
40+
protected Context $context;
41+
protected LinkService $linkService;
42+
43+
public function __construct(int $statusCode, array $configuration)
44+
{
45+
$this->statusCode = $statusCode;
46+
$this->context = GeneralUtility::makeInstance(Context::class);
47+
$this->linkService = GeneralUtility::makeInstance(LinkService::class);
48+
49+
$urlParams = $this->linkService->resolve($configuration['loginRedirectTarget'] ?? '');
50+
$this->loginRedirectPid = (int)($urlParams['pageuid'] ?? 0);
51+
$this->loginRedirectParameter = $configuration['loginRedirectParameter'] ?? 'return_url';
52+
}
53+
54+
public function handlePageError(
55+
ServerRequestInterface $request,
56+
string $message,
57+
array $reasons = []
58+
): ResponseInterface {
59+
$this->checkHandlerConfiguration();
60+
61+
if ($this->shouldHandleRequest($reasons)) {
62+
return $this->handleLoginRedirect($request);
63+
}
64+
65+
// Show general error message with a 403 HTTP statuscode
66+
return $this->getGenericAccessDeniedResponse($message);
67+
}
68+
69+
private function getGenericAccessDeniedResponse(string $reason): ResponseInterface
70+
{
71+
$content = GeneralUtility::makeInstance(ErrorPageController::class)->errorAction(
72+
'Page Not Found',
73+
'The page did not exist or was inaccessible.' . ($reason ? ' Reason: ' . $reason : ''),
74+
0,
75+
$this->statusCode,
76+
);
77+
return new HtmlResponse($content, $this->statusCode);
78+
}
79+
80+
private function handleLoginRedirect(ServerRequestInterface $request): ResponseInterface
81+
{
82+
if ($this->isLoggedIn()) {
83+
return $this->getGenericAccessDeniedResponse(
84+
'The requested page was not accessible with the provided credentials'
85+
);
86+
}
87+
88+
/** @var Site $site */
89+
$site = $request->getAttribute('site');
90+
$language = $request->getAttribute('language');
91+
92+
$loginUrl = $site->getRouter()->generateUri(
93+
$this->loginRedirectPid,
94+
[
95+
'_language' => $language,
96+
$this->loginRedirectParameter => (string)$request->getUri(),
97+
]
98+
);
99+
100+
return new RedirectResponse($loginUrl);
101+
}
102+
103+
private function shouldHandleRequest(array $reasons): bool
104+
{
105+
if (!isset($reasons['code'])) {
106+
return false;
107+
}
108+
109+
$accessDeniedReasons = [
110+
PageAccessFailureReasons::ACCESS_DENIED_PAGE_NOT_RESOLVED,
111+
PageAccessFailureReasons::ACCESS_DENIED_SUBSECTION_NOT_RESOLVED,
112+
];
113+
$isAccessDenied = in_array($reasons['code'], $accessDeniedReasons, true);
114+
115+
return $isAccessDenied || $this->isSimulatedBackendGroup();
116+
}
117+
118+
private function isLoggedIn(): bool
119+
{
120+
return $this->context->getPropertyFromAspect('frontend.user', 'isLoggedIn') || $this->isSimulatedBackendGroup();
121+
}
122+
123+
protected function isSimulatedBackendGroup(): bool
124+
{
125+
// look for special "any group"
126+
return $this->context->getPropertyFromAspect('backend.user', 'isLoggedIn')
127+
&& $this->context->getPropertyFromAspect('frontend.user', 'groupIds')[1] === -2;
128+
}
129+
130+
private function checkHandlerConfiguration(): void
131+
{
132+
if ($this->loginRedirectPid === 0) {
133+
throw new \RuntimeException('No loginRedirectTarget configured for LoginRedirect errorhandler', 1700813537);
134+
}
135+
136+
if ($this->statusCode !== 403) {
137+
throw new \RuntimeException('Invalid HTTP statuscode ' . $this->statusCode . ' for LoginRedirect errorhandler', 1700813545);
138+
}
139+
}
140+
}

typo3/sysext/core/Classes/Site/Entity/Site.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
use TYPO3\CMS\Core\Error\PageErrorHandler\PageContentErrorHandler;
2727
use TYPO3\CMS\Core\Error\PageErrorHandler\PageErrorHandlerInterface;
2828
use TYPO3\CMS\Core\Error\PageErrorHandler\PageErrorHandlerNotConfiguredException;
29+
use TYPO3\CMS\Core\Error\PageErrorHandler\RedirectLoginErrorHandler;
2930
use TYPO3\CMS\Core\ExpressionLanguage\Resolver;
3031
use TYPO3\CMS\Core\Http\Uri;
3132
use TYPO3\CMS\Core\Localization\LanguageService;
@@ -43,6 +44,7 @@ class Site implements SiteInterface
4344
protected const ERRORHANDLER_TYPE_PAGE = 'Page';
4445
protected const ERRORHANDLER_TYPE_FLUID = 'Fluid';
4546
protected const ERRORHANDLER_TYPE_PHP = 'PHP';
47+
protected const ERRORHANDLER_TYPE_LOGIN_REDIRECT = 'LoginRedirect';
4648

4749
/**
4850
* @var string
@@ -305,6 +307,8 @@ public function getErrorHandler(int $statusCode): PageErrorHandlerInterface
305307
return GeneralUtility::makeInstance(FluidPageErrorHandler::class, $statusCode, $errorHandlerConfiguration);
306308
case self::ERRORHANDLER_TYPE_PAGE:
307309
return GeneralUtility::makeInstance(PageContentErrorHandler::class, $statusCode, $errorHandlerConfiguration);
310+
case self::ERRORHANDLER_TYPE_LOGIN_REDIRECT:
311+
return GeneralUtility::makeInstance(RedirectLoginErrorHandler::class, $statusCode, $errorHandlerConfiguration);
308312
case self::ERRORHANDLER_TYPE_PHP:
309313
$handler = GeneralUtility::makeInstance($errorHandlerConfiguration['errorPhpClassFQCN'], $statusCode, $errorHandlerConfiguration);
310314
// Check if the interface is implemented
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
.. include:: /Includes.rst.txt
2+
3+
.. _feature-101252-1715447531:
4+
5+
=============================================================================
6+
Feature: #101252 - Introduce ErrorHandler for 403 errors with redirect option
7+
=============================================================================
8+
9+
See :issue:`101252`
10+
11+
Description
12+
===========
13+
14+
The new error handler :php:`RedirectLoginErrorHandler` has been added,
15+
which makes it possible to redirect the user to a configurable page.
16+
17+
Requesting a login-protected URL would usually return a generic HTTP 403 error
18+
in case of a missing fulfilled access permissions and the configuration
19+
:php:`typolinkLinkAccessRestrictedPages = NONE` (default)
20+
is set.
21+
22+
By enabling this new handler via the site settings, the 403 response
23+
can be handled and a custom redirect can be performed.
24+
25+
The :php:`RedirectLoginErrorHandler` allows to define a
26+
:php:`loginRedirectTarget`, which must be configured to the page, where the
27+
login process is handled. Additionally, the :php:`loginRedirectParameter`
28+
must be set to the URL parameter that will be used to hand over the original
29+
URL to the target page.
30+
31+
The redirect is ensures that the original URL is added to the configured GET
32+
parameter :php:`loginRedirectParameter`, so that the user can be redirected
33+
back to the original page after a successful login.
34+
35+
The error handler allows :php:`return_url` or :php:`redirect_url` as values
36+
for :php:`loginRedirectParameter`. Those values are used in extensions like
37+
`EXT:felogin` or `EXT:oidc`.
38+
39+
.. important::
40+
41+
Redirection to the originating URL via URI arguments requires that
42+
extensions like `EXT:felogin` are configured to allow these redirect modes
43+
(for example via
44+
:typoscript:`plugin.tx_felogin_login.settings.redirectMode=getpost,loginError`)
45+
46+
The new error handler works (with some minor exceptions) similar to the
47+
"Forbidden (HTTP Status 403)" handler in TYPO3 extension `EXT:sierrha`.
48+
It will still emit generic 403 HTTP error messages in certain scenarios,
49+
like when a user is already logged in, but the permissions are not
50+
satisfied.
51+
52+
Impact
53+
======
54+
55+
It is now possible to configure a login redirection process when a user has no
56+
access to a page and a 403 error is thrown, so that after login the
57+
originating URL is requested again. Previously, this required custom
58+
Middlewares or implementations of :php:`PageErrorHandlerInterface`.
59+
60+
.. index:: Frontend, ext:core

0 commit comments

Comments
 (0)