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