TYPO3  7.6
AbstractUserAuthentication.php
Go to the documentation of this file.
1 <?php
2 namespace TYPO3\CMS\Core\Authentication;
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 
19 
30 {
35  public $global_database = '';
36 
41  public $session_table = '';
42 
47  public $name = '';
48 
53  public $get_name = '';
54 
59  public $user_table = '';
60 
65  public $usergroup_table = '';
66 
71  public $username_column = '';
72 
77  public $userident_column = '';
78 
83  public $userid_column = '';
84 
89  public $usergroup_column = '';
90 
95  public $lastLogin_column = '';
96 
101  public $enablecolumns = array(
102  'rootLevel' => '',
103  // Boolean: If TRUE, 'AND pid=0' will be a part of the query...
104  'disabled' => '',
105  'starttime' => '',
106  'endtime' => '',
107  'deleted' => ''
108  );
109 
113  public $showHiddenRecords = false;
114 
119  public $formfield_uname = '';
120 
125  public $formfield_uident = '';
126 
131  public $formfield_status = '';
132 
141 
149  public $lifetime = 0;
150 
157  public $gc_time = 0;
158 
163  public $gc_probability = 1;
164 
169  public $writeStdLog = false;
170 
175  public $writeAttemptLog = false;
176 
181  public $sendNoCacheHeaders = true;
182 
189  public $getFallBack = false;
190 
199  public $hash_length = 32;
200 
206  public $getMethodEnabled = false;
207 
213  public $lockIP = 4;
214 
221  public $lockHashKeyWords = 'useragent';
222 
226  public $warningEmail = '';
227 
232  public $warningPeriod = 3600;
233 
238  public $warningMax = 3;
239 
244  public $checkPid = true;
245 
250  public $checkPid_value = 0;
251 
257  public $id;
258 
263  public $loginFailure = false;
264 
269  public $loginSessionStarted = false;
270 
275  public $user = null;
276 
283  public $get_URL_ID = '';
284 
289  public $newSessionID = false;
290 
295  public $forceSetCookie = false;
296 
301  public $dontSetCookie = false;
302 
306  protected $cookieWasSetOnCurrentRequest = false;
307 
312  public $loginType = '';
313 
318  public $svConfig = array();
319 
324  public $writeDevLog = false;
325 
329  public $uc;
330 
334  protected $db = null;
335 
339  public function __construct()
340  {
341  $this->db = $this->getDatabaseConnection();
342  }
343 
356  public function start()
357  {
358  // Backend or frontend login - used for auth services
359  if (empty($this->loginType)) {
360  throw new \TYPO3\CMS\Core\Exception('No loginType defined, should be set explicitly by subclass');
361  }
362  // Enable dev logging if set
363  if ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_userauth.php']['writeDevLog']) {
364  $this->writeDevLog = true;
365  }
366  if ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_userauth.php']['writeDevLog' . $this->loginType]) {
367  $this->writeDevLog = true;
368  }
369  if (TYPO3_DLOG) {
370  $this->writeDevLog = true;
371  }
372  if ($this->writeDevLog) {
373  GeneralUtility::devLog('## Beginning of auth logging.', \TYPO3\CMS\Core\Authentication\AbstractUserAuthentication::class);
374  }
375  // Init vars.
376  $mode = '';
377  $this->newSessionID = false;
378  // $id is set to ses_id if cookie is present. Else set to FALSE, which will start a new session
379  $id = $this->getCookie($this->name);
380  $this->svConfig = $GLOBALS['TYPO3_CONF_VARS']['SVCONF']['auth'];
381 
382  // If fallback to get mode....
383  if (!$id && $this->getFallBack && $this->get_name) {
384  $id = isset($_GET[$this->get_name]) ? GeneralUtility::_GET($this->get_name) : '';
385  if (strlen($id) != $this->hash_length) {
386  $id = '';
387  }
388  $mode = 'get';
389  }
390 
391  // If new session or client tries to fix session...
392  if (!$id || !$this->isExistingSessionRecord($id)) {
393  // New random session-$id is made
394  $id = $this->createSessionId();
395  // New session
396  $this->newSessionID = true;
397  }
398  // Internal var 'id' is set
399  $this->id = $id;
400  // If fallback to get mode....
401  if ($mode == 'get' && $this->getFallBack && $this->get_name) {
402  $this->get_URL_ID = '&' . $this->get_name . '=' . $id;
403  }
404  // Set session hashKey lock keywords from configuration; currently only 'useragent' can be used.
405  $this->lockHashKeyWords = $GLOBALS['TYPO3_CONF_VARS'][$this->loginType]['lockHashKeyWords'];
406  // Make certain that NO user is set initially
407  $this->user = null;
408  // Set all possible headers that could ensure that the script is not cached on the client-side
409  if ($this->sendNoCacheHeaders && !(TYPO3_REQUESTTYPE & TYPO3_REQUESTTYPE_CLI)) {
410  header('Expires: 0');
411  header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . ' GMT');
412  $cacheControlHeader = 'no-cache, must-revalidate';
413  $pragmaHeader = 'no-cache';
414  // Prevent error message in IE when using a https connection
415  // see http://forge.typo3.org/issues/24125
416  $clientInfo = GeneralUtility::clientInfo();
417  if ($clientInfo['BROWSER'] === 'msie' && GeneralUtility::getIndpEnv('TYPO3_SSL')) {
418  // Some IEs can not handle no-cache
419  // see http://support.microsoft.com/kb/323308/en-us
420  $cacheControlHeader = 'must-revalidate';
421  // IE needs "Pragma: private" if SSL connection
422  $pragmaHeader = 'private';
423  }
424  header('Cache-Control: ' . $cacheControlHeader);
425  header('Pragma: ' . $pragmaHeader);
426  }
427  // Load user session, check to see if anyone has submitted login-information and if so authenticate
428  // the user with the session. $this->user[uid] may be used to write log...
429  $this->checkAuthentication();
430  // Setting cookies
431  if (!$this->dontSetCookie) {
432  $this->setSessionCookie();
433  }
434  // Hook for alternative ways of filling the $this->user array (is used by the "timtaw" extension)
435  if (is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_userauth.php']['postUserLookUp'])) {
436  foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_userauth.php']['postUserLookUp'] as $funcName) {
437  $_params = array(
438  'pObj' => &$this
439  );
440  GeneralUtility::callUserFunction($funcName, $_params, $this);
441  }
442  }
443  // Set $this->gc_time if not explicitly specified
444  if ($this->gc_time == 0) {
445  // Default to 1 day if $this->auth_timeout_field is 0
446  $this->gc_time = $this->auth_timeout_field == 0 ? 86400 : $this->auth_timeout_field;
447  }
448  // If we're lucky we'll get to clean up old sessions....
449  if (rand() % 100 <= $this->gc_probability) {
450  $this->gc();
451  }
452  }
453 
460  protected function setSessionCookie()
461  {
462  $isSetSessionCookie = $this->isSetSessionCookie();
463  $isRefreshTimeBasedCookie = $this->isRefreshTimeBasedCookie();
464  if ($isSetSessionCookie || $isRefreshTimeBasedCookie) {
465  $settings = $GLOBALS['TYPO3_CONF_VARS']['SYS'];
466  // Get the domain to be used for the cookie (if any):
467  $cookieDomain = $this->getCookieDomain();
468  // If no cookie domain is set, use the base path:
469  $cookiePath = $cookieDomain ? '/' : GeneralUtility::getIndpEnv('TYPO3_SITE_PATH');
470  // If the cookie lifetime is set, use it:
471  $cookieExpire = $isRefreshTimeBasedCookie ? $GLOBALS['EXEC_TIME'] + $this->lifetime : 0;
472  // Use the secure option when the current request is served by a secure connection:
473  $cookieSecure = (bool)$settings['cookieSecure'] && GeneralUtility::getIndpEnv('TYPO3_SSL');
474  // Deliver cookies only via HTTP and prevent possible XSS by JavaScript:
475  $cookieHttpOnly = (bool)$settings['cookieHttpOnly'];
476  // Do not set cookie if cookieSecure is set to "1" (force HTTPS) and no secure channel is used:
477  if ((int)$settings['cookieSecure'] !== 1 || GeneralUtility::getIndpEnv('TYPO3_SSL')) {
478  setcookie($this->name, $this->id, $cookieExpire, $cookiePath, $cookieDomain, $cookieSecure, $cookieHttpOnly);
479  $this->cookieWasSetOnCurrentRequest = true;
480  } else {
481  throw new \TYPO3\CMS\Core\Exception('Cookie was not set since HTTPS was forced in $TYPO3_CONF_VARS[SYS][cookieSecure].', 1254325546);
482  }
483  if ($this->writeDevLog) {
484  $devLogMessage = ($isRefreshTimeBasedCookie ? 'Updated Cookie: ' : 'Set Cookie: ') . $this->id;
485  GeneralUtility::devLog($devLogMessage . ($cookieDomain ? ', ' . $cookieDomain : ''), \TYPO3\CMS\Core\Authentication\AbstractUserAuthentication::class);
486  }
487  }
488  }
489 
496  protected function getCookieDomain()
497  {
498  $result = '';
499  $cookieDomain = $GLOBALS['TYPO3_CONF_VARS']['SYS']['cookieDomain'];
500  // If a specific cookie domain is defined for a given TYPO3_MODE,
501  // use that domain
502  if (!empty($GLOBALS['TYPO3_CONF_VARS'][$this->loginType]['cookieDomain'])) {
503  $cookieDomain = $GLOBALS['TYPO3_CONF_VARS'][$this->loginType]['cookieDomain'];
504  }
505  if ($cookieDomain) {
506  if ($cookieDomain[0] == '/') {
507  $match = array();
508  $matchCnt = @preg_match($cookieDomain, GeneralUtility::getIndpEnv('TYPO3_HOST_ONLY'), $match);
509  if ($matchCnt === false) {
510  GeneralUtility::sysLog('The regular expression for the cookie domain (' . $cookieDomain . ') contains errors. The session is not shared across sub-domains.', 'core', GeneralUtility::SYSLOG_SEVERITY_ERROR);
511  } elseif ($matchCnt) {
512  $result = $match[0];
513  }
514  } else {
515  $result = $cookieDomain;
516  }
517  }
518  return $result;
519  }
520 
527  protected function getCookie($cookieName)
528  {
529  return isset($_COOKIE[$cookieName]) ? stripslashes($_COOKIE[$cookieName]) : '';
530  }
531 
538  public function isSetSessionCookie()
539  {
540  return ($this->newSessionID || $this->forceSetCookie) && $this->lifetime == 0;
541  }
542 
549  public function isRefreshTimeBasedCookie()
550  {
551  return $this->lifetime > 0;
552  }
553 
561  public function checkAuthentication()
562  {
563  // No user for now - will be searched by service below
564  $tempuserArr = array();
565  $tempuser = false;
566  // User is not authenticated by default
567  $authenticated = false;
568  // User want to login with passed login data (name/password)
569  $activeLogin = false;
570  // Indicates if an active authentication failed (not auto login)
571  $this->loginFailure = false;
572  if ($this->writeDevLog) {
573  GeneralUtility::devLog('Login type: ' . $this->loginType, \TYPO3\CMS\Core\Authentication\AbstractUserAuthentication::class);
574  }
575  // The info array provide additional information for auth services
576  $authInfo = $this->getAuthInfoArray();
577  // Get Login/Logout data submitted by a form or params
578  $loginData = $this->getLoginFormData();
579  if ($this->writeDevLog) {
580  GeneralUtility::devLog('Login data: ' . GeneralUtility::arrayToLogString($loginData), \TYPO3\CMS\Core\Authentication\AbstractUserAuthentication::class);
581  }
582  // Active logout (eg. with "logout" button)
583  if ($loginData['status'] == 'logout') {
584  if ($this->writeStdLog) {
585  // $type,$action,$error,$details_nr,$details,$data,$tablename,$recuid,$recpid
586  $this->writelog(255, 2, 0, 2, 'User %s logged out', array($this->user['username']), '', 0, 0);
587  }
588  // Logout written to log
589  if ($this->writeDevLog) {
590  GeneralUtility::devLog('User logged out. Id: ' . $this->id, \TYPO3\CMS\Core\Authentication\AbstractUserAuthentication::class, -1);
591  }
592  $this->logoff();
593  }
594  // Active login (eg. with login form)
595  if ($loginData['status'] == 'login') {
596  $activeLogin = true;
597  if ($this->writeDevLog) {
598  GeneralUtility::devLog('Active login (eg. with login form)', \TYPO3\CMS\Core\Authentication\AbstractUserAuthentication::class);
599  }
600  // check referer for submitted login values
601  if ($this->formfield_status && $loginData['uident'] && $loginData['uname']) {
602  $httpHost = GeneralUtility::getIndpEnv('TYPO3_HOST_ONLY');
603  if (!$this->getMethodEnabled && ($httpHost != $authInfo['refInfo']['host'] && !$GLOBALS['TYPO3_CONF_VARS']['SYS']['doNotCheckReferer'])) {
604  throw new \RuntimeException('TYPO3 Fatal Error: Error: This host address ("' . $httpHost . '") and the referer host ("' . $authInfo['refInfo']['host'] . '") mismatches! ' .
605  'It is possible that the environment variable HTTP_REFERER is not passed to the script because of a proxy. ' .
606  'The site administrator can disable this check in the "All Configuration" section of the Install Tool (flag: TYPO3_CONF_VARS[SYS][doNotCheckReferer]).', 1270853930);
607  }
608  // Delete old user session if any
609  $this->logoff();
610  }
611  // Refuse login for _CLI users, if not processing a CLI request type
612  // (although we shouldn't be here in case of a CLI request type)
613  if (strtoupper(substr($loginData['uname'], 0, 5)) == '_CLI_' && !(TYPO3_REQUESTTYPE & TYPO3_REQUESTTYPE_CLI)) {
614  throw new \RuntimeException('TYPO3 Fatal Error: You have tried to login using a CLI user. Access prohibited!', 1270853931);
615  }
616  }
617  // The following code makes auto-login possible (if configured). No submitted data needed
618  // Determine whether we need to skip session update.
619  // This is used mainly for checking session timeout without
620  // refreshing the session itself while checking.
621  $skipSessionUpdate = (bool)GeneralUtility::_GP('skipSessionUpdate');
622  $haveSession = false;
623  if (!$this->newSessionID) {
624  // Read user session
625  $authInfo['userSession'] = $this->fetchUserSession($skipSessionUpdate);
626  $haveSession = is_array($authInfo['userSession']);
627  }
628  if ($this->writeDevLog) {
629  if ($haveSession) {
630  GeneralUtility::devLog('User session found: ' . GeneralUtility::arrayToLogString($authInfo['userSession'], array($this->userid_column, $this->username_column)), \TYPO3\CMS\Core\Authentication\AbstractUserAuthentication::class, 0);
631  } else {
632  GeneralUtility::devLog('No user session found.', \TYPO3\CMS\Core\Authentication\AbstractUserAuthentication::class, 2);
633  }
634  if (is_array($this->svConfig['setup'])) {
635  GeneralUtility::devLog('SV setup: ' . GeneralUtility::arrayToLogString($this->svConfig['setup']), \TYPO3\CMS\Core\Authentication\AbstractUserAuthentication::class, 0);
636  }
637  }
638  // Fetch user if ...
639  if (
640  $activeLogin || $this->svConfig['setup'][$this->loginType . '_alwaysFetchUser']
641  || !$haveSession && $this->svConfig['setup'][$this->loginType . '_fetchUserIfNoSession']
642  ) {
643  // Use 'auth' service to find the user
644  // First found user will be used
645  $serviceChain = '';
646  $subType = 'getUser' . $this->loginType;
647  while (is_object($serviceObj = GeneralUtility::makeInstanceService('auth', $subType, $serviceChain))) {
648  $serviceChain .= ',' . $serviceObj->getServiceKey();
649  $serviceObj->initAuth($subType, $loginData, $authInfo, $this);
650  if ($row = $serviceObj->getUser()) {
651  $tempuserArr[] = $row;
652  if ($this->writeDevLog) {
653  GeneralUtility::devLog('User found: ' . GeneralUtility::arrayToLogString($row, array($this->userid_column, $this->username_column)), \TYPO3\CMS\Core\Authentication\AbstractUserAuthentication::class, 0);
654  }
655  // User found, just stop to search for more if not configured to go on
656  if (!$this->svConfig['setup'][($this->loginType . '_fetchAllUsers')]) {
657  break;
658  }
659  }
660  unset($serviceObj);
661  }
662  unset($serviceObj);
663  if ($this->writeDevLog && $this->svConfig['setup'][$this->loginType . '_alwaysFetchUser']) {
664  GeneralUtility::devLog($this->loginType . '_alwaysFetchUser option is enabled', \TYPO3\CMS\Core\Authentication\AbstractUserAuthentication::class);
665  }
666  if ($this->writeDevLog && $serviceChain) {
667  GeneralUtility::devLog($subType . ' auth services called: ' . $serviceChain, \TYPO3\CMS\Core\Authentication\AbstractUserAuthentication::class);
668  }
669  if ($this->writeDevLog && empty($tempuserArr)) {
670  GeneralUtility::devLog('No user found by services', \TYPO3\CMS\Core\Authentication\AbstractUserAuthentication::class);
671  }
672  if ($this->writeDevLog && !empty($tempuserArr)) {
673  GeneralUtility::devLog(count($tempuserArr) . ' user records found by services', \TYPO3\CMS\Core\Authentication\AbstractUserAuthentication::class);
674  }
675  }
676  // If no new user was set we use the already found user session
677  if (empty($tempuserArr) && $haveSession) {
678  $tempuserArr[] = $authInfo['userSession'];
679  $tempuser = $authInfo['userSession'];
680  // User is authenticated because we found a user session
681  $authenticated = true;
682  if ($this->writeDevLog) {
683  GeneralUtility::devLog('User session used: ' . GeneralUtility::arrayToLogString($authInfo['userSession'], array($this->userid_column, $this->username_column)), \TYPO3\CMS\Core\Authentication\AbstractUserAuthentication::class);
684  }
685  }
686  // Re-auth user when 'auth'-service option is set
687  if ($this->svConfig['setup'][$this->loginType . '_alwaysAuthUser']) {
688  $authenticated = false;
689  if ($this->writeDevLog) {
690  GeneralUtility::devLog('alwaysAuthUser option is enabled', \TYPO3\CMS\Core\Authentication\AbstractUserAuthentication::class);
691  }
692  }
693  // Authenticate the user if needed
694  if (!empty($tempuserArr) && !$authenticated) {
695  foreach ($tempuserArr as $tempuser) {
696  // Use 'auth' service to authenticate the user
697  // If one service returns FALSE then authentication failed
698  // a service might return 100 which means there's no reason to stop but the user can't be authenticated by that service
699  if ($this->writeDevLog) {
700  GeneralUtility::devLog('Auth user: ' . GeneralUtility::arrayToLogString($tempuser), \TYPO3\CMS\Core\Authentication\AbstractUserAuthentication::class);
701  }
702  $serviceChain = '';
703  $subType = 'authUser' . $this->loginType;
704  while (is_object($serviceObj = GeneralUtility::makeInstanceService('auth', $subType, $serviceChain))) {
705  $serviceChain .= ',' . $serviceObj->getServiceKey();
706  $serviceObj->initAuth($subType, $loginData, $authInfo, $this);
707  if (($ret = $serviceObj->authUser($tempuser)) > 0) {
708  // If the service returns >=200 then no more checking is needed - useful for IP checking without password
709  if ((int)$ret >= 200) {
710  $authenticated = true;
711  break;
712  } elseif ((int)$ret >= 100) {
713  } else {
714  $authenticated = true;
715  }
716  } else {
717  $authenticated = false;
718  break;
719  }
720  unset($serviceObj);
721  }
722  unset($serviceObj);
723  if ($this->writeDevLog && $serviceChain) {
724  GeneralUtility::devLog($subType . ' auth services called: ' . $serviceChain, \TYPO3\CMS\Core\Authentication\AbstractUserAuthentication::class);
725  }
726  if ($authenticated) {
727  // Leave foreach() because a user is authenticated
728  break;
729  }
730  }
731  }
732  // If user is authenticated a valid user is in $tempuser
733  if ($authenticated) {
734  // Reset failure flag
735  $this->loginFailure = false;
736  // Insert session record if needed:
737  if (!($haveSession && ($tempuser['ses_id'] == $this->id || $tempuser['uid'] == $authInfo['userSession']['ses_userid']))) {
738  $sessionData = $this->createUserSession($tempuser);
739  if ($sessionData) {
740  $this->user = array_merge(
741  $tempuser,
742  $sessionData
743  );
744  }
745  // The login session is started.
746  $this->loginSessionStarted = true;
747  if ($this->writeDevLog && is_array($this->user)) {
748  GeneralUtility::devLog('User session finally read: ' . GeneralUtility::arrayToLogString($this->user, array($this->userid_column, $this->username_column)), \TYPO3\CMS\Core\Authentication\AbstractUserAuthentication::class, -1);
749  }
750  } elseif ($haveSession) {
751  $this->user = $authInfo['userSession'];
752  }
753  if ($activeLogin && !$this->newSessionID) {
754  $this->regenerateSessionId();
755  }
756  // User logged in - write that to the log!
757  if ($this->writeStdLog && $activeLogin) {
758  $this->writelog(255, 1, 0, 1, 'User %s logged in from %s (%s)', array($tempuser[$this->username_column], GeneralUtility::getIndpEnv('REMOTE_ADDR'), GeneralUtility::getIndpEnv('REMOTE_HOST')), '', '', '', -1, '', $tempuser['uid']);
759  }
760  if ($this->writeDevLog && $activeLogin) {
761  GeneralUtility::devLog('User ' . $tempuser[$this->username_column] . ' logged in from ' . GeneralUtility::getIndpEnv('REMOTE_ADDR') . ' (' . GeneralUtility::getIndpEnv('REMOTE_HOST') . ')', \TYPO3\CMS\Core\Authentication\AbstractUserAuthentication::class, -1);
762  }
763  if ($this->writeDevLog && !$activeLogin) {
764  GeneralUtility::devLog('User ' . $tempuser[$this->username_column] . ' authenticated from ' . GeneralUtility::getIndpEnv('REMOTE_ADDR') . ' (' . GeneralUtility::getIndpEnv('REMOTE_HOST') . ')', \TYPO3\CMS\Core\Authentication\AbstractUserAuthentication::class, -1);
765  }
766  } elseif ($activeLogin || !empty($tempuserArr)) {
767  $this->loginFailure = true;
768  if ($this->writeDevLog && empty($tempuserArr) && $activeLogin) {
769  GeneralUtility::devLog('Login failed: ' . GeneralUtility::arrayToLogString($loginData), \TYPO3\CMS\Core\Authentication\AbstractUserAuthentication::class, 2);
770  }
771  if ($this->writeDevLog && !empty($tempuserArr)) {
772  GeneralUtility::devLog('Login failed: ' . GeneralUtility::arrayToLogString($tempuser, array($this->userid_column, $this->username_column)), \TYPO3\CMS\Core\Authentication\AbstractUserAuthentication::class, 2);
773  }
774  }
775  // If there were a login failure, check to see if a warning email should be sent:
776  if ($this->loginFailure && $activeLogin) {
777  if ($this->writeDevLog) {
778  GeneralUtility::devLog('Call checkLogFailures: ' . GeneralUtility::arrayToLogString(array('warningEmail' => $this->warningEmail, 'warningPeriod' => $this->warningPeriod, 'warningMax' => $this->warningMax)), \TYPO3\CMS\Core\Authentication\AbstractUserAuthentication::class, -1);
779  }
780 
781  // Hook to implement login failure tracking methods
782  if (
783  !empty($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_userauth.php']['postLoginFailureProcessing'])
784  && is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_userauth.php']['postLoginFailureProcessing'])
785  ) {
786  $_params = array();
787  foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_userauth.php']['postLoginFailureProcessing'] as $_funcRef) {
788  GeneralUtility::callUserFunction($_funcRef, $_params, $this);
789  }
790  } else {
791  // If no hook is implemented, wait for 5 seconds
792  sleep(5);
793  }
794 
795  $this->checkLogFailures($this->warningEmail, $this->warningPeriod, $this->warningMax);
796  }
797  }
798 
804  public function createSessionId()
805  {
806  return GeneralUtility::getRandomHexString($this->hash_length);
807  }
808 
814  protected function regenerateSessionId()
815  {
816  $oldSessionId = $this->id;
817  $this->id = $this->createSessionId();
818  // Update session record with new ID
819  $this->db->exec_UPDATEquery(
820  $this->session_table,
821  'ses_id = ' . $this->db->fullQuoteStr($oldSessionId, $this->session_table)
822  . ' AND ses_name = ' . $this->db->fullQuoteStr($this->name, $this->session_table),
823  array('ses_id' => $this->id)
824  );
825  $this->newSessionID = true;
826  }
827 
828  /*************************
829  *
830  * User Sessions
831  *
832  *************************/
840  public function createUserSession($tempuser)
841  {
842  if ($this->writeDevLog) {
843  GeneralUtility::devLog('Create session ses_id = ' . $this->id, \TYPO3\CMS\Core\Authentication\AbstractUserAuthentication::class);
844  }
845  // Delete session entry first
846  $this->db->exec_DELETEquery(
847  $this->session_table,
848  'ses_id = ' . $this->db->fullQuoteStr($this->id, $this->session_table)
849  . ' AND ses_name = ' . $this->db->fullQuoteStr($this->name, $this->session_table)
850  );
851  // Re-create session entry
852  $insertFields = $this->getNewSessionRecord($tempuser);
853  $inserted = (bool)$this->db->exec_INSERTquery($this->session_table, $insertFields);
854  if (!$inserted) {
855  $message = 'Session data could not be written to DB. Error: ' . $this->db->sql_error();
856  GeneralUtility::sysLog($message, 'core', GeneralUtility::SYSLOG_SEVERITY_WARNING);
857  if ($this->writeDevLog) {
858  GeneralUtility::devLog($message, \TYPO3\CMS\Core\Authentication\AbstractUserAuthentication::class, 2);
859  }
860  }
861  // Updating lastLogin_column carrying information about last login.
862  if ($this->lastLogin_column && $inserted) {
863  $this->db->exec_UPDATEquery(
864  $this->user_table,
865  $this->userid_column . '=' . $this->db->fullQuoteStr($tempuser[$this->userid_column], $this->user_table),
866  array($this->lastLogin_column => $GLOBALS['EXEC_TIME'])
867  );
868  }
869 
870  return $inserted ? $insertFields : array();
871  }
872 
880  public function getNewSessionRecord($tempuser)
881  {
882  return array(
883  'ses_id' => $this->id,
884  'ses_name' => $this->name,
885  'ses_iplock' => $tempuser['disableIPlock'] ? '[DISABLED]' : $this->ipLockClause_remoteIPNumber($this->lockIP),
886  'ses_hashlock' => $this->hashLockClause_getHashInt(),
887  'ses_userid' => $tempuser[$this->userid_column],
888  'ses_tstamp' => $GLOBALS['EXEC_TIME'],
889  'ses_data' => ''
890  );
891  }
892 
899  public function fetchUserSession($skipSessionUpdate = false)
900  {
901  $user = false;
902  if ($this->writeDevLog) {
903  GeneralUtility::devLog('Fetch session ses_id = ' . $this->id, \TYPO3\CMS\Core\Authentication\AbstractUserAuthentication::class);
904  }
905 
906  // Fetch the user session from the DB
907  $statement = $this->fetchUserSessionFromDB();
908 
909  if ($statement) {
910  $statement->execute();
911  $user = $statement->fetch();
912  $statement->free();
913  }
914  if ($user) {
915  // A user was found
916  if (\TYPO3\CMS\Core\Utility\MathUtility::canBeInterpretedAsInteger($this->auth_timeout_field)) {
917  // Get timeout from object
918  $timeout = (int)$this->auth_timeout_field;
919  } else {
920  // Get timeout-time from usertable
921  $timeout = (int)$user[$this->auth_timeout_field];
922  }
923  // If timeout > 0 (TRUE) and current time has not exceeded the latest sessions-time plus the timeout in seconds then accept user
924  // Use a gracetime-value to avoid updating a session-record too often
925  if ($timeout > 0 && $GLOBALS['EXEC_TIME'] < $user['ses_tstamp'] + $timeout) {
926  $sessionUpdateGracePeriod = 61;
927  if (!$skipSessionUpdate && $GLOBALS['EXEC_TIME'] > ($user['ses_tstamp'] + $sessionUpdateGracePeriod)) {
928  $this->db->exec_UPDATEquery($this->session_table, 'ses_id=' . $this->db->fullQuoteStr($this->id, $this->session_table)
929  . ' AND ses_name=' . $this->db->fullQuoteStr($this->name, $this->session_table), array('ses_tstamp' => $GLOBALS['EXEC_TIME']));
930  // Make sure that the timestamp is also updated in the array
931  $user['ses_tstamp'] = $GLOBALS['EXEC_TIME'];
932  }
933  } else {
934  // Delete any user set...
935  $this->logoff();
936  $user = false;
937  }
938  }
939  return $user;
940  }
941 
949  public function logoff()
950  {
951  if ($this->writeDevLog) {
952  GeneralUtility::devLog('logoff: ses_id = ' . $this->id, \TYPO3\CMS\Core\Authentication\AbstractUserAuthentication::class);
953  }
954  // Release the locked records
955  \TYPO3\CMS\Backend\Utility\BackendUtility::lockRecords();
956  // Hook for pre-processing the logoff() method, requested and implemented by andreas.otto@dkd.de:
957  if (is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_userauth.php']['logoff_pre_processing'])) {
958  $_params = array();
959  foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_userauth.php']['logoff_pre_processing'] as $_funcRef) {
960  if ($_funcRef) {
961  GeneralUtility::callUserFunction($_funcRef, $_params, $this);
962  }
963  }
964  }
965  $this->db->exec_DELETEquery($this->session_table, 'ses_id = ' . $this->db->fullQuoteStr($this->id, $this->session_table) . '
966  AND ses_name = ' . $this->db->fullQuoteStr($this->name, $this->session_table));
967  $this->user = null;
968  // Hook for post-processing the logoff() method, requested and implemented by andreas.otto@dkd.de:
969  if (is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_userauth.php']['logoff_post_processing'])) {
970  $_params = array();
971  foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_userauth.php']['logoff_post_processing'] as $_funcRef) {
972  if ($_funcRef) {
973  GeneralUtility::callUserFunction($_funcRef, $_params, $this);
974  }
975  }
976  }
977  }
978 
985  public function removeCookie($cookieName)
986  {
987  $cookieDomain = $this->getCookieDomain();
988  // If no cookie domain is set, use the base path
989  $cookiePath = $cookieDomain ? '/' : GeneralUtility::getIndpEnv('TYPO3_SITE_PATH');
990  setcookie($cookieName, null, -1, $cookiePath, $cookieDomain);
991  }
992 
1000  public function isExistingSessionRecord($id)
1001  {
1002  $statement = $this->db->prepare_SELECTquery('COUNT(*)', $this->session_table, 'ses_id = :ses_id');
1003  $statement->execute(array(':ses_id' => $id));
1004  $row = $statement->fetch(\TYPO3\CMS\Core\Database\PreparedStatement::FETCH_NUM);
1005  $statement->free();
1006  return (bool)$row[0];
1007  }
1008 
1015  public function isCookieSet()
1016  {
1017  return $this->cookieWasSetOnCurrentRequest || $this->getCookie($this->name);
1018  }
1019 
1020  /*************************
1021  *
1022  * SQL Functions
1023  *
1024  *************************/
1035  protected function fetchUserSessionFromDB()
1036  {
1037  $statement = null;
1038  $ipLockClause = $this->ipLockClause();
1039  $statement = $this->db->prepare_SELECTquery('*', $this->session_table . ',' . $this->user_table, $this->session_table . '.ses_id = :ses_id
1040  AND ' . $this->session_table . '.ses_name = :ses_name
1041  AND ' . $this->session_table . '.ses_userid = ' . $this->user_table . '.' . $this->userid_column . '
1042  ' . $ipLockClause['where'] . '
1043  ' . $this->hashLockClause() . '
1044  ' . $this->user_where_clause());
1045  $statement->bindValues(array(
1046  ':ses_id' => $this->id,
1047  ':ses_name' => $this->name
1048  ));
1049  $statement->bindValues($ipLockClause['parameters']);
1050  return $statement;
1051  }
1052 
1060  protected function user_where_clause()
1061  {
1062  $whereClause = '';
1063  if ($this->enablecolumns['rootLevel']) {
1064  $whereClause .= 'AND ' . $this->user_table . '.pid=0 ';
1065  }
1066  if ($this->enablecolumns['disabled']) {
1067  $whereClause .= ' AND ' . $this->user_table . '.' . $this->enablecolumns['disabled'] . '=0';
1068  }
1069  if ($this->enablecolumns['deleted']) {
1070  $whereClause .= ' AND ' . $this->user_table . '.' . $this->enablecolumns['deleted'] . '=0';
1071  }
1072  if ($this->enablecolumns['starttime']) {
1073  $whereClause .= ' AND (' . $this->user_table . '.' . $this->enablecolumns['starttime'] . '<=' . $GLOBALS['EXEC_TIME'] . ')';
1074  }
1075  if ($this->enablecolumns['endtime']) {
1076  $whereClause .= ' AND (' . $this->user_table . '.' . $this->enablecolumns['endtime'] . '=0 OR '
1077  . $this->user_table . '.' . $this->enablecolumns['endtime'] . '>' . $GLOBALS['EXEC_TIME'] . ')';
1078  }
1079  return $whereClause;
1080  }
1081 
1088  protected function ipLockClause()
1089  {
1090  $statementClause = array(
1091  'where' => '',
1092  'parameters' => array()
1093  );
1094  if ($this->lockIP) {
1095  $statementClause['where'] = 'AND (
1096  ' . $this->session_table . '.ses_iplock = :ses_iplock
1097  OR ' . $this->session_table . '.ses_iplock=\'[DISABLED]\'
1098  )';
1099  $statementClause['parameters'] = array(
1100  ':ses_iplock' => $this->ipLockClause_remoteIPNumber($this->lockIP)
1101  );
1102  }
1103  return $statementClause;
1104  }
1105 
1114  protected function ipLockClause_remoteIPNumber($parts)
1115  {
1116  $IP = GeneralUtility::getIndpEnv('REMOTE_ADDR');
1117  if ($parts >= 4) {
1118  return $IP;
1119  } else {
1120  $parts = \TYPO3\CMS\Core\Utility\MathUtility::forceIntegerInRange($parts, 1, 3);
1121  $IPparts = explode('.', $IP);
1122  for ($a = 4; $a > $parts; $a--) {
1123  unset($IPparts[$a - 1]);
1124  }
1125  return implode('.', $IPparts);
1126  }
1127  }
1128 
1136  public function veriCode()
1137  {
1138  return substr(md5($this->id . $GLOBALS['TYPO3_CONF_VARS']['SYS']['encryptionKey']), 0, 10);
1139  }
1140 
1147  protected function hashLockClause()
1148  {
1149  return 'AND ' . $this->session_table . '.ses_hashlock=' . $this->hashLockClause_getHashInt();
1150  }
1151 
1158  protected function hashLockClause_getHashInt()
1159  {
1160  $hashStr = '';
1161  if (GeneralUtility::inList($this->lockHashKeyWords, 'useragent')) {
1162  $hashStr .= ':' . GeneralUtility::getIndpEnv('HTTP_USER_AGENT');
1163  }
1164  return GeneralUtility::md5int($hashStr);
1165  }
1166 
1167  /*************************
1168  *
1169  * Session and Configuration Handling
1170  *
1171  *************************/
1180  public function writeUC($variable = '')
1181  {
1182  if (is_array($this->user) && $this->user[$this->userid_column]) {
1183  if (!is_array($variable)) {
1184  $variable = $this->uc;
1185  }
1186  if ($this->writeDevLog) {
1187  GeneralUtility::devLog('writeUC: ' . $this->userid_column . '=' . (int)$this->user[$this->userid_column], \TYPO3\CMS\Core\Authentication\AbstractUserAuthentication::class);
1188  }
1189  $this->db->exec_UPDATEquery($this->user_table, $this->userid_column . '=' . (int)$this->user[$this->userid_column], array('uc' => serialize($variable)));
1190  }
1191  }
1192 
1200  public function unpack_uc($theUC = '')
1201  {
1202  if (!$theUC && isset($this->user['uc'])) {
1203  $theUC = unserialize($this->user['uc']);
1204  }
1205  if (is_array($theUC)) {
1206  $this->uc = $theUC;
1207  }
1208  }
1209 
1220  public function pushModuleData($module, $data, $noSave = 0)
1221  {
1222  $this->uc['moduleData'][$module] = $data;
1223  $this->uc['moduleSessionID'][$module] = $this->id;
1224  if (!$noSave) {
1225  $this->writeUC();
1226  }
1227  }
1228 
1236  public function getModuleData($module, $type = '')
1237  {
1238  if ($type != 'ses' || (isset($this->uc['moduleSessionID'][$module]) && $this->uc['moduleSessionID'][$module] == $this->id)) {
1239  return $this->uc['moduleData'][$module];
1240  }
1241  return null;
1242  }
1243 
1251  public function getSessionData($key)
1252  {
1253  $sesDat = unserialize($this->user['ses_data']);
1254  return $sesDat[$key];
1255  }
1256 
1265  public function setAndSaveSessionData($key, $data)
1266  {
1267  $sesDat = unserialize($this->user['ses_data']);
1268  $sesDat[$key] = $data;
1269  $this->user['ses_data'] = serialize($sesDat);
1270  if ($this->writeDevLog) {
1271  GeneralUtility::devLog('setAndSaveSessionData: ses_id = ' . $this->user['ses_id'], \TYPO3\CMS\Core\Authentication\AbstractUserAuthentication::class);
1272  }
1273  $this->db->exec_UPDATEquery($this->session_table, 'ses_id=' . $this->db->fullQuoteStr($this->user['ses_id'], $this->session_table), array('ses_data' => $this->user['ses_data']));
1274  }
1275 
1276  /*************************
1277  *
1278  * Misc
1279  *
1280  *************************/
1287  public function getLoginFormData()
1288  {
1289  $loginData = array();
1290  $loginData['status'] = GeneralUtility::_GP($this->formfield_status);
1291  if ($this->getMethodEnabled) {
1292  $loginData['uname'] = GeneralUtility::_GP($this->formfield_uname);
1293  $loginData['uident'] = GeneralUtility::_GP($this->formfield_uident);
1294  } else {
1295  $loginData['uname'] = GeneralUtility::_POST($this->formfield_uname);
1296  $loginData['uident'] = GeneralUtility::_POST($this->formfield_uident);
1297  }
1298  // Only process the login data if a login is requested
1299  if ($loginData['status'] === 'login') {
1300  $loginData = $this->processLoginData($loginData);
1301  }
1302  $loginData = array_map('trim', $loginData);
1303  return $loginData;
1304  }
1305 
1315  public function processLoginData($loginData, $passwordTransmissionStrategy = '')
1316  {
1317  $loginSecurityLevel = trim($GLOBALS['TYPO3_CONF_VARS'][$this->loginType]['loginSecurityLevel']) ?: 'normal';
1318  $passwordTransmissionStrategy = $passwordTransmissionStrategy ?: $loginSecurityLevel;
1319  if ($this->writeDevLog) {
1320  GeneralUtility::devLog('Login data before processing: ' . GeneralUtility::arrayToLogString($loginData), \TYPO3\CMS\Core\Authentication\AbstractUserAuthentication::class);
1321  }
1322  $serviceChain = '';
1323  $subType = 'processLoginData' . $this->loginType;
1324  $authInfo = $this->getAuthInfoArray();
1325  $isLoginDataProcessed = false;
1326  $processedLoginData = $loginData;
1327  while (is_object($serviceObject = GeneralUtility::makeInstanceService('auth', $subType, $serviceChain))) {
1328  $serviceChain .= ',' . $serviceObject->getServiceKey();
1329  $serviceObject->initAuth($subType, $loginData, $authInfo, $this);
1330  $serviceResult = $serviceObject->processLoginData($processedLoginData, $passwordTransmissionStrategy);
1331  if (!empty($serviceResult)) {
1332  $isLoginDataProcessed = true;
1333  // If the service returns >=200 then no more processing is needed
1334  if ((int)$serviceResult >= 200) {
1335  unset($serviceObject);
1336  break;
1337  }
1338  }
1339  unset($serviceObject);
1340  }
1341  if ($isLoginDataProcessed) {
1342  $loginData = $processedLoginData;
1343  if ($this->writeDevLog) {
1344  GeneralUtility::devLog('Processed login data: ' . GeneralUtility::arrayToLogString($processedLoginData), \TYPO3\CMS\Core\Authentication\AbstractUserAuthentication::class);
1345  }
1346  }
1347  return $loginData;
1348  }
1349 
1356  public function getAuthInfoArray()
1357  {
1358  $authInfo = array();
1359  $authInfo['loginType'] = $this->loginType;
1360  $authInfo['refInfo'] = parse_url(GeneralUtility::getIndpEnv('HTTP_REFERER'));
1361  $authInfo['HTTP_HOST'] = GeneralUtility::getIndpEnv('HTTP_HOST');
1362  $authInfo['REMOTE_ADDR'] = GeneralUtility::getIndpEnv('REMOTE_ADDR');
1363  $authInfo['REMOTE_HOST'] = GeneralUtility::getIndpEnv('REMOTE_HOST');
1364  $authInfo['showHiddenRecords'] = $this->showHiddenRecords;
1365  // Can be overidden in localconf by SVCONF:
1366  $authInfo['db_user']['table'] = $this->user_table;
1367  $authInfo['db_user']['userid_column'] = $this->userid_column;
1368  $authInfo['db_user']['username_column'] = $this->username_column;
1369  $authInfo['db_user']['userident_column'] = $this->userident_column;
1370  $authInfo['db_user']['usergroup_column'] = $this->usergroup_column;
1371  $authInfo['db_user']['enable_clause'] = $this->user_where_clause();
1372  if ($this->checkPid && $this->checkPid_value !== null) {
1373  $authInfo['db_user']['checkPidList'] = $this->checkPid_value;
1374  $authInfo['db_user']['check_pid_clause'] = ' AND pid IN (' .
1375  $this->db->cleanIntList($this->checkPid_value) . ')';
1376  } else {
1377  $authInfo['db_user']['checkPidList'] = '';
1378  $authInfo['db_user']['check_pid_clause'] = '';
1379  }
1380  $authInfo['db_groups']['table'] = $this->usergroup_table;
1381  return $authInfo;
1382  }
1383 
1392  public function compareUident($user, $loginData, $passwordCompareStrategy = '')
1393  {
1394  return (string)$loginData['uident_text'] === (string)$user[$this->userident_column];
1395  }
1396 
1403  public function gc()
1404  {
1405  $this->db->exec_DELETEquery($this->session_table, 'ses_tstamp < ' . (int)($GLOBALS['EXEC_TIME'] - $this->gc_time) . ' AND ses_name = ' . $this->db->fullQuoteStr($this->name, $this->session_table));
1406  }
1407 
1422  public function writelog($type, $action, $error, $details_nr, $details, $data, $tablename, $recuid, $recpid)
1423  {
1424  }
1425 
1435  public function checkLogFailures($email, $secondsBack, $maxFailures)
1436  {
1437  }
1438 
1452  public function setBeUserByUid($uid)
1453  {
1454  $this->user = $this->getRawUserByUid($uid);
1455  }
1456 
1465  public function setBeUserByName($name)
1466  {
1467  $this->user = $this->getRawUserByName($name);
1468  }
1469 
1477  public function getRawUserByUid($uid)
1478  {
1479  $user = false;
1480  $dbres = $this->db->exec_SELECTquery('*', $this->user_table, 'uid=' . (int)$uid . ' ' . $this->user_where_clause());
1481  if ($dbres) {
1482  $user = $this->db->sql_fetch_assoc($dbres);
1483  $this->db->sql_free_result($dbres);
1484  }
1485  return $user;
1486  }
1487 
1496  public function getRawUserByName($name)
1497  {
1498  $user = false;
1499  $dbres = $this->db->exec_SELECTquery('*', $this->user_table, 'username=' . $this->db->fullQuoteStr($name, $this->user_table) . ' ' . $this->user_where_clause());
1500  if ($dbres) {
1501  $user = $this->db->sql_fetch_assoc($dbres);
1502  $this->db->sql_free_result($dbres);
1503  }
1504  return $user;
1505  }
1506 
1507  /*************************
1508  *
1509  * Create/update user - EXPERIMENTAL
1510  *
1511  *************************/
1521  public function fetchUserRecord($dbUser, $username, $extraWhere = '')
1522  {
1523  $user = false;
1524  $usernameClause = $username ? $dbUser['username_column'] . '=' . $this->db->fullQuoteStr($username, $dbUser['table']) : '1=1';
1525  if ($username || $extraWhere) {
1526  // Look up the user by the username and/or extraWhere:
1527  $dbres = $this->db->exec_SELECTquery('*', $dbUser['table'], $usernameClause . $dbUser['check_pid_clause'] . $dbUser['enable_clause'] . $extraWhere);
1528  if ($dbres) {
1529  $user = $this->db->sql_fetch_assoc($dbres);
1530  $this->db->sql_free_result($dbres);
1531  }
1532  }
1533  return $user;
1534  }
1535 
1540  protected function getDatabaseConnection()
1541  {
1542  return $GLOBALS['TYPO3_DB'];
1543  }
1544 }