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