Files
gutachten_t3/typo3/sysext/install/Classes/Controller/BackendModuleController.php
T
2026-10-19 08:18:39 +02:00

337 lines
12 KiB
PHP

<?php
declare(strict_types = 1);
namespace TYPO3\CMS\Install\Controller;
/*
* 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 TYPO3\CMS\Backend\Routing\UriBuilder;
use TYPO3\CMS\Core\Authentication\AbstractAuthenticationService;
use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
use TYPO3\CMS\Core\Core\Environment;
use TYPO3\CMS\Core\Crypto\PasswordHashing\InvalidPasswordHashException;
use TYPO3\CMS\Core\Crypto\PasswordHashing\PasswordHashFactory;
use TYPO3\CMS\Core\Http\HtmlResponse;
use TYPO3\CMS\Core\Http\RedirectResponse;
use TYPO3\CMS\Core\Http\Uri;
use TYPO3\CMS\Core\Utility\ExtensionManagementUtility;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Fluid\View\StandaloneView;
use TYPO3\CMS\Install\Service\SessionService;
use TYPO3\CMS\Rsaauth\RsaEncryptionEncoder;
/**
* Backend module controller to the Install Tool. Sets an Install Tool session
* marked as "initialized by a valid system administrator backend user" and
* redirects to the Install Tool entry point.
*
* This is a classic backend module that does not interfere with other code
* within the Install Tool, it can be seen as a facade around Install Tool just
* to embed the Install Tool in backend.
* @internal This class is a specific controller implementation and is not considered part of the Public TYPO3 API.
*/
class BackendModuleController
{
private const FLAG_CONFIRMATION_REQUEST = 1;
protected const FLAG_INSTALL_TOOL_PASSWORD = 2;
private const ALLOWED_ACTIONS = ['maintenance', 'settings', 'upgrade', 'environment'];
/**
* @var SessionService
*/
protected $sessionService;
/**
* @var UriBuilder
*/
protected $uriBuilder;
public function __construct()
{
$this->uriBuilder = GeneralUtility::makeInstance(UriBuilder::class);
}
/**
* Shows and handles backend user session confirmation ("sudo mode") for
* accessing a particular Install Tool controller (as given in `$targetController`).
*
* @param ServerRequestInterface $request
* @return ResponseInterface
*/
public function backendUserConfirmationAction(ServerRequestInterface $request): ResponseInterface
{
$flags = (int)($request->getQueryParams()['flags'] ?? 0);
$targetController = (string)($request->getQueryParams()['targetController'] ?? '');
$targetHash = (string)($request->getQueryParams()['targetHash'] ?? '');
$expectedTargetHash = GeneralUtility::hmac($targetController, BackendModuleController::class);
$flagInstallToolPassword = (bool)($flags & self::FLAG_INSTALL_TOOL_PASSWORD);
$flagInvalidPassword = false;
if (!in_array($targetController, self::ALLOWED_ACTIONS, true)
|| !hash_equals($expectedTargetHash, $targetHash)) {
return new HtmlResponse('', 403);
}
if ($flags & self::FLAG_CONFIRMATION_REQUEST) {
if ($flagInstallToolPassword && $this->verifyInstallToolPassword($request)) {
return $this->setAuthorizedAndRedirect($targetController);
}
if (!$flagInstallToolPassword && $this->verifyBackendUserPassword($request)) {
return $this->setAuthorizedAndRedirect($targetController);
}
$flagInvalidPassword = true;
}
$view = GeneralUtility::makeInstance(StandaloneView::class);
$view->getTemplatePaths()->setTemplatePathAndFilename(
ExtensionManagementUtility::extPath(
'install',
'Resources/Private/Templates/BackendModule/BackendUserConfirmation.html'
)
);
$view->assignMultiple([
'flagInvalidPassword' => $flagInvalidPassword,
'flagInstallToolPassword' => $flagInstallToolPassword,
'languageFileReference' => 'LLL:EXT:install/Resources/Private/Language/BackendModule.xlf',
'passwordModeUri' => $this->getBackendUserConfirmationUri([
'targetController' => $targetController,
'targetHash' => $targetHash,
// current flags, unset FLAG_CONFIRMATION_REQUEST, toggle FLAG_INSTALL_TOOL_PASSWORD
'flags' => $flags & ~self::FLAG_CONFIRMATION_REQUEST ^ self::FLAG_INSTALL_TOOL_PASSWORD,
]),
'verifyUri' => $this->getBackendUserConfirmationUri([
'targetController' => $targetController,
'targetHash' => $targetHash,
// current flags, add FLAG_CONFIRMATION_REQUEST
'flags' => $flags | self::FLAG_CONFIRMATION_REQUEST,
]),
'cancelUri' => '',
]);
$this->applyRsaAuthModules();
return new HtmlResponse($view->render());
}
/**
* Initialize session and redirect to "maintenance"
*
* @return ResponseInterface
*/
public function maintenanceAction(): ResponseInterface
{
return $this->getBackendUserConfirmationRedirect('maintenance')
?? $this->setAuthorizedAndRedirect('maintenance');
}
/**
* Initialize session and redirect to "settings"
*
* @return ResponseInterface
*/
public function settingsAction(): ResponseInterface
{
return $this->getBackendUserConfirmationRedirect('settings')
?? $this->setAuthorizedAndRedirect('settings');
}
/**
* Initialize session and redirect to "upgrade"
*
* @return ResponseInterface
*/
public function upgradeAction(): ResponseInterface
{
return $this->getBackendUserConfirmationRedirect('upgrade')
?? $this->setAuthorizedAndRedirect('upgrade');
}
/**
* Initialize session and redirect to "environment"
*
* @return ResponseInterface
*/
public function environmentAction(): ResponseInterface
{
return $this->getBackendUserConfirmationRedirect('environment')
?? $this->setAuthorizedAndRedirect('environment');
}
/**
* Creates redirect response to backend user confirmation (if required).
*
* @param string $targetController
* @return ResponseInterface|null
*/
protected function getBackendUserConfirmationRedirect(string $targetController): ?ResponseInterface
{
if ($this->getSessionService()->isAuthorizedBackendUserSession()) {
return null;
}
if (Environment::getContext()->isDevelopment()) {
return null;
}
$redirectUri = $this->getBackendUserConfirmationUri([
'targetController' => $targetController,
'targetHash' => GeneralUtility::hmac($targetController, BackendModuleController::class),
]);
return new RedirectResponse((string)$redirectUri, 403);
}
protected function getBackendUserConfirmationUri(array $parameters): Uri
{
return $this->uriBuilder->buildUriFromRoute(
'install.backend-user-confirmation',
$parameters
);
}
/**
* Starts / updates the session and redirects to the Install Tool
* with given action.
*
* @param $controller
* @return ResponseInterface
*/
protected function setAuthorizedAndRedirect(string $controller): ResponseInterface
{
$this->getSessionService()->setAuthorizedBackendSession();
$redirectLocation = 'install.php?install[controller]=' . $controller . '&install[context]=backend';
return new RedirectResponse($redirectLocation, 303);
}
/**
* Verifies that provided password matches Install Tool password.
*
* @param ServerRequestInterface $request
* @return bool
*/
protected function verifyInstallToolPassword(ServerRequestInterface $request): bool
{
$parsedBody = $request->getParsedBody();
$password = $parsedBody['confirmationPassword'] ?? null;
$installToolPassword = $GLOBALS['TYPO3_CONF_VARS']['BE']['installToolPassword'] ?? null;
if (!is_string($password) || empty($installToolPassword)) {
return false;
}
try {
$hashFactory = GeneralUtility::makeInstance(PasswordHashFactory::class);
$hashInstance = $hashFactory->get($installToolPassword, 'BE');
return $hashInstance->checkPassword($password, $installToolPassword);
} catch (InvalidPasswordHashException $exception) {
return false;
}
}
/**
* Verifies that provided password is actually correct for current backend user
* by stepping through authentication chain in `$GLOBALS['BE_USER]`.
*
* @param ServerRequestInterface $request
* @return bool
*/
protected function verifyBackendUserPassword(ServerRequestInterface $request): bool
{
$parsedBody = $request->getParsedBody();
$password = empty($parsedBody['confirmationPasswordInternal'])
? ($parsedBody['confirmationPassword'] ?? null) // default field
: $parsedBody['confirmationPasswordInternal']; // filled e.g. by `ext:rsaauth`
if (!is_string($password)) {
return false;
}
// clone current backend user object to avoid
// possible side effects for the real instance
$backendUser = clone $this->getBackendUser();
$loginData = [
'status' => 'sudo-mode',
'origin' => BackendModuleController::class,
'uname' => $backendUser->user['username'],
'uident' => $password,
];
// currently there is no dedicated API to perform authentication
// that's why this process partially has to be simulated here
$loginData = $backendUser->processLoginData($loginData);
$authInfo = $backendUser->getAuthInfoArray();
$authenticated = false;
/** @var AbstractAuthenticationService $service or any other service (sic!) */
foreach ($this->getAuthServices($backendUser, $loginData, $authInfo) as $service) {
$ret = (int)$service->authUser($backendUser->user);
if ($ret <= 0) {
return false;
}
if ($ret >= 200) {
return true;
}
if ($ret >= 100) {
continue;
}
$authenticated = true;
continue;
}
return $authenticated;
}
/**
* Initializes authentication services to be used in a foreach loop
*
* @param BackendUserAuthentication $backendUser
* @param array $loginData
* @param array $authInfo
* @return \Generator<int, object>
*/
protected function getAuthServices(BackendUserAuthentication $backendUser, array $loginData, array $authInfo): \Generator
{
$serviceChain = [];
$subType = 'authUserBE';
while ($service = GeneralUtility::makeInstanceService('auth', $subType, $serviceChain)) {
$serviceChain[] = $service->getServiceKey();
if (!is_object($service)) {
break;
}
$service->initAuth($subType, $loginData, $authInfo, $backendUser);
yield $service;
}
}
protected function getBackendUser(): BackendUserAuthentication
{
return $GLOBALS['BE_USER'];
}
/**
* Install Tool modified sessions meta-data (handler, storage, name) which
* conflicts with existing session that for instance is used for ext:rsaauth.
*
* @return SessionService
*/
protected function getSessionService(): SessionService
{
if ($this->sessionService === null) {
$this->sessionService = new SessionService();
$this->sessionService->startSession();
}
return $this->sessionService;
}
protected function applyRsaAuthModules()
{
if (!ExtensionManagementUtility::isLoaded('rsaauth')) {
return;
}
$rsaEncryptionEncoder = GeneralUtility::makeInstance(RsaEncryptionEncoder::class);
$rsaEncryptionEncoder->enableRsaEncryption(true);
}
}