[TASK] Refactoring of backend 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', 'contrib/placeholdersjs/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 if ($GLOBALS['TBE_STYLES']['logo_login']) {
228 $logo = '<img src="' . htmlspecialchars(($GLOBALS['BACK_PATH'] . $GLOBALS['TBE_STYLES']['logo_login'])) . '" alt="" class="t3-login-logo" />';
229 } else {
230 $logo = '<img' . IconUtility::skinImg($GLOBALS['BACK_PATH'], 'gfx/typo3-transparent@2x.png', 'width="140" height="39"') . ' alt="" class="t3-login-logo t3-default-logo" />';
231 }
232
233 $formType = empty($this->getBackendUserAuthentication()->user['uid']) ? 'loginForm' : 'logoutForm';
234 $loginNewsTitle = $GLOBALS['TYPO3_CONF_VARS']['BE']['loginNewsTitle']
235 ? $GLOBALS['TYPO3_CONF_VARS']['BE']['loginNewsTitle']
236 : $this->getLanguageService()->getLL('newsheadline');
237
238 // Start form
239 $view->assignMultiple(array(
240 'formTag' => $this->startForm(),
241 'labelPrefixPath' => 'LLL:EXT:lang/locallang_login.xlf:',
242 'backendUser' => $this->getBackendUserAuthentication()->user,
243 'hasLoginError' => $this->isLoginInProgress(),
244 'presetUsername' => $this->u,
245 'presetPassword' => $this->p,
246 'presetOpenId' => $this->openIdUrl,
247 'formType' => $formType,
248 'logo' => $logo,
249 'isOpenIdLoaded' => ExtensionManagementUtility::isLoaded('openid'),
250 'copyright' => BackendUtility::TYPO3_copyRightNotice($GLOBALS['TYPO3_CONF_VARS']['SYS']['loginCopyrightShowVersion']),
251 'loginNewsTitle' => $loginNewsTitle,
252 'loginNewsItems' => $this->getSystemNews()
253 ));
254
255 // Initialize interface selectors:
256 $this->makeInterfaceSelectorBox();
257 $view->assignMultiple(array(
258 'interfaceSelector' => $this->interfaceSelector,
259 'interfaceSelectorJump' => $this->interfaceSelector_jump
260 ));
261
262 // Starting page:
263 $this->content .= $this->getDocumentTemplate()->startPage('TYPO3 CMS Login: ' .
264 $GLOBALS['TYPO3_CONF_VARS']['SYS']['sitename'], FALSE);
265 // Add Content:
266 $this->content .= $view->render();
267 $this->content .= $this->getDocumentTemplate()->endPage();
268 }
269
270 /**
271 * Outputting the accumulated content to screen
272 *
273 * @return void
274 */
275 public function printContent() {
276 echo $this->content;
277 }
278
279 /*****************************
280 *
281 * Various functions
282 *
283 ******************************/
284 /**
285 * Checking, if we should perform some sort of redirection OR closing of windows.
286 *
287 * @return void
288 */
289 public function checkRedirect() {
290 /*
291 * Do redirect:
292 *
293 * If a user is logged in AND
294 * a) if either the login is just done (isLoginInProgress) or
295 * b) a loginRefresh is done or c) the interface-selector is NOT enabled
296 * (If it is on the other hand, it should not just load an interface,
297 * because people has to choose then...)
298 */
299 if (!empty($this->getBackendUserAuthentication()->user['uid'])
300 && ($this->isLoginInProgress() || $this->loginRefresh || !$this->interfaceSelector)) {
301 /*
302 * If no cookie has been set previously we tell people that this is a problem.
303 * This assumes that a cookie-setting script (like this one) has been hit at
304 * least once prior to this instance.
305 */
306 if (!$_COOKIE[BackendUserAuthentication::getCookieName()]) {
307 if ($this->commandLI == 'setCookie') {
308 /*
309 * we tried it a second time but still no cookie
310 * 26/4 2005: This does not work anymore, because the saving of challenge values
311 * in $_SESSION means the system will act as if the password was wrong.
312 */
313 throw new \RuntimeException('Login-error: Yeah, that\'s a classic. No cookies, no TYPO3.<br /><br />' .
314 'Please accept cookies from TYPO3 - otherwise you\'ll not be able to use the system.', 1294586846);
315 } else {
316 // try it once again - that might be needed for auto login
317 $this->redirectToURL = 'index.php?commandLI=setCookie';
318 }
319 }
320 $redirectToUrl = (string)$this->getBackendUserAuthentication()->getTSConfigVal('auth.BE.redirectToURL');
321 if (!empty($redirectToUrl)) {
322 $this->redirectToURL = $redirectToUrl;
323 $this->GPinterface = '';
324 }
325 // store interface
326 $this->getBackendUserAuthentication()->uc['interfaceSetup'] = $this->GPinterface;
327 $this->getBackendUserAuthentication()->writeUC();
328 // Based on specific setting of interface we set the redirect script:
329 switch ($this->GPinterface) {
330 case 'backend':
331 $this->redirectToURL = 'backend.php';
332 break;
333 case 'frontend':
334 $this->redirectToURL = '../';
335 break;
336 }
337 /** @var $formProtection \TYPO3\CMS\Core\FormProtection\BackendFormProtection */
338 $formProtection = FormProtectionFactory::get();
339 // If there is a redirect URL AND if loginRefresh is not set...
340 if (!$this->loginRefresh) {
341 $formProtection->storeSessionTokenInRegistry();
342 HttpUtility::redirect($this->redirectToURL);
343 } else {
344 $formProtection->setSessionTokenFromRegistry();
345 $formProtection->persistSessionToken();
346 $this->getDocumentTemplate()->JScode .= $this->getDocumentTemplate()->wrapScriptTags('
347 if (parent.opener && parent.opener.TYPO3 && parent.opener.TYPO3.LoginRefresh) {
348 parent.opener.TYPO3.LoginRefresh.startTask();
349 parent.close();
350 }
351 ');
352 }
353 } elseif (empty($this->getBackendUserAuthentication()->user['uid']) && $this->isLoginInProgress()) {
354 // Wrong password, wait for 5 seconds
355 sleep(5);
356 }
357 }
358
359 /**
360 * Making interface selector:
361 *
362 * @return void
363 */
364 public function makeInterfaceSelectorBox() {
365 // Reset variables:
366 $this->interfaceSelector = '';
367 $this->interfaceSelector_hidden = '';
368 $this->interfaceSelector_jump = '';
369 // If interfaces are defined AND no input redirect URL in GET vars:
370 if ($GLOBALS['TYPO3_CONF_VARS']['BE']['interfaces'] && ($this->isLoginInProgress() || !$this->redirect_url)) {
371 $parts = GeneralUtility::trimExplode(',', $GLOBALS['TYPO3_CONF_VARS']['BE']['interfaces']);
372 // Only if more than one interface is defined will we show the selector:
373 if (count($parts) > 1) {
374 // Initialize:
375 $labels = array(
376 'backend' => $this->getLanguageService()->getLL('interface.backend'),
377 'frontend' => $this->getLanguageService()->getLL('interface.frontend')
378 );
379 $jumpScript = array(
380 'backend' => 'backend.php',
381 'frontend' => '../'
382 );
383 // Traverse the interface keys:
384 foreach ($parts as $valueStr) {
385 $this->interfaceSelector .= '
386 <option value="' . htmlspecialchars($valueStr) . '"' . (GeneralUtility::_GP('interface') == htmlspecialchars($valueStr) ? ' selected="selected"' : '') . '>' . htmlspecialchars($labels[$valueStr]) . '</option>';
387 $this->interfaceSelector_jump .= '
388 <option value="' . htmlspecialchars($jumpScript[$valueStr]) . '">' . htmlspecialchars($labels[$valueStr]) . '</option>';
389 }
390 $this->interfaceSelector = '
391 <select id="t3-interfaceselector" name="interface" class="form-control t3-interfaces input-lg" tabindex="3">' . $this->interfaceSelector . '
392 </select>';
393 $this->interfaceSelector_jump = '
394 <select id="t3-interfaceselector" name="interface" class="form-control t3-interfaces input-lg" tabindex="3" onchange="window.location.href=this.options[this.selectedIndex].value;">' . $this->interfaceSelector_jump . '
395 </select>';
396 } elseif (!$this->redirect_url) {
397 // If there is only ONE interface value set and no redirect_url is present:
398 $this->interfaceSelector_hidden = '<input type="hidden" name="interface" value="' . trim($GLOBALS['TYPO3_CONF_VARS']['BE']['interfaces']) . '" />';
399 }
400 }
401 }
402
403 /**
404 * Gets news from sys_news and converts them into a format suitable for
405 * showing them at the login screen.
406 *
407 * @return array An array of login news.
408 */
409 protected function getSystemNews() {
410 $systemNewsTable = 'sys_news';
411 $systemNews = array();
412 $systemNewsRecords = $this->getDatabaseConntection()->exec_SELECTgetRows('title, content, crdate', $systemNewsTable, '1=1' . BackendUtility::BEenableFields($systemNewsTable) . BackendUtility::deleteClause($systemNewsTable), '', 'crdate DESC');
413 foreach ($systemNewsRecords as $systemNewsRecord) {
414 $systemNews[] = array(
415 'date' => date($GLOBALS['TYPO3_CONF_VARS']['SYS']['ddmmyy'], $systemNewsRecord['crdate']),
416 'header' => $systemNewsRecord['title'],
417 'content' => $systemNewsRecord['content']
418 );
419 }
420 return $systemNews;
421 }
422
423 /**
424 * Returns the form tag
425 *
426 * @return string Opening form tag string
427 */
428 public function startForm() {
429 $output = '';
430 // The form defaults to 'no login'. This prevents plain
431 // text logins to the Backend. The 'sv' extension changes the form to
432 // use superchallenged method and rsaauth extension makes rsa authentication.
433 $form = '<form action="index.php" method="post" name="loginform" ' . 'onsubmit="alert(\'No authentication methods available. Please, contact your TYPO3 administrator.\');return false">';
434 // Call hooks. If they do not return anything, we fail to login
435 if (is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['typo3/index.php']['loginFormHook'])) {
436 foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['typo3/index.php']['loginFormHook'] as $function) {
437 $params = array();
438 $formCode = GeneralUtility::callUserFunction($function, $params, $this);
439 if ($formCode) {
440 $form = $formCode;
441 break;
442 }
443 }
444 }
445 $output .= $form . '<input type="hidden" name="login_status" value="login" />' .
446 '<input type="hidden" id="t3-field-userident" name="userident" value="" />' .
447 '<input type="hidden" name="redirect_url" value="' . htmlspecialchars($this->redirectToURL) . '" />' .
448 '<input type="hidden" name="loginRefresh" value="' . htmlspecialchars($this->loginRefresh) . '" />' .
449 $this->interfaceSelector_hidden . $this->addFields_hidden;
450 return $output;
451 }
452
453 /**
454 * Creates JavaScript for the login form
455 *
456 * @return string JavaScript code
457 * @deprecated since TYPO3 CMS 7, will be removed in TYPO3 CMS 8
458 */
459 public function getJScode() {
460 GeneralUtility::logDeprecatedFunction();
461 }
462
463 /**
464 * Checks if login credentials are currently submitted
465 *
466 * @return bool
467 */
468 protected function isLoginInProgress() {
469 $username = GeneralUtility::_GP('username');
470 return !(empty($username) && empty($this->commandLI));
471 }
472
473 /**
474 * Get the ObjectManager
475 *
476 * @return \TYPO3\CMS\Extbase\Object\ObjectManager
477 */
478 protected function getObjectManager() {
479 return GeneralUtility::makeInstance(\TYPO3\CMS\Extbase\Object\ObjectManager::class);
480 }
481
482 /**
483 * returns a new standalone view, shorthand function
484 *
485 * @param string $templatePathAndFileName optional the path to set the template path and filename
486 *
487 * @return StandaloneView
488 */
489 protected function getFluidTemplateObject($templatePathAndFileName = NULL) {
490 $this->getLanguageService()->includeLLFile('EXT:backend/Resources/Private/Language/locallang.xlf');
491 $this->getLanguageService()->includeLLFile('EXT:lang/locallang_login.xlf');
492
493 $view = GeneralUtility::makeInstance(StandaloneView::class);
494 if ($templatePathAndFileName) {
495 $view->setTemplatePathAndFilename(GeneralUtility::getFileAbsFileName($templatePathAndFileName));
496 }
497 $view->getRequest()->setControllerExtensionName('backend');
498 return $view;
499 }
500
501 /**
502 * @return LanguageService
503 */
504 protected function getLanguageService() {
505 return $GLOBALS['LANG'];
506 }
507
508 /**
509 * @return BackendUserAuthentication
510 */
511 protected function getBackendUserAuthentication() {
512 return $GLOBALS['BE_USER'];
513 }
514
515 /**
516 * @return DatabaseConnection
517 */
518 protected function getDatabaseConntection() {
519 return $GLOBALS['TYPO3_DB'];
520 }
521
522 /**
523 * @return \TYPO3\CMS\Backend\Template\DocumentTemplate
524 */
525 protected function getDocumentTemplate() {
526 return $GLOBALS['TBE_TEMPLATE'];
527 }
528 }