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