2 namespace TYPO3\CMS\Backend\Controller
;
5 * This file is part of the TYPO3 CMS project.
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.
11 * For the full copyright and license information, please read the
12 * LICENSE.txt file that was distributed with this source code.
14 * The TYPO3 project - inspiring people to share!
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\Utility\GeneralUtility
;
24 use TYPO3\CMS\Core\Utility\HttpUtility
;
25 use TYPO3\CMS\Core\Utility\PathUtility
;
26 use TYPO3\CMS\Fluid\View\StandaloneView
;
29 * Script Class for rendering the login form
31 * @author Kasper Skårhøj <kasperYYYY@typo3.com>
32 * @author Frank Nägler <typo3@naegler.net>
34 class LoginController
{
37 * The URL to redirect to after login.
41 protected $redirectUrl;
44 * Set to the redirect URL of the form (may be redirect_url or "backend.php")
48 protected $redirectToURL;
51 * the active login provider identifier
55 protected $loginProviderIdentifier = NULL;
58 * List of registered and sorted login providers
62 protected $loginProviders = [];
65 * Login-refresh bool; The backend will call this script
66 * with this value set when the login is close to being expired
67 * and the form needs to be redrawn.
71 protected $loginRefresh;
74 * Value of forms submit button for login.
78 protected $submitValue;
86 * Initialize the login box. Will also react on a &L=OUT flag and exit.
88 public function __construct() {
89 $this->validateAndSortLoginProviders();
91 // We need a PHP session session for most login levels
93 $this->redirectUrl
= GeneralUtility
::sanitizeLocalUrl(GeneralUtility
::_GP('redirect_url'));
94 $this->loginProviderIdentifier
= $this->detectLoginProvider();
96 $this->loginRefresh
= (bool)GeneralUtility
::_GP('loginRefresh');
97 // Value of "Login" button. If set, the login button was pressed.
98 $this->submitValue
= GeneralUtility
::_GP('commandLI');
100 // Try to get the preferred browser language
101 $preferredBrowserLanguage = $this->getLanguageService()->csConvObj
102 ->getPreferredClientLanguage(GeneralUtility
::getIndpEnv('HTTP_ACCEPT_LANGUAGE'));
104 // If we found a $preferredBrowserLanguage and it is not the default language and no be_user is logged in
105 // initialize $this->getLanguageService() again with $preferredBrowserLanguage
106 if ($preferredBrowserLanguage !== 'default' && empty($this->getBackendUserAuthentication()->user
['uid'])) {
107 $this->getLanguageService()->init($preferredBrowserLanguage);
110 $this->getLanguageService()->includeLLFile('EXT:lang/locallang_login.xlf');
112 // Setting the redirect URL to "backend.php" if no alternative input is given
113 $this->redirectToURL
= $this->redirectUrl ?
: 'backend.php';
115 // If "L" is "OUT", then any logged in is logged out. If redirect_url is given, we redirect to it
116 if (GeneralUtility
::_GP('L') === 'OUT' && is_object($this->getBackendUserAuthentication())) {
117 $this->getBackendUserAuthentication()->logoff();
118 HttpUtility
::redirect($this->redirectUrl
);
121 $this->view
= $this->getFluidTemplateObject();
125 * Main function - creating the login/logout form
130 public function main() {
131 /** @var $pageRenderer \TYPO3\CMS\Core\Page\PageRenderer */
132 $pageRenderer = $this->getDocumentTemplate()->getPageRenderer();
133 $pageRenderer->loadRequireJsModule('TYPO3/CMS/Backend/Login');
135 // support placeholders for IE9 and lower
136 $clientInfo = GeneralUtility
::clientInfo();
137 if ($clientInfo['BROWSER'] === 'msie' && $clientInfo['VERSION'] <= 9) {
138 $pageRenderer->addJsLibrary('placeholders', 'sysext/core/Resources/Public/JavaScript/Contrib/placeholders.jquery.min.js');
141 // Checking, if we should make a redirect.
142 // Might set JavaScript in the header to close window.
143 $this->checkRedirect();
145 // Extension Configuration
146 $extConf = unserialize($GLOBALS['TYPO3_CONF_VARS']['EXT']['extConf']['backend']);
149 if (!empty($extConf['loginBackgroundImage'])) {
150 $backgroundImage = $this->getUriForFileName($extConf['loginBackgroundImage']);
151 $this->getDocumentTemplate()->inDocStylesArray
[] = '
152 @media (min-width: 768px){
153 .typo3-login-carousel-control.right,
154 .typo3-login-carousel-control.left,
155 .panel-login { border: 0; }
156 .typo3-login { background-image: url("' . $backgroundImage . '"); }
161 // Add additional css to use the highlight color in the login screen
162 if (!empty($extConf['loginHighlightColor'])) {
163 $this->getDocumentTemplate()->inDocStylesArray
[] = '
164 .btn-login.disabled, .btn-login[disabled], fieldset[disabled] .btn-login,
165 .btn-login.disabled:hover, .btn-login[disabled]:hover, fieldset[disabled] .btn-login:hover,
166 .btn-login.disabled:focus, .btn-login[disabled]:focus, fieldset[disabled] .btn-login:focus,
167 .btn-login.disabled.focus, .btn-login[disabled].focus, fieldset[disabled] .btn-login.focus,
168 .btn-login.disabled:active, .btn-login[disabled]:active, fieldset[disabled] .btn-login:active,
169 .btn-login.disabled.active, .btn-login[disabled].active, fieldset[disabled] .btn-login.active,
170 .btn-login:hover, .btn-login:focus, .btn-login:active,
171 .btn-login { background-color: ' . $extConf['loginHighlightColor'] . '; }
172 .panel-login .panel-body { border-color: ' . $extConf['loginHighlightColor'] . '; }
177 if (!empty($extConf['loginLogo'])) {
178 $logo = $extConf['loginLogo'];
179 } elseif (!empty($GLOBALS['TBE_STYLES']['logo_login'])) {
180 // Fallback to old TBE_STYLES login logo
181 $logo = $GLOBALS['TBE_STYLES']['logo_login'];
182 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.');
184 // Use TYPO3 logo depending on highlight color
185 if (!empty($extConf['loginHighlightColor'])) {
186 $logo = 'EXT:backend/Resources/Public/Images/typo3_black.svg';
188 $logo = 'EXT:backend/Resources/Public/Images/typo3_orange.svg';
190 $this->getDocumentTemplate()->inDocStylesArray
[] = '
191 .typo3-login-logo .typo3-login-image { max-width: 150px; }
194 $logo = $this->getUriForFileName($logo);
197 $formType = empty($this->getBackendUserAuthentication()->user
['uid']) ?
'LoginForm' : 'LogoutForm';
198 $this->view
->assignMultiple(array(
199 'backendUser' => $this->getBackendUserAuthentication()->user
,
200 'hasLoginError' => $this->isLoginInProgress(),
201 'formType' => $formType,
204 'capslock' => $this->getUriForFileName('EXT:backend/Resources/Public/Images/icon_capslock.svg'),
205 'typo3' => $this->getUriForFileName('EXT:backend/Resources/Public/Images/typo3_orange.svg'),
207 'copyright' => BackendUtility
::TYPO3_copyRightNotice(),
208 'loginNewsItems' => $this->getSystemNews(),
209 'loginProviderIdentifier' => $this->loginProviderIdentifier
,
210 'loginProviders' => $this->loginProviders
213 // Initialize interface selectors:
214 $this->makeInterfaceSelectorBox();
216 /** @var LoginProviderInterface $loginProvider */
217 $loginProvider = GeneralUtility
::makeInstance($this->loginProviders
[$this->loginProviderIdentifier
]['provider']);
218 $loginProvider->render($this->view
, $pageRenderer, $this);
220 $content = $this->getDocumentTemplate()->startPage('TYPO3 CMS Login: ' . $GLOBALS['TYPO3_CONF_VARS']['SYS']['sitename'], FALSE);
221 $content .= $this->view
->render();
222 $content .= $this->getDocumentTemplate()->endPage();
228 * Checking, if we should perform some sort of redirection OR closing of windows.
232 * If a user is logged in AND
233 * a) if either the login is just done (isLoginInProgress) or
234 * b) a loginRefresh is done
236 * @throws \RuntimeException
237 * @throws \UnexpectedValueException
239 protected function checkRedirect() {
240 if (empty($this->getBackendUserAuthentication()->user
['uid'])) {
241 // a) if either the login is just done (isLoginInProgress) or
242 if ($this->isLoginInProgress()) {
243 // Wrong password, wait for 5 seconds
246 // b) a loginRefresh is done
247 } elseif (!$this->loginRefresh
) {
252 * If no cookie has been set previously, we tell people that this is a problem.
253 * This assumes that a cookie-setting script (like this one) has been hit at
254 * least once prior to this instance.
256 if (!$_COOKIE[BackendUserAuthentication
::getCookieName()]) {
257 if ($this->submitValue
=== 'setCookie') {
259 * we tried it a second time but still no cookie
260 * 26/4 2005: This does not work anymore, because the saving of challenge values
261 * in $_SESSION means the system will act as if the password was wrong.
263 throw new \
RuntimeException('Login-error: Yeah, that\'s a classic. No cookies, no TYPO3. ' .
264 'Please accept cookies from TYPO3 - otherwise you\'ll not be able to use the system.', 1294586846);
266 // try it once again - that might be needed for auto login
267 $this->redirectToURL
= 'index.php?commandLI=setCookie';
270 $redirectToUrl = (string)$this->getBackendUserAuthentication()->getTSConfigVal('auth.BE.redirectToURL');
271 if (empty($redirectToUrl)) {
272 // Based on the interface we set the redirect script
273 switch (GeneralUtility
::_GP('interface')) {
275 $interface = 'frontend';
276 $this->redirectToURL
= '../';
279 $interface = 'backend';
280 $this->redirectToURL
= 'backend.php';
286 $this->redirectToURL
= $redirectToUrl;
290 $this->getBackendUserAuthentication()->uc
['interfaceSetup'] = $interface;
291 $this->getBackendUserAuthentication()->writeUC();
293 $formProtection = FormProtectionFactory
::get();
294 if (!$formProtection instanceof BackendFormProtection
) {
295 throw new \
RuntimeException('The Form Protection retrieved does not match the expected one.', 1432080411);
297 if ($this->loginRefresh
) {
298 $formProtection->setSessionTokenFromRegistry();
299 $formProtection->persistSessionToken();
300 $this->getDocumentTemplate()->JScode
.= $this->getDocumentTemplate()->wrapScriptTags('
301 if (parent.opener && parent.opener.TYPO3 && parent.opener.TYPO3.LoginRefresh) {
302 parent.opener.TYPO3.LoginRefresh.startTask();
307 $formProtection->storeSessionTokenInRegistry();
308 HttpUtility
::redirect($this->redirectToURL
);
313 * Making interface selector:
317 public function makeInterfaceSelectorBox() {
318 // If interfaces are defined AND no input redirect URL in GET vars:
319 if ($GLOBALS['TYPO3_CONF_VARS']['BE']['interfaces'] && ($this->isLoginInProgress() ||
!$this->redirectUrl
)) {
320 $parts = GeneralUtility
::trimExplode(',', $GLOBALS['TYPO3_CONF_VARS']['BE']['interfaces']);
321 if (count($parts) > 1) {
322 // Only if more than one interface is defined we will show the selector
325 'label' => $this->getLanguageService()->getLL('interface.backend'),
326 'jumpScript' => 'backend.php',
327 'interface' => 'backend'
330 'label' => $this->getLanguageService()->getLL('interface.frontend'),
331 'jumpScript' => '../',
332 'interface' => 'frontend'
336 $this->view
->assign('showInterfaceSelector', TRUE);
337 $this->view
->assign('interfaces', $interfaces);
338 } elseif (!$this->redirectUrl
) {
339 // If there is only ONE interface value set and no redirect_url is present
340 $this->view
->assign('showInterfaceSelector', FALSE);
341 $this->view
->assign('interface', $parts[0]);
347 * Gets news from sys_news and converts them into a format suitable for
348 * showing them at the login screen.
350 * @return array An array of login news.
352 protected function getSystemNews() {
353 $systemNewsTable = 'sys_news';
354 $systemNews = array();
355 $systemNewsRecords = $this->getDatabaseConnection()->exec_SELECTgetRows('title, content, crdate', $systemNewsTable, '1=1' . BackendUtility
::BEenableFields($systemNewsTable) . BackendUtility
::deleteClause($systemNewsTable), '', 'crdate DESC');
356 foreach ($systemNewsRecords as $systemNewsRecord) {
357 $systemNews[] = array(
358 'date' => date($GLOBALS['TYPO3_CONF_VARS']['SYS']['ddmmyy'], $systemNewsRecord['crdate']),
359 'header' => $systemNewsRecord['title'],
360 'content' => $systemNewsRecord['content']
367 * Returns the uri of a relative reference, resolves the "EXT:" prefix
368 * (way of referring to files inside extensions) and checks that the file is inside
369 * the PATH_site of the TYPO3 installation
371 * @param string $filename The input filename/filepath to evaluate
372 * @return string Returns the filename of $filename if valid, otherwise blank string.
375 private function getUriForFileName($filename) {
376 if (strpos($filename, '://')) {
380 if (strpos($filename, 'EXT:') === 0) {
381 $absoluteFilename = GeneralUtility
::getFileAbsFileName($filename);
383 if ($absoluteFilename !== '') {
384 $filename = PathUtility
::getAbsoluteWebPath($absoluteFilename);
386 } elseif (strpos($filename, '/') !== 0) {
387 $urlPrefix = GeneralUtility
::getIndpEnv('TYPO3_SITE_PATH');
389 return $urlPrefix . $filename;
393 * Checks if login credentials are currently submitted
397 protected function isLoginInProgress() {
398 $username = GeneralUtility
::_GP('username');
399 return !empty($username) ||
!empty($this->submitValue
);
403 * returns a new standalone view, shorthand function
405 * @return StandaloneView
407 protected function getFluidTemplateObject() {
408 /** @var StandaloneView $view */
409 $view = GeneralUtility
::makeInstance(StandaloneView
::class);
410 $view->setLayoutRootPaths(array(GeneralUtility
::getFileAbsFileName('EXT:backend/Resources/Private/Layouts')));
411 $view->setPartialRootPaths(array(GeneralUtility
::getFileAbsFileName('EXT:backend/Resources/Private/Partials')));
412 $view->setTemplateRootPaths(array(GeneralUtility
::getFileAbsFileName('EXT:backend/Resources/Private/Templates')));
414 $view->getRequest()->setControllerExtensionName('Backend');
419 * Validates the registered login providers
421 * @throws \RuntimeException
423 protected function validateAndSortLoginProviders() {
425 !isset($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['backend']['loginProviders'])
426 ||
!is_array($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['backend']['loginProviders'])
427 ||
empty($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['backend']['loginProviders'])
429 throw new \
RuntimeException('No login providers are registered in $GLOBALS[\'TYPO3_CONF_VARS\'][\'EXTCONF\'][\'backend\'][\'loginProviders\'].', 1433417281);
431 $providers = $GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['backend']['loginProviders'];
432 foreach ($providers as $identifier => $configuration) {
433 if (empty($configuration) ||
!is_array($configuration)) {
434 throw new \
RuntimeException('Missing configuration for login provider "' . $identifier . '".', 1433416043);
436 if (!is_string($configuration['provider']) ||
empty($configuration['provider']) ||
!class_exists($configuration['provider']) ||
!is_subclass_of($configuration['provider'], LoginProviderInterface
::class)) {
437 throw new \
RuntimeException('The login provider "' . $identifier . '" defines an invalid provider. Ensure the class exists and implements the "' . LoginProviderInterface
::class . '".', 1433416043);
439 if (empty($configuration['label'])) {
440 throw new \
RuntimeException('Missing label definition for login provider "' . $identifier . '".', 1433416044);
442 if (empty($configuration['icon-class'])) {
443 throw new \
RuntimeException('Missing icon definition for login provider "' . $identifier . '".', 1433416045);
445 if (!isset($configuration['sorting'])) {
446 throw new \
RuntimeException('Missing sorting definition for login provider "' . $identifier . '".', 1433416046);
450 uasort($providers, function($a, $b) {
451 return $b['sorting'] - $a['sorting'];
453 $this->loginProviders
= $providers;
457 * Detect the login provider, get from request or choose the
458 * first one as default
462 protected function detectLoginProvider() {
463 $loginProvider = GeneralUtility
::_GP('loginProvider');
464 if ((empty($loginProvider) ||
!isset($this->loginProviders
[$loginProvider])) && !empty($_COOKIE['be_lastLoginProvider'])) {
465 $loginProvider = $_COOKIE['be_lastLoginProvider'];
467 if (empty($loginProvider) ||
!isset($this->loginProviders
[$loginProvider])) {
468 reset($this->loginProviders
);
469 $loginProvider = key($this->loginProviders
);
471 setcookie('be_lastLoginProvider', $loginProvider);
472 return $loginProvider;
478 public function getLoginProviderIdentifier() {
479 return $this->loginProviderIdentifier
;
483 * Returns LanguageService
485 * @return \TYPO3\CMS\Lang\LanguageService
487 protected function getLanguageService() {
488 return $GLOBALS['LANG'];
492 * @return BackendUserAuthentication
494 protected function getBackendUserAuthentication() {
495 return $GLOBALS['BE_USER'];
499 * Returns the database connection
501 * @return \TYPO3\CMS\Core\Database\DatabaseConnection
503 protected function getDatabaseConnection() {
504 return $GLOBALS['TYPO3_DB'];
508 * Returns an instance of DocumentTemplate
510 * @return \TYPO3\CMS\Backend\Template\DocumentTemplate
512 protected function getDocumentTemplate() {
513 return $GLOBALS['TBE_TEMPLATE'];