Skip to content
Snippets Groups Projects
Commit 1bc57510 authored by Benni Mack's avatar Benni Mack Committed by Susanne Moog
Browse files

[BUGFIX] Send HTTP headers with PSR-7 response

Current Backend User Authentication sends header()
which is not appended to the headers of the PSR-7
response and cannot be tested or validated properly.

This is mainly due to legacy reasons as AbstractUserAuthentication
sends these headers.

When using TYPO3 in a scenario to do sub-requests
within one PHP process, it is not possible to properly
evaluate these Response headers.

To enable this, the BackendUserAuthenticator middlewares
now apply the headers to the Response object.

Resolves: #89911
Releases: master
Change-Id: Id22ca1a65e52f101d3775fbe79ea0ef1622e9fa9
Reviewed-on: https://review.typo3.org/c/Packages/TYPO3.CMS/+/62593


Tested-by: default avatarTYPO3com <noreply@typo3.com>
Tested-by: default avatarTobi Kretschmann <tobi@tobishome.de>
Tested-by: default avatarSusanne Moog <look@susi.dev>
Reviewed-by: default avatarJörg Bösche <typo3@joergboesche.de>
Reviewed-by: default avatarSascha Rademacher <sascha.rademacher+typo3@gmail.com>
Reviewed-by: default avatarTobi Kretschmann <tobi@tobishome.de>
Reviewed-by: default avatarSusanne Moog <look@susi.dev>
parent 6d374b66
Branches
Tags
No related merge requests found
......@@ -17,12 +17,8 @@ namespace TYPO3\CMS\Backend\Middleware;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;
use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
use TYPO3\CMS\Core\Context\Context;
use TYPO3\CMS\Core\Context\UserAspect;
use TYPO3\CMS\Core\Context\WorkspaceAspect;
use TYPO3\CMS\Core\Core\Bootstrap;
use TYPO3\CMS\Core\Localization\LanguageService;
use TYPO3\CMS\Core\Utility\GeneralUtility;
......@@ -32,7 +28,7 @@ use TYPO3\CMS\Core\Utility\GeneralUtility;
*
* @internal
*/
class BackendUserAuthenticator implements MiddlewareInterface
class BackendUserAuthenticator extends \TYPO3\CMS\Core\Middleware\BackendUserAuthenticator
{
/**
* List of requests that don't need a valid BE user
......@@ -50,16 +46,6 @@ class BackendUserAuthenticator implements MiddlewareInterface
'/ajax/core/requirejs',
];
/**
* @var Context
*/
protected $context;
public function __construct(Context $context)
{
$this->context = $context;
}
/**
* Calls the bootstrap process to set up $GLOBALS['BE_USER'] AND $GLOBALS['LANG']
*
......@@ -71,14 +57,21 @@ class BackendUserAuthenticator implements MiddlewareInterface
{
$pathToRoute = $request->getAttribute('routePath', '/login');
Bootstrap::initializeBackendUser();
// The global must be available very early, because methods below
// might trigger code which relies on it. See: #45625
$GLOBALS['BE_USER'] = GeneralUtility::makeInstance(BackendUserAuthentication::class);
$GLOBALS['BE_USER']->start();
// @todo: once this logic is in this method, the redirect URL should be handled as response here
Bootstrap::initializeBackendAuthentication($this->isLoggedInBackendUserRequired($pathToRoute));
$GLOBALS['BE_USER']->backendCheckLogin($this->isLoggedInBackendUserRequired($pathToRoute));
$GLOBALS['LANG'] = LanguageService::createFromUserPreferences($GLOBALS['BE_USER']);
// Register the backend user as aspect
$this->setBackendUserAspect($GLOBALS['BE_USER']);
return $handler->handle($request);
$response = $handler->handle($request);
// Additional headers to never cache any PHP request should be sent at any time when
// accessing the TYPO3 Backend
return $this->applyHeadersToResponse($response);
}
/**
......@@ -92,15 +85,4 @@ class BackendUserAuthenticator implements MiddlewareInterface
{
return in_array($routePath, $this->publicRoutes, true);
}
/**
* Register the backend user as aspect
*
* @param BackendUserAuthentication $user
*/
protected function setBackendUserAspect(BackendUserAuthentication $user)
{
$this->context->setAspect('backend.user', GeneralUtility::makeInstance(UserAspect::class, $user));
$this->context->setAspect('workspace', GeneralUtility::makeInstance(WorkspaceAspect::class, $user->workspace));
}
}
<?php
declare(strict_types = 1);
namespace TYPO3\CMS\Core\Middleware;
/*
* This file is part of the TYPO3 CMS project.
*
* It is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, either version 2
* of the License, or any later version.
*
* For the full copyright and license information, please read the
* LICENSE.txt file that was distributed with this source code.
*
* The TYPO3 project - inspiring people to share!
*/
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;
use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
use TYPO3\CMS\Core\Context\Context;
use TYPO3\CMS\Core\Context\UserAspect;
use TYPO3\CMS\Core\Context\WorkspaceAspect;
use TYPO3\CMS\Core\Utility\GeneralUtility;
/**
* Boilerplate to authenticate a backend user in the current workflow, can be used
* for TYPO3 Backend and Frontend requests.
*
* The actual authentication and the selection if no-cache headers to responses should
* be applied should still reside in the "process()" method which should be
* extended by derivative classes.
*
* In derivative classes, the Context API can be used to detect, if a backend user is logged in
* like this:
*
* $response = $handler->handle($request);
* if ($this->context->getAspect('backend.user')->isLoggedIn()) {
* return $this->applyHeadersToResponse($response);
* }
*
* @internal this class might get merged again with the subclasses
*/
abstract class BackendUserAuthenticator implements MiddlewareInterface
{
/**
* @var Context
*/
protected $context;
public function __construct(Context $context)
{
$this->context = $context;
}
/**
* @inheritDoc
*/
abstract public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface;
/**
* Adding headers to the response to avoid caching on the client side.
* These headers will override any previous headers of these names sent.
* Get the http headers to be sent if an authenticated user is available,
* in order to disallow browsers to store the response on the client side.
*
* @param ResponseInterface $response
* @return ResponseInterface the modified response object.
*/
protected function applyHeadersToResponse(ResponseInterface $response): ResponseInterface
{
$headers = [
'Expires' => 0,
'Last-Modified' => gmdate('D, d M Y H:i:s') . ' GMT',
'Cache-Control' => 'no-cache, must-revalidate',
// HTTP 1.0 compatibility, see https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Pragma
'Pragma' => 'no-cache'
];
foreach ($headers as $headerName => $headerValue) {
$response = $response->withHeader($headerName, (string)$headerValue);
}
return $response;
}
/**
* Register the backend user as aspect
*
* @param BackendUserAuthentication|null $user
*/
protected function setBackendUserAspect(?BackendUserAuthentication $user): void
{
$this->context->setAspect('backend.user', GeneralUtility::makeInstance(UserAspect::class, $user));
$this->context->setAspect('workspace', GeneralUtility::makeInstance(WorkspaceAspect::class, $user ? $user->workspace : 0));
}
}
......@@ -18,13 +18,9 @@ namespace TYPO3\CMS\Frontend\Middleware;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;
use TYPO3\CMS\Backend\FrontendBackendUserAuthentication;
use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
use TYPO3\CMS\Core\Context\Context;
use TYPO3\CMS\Core\Context\UserAspect;
use TYPO3\CMS\Core\Context\WorkspaceAspect;
use TYPO3\CMS\Core\Core\Bootstrap;
use TYPO3\CMS\Core\Http\NormalizedParams;
use TYPO3\CMS\Core\Localization\LanguageService;
......@@ -38,18 +34,8 @@ use TYPO3\CMS\Core\Utility\GeneralUtility;
* page due to rights management. As this can only happen once the page ID is resolved, this will happen
* after the routing middleware.
*/
class BackendUserAuthenticator implements MiddlewareInterface
class BackendUserAuthenticator extends \TYPO3\CMS\Core\Middleware\BackendUserAuthenticator
{
/**
* @var Context
*/
protected $context;
public function __construct(Context $context)
{
$this->context = $context;
}
/**
* Creates a backend user authentication object, tries to authenticate a user
*
......@@ -77,7 +63,13 @@ class BackendUserAuthenticator implements MiddlewareInterface
$this->setBackendUserAspect($GLOBALS['BE_USER']);
}
return $handler->handle($request);
$response = $handler->handle($request);
// If, when building the response, the user is still available, then ensure that the headers are sent properly
if ($this->context->getAspect('backend.user')->isLoggedIn()) {
return $this->applyHeadersToResponse($response);
}
return $response;
}
/**
......@@ -123,15 +115,4 @@ class BackendUserAuthenticator implements MiddlewareInterface
}
return $user->backendCheckLogin();
}
/**
* Register the backend user as aspect
*
* @param BackendUserAuthentication|null $user
*/
protected function setBackendUserAspect(BackendUserAuthentication $user)
{
$this->context->setAspect('backend.user', GeneralUtility::makeInstance(UserAspect::class, $user));
$this->context->setAspect('workspace', GeneralUtility::makeInstance(WorkspaceAspect::class, $user->workspace));
}
}
<?php
declare(strict_types = 1);
namespace TYPO3\CMS\Frontend\Tests\Functional\Middleware;
/*
* This file is part of the TYPO3 CMS project.
*
* It is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, either version 2
* of the License, or any later version.
*
* For the full copyright and license information, please read the
* LICENSE.txt file that was distributed with this source code.
*
* The TYPO3 project - inspiring people to share!
*/
use TYPO3\CMS\Core\Tests\Functional\SiteHandling\SiteBasedTestTrait;
use TYPO3\TestingFramework\Core\Functional\Framework\Frontend\InternalRequest;
use TYPO3\TestingFramework\Core\Functional\Framework\Frontend\InternalRequestContext;
use TYPO3\TestingFramework\Core\Functional\FunctionalTestCase;
class BackendUserAuthenticatorTest extends FunctionalTestCase
{
use SiteBasedTestTrait;
protected const LANGUAGE_PRESETS = [
'EN' => ['id' => 0, 'title' => 'English', 'locale' => 'en_US.UTF8', 'iso' => 'en', 'hrefLang' => 'en-US', 'direction' => ''],
];
protected function setUp(): void
{
parent::setUp();
$this->importDataSet('EXT:core/Tests/Functional/Fixtures/pages.xml');
$this->setUpBackendUserFromFixture(1);
$this->writeSiteConfiguration(
'acme-com',
$this->buildSiteConfiguration(1, 'https://acme.com/'),
[
$this->buildDefaultLanguageConfiguration('EN', '/'),
]
);
}
/**
* @test
*/
public function nonAuthenticatedRequestDoesNotSendHeaders(): void
{
$response = $this->executeFrontendRequest(
(new InternalRequest('/'))->withPageId(1),
(new InternalRequestContext())
);
self::assertArrayNotHasKey('Cache-Control', $response->getHeaders());
self::assertArrayNotHasKey('Pragma', $response->getHeaders());
self::assertArrayNotHasKey('Expires', $response->getHeaders());
}
/**
* @test
*/
public function authenticatedRequestIncludesInvalidCacheHeaders(): void
{
$response = $this->executeFrontendRequest(
(new InternalRequest('/'))->withPageId(1),
(new InternalRequestContext())
->withBackendUserId(1)
);
self::assertEquals('no-cache, must-revalidate', $response->getHeaders()['Cache-Control'][0]);
self::assertEquals('no-cache', $response->getHeaders()['Pragma'][0]);
self::assertEquals(0, $response->getHeaders()['Expires'][0]);
}
}
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment