6647c043786385000ea49eda2b20242ca0719b33
[Packages/TYPO3.CMS.git] / typo3 / sysext / backend / Classes / Controller / LoginController.php
1 <?php
2 namespace TYPO3\CMS\Backend\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\Backend\Exception;
18 use TYPO3\CMS\Backend\LoginProvider\LoginProviderInterface;
19 use TYPO3\CMS\Backend\Utility\BackendUtility;
20 use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
21 use TYPO3\CMS\Core\FormProtection\BackendFormProtection;
22 use TYPO3\CMS\Core\FormProtection\FormProtectionFactory;
23 use TYPO3\CMS\Core\Page\PageRenderer;
24 use TYPO3\CMS\Core\Utility\GeneralUtility;
25 use TYPO3\CMS\Core\Utility\HttpUtility;
26 use TYPO3\CMS\Core\Utility\PathUtility;
27 use TYPO3\CMS\Fluid\View\StandaloneView;
28
29 /**
30 * Script Class for rendering the login form
31 */
32 class LoginController implements \TYPO3\CMS\Core\Http\ControllerInterface {
33
34 /**
35 * The URL to redirect to after login.
36 *
37 * @var string
38 */
39 protected $redirectUrl;
40
41 /**
42 * Set to the redirect URL of the form (may be redirect_url or "backend.php")
43 *
44 * @var string
45 */
46 protected $redirectToURL;
47
48 /**
49 * the active login provider identifier
50 *
51 * @var string
52 */
53 protected $loginProviderIdentifier = NULL;
54
55 /**
56 * List of registered and sorted login providers
57 *
58 * @var array
59 */
60 protected $loginProviders = [];
61
62 /**
63 * Login-refresh bool; The backend will call this script
64 * with this value set when the login is close to being expired
65 * and the form needs to be redrawn.
66 *
67 * @var bool
68 */
69 protected $loginRefresh;
70
71 /**
72 * Value of forms submit button for login.
73 *
74 * @var string
75 */
76 protected $submitValue;
77
78 /**
79 * @var StandaloneView
80 */
81 protected $view;
82
83 /**
84 * Initialize the login box. Will also react on a &L=OUT flag and exit.
85 */
86 public function __construct() {
87 $this->validateAndSortLoginProviders();
88
89 // We need a PHP session session for most login levels
90 session_start();
91 $this->redirectUrl = GeneralUtility::sanitizeLocalUrl(GeneralUtility::_GP('redirect_url'));
92 $this->loginProviderIdentifier = $this->detectLoginProvider();
93
94 $this->loginRefresh = (bool)GeneralUtility::_GP('loginRefresh');
95 // Value of "Login" button. If set, the login button was pressed.
96 $this->submitValue = GeneralUtility::_GP('commandLI');
97
98 // Try to get the preferred browser language
99 $preferredBrowserLanguage = $this->getLanguageService()->csConvObj
100 ->getPreferredClientLanguage(GeneralUtility::getIndpEnv('HTTP_ACCEPT_LANGUAGE'));
101
102 // If we found a $preferredBrowserLanguage and it is not the default language and no be_user is logged in
103 // initialize $this->getLanguageService() again with $preferredBrowserLanguage
104 if ($preferredBrowserLanguage !== 'default' && empty($this->getBackendUserAuthentication()->user['uid'])) {
105 $this->getLanguageService()->init($preferredBrowserLanguage);
106 }
107
108 $this->getLanguageService()->includeLLFile('EXT:lang/locallang_login.xlf');
109
110 // Setting the redirect URL to "backend.php" if no alternative input is given
111 $this->redirectToURL = $this->redirectUrl ?: 'backend.php';
112
113 // If "L" is "OUT", then any logged in is logged out. If redirect_url is given, we redirect to it
114 if (GeneralUtility::_GP('L') === 'OUT' && is_object($this->getBackendUserAuthentication())) {
115 $this->getBackendUserAuthentication()->logoff();
116 HttpUtility::redirect($this->redirectUrl);
117 }
118
119 $this->view = $this->getFluidTemplateObject();
120 }
121
122 /**
123 * Injects the request object for the current request or subrequest
124 * As this controller goes only through the main() method, it is rather simple for now
125 * This will be split up in an abstract controller once proper routing/dispatcher is in place.
126 *
127 * @param \Psr\Http\Message\RequestInterface $request
128 * @return \Psr\Http\Message\ResponseInterface $response
129 */
130 public function processRequest(\Psr\Http\Message\RequestInterface $request) {
131 $content = $this->main();
132 /** @var \TYPO3\CMS\Core\Http\Response $response */
133 $response = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Http\Response::class);
134 $response->getBody()->write($content);
135 return $response;
136 }
137
138 /**
139 * Main function - creating the login/logout form
140 *
141 * @throws Exception
142 * @return string The content to output
143 */
144 public function main() {
145 /** @var $pageRenderer PageRenderer */
146 $pageRenderer = GeneralUtility::makeInstance(PageRenderer::class);
147 $pageRenderer->loadRequireJsModule('TYPO3/CMS/Backend/Login');
148
149 // support placeholders for IE9 and lower
150 $clientInfo = GeneralUtility::clientInfo();
151 if ($clientInfo['BROWSER'] === 'msie' && $clientInfo['VERSION'] <= 9) {
152 $pageRenderer->addJsLibrary('placeholders', 'sysext/core/Resources/Public/JavaScript/Contrib/placeholders.jquery.min.js');
153 }
154
155 // Checking, if we should make a redirect.
156 // Might set JavaScript in the header to close window.
157 $this->checkRedirect();
158
159 // Extension Configuration
160 $extConf = unserialize($GLOBALS['TYPO3_CONF_VARS']['EXT']['extConf']['backend']);
161
162 // Background Image
163 if (!empty($extConf['loginBackgroundImage'])) {
164 $backgroundImage = $this->getUriForFileName($extConf['loginBackgroundImage']);
165 $this->getDocumentTemplate()->inDocStylesArray[] = '
166 @media (min-width: 768px){
167 .typo3-login-carousel-control.right,
168 .typo3-login-carousel-control.left,
169 .panel-login { border: 0; }
170 .typo3-login { background-image: url("' . $backgroundImage . '"); }
171 }
172 ';
173 }
174
175 // Add additional css to use the highlight color in the login screen
176 if (!empty($extConf['loginHighlightColor'])) {
177 $this->getDocumentTemplate()->inDocStylesArray[] = '
178 .btn-login.disabled, .btn-login[disabled], fieldset[disabled] .btn-login,
179 .btn-login.disabled:hover, .btn-login[disabled]:hover, fieldset[disabled] .btn-login:hover,
180 .btn-login.disabled:focus, .btn-login[disabled]:focus, fieldset[disabled] .btn-login:focus,
181 .btn-login.disabled.focus, .btn-login[disabled].focus, fieldset[disabled] .btn-login.focus,
182 .btn-login.disabled:active, .btn-login[disabled]:active, fieldset[disabled] .btn-login:active,
183 .btn-login.disabled.active, .btn-login[disabled].active, fieldset[disabled] .btn-login.active,
184 .btn-login:hover, .btn-login:focus, .btn-login:active,
185 .btn-login { background-color: ' . $extConf['loginHighlightColor'] . '; }
186 .panel-login .panel-body { border-color: ' . $extConf['loginHighlightColor'] . '; }
187 ';
188 }
189
190 // Logo
191 if (!empty($extConf['loginLogo'])) {
192 $logo = $extConf['loginLogo'];
193 } elseif (!empty($GLOBALS['TBE_STYLES']['logo_login'])) {
194 // Fallback to old TBE_STYLES login logo
195 $logo = $GLOBALS['TBE_STYLES']['logo_login'];
196 GeneralUtility::deprecationLog('$GLOBALS["TBE_STYLES"]["logo_login"] is deprecated since TYPO3 CMS 7 and will be removed in TYPO3 CMS 8, please use the backend extension\'s configuration instead.');
197 } else {
198 // Use TYPO3 logo depending on highlight color
199 if (!empty($extConf['loginHighlightColor'])) {
200 $logo = 'EXT:backend/Resources/Public/Images/typo3_black.svg';
201 } else {
202 $logo = 'EXT:backend/Resources/Public/Images/typo3_orange.svg';
203 }
204 $this->getDocumentTemplate()->inDocStylesArray[] = '
205 .typo3-login-logo .typo3-login-image { max-width: 150px; }
206 ';
207 }
208 $logo = $this->getUriForFileName($logo);
209
210 // Start form
211 $formType = empty($this->getBackendUserAuthentication()->user['uid']) ? 'LoginForm' : 'LogoutForm';
212 $this->view->assignMultiple(array(
213 'backendUser' => $this->getBackendUserAuthentication()->user,
214 'hasLoginError' => $this->isLoginInProgress(),
215 'formType' => $formType,
216 'logo' => $logo,
217 'images' => array(
218 'capslock' => $this->getUriForFileName('EXT:backend/Resources/Public/Images/icon_capslock.svg'),
219 'typo3' => $this->getUriForFileName('EXT:backend/Resources/Public/Images/typo3_orange.svg'),
220 ),
221 'copyright' => BackendUtility::TYPO3_copyRightNotice(),
222 'loginNewsItems' => $this->getSystemNews(),
223 'loginProviderIdentifier' => $this->loginProviderIdentifier,
224 'loginProviders' => $this->loginProviders
225 ));
226
227 // Initialize interface selectors:
228 $this->makeInterfaceSelectorBox();
229
230 /** @var LoginProviderInterface $loginProvider */
231 $loginProvider = GeneralUtility::makeInstance($this->loginProviders[$this->loginProviderIdentifier]['provider']);
232 $loginProvider->render($this->view, $pageRenderer, $this);
233
234 $content = $this->getDocumentTemplate()->startPage('TYPO3 CMS Login: ' . $GLOBALS['TYPO3_CONF_VARS']['SYS']['sitename'], FALSE);
235 $content .= $this->view->render();
236 $content .= $this->getDocumentTemplate()->endPage();
237
238 return $content;
239 }
240
241 /**
242 * Checking, if we should perform some sort of redirection OR closing of windows.
243 *
244 * Do redirect:
245 *
246 * If a user is logged in AND
247 * a) if either the login is just done (isLoginInProgress) or
248 * b) a loginRefresh is done
249 *
250 * @throws \RuntimeException
251 * @throws \UnexpectedValueException
252 */
253 protected function checkRedirect() {
254 if (
255 empty($this->getBackendUserAuthentication()->user['uid'])
256 && ($this->isLoginInProgress() || !$this->loginRefresh)
257 ) {
258 return;
259 }
260
261 /*
262 * If no cookie has been set previously, we tell people that this is a problem.
263 * This assumes that a cookie-setting script (like this one) has been hit at
264 * least once prior to this instance.
265 */
266 if (!$_COOKIE[BackendUserAuthentication::getCookieName()]) {
267 if ($this->submitValue === 'setCookie') {
268 /*
269 * we tried it a second time but still no cookie
270 * 26/4 2005: This does not work anymore, because the saving of challenge values
271 * in $_SESSION means the system will act as if the password was wrong.
272 */
273 throw new \RuntimeException('Login-error: Yeah, that\'s a classic. No cookies, no TYPO3. ' .
274 'Please accept cookies from TYPO3 - otherwise you\'ll not be able to use the system.', 1294586846);
275 } else {
276 // try it once again - that might be needed for auto login
277 $this->redirectToURL = 'index.php?commandLI=setCookie';
278 }
279 }
280 $redirectToUrl = (string)$this->getBackendUserAuthentication()->getTSConfigVal('auth.BE.redirectToURL');
281 if (empty($redirectToUrl)) {
282 // Based on the interface we set the redirect script
283 switch (GeneralUtility::_GP('interface')) {
284 case 'frontend':
285 $interface = 'frontend';
286 $this->redirectToURL = '../';
287 break;
288 case 'backend':
289 $interface = 'backend';
290 $this->redirectToURL = 'backend.php';
291 break;
292 default:
293 $interface = '';
294 }
295 } else {
296 $this->redirectToURL = $redirectToUrl;
297 $interface = '';
298 }
299 // store interface
300 $this->getBackendUserAuthentication()->uc['interfaceSetup'] = $interface;
301 $this->getBackendUserAuthentication()->writeUC();
302
303 $formProtection = FormProtectionFactory::get();
304 if (!$formProtection instanceof BackendFormProtection) {
305 throw new \RuntimeException('The Form Protection retrieved does not match the expected one.', 1432080411);
306 }
307 if ($this->loginRefresh) {
308 $formProtection->setSessionTokenFromRegistry();
309 $formProtection->persistSessionToken();
310 $this->getDocumentTemplate()->JScode .= $this->getDocumentTemplate()->wrapScriptTags('
311 if (parent.opener && parent.opener.TYPO3 && parent.opener.TYPO3.LoginRefresh) {
312 parent.opener.TYPO3.LoginRefresh.startTask();
313 parent.close();
314 }
315 ');
316 } else {
317 $formProtection->storeSessionTokenInRegistry();
318 HttpUtility::redirect($this->redirectToURL);
319 }
320 }
321
322 /**
323 * Making interface selector:
324 *
325 * @return void
326 */
327 public function makeInterfaceSelectorBox() {
328 // If interfaces are defined AND no input redirect URL in GET vars:
329 if ($GLOBALS['TYPO3_CONF_VARS']['BE']['interfaces'] && ($this->isLoginInProgress() || !$this->redirectUrl)) {
330 $parts = GeneralUtility::trimExplode(',', $GLOBALS['TYPO3_CONF_VARS']['BE']['interfaces']);
331 if (count($parts) > 1) {
332 // Only if more than one interface is defined we will show the selector
333 $interfaces = array(
334 'backend' => array(
335 'label' => $this->getLanguageService()->getLL('interface.backend'),
336 'jumpScript' => 'backend.php',
337 'interface' => 'backend'
338 ),
339 'frontend' => array(
340 'label' => $this->getLanguageService()->getLL('interface.frontend'),
341 'jumpScript' => '../',
342 'interface' => 'frontend'
343 )
344 );
345
346 $this->view->assign('showInterfaceSelector', TRUE);
347 $this->view->assign('interfaces', $interfaces);
348 } elseif (!$this->redirectUrl) {
349 // If there is only ONE interface value set and no redirect_url is present
350 $this->view->assign('showInterfaceSelector', FALSE);
351 $this->view->assign('interface', $parts[0]);
352 }
353 }
354 }
355
356 /**
357 * Gets news from sys_news and converts them into a format suitable for
358 * showing them at the login screen.
359 *
360 * @return array An array of login news.
361 */
362 protected function getSystemNews() {
363 $systemNewsTable = 'sys_news';
364 $systemNews = array();
365 $systemNewsRecords = $this->getDatabaseConnection()->exec_SELECTgetRows('title, content, crdate', $systemNewsTable, '1=1' . BackendUtility::BEenableFields($systemNewsTable) . BackendUtility::deleteClause($systemNewsTable), '', 'crdate DESC');
366 foreach ($systemNewsRecords as $systemNewsRecord) {
367 $systemNews[] = array(
368 'date' => date($GLOBALS['TYPO3_CONF_VARS']['SYS']['ddmmyy'], $systemNewsRecord['crdate']),
369 'header' => $systemNewsRecord['title'],
370 'content' => $systemNewsRecord['content']
371 );
372 }
373 return $systemNews;
374 }
375
376 /**
377 * Returns the uri of a relative reference, resolves the "EXT:" prefix
378 * (way of referring to files inside extensions) and checks that the file is inside
379 * the PATH_site of the TYPO3 installation
380 *
381 * @param string $filename The input filename/filepath to evaluate
382 * @return string Returns the filename of $filename if valid, otherwise blank string.
383 * @internal
384 */
385 private function getUriForFileName($filename) {
386 if (strpos($filename, '://')) {
387 return $filename;
388 }
389 $urlPrefix = '';
390 if (strpos($filename, 'EXT:') === 0) {
391 $absoluteFilename = GeneralUtility::getFileAbsFileName($filename);
392 $filename = '';
393 if ($absoluteFilename !== '') {
394 $filename = PathUtility::getAbsoluteWebPath($absoluteFilename);
395 }
396 } elseif (strpos($filename, '/') !== 0) {
397 $urlPrefix = GeneralUtility::getIndpEnv('TYPO3_SITE_PATH');
398 }
399 return $urlPrefix . $filename;
400 }
401
402 /**
403 * Checks if login credentials are currently submitted
404 *
405 * @return bool
406 */
407 protected function isLoginInProgress() {
408 $username = GeneralUtility::_GP('username');
409 return !empty($username) || !empty($this->submitValue);
410 }
411
412 /**
413 * returns a new standalone view, shorthand function
414 *
415 * @return StandaloneView
416 */
417 protected function getFluidTemplateObject() {
418 /** @var StandaloneView $view */
419 $view = GeneralUtility::makeInstance(StandaloneView::class);
420 $view->setLayoutRootPaths(array(GeneralUtility::getFileAbsFileName('EXT:backend/Resources/Private/Layouts')));
421 $view->setPartialRootPaths(array(GeneralUtility::getFileAbsFileName('EXT:backend/Resources/Private/Partials')));
422 $view->setTemplateRootPaths(array(GeneralUtility::getFileAbsFileName('EXT:backend/Resources/Private/Templates')));
423
424 $view->getRequest()->setControllerExtensionName('Backend');
425 return $view;
426 }
427
428 /**
429 * Validates the registered login providers
430 *
431 * @throws \RuntimeException
432 */
433 protected function validateAndSortLoginProviders() {
434 if (
435 !isset($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['backend']['loginProviders'])
436 || !is_array($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['backend']['loginProviders'])
437 || empty($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['backend']['loginProviders'])
438 ) {
439 throw new \RuntimeException('No login providers are registered in $GLOBALS[\'TYPO3_CONF_VARS\'][\'EXTCONF\'][\'backend\'][\'loginProviders\'].', 1433417281);
440 }
441 $providers = $GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['backend']['loginProviders'];
442 foreach ($providers as $identifier => $configuration) {
443 if (empty($configuration) || !is_array($configuration)) {
444 throw new \RuntimeException('Missing configuration for login provider "' . $identifier . '".', 1433416043);
445 }
446 if (!is_string($configuration['provider']) || empty($configuration['provider']) || !class_exists($configuration['provider']) || !is_subclass_of($configuration['provider'], LoginProviderInterface::class)) {
447 throw new \RuntimeException('The login provider "' . $identifier . '" defines an invalid provider. Ensure the class exists and implements the "' . LoginProviderInterface::class . '".', 1433416043);
448 }
449 if (empty($configuration['label'])) {
450 throw new \RuntimeException('Missing label definition for login provider "' . $identifier . '".', 1433416044);
451 }
452 if (empty($configuration['icon-class'])) {
453 throw new \RuntimeException('Missing icon definition for login provider "' . $identifier . '".', 1433416045);
454 }
455 if (!isset($configuration['sorting'])) {
456 throw new \RuntimeException('Missing sorting definition for login provider "' . $identifier . '".', 1433416046);
457 }
458 }
459 // sort providers
460 uasort($providers, function($a, $b) {
461 return $b['sorting'] - $a['sorting'];
462 });
463 $this->loginProviders = $providers;
464 }
465
466 /**
467 * Detect the login provider, get from request or choose the
468 * first one as default
469 *
470 * @return string
471 */
472 protected function detectLoginProvider() {
473 $loginProvider = GeneralUtility::_GP('loginProvider');
474 if ((empty($loginProvider) || !isset($this->loginProviders[$loginProvider])) && !empty($_COOKIE['be_lastLoginProvider'])) {
475 $loginProvider = $_COOKIE['be_lastLoginProvider'];
476 }
477 if (empty($loginProvider) || !isset($this->loginProviders[$loginProvider])) {
478 reset($this->loginProviders);
479 $loginProvider = key($this->loginProviders);
480 }
481 setcookie('be_lastLoginProvider', $loginProvider, $GLOBALS['EXEC_TIME'] + 7776000); // 90 days
482 return $loginProvider;
483 }
484
485 /**
486 * @return string
487 */
488 public function getLoginProviderIdentifier() {
489 return $this->loginProviderIdentifier;
490 }
491
492 /**
493 * Returns LanguageService
494 *
495 * @return \TYPO3\CMS\Lang\LanguageService
496 */
497 protected function getLanguageService() {
498 return $GLOBALS['LANG'];
499 }
500
501 /**
502 * @return BackendUserAuthentication
503 */
504 protected function getBackendUserAuthentication() {
505 return $GLOBALS['BE_USER'];
506 }
507
508 /**
509 * Returns the database connection
510 *
511 * @return \TYPO3\CMS\Core\Database\DatabaseConnection
512 */
513 protected function getDatabaseConnection() {
514 return $GLOBALS['TYPO3_DB'];
515 }
516
517 /**
518 * Returns an instance of DocumentTemplate
519 *
520 * @return \TYPO3\CMS\Backend\Template\DocumentTemplate
521 */
522 protected function getDocumentTemplate() {
523 return $GLOBALS['TBE_TEMPLATE'];
524 }
525
526 }