IconController::class, 'layout' => LayoutController::class, 'login' => LoginController::class, 'maintenance' => MaintenanceController::class, 'settings' => SettingsController::class, 'upgrade' => UpgradeController::class, 'environment' => EnvironmentController::class, ]; public function __construct( protected readonly FailsafePackageManager $packageManager, protected readonly ConfigurationManager $configurationManager, protected readonly PasswordHashFactory $passwordHashFactory, protected readonly ContainerInterface $container, protected readonly FormProtectionFactory $formProtectionFactory, protected readonly SessionService $sessionService ) {} /** * Handles an Install Tool request for normal operations */ public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface { if (!$this->canHandleRequest()) { return $handler->handle($request); } if (($GLOBALS['TYPO3_CONF_VARS']['BE']['installToolPassword'] ?? '') === '') { return new HtmlResponse('$GLOBALS[\'TYPO3_CONF_VARS\'][\'BE\'][\'installToolPassword\'] must not be empty.', 500); } // This is required for icon API, that still has no way to pass // a request/ normalizedParams to the icon URL generation $GLOBALS['TYPO3_REQUEST'] = $request; $controllerName = $request->getQueryParams()['install']['controller'] ?? 'layout'; $actionName = $request->getParsedBody()['install']['action'] ?? $request->getQueryParams()['install']['action'] ?? 'init'; if ($actionName === 'showEnableInstallToolFile' && EnableFileService::isInstallToolEnableFilePermanent()) { $actionName = 'showLogin'; } $action = $actionName . 'Action'; // not session related actions if ($actionName === 'init') { $controller = $this->container->get(LayoutController::class); return $controller->initAction($request); } if ($actionName === 'checkEnableInstallToolFile') { return new JsonResponse([ 'success' => $this->checkEnableInstallToolFile(), ]); } if ($actionName === 'showEnableInstallToolFile') { $controller = $this->container->get(LoginController::class); return $controller->showEnableInstallToolFileAction($request); } if ($actionName === 'showLogin') { if (!$this->checkEnableInstallToolFile()) { throw new \RuntimeException('Not authorized', 1505564888); } $controller = $this->container->get(LoginController::class); return $controller->showLoginAction($request); } $this->sessionService->installSessionHandler($request); // the backend user has an active session but the admin / maintainer // rights have been revoked or the user was disabled or deleted in the meantime if ($this->sessionService->isAuthorizedBackendUserSession($request) && !$this->sessionService->hasActiveBackendUserRoleAndSession()) { // log out the user and destroy the session $this->sessionService->resetSession(); $this->sessionService->destroySession($request); $formProtection = $this->formProtectionFactory->createFromRequest($request); $formProtection->clean(); return new HtmlResponse('', 403); } if ($actionName === 'preAccessCheck') { $response = new JsonResponse([ 'installToolLocked' => !$this->checkEnableInstallToolFile(), 'isAuthorized' => $this->sessionService->isAuthorized($request), ]); } elseif ($actionName === 'checkLogin') { if (!$this->checkEnableInstallToolFile() && !$this->sessionService->isAuthorizedBackendUserSession($request)) { throw new \RuntimeException('Not authorized', 1505563556); } if ($this->sessionService->isAuthorized($request)) { $this->sessionService->refreshSession(); $response = new JsonResponse([ 'success' => true, ]); } else { // Session expired, log out user, start new session $this->sessionService->resetSession(); $this->sessionService->startSession(); $response = new JsonResponse([ 'success' => false, ]); } } elseif ($actionName === 'login') { $this->sessionService->initializeSession(); if (!$this->checkEnableInstallToolFile()) { throw new \RuntimeException('Not authorized', 1505567462); } $this->checkSessionToken($request); $this->checkSessionLifetime($request); $password = $request->getParsedBody()['install']['password'] ?? null; $authService = $this->container->get(AuthenticationService::class); if ($authService->loginWithPassword($password, $request, $this->sessionService)) { $response = new JsonResponse([ 'success' => true, ]); } else { if ($password === null || empty($password)) { $messageQueue = new FlashMessageQueue('install'); $messageQueue->enqueue( new FlashMessage('Please enter the install tool password', '', ContextualFeedbackSeverity::ERROR) ); } else { $hashInstance = $this->passwordHashFactory->getDefaultHashInstance('BE'); $hashedPassword = $hashInstance->getHashedPassword($password); $messageQueue = new FlashMessageQueue('install'); $messageQueue->enqueue( new FlashMessage( 'Given password does not match the install tool login password. Calculated hash: ' . $hashedPassword, '', ContextualFeedbackSeverity::ERROR ) ); } $response = new JsonResponse([ 'success' => false, 'status' => $messageQueue, ]); } } elseif ($actionName === 'logout') { if (EnableFileService::installToolEnableFileExists() && !EnableFileService::isInstallToolEnableFilePermanent()) { EnableFileService::removeInstallToolEnableFile(); } $formProtection = $this->formProtectionFactory->createFromRequest($request); $formProtection->clean(); $this->sessionService->destroySession($request); $response = new JsonResponse([ 'success' => true, ]); } else { $enforceReferrerResponse = $this->enforceReferrer($request); if ($enforceReferrerResponse !== null) { return $enforceReferrerResponse; } $this->sessionService->initializeSession(); if ( !$this->checkSessionToken($request) || !$this->checkSessionLifetime($request) || !$this->sessionService->isAuthorized($request) ) { return new HtmlResponse('', 403); } $this->sessionService->refreshSession(); if (!array_key_exists($controllerName, $this->controllers)) { throw new \RuntimeException( 'Unknown controller ' . $controllerName, 1505215756 ); } $this->packageManager->recreatePackageStatesFileIfMissing(); $className = $this->controllers[$controllerName]; /** @var AbstractController $controller */ $controller = $this->container->get($className); if (!method_exists($controller, $action)) { throw new \RuntimeException( 'Unknown action method ' . $action . ' in controller ' . $controllerName, 1505216027 ); } $response = $controller->$action($request); } return $response; } /** * This request handler is only accessible when basic system integrity constraints are fulfilled. */ protected function canHandleRequest(): bool { $basicIntegrity = $this->checkIfEssentialConfigurationExists() && !EnableFileService::isFirstInstallAllowed(); if (!$basicIntegrity) { return false; } return true; } /** * Checks if ENABLE_INSTALL_TOOL exists. */ protected function checkEnableInstallToolFile(): bool { return EnableFileService::checkInstallToolEnableFile(); } /** * Use form protection API to find out if protected POST forms are ok. */ protected function checkSessionToken(ServerRequestInterface $request): bool { $postValues = $request->getParsedBody()['install'] ?? null; // no post data is there, so no token check necessary if (empty($postValues)) { return true; } $tokenOk = false; // A token must be given as soon as there is POST data if (isset($postValues['token'])) { $formProtection = $this->formProtectionFactory->createFromRequest($request); $action = (string)$postValues['action']; if ($action === '') { throw new \RuntimeException( 'No POST action given for token check', 1369326593 ); } $tokenOk = $formProtection->validateToken($postValues['token'], 'installTool', $action); } if (!$tokenOk) { $this->sessionService->resetSession(); $this->sessionService->startSession(); } return $tokenOk; } /** * Check if session expired. * If the session has expired, the login form is displayed. * * @return bool True if session lifetime is OK */ protected function checkSessionLifetime(ServerRequestInterface $request): bool { $isExpired = $this->sessionService->isExpired($request); if ($isExpired) { // Session expired, log out user, start new session $this->sessionService->resetSession(); $this->sessionService->startSession(); } return !$isExpired; } /** * Check if system/settings.php exists (PackageStates is optional) * * @return bool TRUE when the essential configuration is available, otherwise FALSE */ protected function checkIfEssentialConfigurationExists(): bool { return file_exists($this->configurationManager->getSystemConfigurationFileLocation()); } /** * Evaluates HTTP `Referer` header (which is denied by client to be a custom * value) - attempts to ensure the value is given using a HTML client refresh. * see: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Referer */ protected function enforceReferrer(ServerRequestInterface $request): ?ResponseInterface { if (!(new Features())->isFeatureEnabled('security.backend.enforceReferrer')) { return null; } return (new ReferrerEnforcer())->handle($request, [ 'flags' => ['refresh-always'], 'subject' => 'Install Tool', ]); } }