[TASK] Streamline PHPDoc comment matches function/method signature
[Packages/TYPO3.CMS.git] / typo3 / sysext / install / Classes / Http / RequestHandler.php
1 <?php
2 declare(strict_types = 1);
3 namespace TYPO3\CMS\Install\Http;
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 use Psr\Http\Message\ResponseInterface;
19 use Psr\Http\Message\ServerRequestInterface;
20 use TYPO3\CMS\Core\Configuration\ConfigurationManager;
21 use TYPO3\CMS\Core\Core\Environment;
22 use TYPO3\CMS\Core\Crypto\PasswordHashing\PasswordHashFactory;
23 use TYPO3\CMS\Core\FormProtection\FormProtectionFactory;
24 use TYPO3\CMS\Core\FormProtection\InstallToolFormProtection;
25 use TYPO3\CMS\Core\Http\HtmlResponse;
26 use TYPO3\CMS\Core\Http\JsonResponse;
27 use TYPO3\CMS\Core\Http\RequestHandlerInterface;
28 use TYPO3\CMS\Core\Messaging\FlashMessage;
29 use TYPO3\CMS\Core\Messaging\FlashMessageQueue;
30 use TYPO3\CMS\Core\Package\PackageInterface;
31 use TYPO3\CMS\Core\Package\PackageManager;
32 use TYPO3\CMS\Core\Utility\GeneralUtility;
33 use TYPO3\CMS\Install\Authentication\AuthenticationService;
34 use TYPO3\CMS\Install\Controller\AbstractController;
35 use TYPO3\CMS\Install\Controller\EnvironmentController;
36 use TYPO3\CMS\Install\Controller\IconController;
37 use TYPO3\CMS\Install\Controller\LayoutController;
38 use TYPO3\CMS\Install\Controller\LoginController;
39 use TYPO3\CMS\Install\Controller\MaintenanceController;
40 use TYPO3\CMS\Install\Controller\SettingsController;
41 use TYPO3\CMS\Install\Controller\UpgradeController;
42 use TYPO3\CMS\Install\Service\EnableFileService;
43 use TYPO3\CMS\Install\Service\SessionService;
44
45 /**
46 * Default request handler for all requests inside the TYPO3 Install Tool, which does a simple hardcoded
47 * dispatching to a controller based on the get/post variable.
48 */
49 class RequestHandler implements RequestHandlerInterface
50 {
51 /**
52 * @var ConfigurationManager
53 */
54 protected $configurationManager;
55
56 /**
57 * @var array List of valid controllers
58 */
59 protected $controllers = [
60 'icon' => IconController::class,
61 'layout' => LayoutController::class,
62 'login' => LoginController::class,
63 'maintenance' => MaintenanceController::class,
64 'settings' => SettingsController::class,
65 'upgrade' => UpgradeController::class,
66 'environment' => EnvironmentController::class,
67 ];
68
69 /**
70 * @param ConfigurationManager $configurationManager
71 */
72 public function __construct(ConfigurationManager $configurationManager)
73 {
74 $this->configurationManager = $configurationManager;
75 }
76
77 /**
78 * Handles an install tool request for normal operations
79 *
80 * @param ServerRequestInterface $request
81 * @return ResponseInterface
82 */
83 public function handleRequest(ServerRequestInterface $request): ResponseInterface
84 {
85 $controllerName = $request->getQueryParams()['install']['controller'] ?? 'layout';
86 $actionName = $request->getParsedBody()['install']['action'] ?? $request->getQueryParams()['install']['action'] ?? 'init';
87 $action = $actionName . 'Action';
88
89 $session = $this->initializeSession();
90 if ($actionName === 'init') {
91 $controller = new LayoutController();
92 $response = $controller->initAction($request);
93 } elseif ($actionName === 'checkEnableInstallToolFile') {
94 $response = new JsonResponse([
95 'success' => $this->checkEnableInstallToolFile(),
96 ]);
97 } elseif ($actionName === 'showEnableInstallToolFile') {
98 $controller = new LoginController();
99 $response = $controller->showEnableInstallToolFileAction($request);
100 } elseif ($actionName === 'checkLogin') {
101 if (!$this->checkEnableInstallToolFile() && !$session->isAuthorizedBackendUserSession()) {
102 throw new \RuntimeException('Not authorized', 1505563556);
103 }
104 if ($session->isExpired() || !$session->isAuthorized()) {
105 // Session expired, log out user, start new session
106 $session->resetSession();
107 $session->startSession();
108 $response = new JsonResponse([
109 'success' => false,
110 ]);
111 } else {
112 $session->refreshSession();
113 $response = new JsonResponse([
114 'success' => true,
115 ]);
116 }
117 } elseif ($actionName === 'showLogin') {
118 if (!$this->checkEnableInstallToolFile()) {
119 throw new \RuntimeException('Not authorized', 1505564888);
120 }
121 $controller = new LoginController();
122 $response = $controller->showLoginAction($request);
123 } elseif ($actionName === 'login') {
124 if (!$this->checkEnableInstallToolFile()) {
125 throw new \RuntimeException('Not authorized', 1505567462);
126 }
127 $this->checkSessionToken($request, $session);
128 $this->checkSessionLifetime($session);
129 $password = $request->getParsedBody()['install']['password'] ?? null;
130 $authService = new AuthenticationService($session);
131 if ($authService->loginWithPassword($password)) {
132 $response = new JsonResponse([
133 'success' => true,
134 ]);
135 } else {
136 if ($password === null || empty($password)) {
137 $messageQueue = (new FlashMessageQueue('install'))->enqueue(
138 new FlashMessage('Please enter the install tool password', '', FlashMessage::ERROR)
139 );
140 } else {
141 $hashInstance = GeneralUtility::makeInstance(PasswordHashFactory::class)->getDefaultHashInstance('BE');
142 $hashedPassword = $hashInstance->getHashedPassword($password);
143 $messageQueue = (new FlashMessageQueue('install'))->enqueue(
144 new FlashMessage(
145 'Given password does not match the install tool login password. Calculated hash: ' . $hashedPassword,
146 '',
147 FlashMessage::ERROR
148 )
149 );
150 }
151 $response = new JsonResponse([
152 'success' => false,
153 'status' => $messageQueue,
154 ]);
155 }
156 } elseif ($actionName === 'logout') {
157 if (EnableFileService::installToolEnableFileExists() && !EnableFileService::isInstallToolEnableFilePermanent()) {
158 EnableFileService::removeInstallToolEnableFile();
159 }
160 $formProtection = FormProtectionFactory::get(
161 InstallToolFormProtection::class
162 );
163 $formProtection->clean();
164 $session->destroySession();
165 $response = new JsonResponse([
166 'success' => true,
167 ]);
168 } else {
169 if (
170 !$this->checkSessionToken($request, $session)
171 || !$this->checkSessionLifetime($session)
172 || !$session->isAuthorized()
173 ) {
174 return new HtmlResponse('', 401, ['WWW-Authenticate' => 'FormBased']);
175 }
176 $session->refreshSession();
177 if (!array_key_exists($controllerName, $this->controllers)) {
178 throw new \RuntimeException(
179 'Unknown controller ' . $controllerName,
180 1505215756
181 );
182 }
183 $this->recreatePackageStatesFileIfMissing();
184 /** @var AbstractController $controller */
185 $controller = new $this->controllers[$controllerName];
186 if (!method_exists($controller, $action)) {
187 throw new \RuntimeException(
188 'Unknown action method ' . $action . ' in controller ' . $controllerName,
189 1505216027
190 );
191 }
192 $response = $controller->$action($request);
193 }
194
195 return $response;
196 }
197
198 /**
199 * This request handler can handle any request when not in CLI mode.
200 * Warning: Order of these methods is security relevant and interferes with different access
201 * conditions (new/existing installation). See the single method comments for details.
202 *
203 * @param ServerRequestInterface $request
204 * @return bool Returns always TRUE
205 */
206 public function canHandleRequest(ServerRequestInterface $request): bool
207 {
208 $basicIntegrity = $this->checkIfEssentialConfigurationExists()
209 && !empty($GLOBALS['TYPO3_CONF_VARS']['BE']['installToolPassword'])
210 && !EnableFileService::isFirstInstallAllowed();
211 if (!$basicIntegrity) {
212 return false;
213 }
214 return true;
215 }
216
217 /**
218 * Returns the priority - how eager the handler is to actually handle the request.
219 *
220 * @return int The priority of the request handler.
221 */
222 public function getPriority(): int
223 {
224 return 50;
225 }
226
227 /**
228 * Checks if ENABLE_INSTALL_TOOL exists.
229 *
230 * @return bool
231 */
232 protected function checkEnableInstallToolFile()
233 {
234 return EnableFileService::checkInstallToolEnableFile();
235 }
236
237 /**
238 * Initialize session object.
239 * Subclass will throw exception if session can not be created or if
240 * preconditions like a valid encryption key are not set.
241 *
242 * @return SessionService
243 */
244 protected function initializeSession()
245 {
246 $session = new SessionService();
247 if (!$session->hasSession()) {
248 $session->startSession();
249 }
250 return $session;
251 }
252
253 /**
254 * Use form protection API to find out if protected POST forms are ok.
255 *
256 * @param ServerRequestInterface $request
257 * @param SessionService $session
258 * @return bool
259 */
260 protected function checkSessionToken(ServerRequestInterface $request, SessionService $session): bool
261 {
262 $postValues = $request->getParsedBody()['install'];
263 // no post data is there, so no token check necessary
264 if (empty($postValues)) {
265 return true;
266 }
267 $tokenOk = false;
268 // A token must be given as soon as there is POST data
269 if (isset($postValues['token'])) {
270 $formProtection = FormProtectionFactory::get(
271 InstallToolFormProtection::class
272 );
273 $action = (string)$postValues['action'];
274 if ($action === '') {
275 throw new \RuntimeException(
276 'No POST action given for token check',
277 1369326593
278 );
279 }
280 $tokenOk = $formProtection->validateToken($postValues['token'], 'installTool', $action);
281 }
282 if (!$tokenOk) {
283 $session->resetSession();
284 $session->startSession();
285 }
286 return $tokenOk;
287 }
288
289 /**
290 * Check if session expired.
291 * If the session has expired, the login form is displayed.
292 *
293 * @param SessionService $session
294 * @return bool True if session lifetime is OK
295 */
296 protected function checkSessionLifetime(SessionService $session): bool
297 {
298 $isExpired = $session->isExpired();
299 if ($isExpired) {
300 // Session expired, log out user, start new session
301 $session->resetSession();
302 $session->startSession();
303 }
304 return !$isExpired;
305 }
306
307 /**
308 * Check if LocalConfiguration.php and PackageStates.php exist
309 *
310 * @return bool TRUE when the essential configuration is available, otherwise FALSE
311 */
312 protected function checkIfEssentialConfigurationExists(): bool
313 {
314 return file_exists($this->configurationManager->getLocalConfigurationFileLocation());
315 }
316
317 /**
318 * Create PackageStates.php if missing and LocalConfiguration exists.
319 *
320 * It is fired if PackageStates.php is deleted on a running instance,
321 * all packages marked as "part of minimal system" are activated in this case.
322 */
323 protected function recreatePackageStatesFileIfMissing(): void
324 {
325 if (!file_exists(Environment::getLegacyConfigPath() . '/PackageStates.php')) {
326 // We need a FailsafePackageManager at this moment, however this is given
327 // As Bootstrap is registering the FailsafePackageManager object as a singleton instance
328 // of the main PackageManager class. See \TYPO3\CMS\Core\Core\Bootstrap::init()
329 /** @var \TYPO3\CMS\Core\Package\FailsafePackageManager $packageManager */
330 $packageManager = GeneralUtility::makeInstance(PackageManager::class);
331 $packages = $packageManager->getAvailablePackages();
332 foreach ($packages as $package) {
333 if ($package instanceof PackageInterface && $package->isPartOfMinimalUsableSystem()) {
334 $packageManager->activatePackage($package->getPackageKey());
335 }
336 }
337 $packageManager->forceSortAndSavePackageStates();
338 }
339 }
340 }