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