[TASK] Get rid of ObjectManager in install tool
[Packages/TYPO3.CMS.git] / typo3 / sysext / install / Classes / Controller / AbstractController.php
1 <?php
2 namespace TYPO3\CMS\Install\Controller;
3
4 /*
5 * This file is part of the TYPO3 CMS project.
6 *
7 * It is free software; you can redistribute it and/or modify it under
8 * the terms of the GNU General Public License, either version 2
9 * of the License, or any later version.
10 *
11 * For the full copyright and license information, please read the
12 * LICENSE.txt file that was distributed with this source code.
13 *
14 * The TYPO3 project - inspiring people to share!
15 */
16
17 use TYPO3\CMS\Core\Cache\Backend\NullBackend;
18 use TYPO3\CMS\Core\Utility\GeneralUtility;
19 use TYPO3\CMS\Install\Service\EnableFileService;
20
21 /**
22 * Controller abstract for shared parts of Tool, Step and Ajax controller
23 */
24 class AbstractController
25 {
26 /**
27 * @var \TYPO3\CMS\Install\Service\SessionService
28 */
29 protected $session = null;
30
31 /**
32 * @var array List of valid action names that need authentication
33 */
34 protected $authenticationActions = array();
35
36 /**
37 * @return bool
38 */
39 protected function isInstallToolAvailable()
40 {
41 /** @var \TYPO3\CMS\Install\Service\EnableFileService $installToolEnableService */
42 $installToolEnableService = GeneralUtility::makeInstance(\TYPO3\CMS\Install\Service\EnableFileService::class);
43 if ($installToolEnableService->isFirstInstallAllowed()) {
44 return true;
45 }
46 return $installToolEnableService->checkInstallToolEnableFile();
47 }
48
49 /**
50 * Guard method checking typo3conf/ENABLE_INSTALL_TOOL
51 *
52 * Checking ENABLE_INSTALL_TOOL validity is simple:
53 * As soon as there is a typo3conf directory at all (not step 1 of "first install"),
54 * the file must be there and valid in order to proceed.
55 *
56 * @return void
57 */
58 protected function outputInstallToolNotEnabledMessageIfNeeded()
59 {
60 if (!$this->isInstallToolAvailable()) {
61 if (!EnableFileService::isFirstInstallAllowed() && !\TYPO3\CMS\Core\Core\Bootstrap::getInstance()->checkIfEssentialConfigurationExists()) {
62 /** @var \TYPO3\CMS\Install\Controller\Action\ActionInterface $action */
63 $action = GeneralUtility::makeInstance(\TYPO3\CMS\Install\Controller\Action\Common\FirstInstallAction::class);
64 $action->setAction('firstInstall');
65 } else {
66 /** @var \TYPO3\CMS\Install\Controller\Action\ActionInterface $action */
67 $action = GeneralUtility::makeInstance(\TYPO3\CMS\Install\Controller\Action\Common\InstallToolDisabledAction::class);
68 $action->setAction('installToolDisabled');
69 }
70 $action->setController('common');
71 $this->output($action->handle());
72 }
73 }
74
75 /**
76 * Guard method checking for valid install tool password
77 *
78 * If installation is completed - LocalConfiguration exists and
79 * installProcess is not running, and installToolPassword must be set
80 */
81 protected function outputInstallToolPasswordNotSetMessageIfNeeded()
82 {
83 if (!$this->isInitialInstallationInProgress()
84 && (empty($GLOBALS['TYPO3_CONF_VARS']['BE']['installToolPassword']))
85 ) {
86 /** @var \TYPO3\CMS\Install\Controller\Action\ActionInterface $action */
87 $action = GeneralUtility::makeInstance(\TYPO3\CMS\Install\Controller\Action\Common\InstallToolPasswordNotSetAction::class);
88 $action->setController('common');
89 $action->setAction('installToolPasswordNotSet');
90 $this->output($action->handle());
91 }
92 }
93
94 /**
95 * Use form protection API to find out if protected POST forms are ok.
96 *
97 * @throws Exception
98 * @return void
99 */
100 protected function checkSessionToken()
101 {
102 $postValues = $this->getPostValues();
103 $tokenOk = false;
104 if (!empty($postValues)) {
105 // A token must be given as soon as there is POST data
106 if (isset($postValues['token'])) {
107 /** @var $formProtection \TYPO3\CMS\Core\FormProtection\InstallToolFormProtection */
108 $formProtection = \TYPO3\CMS\Core\FormProtection\FormProtectionFactory::get(
109 \TYPO3\CMS\Core\FormProtection\InstallToolFormProtection::class
110 );
111 $action = $this->getAction();
112 if ($action === '') {
113 throw new Exception(
114 'No POST action given for token check',
115 1369326593
116 );
117 }
118 $tokenOk = $formProtection->validateToken($postValues['token'], 'installTool', $action);
119 }
120 } else {
121 $tokenOk = true;
122 }
123
124 $this->handleSessionTokenCheck($tokenOk);
125 }
126
127 /**
128 * If session token was not ok, the session is reset and either
129 * a redirect is initialized (will load the same step step controller again) or
130 * if in install tool, the login form is displayed.
131 *
132 * @param bool $tokenOk
133 * @return void
134 */
135 protected function handleSessionTokenCheck($tokenOk)
136 {
137 if (!$tokenOk) {
138 $this->session->resetSession();
139 $this->session->startSession();
140
141 if ($this->isInitialInstallationInProgress()) {
142 $this->redirect();
143 } else {
144 /** @var $message \TYPO3\CMS\Install\Status\ErrorStatus */
145 $message = GeneralUtility::makeInstance(\TYPO3\CMS\Install\Status\ErrorStatus::class);
146 $message->setTitle('Invalid form token');
147 $message->setMessage(
148 'The form protection token was invalid. You have been logged out, please log in and try again.'
149 );
150 $this->output($this->loginForm($message));
151 }
152 }
153 }
154
155 /**
156 * Check if session expired.
157 *
158 * @return void
159 */
160 protected function checkSessionLifetime()
161 {
162 if ($this->session->isExpired()) {
163 // Session expired, log out user, start new session
164 $this->session->resetSession();
165 $this->session->startSession();
166
167 $this->handleSessionLifeTimeExpired();
168 }
169 }
170
171 /**
172 * If session expired, the current step of step controller is reloaded
173 * (if first installation is running) - or the login form is displayed.
174 *
175 * @return void
176 */
177 protected function handleSessionLifeTimeExpired()
178 {
179 if ($this->isInitialInstallationInProgress()) {
180 $this->redirect();
181 } else {
182 /** @var $message \TYPO3\CMS\Install\Status\ErrorStatus */
183 $message = GeneralUtility::makeInstance(\TYPO3\CMS\Install\Status\ErrorStatus::class);
184 $message->setTitle('Session expired');
185 $message->setMessage(
186 'Your Install Tool session has expired. You have been logged out, please log in and try again.'
187 );
188 $this->output($this->loginForm($message));
189 }
190 }
191
192 /**
193 * Show login form
194 *
195 * @param \TYPO3\CMS\Install\Status\StatusInterface $message Optional status message from controller
196 * @return string Rendered HTML
197 */
198 protected function loginForm(\TYPO3\CMS\Install\Status\StatusInterface $message = null)
199 {
200 /** @var \TYPO3\CMS\Install\Controller\Action\Common\LoginForm $action */
201 $action = GeneralUtility::makeInstance(\TYPO3\CMS\Install\Controller\Action\Common\LoginForm::class);
202 $action->setController('common');
203 $action->setAction('login');
204 $action->setToken($this->generateTokenForAction('login'));
205 $action->setPostValues($this->getPostValues());
206 if ($message) {
207 $action->setMessages(array($message));
208 }
209 $content = $action->handle();
210 return $content;
211 }
212
213 /**
214 * Validate install tool password and login user if requested
215 *
216 * @return void
217 */
218 protected function loginIfRequested()
219 {
220 $action = $this->getAction();
221 $postValues = $this->getPostValues();
222 if ($action === 'login') {
223 $password = '';
224 $validPassword = false;
225 if (isset($postValues['values']['password'])) {
226 $password = $postValues['values']['password'];
227 $installToolPassword = $GLOBALS['TYPO3_CONF_VARS']['BE']['installToolPassword'];
228 $saltFactory = \TYPO3\CMS\Saltedpasswords\Salt\SaltFactory::getSaltingInstance($installToolPassword);
229 if (is_object($saltFactory)) {
230 $validPassword = $saltFactory->checkPassword($password, $installToolPassword);
231 } elseif (md5($password) === $installToolPassword) {
232 // Update install tool password
233 $saltFactory = \TYPO3\CMS\Saltedpasswords\Salt\SaltFactory::getSaltingInstance(null, 'BE');
234 /** @var $configurationManager \TYPO3\CMS\Core\Configuration\ConfigurationManager */
235 $configurationManager = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Configuration\ConfigurationManager::class);
236 $configurationManager->setLocalConfigurationValueByPath(
237 'BE/installToolPassword',
238 $saltFactory->getHashedPassword($password)
239 );
240 $validPassword = true;
241 }
242 }
243 if ($validPassword) {
244 $this->session->setAuthorized();
245 $this->sendLoginSuccessfulMail();
246 $this->redirect();
247 } else {
248 $saltFactory = \TYPO3\CMS\Saltedpasswords\Salt\SaltFactory::getSaltingInstance(null, 'BE');
249 $hashedPassword = $saltFactory->getHashedPassword($password);
250 /** @var $message \TYPO3\CMS\Install\Status\ErrorStatus */
251 $message = GeneralUtility::makeInstance(\TYPO3\CMS\Install\Status\ErrorStatus::class);
252 $message->setTitle('Login failed');
253 $message->setMessage('Given password does not match the install tool login password. ' .
254 'Calculated hash: ' . $hashedPassword);
255 $this->sendLoginFailedMail();
256 $this->output($this->loginForm($message));
257 }
258 }
259 }
260
261 /**
262 * Show login for if user is not authorized yet and if
263 * not in first installation process.
264 *
265 * @return void
266 */
267 protected function outputLoginFormIfNotAuthorized()
268 {
269 if (!$this->session->isAuthorized()
270 && !$this->isInitialInstallationInProgress()
271 ) {
272 $this->output($this->loginForm());
273 } else {
274 $this->session->refreshSession();
275 }
276 }
277
278 /**
279 * If install tool login mail is set, send a mail for a successful login.
280 *
281 * @return void
282 */
283 protected function sendLoginSuccessfulMail()
284 {
285 $warningEmailAddress = $GLOBALS['TYPO3_CONF_VARS']['BE']['warning_email_addr'];
286 if ($warningEmailAddress) {
287 /** @var \TYPO3\CMS\Core\Mail\MailMessage $mailMessage */
288 $mailMessage = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Mail\MailMessage::class);
289 $mailMessage
290 ->addTo($warningEmailAddress)
291 ->setSubject('Install Tool Login at \'' . $GLOBALS['TYPO3_CONF_VARS']['SYS']['sitename'] . '\'')
292 ->addFrom($this->getSenderEmailAddress(), $this->getSenderEmailName())
293 ->setBody('There has been an Install Tool login at TYPO3 site'
294 . ' \'' . $GLOBALS['TYPO3_CONF_VARS']['SYS']['sitename'] . '\''
295 . ' (' . GeneralUtility::getIndpEnv('HTTP_HOST') . ')'
296 . ' from remote address \'' . GeneralUtility::getIndpEnv('REMOTE_ADDR') . '\''
297 . ' (' . GeneralUtility::getIndpEnv('REMOTE_HOST') . ')')
298 ->send();
299 }
300 }
301
302 /**
303 * If install tool login mail is set, send a mail for a failed login.
304 *
305 * @return void
306 */
307 protected function sendLoginFailedMail()
308 {
309 $formValues = GeneralUtility::_GP('install');
310 $warningEmailAddress = $GLOBALS['TYPO3_CONF_VARS']['BE']['warning_email_addr'];
311 if ($warningEmailAddress) {
312 /** @var \TYPO3\CMS\Core\Mail\MailMessage $mailMessage */
313 $mailMessage = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Mail\MailMessage::class);
314 $mailMessage
315 ->addTo($warningEmailAddress)
316 ->setSubject('Install Tool Login ATTEMPT at \'' . $GLOBALS['TYPO3_CONF_VARS']['SYS']['sitename'] . '\'')
317 ->addFrom($this->getSenderEmailAddress(), $this->getSenderEmailName())
318 ->setBody('There has been an Install Tool login attempt at TYPO3 site'
319 . ' \'' . $GLOBALS['TYPO3_CONF_VARS']['SYS']['sitename'] . '\''
320 . ' (' . GeneralUtility::getIndpEnv('HTTP_HOST') . ')'
321 . ' The last 5 characters of the MD5 hash of the password tried was \'' . substr(md5($formValues['password']), -5) . '\''
322 . ' remote address was \'' . GeneralUtility::getIndpEnv('REMOTE_ADDR') . '\''
323 . ' (' . GeneralUtility::getIndpEnv('REMOTE_HOST') . ')')
324 ->send();
325 }
326 }
327
328 /**
329 * Generate token for specific action
330 *
331 * @param string $action Action name
332 * @return string Form protection token
333 * @throws Exception
334 */
335 protected function generateTokenForAction($action = null)
336 {
337 if (!$action) {
338 $action = $this->getAction();
339 }
340 if ($action === '') {
341 throw new Exception(
342 'Token must have a valid action name',
343 1369326592
344 );
345 }
346 /** @var $formProtection \TYPO3\CMS\Core\FormProtection\InstallToolFormProtection */
347 $formProtection = \TYPO3\CMS\Core\FormProtection\FormProtectionFactory::get(
348 \TYPO3\CMS\Core\FormProtection\InstallToolFormProtection::class
349 );
350 return $formProtection->generateToken('installTool', $action);
351 }
352
353 /**
354 * First installation is in progress, if LocalConfiguration does not exist,
355 * or if isInitialInstallationInProgress is not set or FALSE.
356 *
357 * @return bool TRUE if installation is in progress
358 */
359 protected function isInitialInstallationInProgress()
360 {
361 /** @var \TYPO3\CMS\Core\Configuration\ConfigurationManager $configurationManager */
362 $configurationManager = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Configuration\ConfigurationManager::class);
363
364 $localConfigurationFileLocation = $configurationManager->getLocalConfigurationFileLocation();
365 $localConfigurationFileExists = @is_file($localConfigurationFileLocation);
366 $result = false;
367 if (!$localConfigurationFileExists
368 || !empty($GLOBALS['TYPO3_CONF_VARS']['SYS']['isInitialInstallationInProgress'])
369 ) {
370 $result = true;
371 }
372 return $result;
373 }
374
375 /**
376 * Initialize session object.
377 * Subclass will throw exception if session can not be created or if
378 * preconditions like a valid encryption key are not set.
379 *
380 * @return void
381 */
382 protected function initializeSession()
383 {
384 /** @var \TYPO3\CMS\Install\Service\SessionService $session */
385 $this->session = GeneralUtility::makeInstance(\TYPO3\CMS\Install\Service\SessionService::class);
386 if (!$this->session->hasSession()) {
387 $this->session->startSession();
388 }
389 }
390
391 /**
392 * Add status messages to session.
393 * Used to output messages between requests, especially in step controller
394 *
395 * @param array<\TYPO3\CMS\Install\Status\StatusInterface> $messages
396 */
397 protected function addSessionMessages(array $messages)
398 {
399 foreach ($messages as $message) {
400 $this->session->addMessage($message);
401 }
402 }
403
404 /**
405 * Require dbal ext_localconf if extension is loaded
406 * Required extbase ext_localconf
407 * Set caching to NullBackend, install tool must not cache anything
408 *
409 * @return void
410 */
411 protected function loadBaseExtensions()
412 {
413 if ($this->isDbalEnabled()) {
414 require(\TYPO3\CMS\Core\Utility\ExtensionManagementUtility::extPath('dbal') . 'ext_localconf.php');
415 }
416
417 // @todo: Find out if this could be left out
418 require(\TYPO3\CMS\Core\Utility\ExtensionManagementUtility::extPath('extbase') . 'ext_localconf.php');
419
420 $cacheConfigurations = $GLOBALS['TYPO3_CONF_VARS']['SYS']['caching']['cacheConfigurations'];
421
422 $cacheConfigurationsWithCachesSetToNullBackend = array();
423 foreach ($cacheConfigurations as $cacheName => $cacheConfiguration) {
424 // cache_core is handled in bootstrap already
425 if (is_array($cacheConfiguration) && $cacheName !== 'cache_core') {
426 $cacheConfiguration['backend'] = NullBackend::class;
427 $cacheConfiguration['options'] = array();
428 }
429 $cacheConfigurationsWithCachesSetToNullBackend[$cacheName] = $cacheConfiguration;
430 }
431
432 /** @var $cacheManager \TYPO3\CMS\Core\Cache\CacheManager */
433 $cacheManager = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Cache\CacheManager::class);
434 $cacheManager->setCacheConfigurations($cacheConfigurationsWithCachesSetToNullBackend);
435 }
436
437 /**
438 * Return TRUE if dbal and adodb extension is loaded.
439 *
440 * @return bool TRUE if dbal and adodb is loaded
441 */
442 protected function isDbalEnabled()
443 {
444 if (\TYPO3\CMS\Core\Utility\ExtensionManagementUtility::isLoaded('adodb')
445 && \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::isLoaded('dbal')
446 ) {
447 return true;
448 }
449 return false;
450 }
451
452 /**
453 * Check given action name is one of the allowed actions.
454 *
455 * @param string $action Given action to validate
456 * @throws Exception
457 */
458 protected function validateAuthenticationAction($action)
459 {
460 if (!in_array($action, $this->authenticationActions)) {
461 throw new Exception(
462 $action . ' is not a valid authentication action',
463 1369345838
464 );
465 }
466 }
467
468 /**
469 * Retrieve parameter from GET or POST and sanitize
470 *
471 * @throws Exception
472 * @return string Empty string if no action is given or sanitized action string
473 */
474 protected function getAction()
475 {
476 $formValues = GeneralUtility::_GP('install');
477 $action = '';
478 if (isset($formValues['action'])) {
479 $action = $formValues['action'];
480 }
481 if ($action !== ''
482 && $action !== 'login'
483 && $action !== 'loginForm'
484 && $action !== 'logout'
485 && !in_array($action, $this->authenticationActions)
486 ) {
487 throw new Exception(
488 'Invalid action ' . $action,
489 1369325619
490 );
491 }
492 return $action;
493 }
494
495 /**
496 * Get POST form values of install tool.
497 * All POST data is secured by form token protection, except in very installation step.
498 *
499 * @return array
500 */
501 protected function getPostValues()
502 {
503 $postValues = GeneralUtility::_POST('install');
504 if (!is_array($postValues)) {
505 $postValues = array();
506 }
507 return $postValues;
508 }
509
510 /**
511 * HTTP redirect to self, preserving allowed GET variables.
512 * WARNING: This exits the script execution!
513 *
514 * @param string $controller Can be set to 'tool' to redirect from step to tool controller
515 * @param string $action Set specific action for next request, used in step controller to specify next step
516 * @throws Exception\RedirectLoopException
517 * @return void
518 */
519 protected function redirect($controller = '', $action = '')
520 {
521 $getPostValues = GeneralUtility::_GP('install');
522
523 $parameters = array();
524
525 // Current redirect count
526 if (isset($getPostValues['redirectCount'])) {
527 $redirectCount = (int)$getPostValues['redirectCount'] + 1;
528 } else {
529 $redirectCount = 0;
530 }
531 if ($redirectCount >= 10) {
532 // Abort a redirect loop by throwing an exception. Calling this method
533 // some times in a row is ok, but break a loop if this happens too often.
534 throw new Exception\RedirectLoopException(
535 'Redirect loop aborted. If this message is shown again after a reload,' .
536 ' your setup is so weird that the install tool is unable to handle it.' .
537 ' Please make sure to remove the "install[redirectCount]" parameter from your request or' .
538 ' restart the install tool from the backend navigation.',
539 1380581244
540 );
541 }
542 $parameters[] = 'install[redirectCount]=' . $redirectCount;
543
544 // Add context parameter in case this script was called within backend scope
545 $context = 'install[context]=standalone';
546 if (isset($getPostValues['context']) && $getPostValues['context'] === 'backend') {
547 $context = 'install[context]=backend';
548 }
549 $parameters[] = $context;
550
551 // Add controller parameter
552 $controllerParameter = 'install[controller]=step';
553 if ((isset($getPostValues['controller']) && $getPostValues['controller'] === 'tool')
554 || $controller === 'tool'
555 ) {
556 $controllerParameter = 'install[controller]=tool';
557 }
558 $parameters[] = $controllerParameter;
559
560 // Add action if specified
561 if ((string)$action !== '') {
562 $parameters[] = 'install[action]=' . $action;
563 }
564
565 $redirectLocation = 'Install.php?' . implode('&', $parameters);
566
567 \TYPO3\CMS\Core\Utility\HttpUtility::redirect(
568 $redirectLocation,
569 \TYPO3\CMS\Core\Utility\HttpUtility::HTTP_STATUS_303
570 );
571 }
572
573 /**
574 * Output content.
575 * WARNING: This exits the script execution!
576 *
577 * @param string $content Content to output
578 */
579 protected function output($content = '')
580 {
581 header('Content-Type: text/html; charset=utf-8');
582 header('Cache-Control: no-cache, must-revalidate');
583 header('Pragma: no-cache');
584 echo $content;
585 die;
586 }
587
588 /**
589 * Get sender address from configuration
590 * ['TYPO3_CONF_VARS']['MAIL']['defaultMailFromAddress']
591 * If this setting is empty fall back to 'no-reply@example.com'
592 *
593 * @return string Returns an email address
594 */
595 protected function getSenderEmailAddress()
596 {
597 return !empty($GLOBALS['TYPO3_CONF_VARS']['MAIL']['defaultMailFromAddress'])
598 ? $GLOBALS['TYPO3_CONF_VARS']['MAIL']['defaultMailFromAddress']
599 : 'no-reply@example.com';
600 }
601
602 /**
603 * Gets sender name from configuration
604 * ['TYPO3_CONF_VARS']['MAIL']['defaultMailFromName']
605 * If this setting is empty, it falls back to a default string.
606 *
607 * @return string
608 */
609 protected function getSenderEmailName()
610 {
611 return !empty($GLOBALS['TYPO3_CONF_VARS']['MAIL']['defaultMailFromName'])
612 ? $GLOBALS['TYPO3_CONF_VARS']['MAIL']['defaultMailFromName']
613 : 'TYPO3 CMS install tool';
614 }
615 }