IconController::class, 'layout' => LayoutController::class, 'login' => LoginController::class, 'maintenance' => MaintenanceController::class, 'settings' => SettingsController::class, 'upgrade' => UpgradeController::class, 'environment' => EnvironmentController::class, ]; /** * @param ConfigurationManager $configurationManager */ public function __construct(ConfigurationManager $configurationManager) { $this->configurationManager = $configurationManager; } /** * Handles an Install Tool request for normal operations * * @param ServerRequestInterface $request * @return ResponseInterface */ public function handleRequest(ServerRequestInterface $request): ResponseInterface { return $this->handle($request); } /** * Handles an Install Tool request for normal operations * * @param ServerRequestInterface $request * @return ResponseInterface */ public function handle(ServerRequestInterface $request): ResponseInterface { $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'; $session = $this->initializeSession(); if ($actionName === 'preAccessCheck') { $response = new JsonResponse([ 'installToolLocked' => !$this->checkEnableInstallToolFile(), 'isAuthorized' => $session->isAuthorized() ]); } elseif ($actionName === 'init') { $controller = new LayoutController(); $response = $controller->initAction($request); } elseif ($actionName === 'checkEnableInstallToolFile') { $response = new JsonResponse([ 'success' => $this->checkEnableInstallToolFile(), ]); } elseif ($actionName === 'showEnableInstallToolFile') { $controller = new LoginController(); $response = $controller->showEnableInstallToolFileAction($request); } elseif ($actionName === 'checkLogin') { if (!$this->checkEnableInstallToolFile() && !$session->isAuthorizedBackendUserSession()) { throw new \RuntimeException('Not authorized', 1505563556); } if ($session->isExpired() || !$session->isAuthorized()) { // Session expired, log out user, start new session $session->resetSession(); $session->startSession(); $response = new JsonResponse([ 'success' => false, ]); } else { $session->refreshSession(); $response = new JsonResponse([ 'success' => true, ]); } } elseif ($actionName === 'showLogin') { if (!$this->checkEnableInstallToolFile()) { throw new \RuntimeException('Not authorized', 1505564888); } $controller = new LoginController(); $response = $controller->showLoginAction($request); } elseif ($actionName === 'login') { if (!$this->checkEnableInstallToolFile()) { throw new \RuntimeException('Not authorized', 1505567462); } $this->checkSessionToken($request, $session); $this->checkSessionLifetime($session); $password = $request->getParsedBody()['install']['password'] ?? null; $authService = new AuthenticationService($session); if ($authService->loginWithPassword($password)) { $response = new JsonResponse([ 'success' => true, ]); } else { if ($password === null || empty($password)) { $messageQueue = (new FlashMessageQueue('install'))->enqueue( new FlashMessage('Please enter the install tool password', '', FlashMessage::ERROR) ); } else { $hashInstance = GeneralUtility::makeInstance(PasswordHashFactory::class)->getDefaultHashInstance('BE'); $hashedPassword = $hashInstance->getHashedPassword($password); $messageQueue = (new FlashMessageQueue('install'))->enqueue( new FlashMessage( 'Given password does not match the install tool login password. Calculated hash: ' . $hashedPassword, '', FlashMessage::ERROR ) ); } $response = new JsonResponse([ 'success' => false, 'status' => $messageQueue, ]); } } elseif ($actionName === 'logout') { if (EnableFileService::installToolEnableFileExists() && !EnableFileService::isInstallToolEnableFilePermanent()) { EnableFileService::removeInstallToolEnableFile(); } $formProtection = FormProtectionFactory::get( InstallToolFormProtection::class ); $formProtection->clean(); $session->destroySession(); $response = new JsonResponse([ 'success' => true, ]); } else { $enforceReferrerResponse = $this->enforceReferrer($request); if ($enforceReferrerResponse instanceof ResponseInterface) { return $enforceReferrerResponse; } if ( !$this->checkSessionToken($request, $session) || !$this->checkSessionLifetime($session) || !$session->isAuthorized() ) { return new HtmlResponse('', 403); } $session->refreshSession(); if (!array_key_exists($controllerName, $this->controllers)) { throw new \RuntimeException( 'Unknown controller ' . $controllerName, 1505215756 ); } $this->recreatePackageStatesFileIfMissing(); /** @var AbstractController $controller */ $controller = new $this->controllers[$controllerName]; 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 can handle any request when not in CLI mode. * Warning: Order of these methods is security relevant and interferes with different access * conditions (new/existing installation). See the single method comments for details. * * @param ServerRequestInterface $request * @return bool Returns always TRUE */ public function canHandleRequest(ServerRequestInterface $request): bool { $basicIntegrity = $this->checkIfEssentialConfigurationExists() && !empty($GLOBALS['TYPO3_CONF_VARS']['BE']['installToolPassword']) && !EnableFileService::isFirstInstallAllowed(); if (!$basicIntegrity) { return false; } return true; } /** * Returns the priority - how eager the handler is to actually handle the request. * * @return int The priority of the request handler. */ public function getPriority(): int { return 50; } /** * Checks if ENABLE_INSTALL_TOOL exists. * * @return bool */ protected function checkEnableInstallToolFile() { return EnableFileService::checkInstallToolEnableFile(); } /** * Initialize session object. * Subclass will throw exception if session can not be created or if * preconditions like a valid encryption key are not set. * * @return SessionService */ protected function initializeSession() { $session = new SessionService(); if (!$session->hasSession()) { $session->startSession(); } return $session; } /** * Use form protection API to find out if protected POST forms are ok. * * @param ServerRequestInterface $request * @param SessionService $session * @return bool */ protected function checkSessionToken(ServerRequestInterface $request, SessionService $session): bool { $postValues = $request->getParsedBody()['install']; // 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 = FormProtectionFactory::get( InstallToolFormProtection::class ); $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) { $session->resetSession(); $session->startSession(); } return $tokenOk; } /** * Check if session expired. * If the session has expired, the login form is displayed. * * @param SessionService $session * @return bool True if session lifetime is OK */ protected function checkSessionLifetime(SessionService $session): bool { $isExpired = $session->isExpired(); if ($isExpired) { // Session expired, log out user, start new session $session->resetSession(); $session->startSession(); } return !$isExpired; } /** * Check if LocalConfiguration.php and PackageStates.php exist * * @return bool TRUE when the essential configuration is available, otherwise FALSE */ protected function checkIfEssentialConfigurationExists(): bool { return file_exists($this->configurationManager->getLocalConfigurationFileLocation()); } /** * Create PackageStates.php if missing and LocalConfiguration exists. * * It is fired if PackageStates.php is deleted on a running instance, * all packages marked as "part of minimal system" are activated in this case. */ protected function recreatePackageStatesFileIfMissing(): void { if (!file_exists(Environment::getLegacyConfigPath() . '/PackageStates.php')) { // We need a FailsafePackageManager at this moment, however this is given // As Bootstrap is registering the FailsafePackageManager object as a singleton instance // of the main PackageManager class. See \TYPO3\CMS\Core\Core\Bootstrap::init() /** @var \TYPO3\CMS\Core\Package\FailsafePackageManager $packageManager */ $packageManager = GeneralUtility::makeInstance(PackageManager::class); $packages = $packageManager->getAvailablePackages(); foreach ($packages as $package) { if ($package instanceof PackageInterface && $package->isPartOfMinimalUsableSystem()) { $packageManager->activatePackage($package->getPackageKey()); } } $packageManager->forceSortAndSavePackageStates(); } } /** * 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 * * @param ServerRequestInterface $request * @return ResponseInterface|null */ protected function enforceReferrer(ServerRequestInterface $request): ?ResponseInterface { if (!(new Features())->isFeatureEnabled('security.backend.enforceReferrer')) { return null; } return (new ReferrerEnforcer($request))->handle([ 'flags' => ['refresh-always'], 'subject' => 'Install Tool', ]); } }