[TASK] UX enhancement for EXT:documentation
[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 textfile 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 and Step 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 login 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 login 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 MD5 hash of the last 5 characters of the password tried was \'' . substr(md5($formValues['password']), -5) . '\''
310 . ' remote addres 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 }
412
413 require(\TYPO3\CMS\Core\Utility\ExtensionManagementUtility::extPath('extbase') . 'ext_localconf.php');
414 require(\TYPO3\CMS\Core\Utility\ExtensionManagementUtility::extPath('fluid') . 'ext_localconf.php');
415
416 $GLOBALS['TYPO3_CONF_VARS']['SYS']['caching']['cacheConfigurations']['extbase_datamapfactory_datamap']['backend']
417 = 'TYPO3\\CMS\\Core\\Cache\\Backend\\NullBackend';
418 $GLOBALS['TYPO3_CONF_VARS']['SYS']['caching']['cacheConfigurations']['extbase_object']['backend']
419 = 'TYPO3\\CMS\\Core\\Cache\\Backend\\NullBackend';
420 $GLOBALS['TYPO3_CONF_VARS']['SYS']['caching']['cacheConfigurations']['extbase_reflection']['backend']
421 = 'TYPO3\\CMS\\Core\\Cache\\Backend\\NullBackend';
422 $GLOBALS['TYPO3_CONF_VARS']['SYS']['caching']['cacheConfigurations']['extbase_typo3dbbackend_tablecolumns']['backend']
423 = 'TYPO3\\CMS\\Core\\Cache\\Backend\\NullBackend';
424 $GLOBALS['TYPO3_CONF_VARS']['SYS']['caching']['cacheConfigurations']['fluid_template']['backend']
425 = 'TYPO3\\CMS\\Core\\Cache\\Backend\\NullBackend';
426
427 /** @var $cacheManager \TYPO3\CMS\Core\Cache\CacheManager */
428 $cacheManager = $GLOBALS['typo3CacheManager'];
429 $cacheManager->setCacheConfigurations($GLOBALS['TYPO3_CONF_VARS']['SYS']['caching']['cacheConfigurations']);
430 }
431
432 /**
433 * Return TRUE if dbal and adodb extension is loaded.
434 *
435 * @return boolean TRUE if dbal and adodb is loaded
436 */
437 protected function isDbalEnabled() {
438 if (\TYPO3\CMS\Core\Utility\ExtensionManagementUtility::isLoaded('adodb')
439 && \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::isLoaded('dbal')
440 ) {
441 return TRUE;
442 }
443 return FALSE;
444 }
445
446 /**
447 * Check given action name is one of the allowed actions.
448 *
449 * @param string $action Given action to validate
450 * @throws Exception
451 */
452 protected function validateAuthenticationAction($action) {
453 if (!in_array($action, $this->authenticationActions)) {
454 throw new Exception(
455 $action . ' is not a valid authentication action',
456 1369345838
457 );
458 }
459 }
460
461 /**
462 * Retrieve parameter from GET or POST and sanitize
463 *
464 * @throws Exception
465 * @return string Empty string if no action is given or sanitized action string
466 */
467 protected function getAction() {
468 $formValues = GeneralUtility::_GP('install');
469 $action = '';
470 if (isset($formValues['action'])) {
471 $action = $formValues['action'];
472 }
473 if ($action !== ''
474 && $action !== 'login'
475 && $action !== 'loginForm'
476 && $action !== 'logout'
477 && !in_array($action, $this->authenticationActions)
478 ) {
479 throw new Exception(
480 'Invalid action ' . $action,
481 1369325619
482 );
483 }
484 return $action;
485 }
486
487 /**
488 * Get POST form values of install tool.
489 * All POST data is secured by form token protection, except in very installation step.
490 *
491 * @return array
492 */
493 protected function getPostValues() {
494 $postValues = GeneralUtility::_POST('install');
495 if (!is_array($postValues)) {
496 $postValues = array();
497 }
498 return $postValues;
499 }
500
501 /**
502 * HTTP redirect to self, preserving allowed GET variables.
503 * WARNING: This exits the script execution!
504 *
505 * @param string $controller Can be set to 'tool' to redirect from step to tool controller
506 * @param string $action Set specific action for next request, used in step controller to specify next step
507 * @return void
508 */
509 protected function redirect($controller = '', $action = '') {
510 $getPostValues = GeneralUtility::_GP('install');
511
512 $parameters = array();
513
514 // Current redirect count
515 if (isset($getPostValues['redirectCount'])) {
516 $redirectCount = (int)$getPostValues['redirectCount'] + 1;
517 } else {
518 $redirectCount = 0;
519 }
520 if ($redirectCount >= 10) {
521 // Abort a redirect loop by throwing an exception. Calling this method
522 // some times in a row is ok, but break a loop if this happens too often.
523 throw new Exception\RedirectLoopException(
524 'Redirect loop aborted. If this message is shown again after a reload,' .
525 ' your setup is so weird that the install tool is unable to handle it.' .
526 ' Please make sure to remove the "install[redirectCount]" parameter from your request or' .
527 ' restart the install tool from the backend navigation.',
528 1380581244
529 );
530 }
531 $parameters[] = 'install[redirectCount]=' . $redirectCount;
532
533 // Add context parameter in case this script was called within backend scope
534 $context = 'install[context]=standalone';
535 if (isset($getPostValues['context']) && $getPostValues['context'] === 'backend') {
536 $context = 'install[context]=backend';
537 }
538 $parameters[] = $context;
539
540 // Add controller parameter
541 $controllerParameter = 'install[controller]=step';
542 if ((isset($getPostValues['controller']) && $getPostValues['controller'] === 'tool')
543 || $controller === 'tool'
544 ) {
545 $controllerParameter = 'install[controller]=tool';
546 }
547 $parameters[] = $controllerParameter;
548
549 // Add action if specified
550 if (strlen($action) > 0) {
551 $parameters[] = 'install[action]=' . $action;
552 }
553
554 $redirectLocation = 'Install.php?' . implode('&', $parameters);
555
556 \TYPO3\CMS\Core\Utility\HttpUtility::redirect(
557 $redirectLocation,
558 \TYPO3\CMS\Core\Utility\HttpUtility::HTTP_STATUS_303
559 );
560 }
561
562 /**
563 * Output content.
564 * WARNING: This exits the script execution!
565 *
566 * @param string $content Content to output
567 */
568 protected function output($content = '') {
569 header('Content-Type: text/html; charset=utf-8');
570 header('Cache-Control: no-cache, must-revalidate');
571 header('Pragma: no-cache');
572 echo $content;
573 die;
574 }
575 }