[!!!][TASK] New Login Screen
[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\Utility\BackendUtility;
18 use TYPO3\CMS\Backend\Utility\IconUtility;
19 use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
20 use TYPO3\CMS\Core\Database\DatabaseConnection;
21 use TYPO3\CMS\Core\FormProtection\FormProtectionFactory;
22 use TYPO3\CMS\Core\Utility\ExtensionManagementUtility;
23 use TYPO3\CMS\Core\Utility\GeneralUtility;
24 use TYPO3\CMS\Core\Utility\HttpUtility;
25 use TYPO3\CMS\Fluid\View\StandaloneView;
26 use TYPO3\CMS\Lang\LanguageService;
27
28 /**
29 * Script Class for rendering the login form
30 *
31 * @author Kasper Skårhøj <kasperYYYY@typo3.com>
32 */
33 class LoginController {
34 /**
35 * The URL to redirect to after login.
36 *
37 * @var string
38 */
39 public $redirect_url;
40
41 /**
42 * Defines which interface to load (from interface selector)
43 *
44 * @var string
45 */
46 public $GPinterface;
47
48 /**
49 * preset username
50 *
51 * @var string
52 */
53 public $u;
54
55 /**
56 * preset password
57 *
58 * @var string
59 */
60 public $p;
61
62 /**
63 * OpenID URL submitted by form
64 *
65 * @var string
66 */
67 protected $openIdUrl;
68
69 /**
70 * If "L" is "OUT", then any logged in used is logged out. If redirect_url is given, we redirect to it
71 *
72 * @var string
73 */
74 public $L;
75
76 /**
77 * Login-refresh boolean; The backend will call this script
78 * with this value set when the login is close to being expired
79 * and the form needs to be redrawn.
80 *
81 * @var bool
82 */
83 public $loginRefresh;
84
85 /**
86 * Value of forms submit button for login.
87 *
88 * @var string
89 */
90 public $commandLI;
91
92 /**
93 * Set to the redirect URL of the form (may be redirect_url or "backend.php")
94 *
95 * @var string
96 */
97 public $redirectToURL;
98
99 /**
100 * Content accumulation
101 *
102 * @var string
103 */
104 public $content;
105
106 /**
107 * A selector box for selecting value for "interface" may be rendered into this variable
108 *
109 * @var string
110 */
111 public $interfaceSelector;
112
113 /**
114 * A selector box for selecting value for "interface" may be rendered into this variable
115 * this will have an onchange action which will redirect the user to the selected interface right away
116 *
117 * @var string
118 */
119 public $interfaceSelector_jump;
120
121 /**
122 * A hidden field, if the interface is not set.
123 *
124 * @var string
125 */
126 public $interfaceSelector_hidden;
127
128 /**
129 * Additional hidden fields to be placed at the login form
130 *
131 * @var string
132 */
133 public $addFields_hidden = '';
134
135 /**
136 * Sets the level of security. *'normal' = clear-text. 'challenged' = hashed
137 * password/username from form in $formfield_uident. 'superchallenged' = hashed password hashed again with username.
138 *
139 * @var string
140 */
141 public $loginSecurityLevel = 'superchallenged';
142
143 /**
144 * Constructor
145 */
146 public function __construct() {
147 $this->init();
148 }
149
150 /**
151 * Initialize the login box. Will also react on a &L=OUT flag and exit.
152 *
153 * @return void
154 */
155 public function init() {
156 // We need a PHP session session for most login levels
157 session_start();
158 $this->redirect_url = GeneralUtility::sanitizeLocalUrl(GeneralUtility::_GP('redirect_url'));
159 $this->GPinterface = GeneralUtility::_GP('interface');
160 // Grabbing preset username and password, for security reasons this feature only works if SSL is used
161 if (GeneralUtility::getIndpEnv('TYPO3_SSL')) {
162 $this->u = GeneralUtility::_GP('u');
163 $this->p = GeneralUtility::_GP('p');
164 $this->openIdUrl = GeneralUtility::_GP('openid_url');
165 }
166 // If "L" is "OUT", then any logged in is logged out. If redirect_url is given, we redirect to it
167 $this->L = GeneralUtility::_GP('L');
168 // Login
169 $this->loginRefresh = GeneralUtility::_GP('loginRefresh');
170 // Value of "Login" button. If set, the login button was pressed.
171 $this->commandLI = GeneralUtility::_GP('commandLI');
172 // Sets the level of security from conf vars
173 if ($GLOBALS['TYPO3_CONF_VARS']['BE']['loginSecurityLevel']) {
174 $this->loginSecurityLevel = $GLOBALS['TYPO3_CONF_VARS']['BE']['loginSecurityLevel'];
175 }
176 // Try to get the preferred browser language
177 $preferredBrowserLanguage = $this->getLanguageService()->csConvObj->getPreferredClientLanguage(GeneralUtility::getIndpEnv('HTTP_ACCEPT_LANGUAGE'));
178 // If we found a $preferredBrowserLanguage and it is not the default language and no be_user is logged in
179 // initialize $this->getLanguageService() again with $preferredBrowserLanguage
180 if ($preferredBrowserLanguage !== 'default' && empty($this->getBackendUserAuthentication()->user['uid'])) {
181 $this->getLanguageService()->init($preferredBrowserLanguage);
182 }
183 // Setting the redirect URL to "backend.php" if no alternative input is given
184 $this->redirectToURL = $this->redirect_url ?: 'backend.php';
185 // Do a logout if the command is set
186 if ($this->L == 'OUT' && is_object($this->getBackendUserAuthentication())) {
187 $this->getBackendUserAuthentication()->logoff();
188 HttpUtility::redirect($this->redirect_url);
189 }
190 }
191
192 /**
193 * Main function - creating the login/logout form
194 *
195 * @return void
196 */
197 public function main() {
198 // Initialize template object:
199 $view = $this->getFluidTemplateObject('EXT:backend/Resources/Private/Templates/Login.html');
200
201 /** @var $pageRenderer \TYPO3\CMS\Core\Page\PageRenderer */
202 $pageRenderer = $this->getDocumentTemplate()->getPageRenderer();
203 $pageRenderer->loadJquery();
204
205 // support placeholders for IE9 and lower
206 $clientInfo = GeneralUtility::clientInfo();
207 if ($clientInfo['BROWSER'] == 'msie' && $clientInfo['VERSION'] <= 9) {
208 $pageRenderer->addJsLibrary('placeholders', 'sysext/core/Resources/Public/JavaScript/Contrib/placeholders.jquery.min.js');
209 }
210
211 $pageRenderer->loadRequireJsModule('TYPO3/CMS/Backend/Login');
212 if (is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['typo3/index.php']['loginScriptHook'])) {
213 foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['typo3/index.php']['loginScriptHook'] as $function) {
214 $params = array();
215 $javaScriptCode = GeneralUtility::callUserFunction($function, $params, $this);
216 if ($javaScriptCode) {
217 $this->getDocumentTemplate()->JScode .= $javaScriptCode;
218 break;
219 }
220 }
221 }
222
223 // Checking, if we should make a redirect.
224 // Might set JavaScript in the header to close window.
225 $this->checkRedirect();
226
227 // Extension Configuration
228 $extConf = unserialize($GLOBALS['TYPO3_CONF_VARS']['EXT']['extConf']['backend']);
229
230 // Background Image
231 if (!empty($extConf['loginBackgroundImage'])) {
232 $this->getDocumentTemplate()->inDocStylesArray[] = '
233 @media (min-width: 768px){
234 .typo3-login { background-image: url("' . $extConf['loginBackgroundImage'] . '"); }
235 }
236 ';
237 }
238
239 // Add additional css to use the highlight color in the login screen
240 if (!empty($extConf['loginHighlightColor'])) {
241 $this->getDocumentTemplate()->inDocStylesArray[] = '
242 .btn-login.disabled, .btn-login[disabled], fieldset[disabled] .btn-login,
243 .btn-login.disabled:hover, .btn-login[disabled]:hover, fieldset[disabled] .btn-login:hover,
244 .btn-login.disabled:focus, .btn-login[disabled]:focus, fieldset[disabled] .btn-login:focus,
245 .btn-login.disabled.focus, .btn-login[disabled].focus, fieldset[disabled] .btn-login.focus,
246 .btn-login.disabled:active, .btn-login[disabled]:active, fieldset[disabled] .btn-login:active,
247 .btn-login.disabled.active, .btn-login[disabled].active, fieldset[disabled] .btn-login.active,
248 .btn-login:hover, .btn-login:focus, .btn-login:active,
249 .btn-login { background-color: ' . $extConf['loginHighlightColor'] . '; }
250 .panel-login .panel-body { border-color: ' . $extConf['loginHighlightColor'] . '; }
251 ';
252 }
253
254 // Logo
255 $logo = '';
256 if (!empty($extConf['loginLogo'])) {
257 $logo = $extConf['loginLogo'];
258 } elseif (!empty($GLOBALS['TBE_STYLES']['logo_login'])) {
259 // Fallback to old TBE_STYLES login logo
260 $logo = $GLOBALS['TBE_STYLES']['logo_login'];
261 GeneralUtility::deprecationLog('$GLOBALS["TBE_STYLES"]["logo_login"] is deprecated since TYPO3 CMS 7 and will be removed in TYPO3 CMS 8, please head to the backend extension configuration instead.');
262 } else {
263 // Use TYPO3 logo depending on highlight color
264 if (!empty($extConf['loginHighlightColor'])) {
265 $logo = 'EXT:backend/Resources/Public/Images/typo3_black.svg';
266 } else {
267 $logo = 'EXT:backend/Resources/Public/Images/typo3_orange.svg';
268 }
269 }
270
271 // Start form
272 $formType = empty($this->getBackendUserAuthentication()->user['uid']) ? 'loginForm' : 'logoutForm';
273 $view->assignMultiple(array(
274 'formTag' => $this->startForm(),
275 'labelPrefixPath' => 'LLL:EXT:lang/locallang_login.xlf:',
276 'backendUser' => $this->getBackendUserAuthentication()->user,
277 'hasLoginError' => $this->isLoginInProgress(),
278 'presetUsername' => $this->u,
279 'presetPassword' => $this->p,
280 'presetOpenId' => $this->openIdUrl,
281 'formType' => $formType,
282 'logo' => $logo,
283 'isOpenIdLoaded' => ExtensionManagementUtility::isLoaded('openid'),
284 'copyright' => BackendUtility::TYPO3_copyRightNotice($GLOBALS['TYPO3_CONF_VARS']['SYS']['loginCopyrightShowVersion']),
285 'loginNewsItems' => $this->getSystemNews()
286 ));
287
288 // Initialize interface selectors:
289 $this->makeInterfaceSelectorBox();
290 $view->assignMultiple(array(
291 'interfaceSelector' => $this->interfaceSelector,
292 'interfaceSelectorJump' => $this->interfaceSelector_jump
293 ));
294
295 // Starting page:
296 $this->content .= $this->getDocumentTemplate()->startPage('TYPO3 CMS Login: ' .
297 $GLOBALS['TYPO3_CONF_VARS']['SYS']['sitename'], FALSE);
298 // Add Content:
299 $this->content .= $view->render();
300 $this->content .= $this->getDocumentTemplate()->endPage();
301 }
302
303 /**
304 * Outputting the accumulated content to screen
305 *
306 * @return void
307 */
308 public function printContent() {
309 echo $this->content;
310 }
311
312 /*****************************
313 *
314 * Various functions
315 *
316 ******************************/
317 /**
318 * Checking, if we should perform some sort of redirection OR closing of windows.
319 *
320 * @return void
321 */
322 public function checkRedirect() {
323 /*
324 * Do redirect:
325 *
326 * If a user is logged in AND
327 * a) if either the login is just done (isLoginInProgress) or
328 * b) a loginRefresh is done or c) the interface-selector is NOT enabled
329 * (If it is on the other hand, it should not just load an interface,
330 * because people has to choose then...)
331 */
332 if (!empty($this->getBackendUserAuthentication()->user['uid'])
333 && ($this->isLoginInProgress() || $this->loginRefresh || !$this->interfaceSelector)) {
334 /*
335 * If no cookie has been set previously we tell people that this is a problem.
336 * This assumes that a cookie-setting script (like this one) has been hit at
337 * least once prior to this instance.
338 */
339 if (!$_COOKIE[BackendUserAuthentication::getCookieName()]) {
340 if ($this->commandLI == 'setCookie') {
341 /*
342 * we tried it a second time but still no cookie
343 * 26/4 2005: This does not work anymore, because the saving of challenge values
344 * in $_SESSION means the system will act as if the password was wrong.
345 */
346 throw new \RuntimeException('Login-error: Yeah, that\'s a classic. No cookies, no TYPO3.<br /><br />' .
347 'Please accept cookies from TYPO3 - otherwise you\'ll not be able to use the system.', 1294586846);
348 } else {
349 // try it once again - that might be needed for auto login
350 $this->redirectToURL = 'index.php?commandLI=setCookie';
351 }
352 }
353 $redirectToUrl = (string)$this->getBackendUserAuthentication()->getTSConfigVal('auth.BE.redirectToURL');
354 if (!empty($redirectToUrl)) {
355 $this->redirectToURL = $redirectToUrl;
356 $this->GPinterface = '';
357 }
358 // store interface
359 $this->getBackendUserAuthentication()->uc['interfaceSetup'] = $this->GPinterface;
360 $this->getBackendUserAuthentication()->writeUC();
361 // Based on specific setting of interface we set the redirect script:
362 switch ($this->GPinterface) {
363 case 'backend':
364 $this->redirectToURL = 'backend.php';
365 break;
366 case 'frontend':
367 $this->redirectToURL = '../';
368 break;
369 }
370 /** @var $formProtection \TYPO3\CMS\Core\FormProtection\BackendFormProtection */
371 $formProtection = FormProtectionFactory::get();
372 // If there is a redirect URL AND if loginRefresh is not set...
373 if (!$this->loginRefresh) {
374 $formProtection->storeSessionTokenInRegistry();
375 HttpUtility::redirect($this->redirectToURL);
376 } else {
377 $formProtection->setSessionTokenFromRegistry();
378 $formProtection->persistSessionToken();
379 $this->getDocumentTemplate()->JScode .= $this->getDocumentTemplate()->wrapScriptTags('
380 if (parent.opener && parent.opener.TYPO3 && parent.opener.TYPO3.LoginRefresh) {
381 parent.opener.TYPO3.LoginRefresh.startTask();
382 parent.close();
383 }
384 ');
385 }
386 } elseif (empty($this->getBackendUserAuthentication()->user['uid']) && $this->isLoginInProgress()) {
387 // Wrong password, wait for 5 seconds
388 sleep(5);
389 }
390 }
391
392 /**
393 * Making interface selector:
394 *
395 * @return void
396 */
397 public function makeInterfaceSelectorBox() {
398 // Reset variables:
399 $this->interfaceSelector = '';
400 $this->interfaceSelector_hidden = '';
401 $this->interfaceSelector_jump = '';
402 // If interfaces are defined AND no input redirect URL in GET vars:
403 if ($GLOBALS['TYPO3_CONF_VARS']['BE']['interfaces'] && ($this->isLoginInProgress() || !$this->redirect_url)) {
404 $parts = GeneralUtility::trimExplode(',', $GLOBALS['TYPO3_CONF_VARS']['BE']['interfaces']);
405 // Only if more than one interface is defined will we show the selector:
406 if (count($parts) > 1) {
407 // Initialize:
408 $labels = array(
409 'backend' => $this->getLanguageService()->getLL('interface.backend'),
410 'frontend' => $this->getLanguageService()->getLL('interface.frontend')
411 );
412 $jumpScript = array(
413 'backend' => 'backend.php',
414 'frontend' => '../'
415 );
416 // Traverse the interface keys:
417 foreach ($parts as $valueStr) {
418 $this->interfaceSelector .= '
419 <option value="' . htmlspecialchars($valueStr) . '"' . (GeneralUtility::_GP('interface') == htmlspecialchars($valueStr) ? ' selected="selected"' : '') . '>' . htmlspecialchars($labels[$valueStr]) . '</option>';
420 $this->interfaceSelector_jump .= '
421 <option value="' . htmlspecialchars($jumpScript[$valueStr]) . '">' . htmlspecialchars($labels[$valueStr]) . '</option>';
422 }
423 $this->interfaceSelector = '
424 <select id="t3-interfaceselector" name="interface" class="form-control input-login t3js-login-interface-field" tabindex="3">' . $this->interfaceSelector . '
425 </select>';
426 $this->interfaceSelector_jump = '
427 <select id="t3-interfaceselector" name="interface" class="form-control input-login t3js-login-interface-field" tabindex="3" onchange="window.location.href=this.options[this.selectedIndex].value;">' . $this->interfaceSelector_jump . '
428 </select>';
429 } elseif (!$this->redirect_url) {
430 // If there is only ONE interface value set and no redirect_url is present:
431 $this->interfaceSelector_hidden = '<input type="hidden" name="interface" value="' . trim($GLOBALS['TYPO3_CONF_VARS']['BE']['interfaces']) . '" />';
432 }
433 }
434 }
435
436 /**
437 * Gets news from sys_news and converts them into a format suitable for
438 * showing them at the login screen.
439 *
440 * @return array An array of login news.
441 */
442 protected function getSystemNews() {
443 $systemNewsTable = 'sys_news';
444 $systemNews = array();
445 $systemNewsRecords = $this->getDatabaseConntection()->exec_SELECTgetRows('title, content, crdate', $systemNewsTable, '1=1' . BackendUtility::BEenableFields($systemNewsTable) . BackendUtility::deleteClause($systemNewsTable), '', 'crdate DESC');
446 foreach ($systemNewsRecords as $systemNewsRecord) {
447 $systemNews[] = array(
448 'date' => date($GLOBALS['TYPO3_CONF_VARS']['SYS']['ddmmyy'], $systemNewsRecord['crdate']),
449 'header' => $systemNewsRecord['title'],
450 'content' => $systemNewsRecord['content']
451 );
452 }
453 return $systemNews;
454 }
455
456 /**
457 * Returns the form tag
458 *
459 * @return string Opening form tag string
460 */
461 public function startForm() {
462 $output = '';
463 // The form defaults to 'no login'. This prevents plain
464 // text logins to the Backend. The 'sv' extension changes the form to
465 // use superchallenged method and rsaauth extension makes rsa authentication.
466 $form = '<form action="index.php" method="post" name="loginform" ' . 'onsubmit="alert(\'No authentication methods available. Please, contact your TYPO3 administrator.\');return false">';
467 // Call hooks. If they do not return anything, we fail to login
468 if (is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['typo3/index.php']['loginFormHook'])) {
469 foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['typo3/index.php']['loginFormHook'] as $function) {
470 $params = array();
471 $formCode = GeneralUtility::callUserFunction($function, $params, $this);
472 if ($formCode) {
473 $form = $formCode;
474 break;
475 }
476 }
477 }
478 $output .= $form . '<input type="hidden" name="login_status" value="login" />' .
479 '<input type="hidden" id="t3-field-userident" class="t3js-login-userident-field" name="userident" value="" />' .
480 '<input type="hidden" name="redirect_url" value="' . htmlspecialchars($this->redirectToURL) . '" />' .
481 '<input type="hidden" name="loginRefresh" value="' . htmlspecialchars($this->loginRefresh) . '" />' .
482 $this->interfaceSelector_hidden . $this->addFields_hidden;
483 return $output;
484 }
485
486 /**
487 * Creates JavaScript for the login form
488 *
489 * @return string JavaScript code
490 * @deprecated since TYPO3 CMS 7, will be removed in TYPO3 CMS 8
491 */
492 public function getJScode() {
493 GeneralUtility::logDeprecatedFunction();
494 }
495
496 /**
497 * Checks if login credentials are currently submitted
498 *
499 * @return bool
500 */
501 protected function isLoginInProgress() {
502 $username = GeneralUtility::_GP('username');
503 return !(empty($username) && empty($this->commandLI));
504 }
505
506 /**
507 * Get the ObjectManager
508 *
509 * @return \TYPO3\CMS\Extbase\Object\ObjectManager
510 */
511 protected function getObjectManager() {
512 return GeneralUtility::makeInstance(\TYPO3\CMS\Extbase\Object\ObjectManager::class);
513 }
514
515 /**
516 * returns a new standalone view, shorthand function
517 *
518 * @param string $templatePathAndFileName optional the path to set the template path and filename
519 *
520 * @return StandaloneView
521 */
522 protected function getFluidTemplateObject($templatePathAndFileName = NULL) {
523 $this->getLanguageService()->includeLLFile('EXT:backend/Resources/Private/Language/locallang.xlf');
524 $this->getLanguageService()->includeLLFile('EXT:lang/locallang_login.xlf');
525
526 $view = GeneralUtility::makeInstance(StandaloneView::class);
527 if ($templatePathAndFileName) {
528 $view->setTemplatePathAndFilename(GeneralUtility::getFileAbsFileName($templatePathAndFileName));
529 }
530 $view->getRequest()->setControllerExtensionName('backend');
531 return $view;
532 }
533
534 /**
535 * @return LanguageService
536 */
537 protected function getLanguageService() {
538 return $GLOBALS['LANG'];
539 }
540
541 /**
542 * @return BackendUserAuthentication
543 */
544 protected function getBackendUserAuthentication() {
545 return $GLOBALS['BE_USER'];
546 }
547
548 /**
549 * @return DatabaseConnection
550 */
551 protected function getDatabaseConntection() {
552 return $GLOBALS['TYPO3_DB'];
553 }
554
555 /**
556 * @return \TYPO3\CMS\Backend\Template\DocumentTemplate
557 */
558 protected function getDocumentTemplate() {
559 return $GLOBALS['TBE_TEMPLATE'];
560 }
561 }