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