TYPO3CMS  8
 All Classes Namespaces Files Functions Variables Pages
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 
32 
43 {
48  public $session_table = '';
49 
54  public $name = '';
55 
60  public $get_name = '';
61 
66  public $user_table = '';
67 
72  public $usergroup_table = '';
73 
78  public $username_column = '';
79 
84  public $userident_column = '';
85 
90  public $userid_column = '';
91 
96  public $usergroup_column = '';
97 
102  public $lastLogin_column = '';
103 
108  public $enablecolumns = [
109  'rootLevel' => '',
110  // Boolean: If TRUE, 'AND pid=0' will be a part of the query...
111  'disabled' => '',
112  'starttime' => '',
113  'endtime' => '',
114  'deleted' => ''
115  ];
116 
120  public $showHiddenRecords = false;
121 
126  public $formfield_uname = '';
127 
132  public $formfield_uident = '';
133 
138  public $formfield_status = '';
139 
148  public $sessionTimeout = 0;
149 
155  public $auth_timeout_field = '';
156 
166  public $lifetime = 0;
167 
174  public $gc_time = 0;
175 
180  public $gc_probability = 1;
181 
186  public $writeStdLog = false;
187 
192  public $writeAttemptLog = false;
193 
198  public $sendNoCacheHeaders = true;
199 
206  public $getFallBack = false;
207 
216  public $hash_length = 32;
217 
223  public $getMethodEnabled = false;
224 
230  public $lockIP = 4;
231 
238  public $lockHashKeyWords = 'useragent';
239 
243  public $warningEmail = '';
244 
249  public $warningPeriod = 3600;
250 
255  public $warningMax = 3;
256 
261  public $checkPid = true;
262 
267  public $checkPid_value = 0;
268 
274  public $id;
275 
280  public $loginFailure = false;
281 
286  public $loginSessionStarted = false;
287 
292  public $user = null;
293 
300  public $get_URL_ID = '';
301 
306  public $newSessionID = false;
307 
312  public $forceSetCookie = false;
313 
318  public $dontSetCookie = false;
319 
323  protected $cookieWasSetOnCurrentRequest = false;
324 
329  public $loginType = '';
330 
335  public $svConfig = [];
336 
341  public $writeDevLog = false;
342 
346  public $uc;
347 
351  public function __construct()
352  {
353  // This function has to stay even if it's empty
354  // Implementations of that abstract class might call parent::__construct();
355  }
356 
369  public function start()
370  {
371  // Backend or frontend login - used for auth services
372  if (empty($this->loginType)) {
373  throw new Exception('No loginType defined, should be set explicitly by subclass', 1476045345);
374  }
375  // Enable dev logging if set
376  if ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_userauth.php']['writeDevLog']) {
377  $this->writeDevLog = true;
378  }
379  if ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_userauth.php']['writeDevLog' . $this->loginType]) {
380  $this->writeDevLog = true;
381  }
382  if (TYPO3_DLOG) {
383  $this->writeDevLog = true;
384  }
385  if ($this->writeDevLog) {
386  GeneralUtility::devLog('## Beginning of auth logging.', self::class);
387  }
388  // Init vars.
389  $mode = '';
390  $this->newSessionID = false;
391  // $id is set to ses_id if cookie is present. Else set to FALSE, which will start a new session
392  $id = $this->getCookie($this->name);
393  $this->svConfig = $GLOBALS['TYPO3_CONF_VARS']['SVCONF']['auth'];
394 
395  // If fallback to get mode....
396  if (!$id && $this->getFallBack && $this->get_name) {
397  $id = isset($_GET[$this->get_name]) ? GeneralUtility::_GET($this->get_name) : '';
398  if (strlen($id) != $this->hash_length) {
399  $id = '';
400  }
401  $mode = 'get';
402  }
403 
404  // If new session or client tries to fix session...
405  if (!$id || !$this->isExistingSessionRecord($id)) {
406  // New random session-$id is made
407  $id = $this->createSessionId();
408  // New session
409  $this->newSessionID = true;
410  }
411  // Internal var 'id' is set
412  $this->id = $id;
413  // If fallback to get mode....
414  if ($mode == 'get' && $this->getFallBack && $this->get_name) {
415  $this->get_URL_ID = '&' . $this->get_name . '=' . $id;
416  }
417  // Set session hashKey lock keywords from configuration; currently only 'useragent' can be used.
418  $this->lockHashKeyWords = $GLOBALS['TYPO3_CONF_VARS'][$this->loginType]['lockHashKeyWords'];
419  // Make certain that NO user is set initially
420  $this->user = null;
421  // Set all possible headers that could ensure that the script is not cached on the client-side
422  if ($this->sendNoCacheHeaders && !(TYPO3_REQUESTTYPE & TYPO3_REQUESTTYPE_CLI)) {
423  header('Expires: 0');
424  header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . ' GMT');
425  $cacheControlHeader = 'no-cache, must-revalidate';
426  $pragmaHeader = 'no-cache';
427  // Prevent error message in IE when using a https connection
428  // see http://forge.typo3.org/issues/24125
429  $clientInfo = GeneralUtility::clientInfo();
430  if ($clientInfo['BROWSER'] === 'msie' && GeneralUtility::getIndpEnv('TYPO3_SSL')) {
431  // Some IEs can not handle no-cache
432  // see http://support.microsoft.com/kb/323308/en-us
433  $cacheControlHeader = 'must-revalidate';
434  // IE needs "Pragma: private" if SSL connection
435  $pragmaHeader = 'private';
436  }
437  header('Cache-Control: ' . $cacheControlHeader);
438  header('Pragma: ' . $pragmaHeader);
439  }
440  // Load user session, check to see if anyone has submitted login-information and if so authenticate
441  // the user with the session. $this->user[uid] may be used to write log...
442  $this->checkAuthentication();
443  // Setting cookies
444  if (!$this->dontSetCookie) {
445  $this->setSessionCookie();
446  }
447  // Hook for alternative ways of filling the $this->user array (is used by the "timtaw" extension)
448  if (is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_userauth.php']['postUserLookUp'])) {
449  foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_userauth.php']['postUserLookUp'] as $funcName) {
450  $_params = [
451  'pObj' => &$this
452  ];
453  GeneralUtility::callUserFunction($funcName, $_params, $this);
454  }
455  }
456  // Set $this->gc_time if not explicitly specified
457  if ($this->gc_time === 0) {
458  // Default to 86400 seconds (1 day) if $this->sessionTimeout is 0
459  $this->gc_time = $this->sessionTimeout === 0 ? 86400 : $this->sessionTimeout;
460  }
461  // If we're lucky we'll get to clean up old sessions
462  if (rand() % 100 <= $this->gc_probability) {
463  $this->gc();
464  }
465  }
466 
473  protected function setSessionCookie()
474  {
475  $isSetSessionCookie = $this->isSetSessionCookie();
476  $isRefreshTimeBasedCookie = $this->isRefreshTimeBasedCookie();
477  if ($isSetSessionCookie || $isRefreshTimeBasedCookie) {
478  $settings = $GLOBALS['TYPO3_CONF_VARS']['SYS'];
479  // Get the domain to be used for the cookie (if any):
480  $cookieDomain = $this->getCookieDomain();
481  // If no cookie domain is set, use the base path:
482  $cookiePath = $cookieDomain ? '/' : GeneralUtility::getIndpEnv('TYPO3_SITE_PATH');
483  // If the cookie lifetime is set, use it:
484  $cookieExpire = $isRefreshTimeBasedCookie ? $GLOBALS['EXEC_TIME'] + $this->lifetime : 0;
485  // Use the secure option when the current request is served by a secure connection:
486  $cookieSecure = (bool)$settings['cookieSecure'] && GeneralUtility::getIndpEnv('TYPO3_SSL');
487  // Deliver cookies only via HTTP and prevent possible XSS by JavaScript:
488  $cookieHttpOnly = (bool)$settings['cookieHttpOnly'];
489  // Do not set cookie if cookieSecure is set to "1" (force HTTPS) and no secure channel is used:
490  if ((int)$settings['cookieSecure'] !== 1 || GeneralUtility::getIndpEnv('TYPO3_SSL')) {
491  setcookie($this->name, $this->id, $cookieExpire, $cookiePath, $cookieDomain, $cookieSecure, $cookieHttpOnly);
492  $this->cookieWasSetOnCurrentRequest = true;
493  } else {
494  throw new Exception('Cookie was not set since HTTPS was forced in $TYPO3_CONF_VARS[SYS][cookieSecure].', 1254325546);
495  }
496  if ($this->writeDevLog) {
497  $devLogMessage = ($isRefreshTimeBasedCookie ? 'Updated Cookie: ' : 'Set Cookie: ') . $this->id;
498  GeneralUtility::devLog($devLogMessage . ($cookieDomain ? ', ' . $cookieDomain : ''), self::class);
499  }
500  }
501  }
502 
509  protected function getCookieDomain()
510  {
511  $result = '';
512  $cookieDomain = $GLOBALS['TYPO3_CONF_VARS']['SYS']['cookieDomain'];
513  // If a specific cookie domain is defined for a given TYPO3_MODE,
514  // use that domain
515  if (!empty($GLOBALS['TYPO3_CONF_VARS'][$this->loginType]['cookieDomain'])) {
516  $cookieDomain = $GLOBALS['TYPO3_CONF_VARS'][$this->loginType]['cookieDomain'];
517  }
518  if ($cookieDomain) {
519  if ($cookieDomain[0] == '/') {
520  $match = [];
521  $matchCnt = @preg_match($cookieDomain, GeneralUtility::getIndpEnv('TYPO3_HOST_ONLY'), $match);
522  if ($matchCnt === false) {
523  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);
524  } elseif ($matchCnt) {
525  $result = $match[0];
526  }
527  } else {
528  $result = $cookieDomain;
529  }
530  }
531  return $result;
532  }
533 
540  protected function getCookie($cookieName)
541  {
542  return isset($_COOKIE[$cookieName]) ? stripslashes($_COOKIE[$cookieName]) : '';
543  }
544 
551  public function isSetSessionCookie()
552  {
553  return ($this->newSessionID || $this->forceSetCookie) && $this->lifetime == 0;
554  }
555 
562  public function isRefreshTimeBasedCookie()
563  {
564  return $this->lifetime > 0;
565  }
566 
574  public function checkAuthentication()
575  {
576  // No user for now - will be searched by service below
577  $tempuserArr = [];
578  $tempuser = false;
579  // User is not authenticated by default
580  $authenticated = false;
581  // User want to login with passed login data (name/password)
582  $activeLogin = false;
583  // Indicates if an active authentication failed (not auto login)
584  $this->loginFailure = false;
585  if ($this->writeDevLog) {
586  GeneralUtility::devLog('Login type: ' . $this->loginType, self::class);
587  }
588  // The info array provide additional information for auth services
589  $authInfo = $this->getAuthInfoArray();
590  // Get Login/Logout data submitted by a form or params
591  $loginData = $this->getLoginFormData();
592  if ($this->writeDevLog) {
593  GeneralUtility::devLog('Login data: ' . GeneralUtility::arrayToLogString($loginData), self::class);
594  }
595  // Active logout (eg. with "logout" button)
596  if ($loginData['status'] === 'logout') {
597  if ($this->writeStdLog) {
598  // $type,$action,$error,$details_nr,$details,$data,$tablename,$recuid,$recpid
599  $this->writelog(255, 2, 0, 2, 'User %s logged out', [$this->user['username']], '', 0, 0);
600  }
601  // Logout written to log
602  if ($this->writeDevLog) {
603  GeneralUtility::devLog('User logged out. Id: ' . $this->id, self::class, -1);
604  }
605  $this->logoff();
606  }
607  // Determine whether we need to skip session update.
608  // This is used mainly for checking session timeout in advance without refreshing the current session's timeout.
609  $skipSessionUpdate = (bool)GeneralUtility::_GP('skipSessionUpdate');
610  $haveSession = false;
611  if (!$this->newSessionID) {
612  // Read user session
613  $authInfo['userSession'] = $this->fetchUserSession($skipSessionUpdate);
614  $haveSession = is_array($authInfo['userSession']);
615  }
616  // Active login (eg. with login form)
617  if (!$haveSession && $loginData['status'] === 'login') {
618  $activeLogin = true;
619  if ($this->writeDevLog) {
620  GeneralUtility::devLog('Active login (eg. with login form)', self::class);
621  }
622  // check referer for submitted login values
623  if ($this->formfield_status && $loginData['uident'] && $loginData['uname']) {
624  $httpHost = GeneralUtility::getIndpEnv('TYPO3_HOST_ONLY');
625  if (!$this->getMethodEnabled && ($httpHost != $authInfo['refInfo']['host'] && !$GLOBALS['TYPO3_CONF_VARS']['SYS']['doNotCheckReferer'])) {
626  throw new \RuntimeException('TYPO3 Fatal Error: Error: This host address ("' . $httpHost . '") and the referer host ("' . $authInfo['refInfo']['host'] . '") mismatches! ' .
627  'It is possible that the environment variable HTTP_REFERER is not passed to the script because of a proxy. ' .
628  'The site administrator can disable this check in the "All Configuration" section of the Install Tool (flag: TYPO3_CONF_VARS[SYS][doNotCheckReferer]).', 1270853930);
629  }
630  // Delete old user session if any
631  $this->logoff();
632  }
633  // Refuse login for _CLI users, if not processing a CLI request type
634  // (although we shouldn't be here in case of a CLI request type)
635  if (strtoupper(substr($loginData['uname'], 0, 5)) == '_CLI_' && !(TYPO3_REQUESTTYPE & TYPO3_REQUESTTYPE_CLI)) {
636  throw new \RuntimeException('TYPO3 Fatal Error: You have tried to login using a CLI user. Access prohibited!', 1270853931);
637  }
638  }
639  if ($this->writeDevLog) {
640  if ($haveSession) {
641  GeneralUtility::devLog('User session found: ' . GeneralUtility::arrayToLogString($authInfo['userSession'], [$this->userid_column, $this->username_column]), self::class, 0);
642  } else {
643  GeneralUtility::devLog('No user session found.', self::class, 2);
644  }
645  if (is_array($this->svConfig['setup'])) {
646  GeneralUtility::devLog('SV setup: ' . GeneralUtility::arrayToLogString($this->svConfig['setup']), self::class, 0);
647  }
648  }
649  // Fetch user if ...
650  if (
651  $activeLogin || $this->svConfig['setup'][$this->loginType . '_alwaysFetchUser']
652  || !$haveSession && $this->svConfig['setup'][$this->loginType . '_fetchUserIfNoSession']
653  ) {
654  // Use 'auth' service to find the user
655  // First found user will be used
656  $serviceChain = '';
657  $subType = 'getUser' . $this->loginType;
658  while (is_object($serviceObj = GeneralUtility::makeInstanceService('auth', $subType, $serviceChain))) {
659  $serviceChain .= ',' . $serviceObj->getServiceKey();
660  $serviceObj->initAuth($subType, $loginData, $authInfo, $this);
661  if ($row = $serviceObj->getUser()) {
662  $tempuserArr[] = $row;
663  if ($this->writeDevLog) {
664  GeneralUtility::devLog('User found: ' . GeneralUtility::arrayToLogString($row, [$this->userid_column, $this->username_column]), self::class, 0);
665  }
666  // User found, just stop to search for more if not configured to go on
667  if (!$this->svConfig['setup'][$this->loginType . '_fetchAllUsers']) {
668  break;
669  }
670  }
671  unset($serviceObj);
672  }
673  unset($serviceObj);
674  if ($this->writeDevLog && $this->svConfig['setup'][$this->loginType . '_alwaysFetchUser']) {
675  GeneralUtility::devLog($this->loginType . '_alwaysFetchUser option is enabled', self::class);
676  }
677  if ($this->writeDevLog && $serviceChain) {
678  GeneralUtility::devLog($subType . ' auth services called: ' . $serviceChain, self::class);
679  }
680  if ($this->writeDevLog && empty($tempuserArr)) {
681  GeneralUtility::devLog('No user found by services', self::class);
682  }
683  if ($this->writeDevLog && !empty($tempuserArr)) {
684  GeneralUtility::devLog(count($tempuserArr) . ' user records found by services', self::class);
685  }
686  }
687  // If no new user was set we use the already found user session
688  if (empty($tempuserArr) && $haveSession) {
689  $tempuserArr[] = $authInfo['userSession'];
690  $tempuser = $authInfo['userSession'];
691  // User is authenticated because we found a user session
692  $authenticated = true;
693  if ($this->writeDevLog) {
694  GeneralUtility::devLog('User session used: ' . GeneralUtility::arrayToLogString($authInfo['userSession'], [$this->userid_column, $this->username_column]), self::class);
695  }
696  }
697  // Re-auth user when 'auth'-service option is set
698  if ($this->svConfig['setup'][$this->loginType . '_alwaysAuthUser']) {
699  $authenticated = false;
700  if ($this->writeDevLog) {
701  GeneralUtility::devLog('alwaysAuthUser option is enabled', self::class);
702  }
703  }
704  // Authenticate the user if needed
705  if (!empty($tempuserArr) && !$authenticated) {
706  foreach ($tempuserArr as $tempuser) {
707  // Use 'auth' service to authenticate the user
708  // If one service returns FALSE then authentication failed
709  // a service might return 100 which means there's no reason to stop but the user can't be authenticated by that service
710  if ($this->writeDevLog) {
711  GeneralUtility::devLog('Auth user: ' . GeneralUtility::arrayToLogString($tempuser), self::class);
712  }
713  $serviceChain = '';
714  $subType = 'authUser' . $this->loginType;
715  while (is_object($serviceObj = GeneralUtility::makeInstanceService('auth', $subType, $serviceChain))) {
716  $serviceChain .= ',' . $serviceObj->getServiceKey();
717  $serviceObj->initAuth($subType, $loginData, $authInfo, $this);
718  if (($ret = $serviceObj->authUser($tempuser)) > 0) {
719  // If the service returns >=200 then no more checking is needed - useful for IP checking without password
720  if ((int)$ret >= 200) {
721  $authenticated = true;
722  break;
723  } elseif ((int)$ret >= 100) {
724  } else {
725  $authenticated = true;
726  }
727  } else {
728  $authenticated = false;
729  break;
730  }
731  unset($serviceObj);
732  }
733  unset($serviceObj);
734  if ($this->writeDevLog && $serviceChain) {
735  GeneralUtility::devLog($subType . ' auth services called: ' . $serviceChain, self::class);
736  }
737  if ($authenticated) {
738  // Leave foreach() because a user is authenticated
739  break;
740  }
741  }
742  }
743  // If user is authenticated a valid user is in $tempuser
744  if ($authenticated) {
745  // Reset failure flag
746  $this->loginFailure = false;
747  // Insert session record if needed:
748  if (!($haveSession && ($tempuser['ses_id'] == $this->id || $tempuser['uid'] == $authInfo['userSession']['ses_userid']))) {
749  $sessionData = $this->createUserSession($tempuser);
750  if ($sessionData) {
751  $this->user = array_merge(
752  $tempuser,
753  $sessionData
754  );
755  }
756  // The login session is started.
757  $this->loginSessionStarted = true;
758  if ($this->writeDevLog && is_array($this->user)) {
759  GeneralUtility::devLog('User session finally read: ' . GeneralUtility::arrayToLogString($this->user, [$this->userid_column, $this->username_column]), self::class, -1);
760  }
761  } elseif ($haveSession) {
762  $this->user = $authInfo['userSession'];
763  }
764  if ($activeLogin && !$this->newSessionID) {
765  $this->regenerateSessionId();
766  }
767  // User logged in - write that to the log!
768  if ($this->writeStdLog && $activeLogin) {
769  $this->writelog(255, 1, 0, 1, 'User %s logged in from %s (%s)', [$tempuser[$this->username_column], GeneralUtility::getIndpEnv('REMOTE_ADDR'), GeneralUtility::getIndpEnv('REMOTE_HOST')], '', '', '', -1, '', $tempuser['uid']);
770  }
771  if ($this->writeDevLog && $activeLogin) {
772  GeneralUtility::devLog('User ' . $tempuser[$this->username_column] . ' logged in from ' . GeneralUtility::getIndpEnv('REMOTE_ADDR') . ' (' . GeneralUtility::getIndpEnv('REMOTE_HOST') . ')', self::class, -1);
773  }
774  if ($this->writeDevLog && !$activeLogin) {
775  GeneralUtility::devLog('User ' . $tempuser[$this->username_column] . ' authenticated from ' . GeneralUtility::getIndpEnv('REMOTE_ADDR') . ' (' . GeneralUtility::getIndpEnv('REMOTE_HOST') . ')', self::class, -1);
776  }
777  } elseif ($activeLogin || !empty($tempuserArr)) {
778  $this->loginFailure = true;
779  if ($this->writeDevLog && empty($tempuserArr) && $activeLogin) {
780  GeneralUtility::devLog('Login failed: ' . GeneralUtility::arrayToLogString($loginData), self::class, 2);
781  }
782  if ($this->writeDevLog && !empty($tempuserArr)) {
783  GeneralUtility::devLog('Login failed: ' . GeneralUtility::arrayToLogString($tempuser, [$this->userid_column, $this->username_column]), self::class, 2);
784  }
785  }
786  // If there were a login failure, check to see if a warning email should be sent:
787  if ($this->loginFailure && $activeLogin) {
788  if ($this->writeDevLog) {
789  GeneralUtility::devLog('Call checkLogFailures: ' . GeneralUtility::arrayToLogString(['warningEmail' => $this->warningEmail, 'warningPeriod' => $this->warningPeriod, 'warningMax' => $this->warningMax]), self::class, -1);
790  }
791 
792  // Hook to implement login failure tracking methods
793  if (
794  !empty($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_userauth.php']['postLoginFailureProcessing'])
795  && is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_userauth.php']['postLoginFailureProcessing'])
796  ) {
797  $_params = [];
798  foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_userauth.php']['postLoginFailureProcessing'] as $_funcRef) {
799  GeneralUtility::callUserFunction($_funcRef, $_params, $this);
800  }
801  } else {
802  // If no hook is implemented, wait for 5 seconds
803  sleep(5);
804  }
805 
806  $this->checkLogFailures($this->warningEmail, $this->warningPeriod, $this->warningMax);
807  }
808  }
809 
815  public function createSessionId()
816  {
817  return GeneralUtility::makeInstance(Random::class)->generateRandomHexString($this->hash_length);
818  }
819 
825  protected function regenerateSessionId()
826  {
827  $oldSessionId = $this->id;
828  $this->id = $this->createSessionId();
829  // Update session record with new ID
830  GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable($this->session_table)->update(
831  $this->session_table,
832  ['ses_id' => $this->id],
833  ['ses_id' => $oldSessionId, 'ses_name' => $this->name]
834  );
835  $this->user['ses_id'] = $this->id;
836  $this->newSessionID = true;
837  }
838 
839  /*************************
840  *
841  * User Sessions
842  *
843  *************************/
851  public function createUserSession($tempuser)
852  {
853  if ($this->writeDevLog) {
854  GeneralUtility::devLog('Create session ses_id = ' . $this->id, self::class);
855  }
856  // Delete session entry first
857  $connection = GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable($this->session_table);
858  $connection->delete(
859  $this->session_table,
860  ['ses_id' => $this->id, 'ses_name' => $this->name]
861  );
862 
863  // Re-create session entry
864  $insertFields = $this->getNewSessionRecord($tempuser);
865  $inserted = (bool)$connection->insert($this->session_table, $insertFields);
866  if (!$inserted) {
867  $message = 'Session data could not be written to DB. Error: ' . $connection->errorInfo();
868  GeneralUtility::sysLog($message, 'core', GeneralUtility::SYSLOG_SEVERITY_WARNING);
869  if ($this->writeDevLog) {
870  GeneralUtility::devLog($message, self::class, 2);
871  }
872  }
873  // Updating lastLogin_column carrying information about last login.
874  if ($this->lastLogin_column && $inserted) {
875  $connection = GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable($this->user_table);
876  $connection->update(
877  $this->user_table,
878  [$this->lastLogin_column => $GLOBALS['EXEC_TIME']],
879  [$this->userid_column => $tempuser[$this->userid_column]]
880  );
881  }
882 
883  return $inserted ? $insertFields : [];
884  }
885 
893  public function getNewSessionRecord($tempuser)
894  {
895  $sessionIpLock = '[DISABLED]';
896  if ($this->lockIP && empty($tempuser['disableIPlock'])) {
897  $sessionIpLock = $this->ipLockClause_remoteIPNumber($this->lockIP);
898  }
899 
900  return [
901  'ses_id' => $this->id,
902  'ses_name' => $this->name,
903  'ses_iplock' => $sessionIpLock,
904  'ses_hashlock' => $this->hashLockClause_getHashInt(),
905  'ses_userid' => $tempuser[$this->userid_column],
906  'ses_tstamp' => $GLOBALS['EXEC_TIME'],
907  'ses_data' => ''
908  ];
909  }
910 
917  public function fetchUserSession($skipSessionUpdate = false)
918  {
919  if ($this->writeDevLog) {
920  GeneralUtility::devLog('Fetch session ses_id = ' . $this->id, self::class);
921  }
922 
923  // Fetch the user session from the DB
924  $user = $this->fetchUserSessionFromDB();
925 
926  if ($user) {
927  // A user was found
928  $user['ses_tstamp'] = (int)$user['ses_tstamp'];
929 
930  if (!empty($this->auth_timeout_field)) {
931  // Get timeout-time from usertable
932  $timeout = (int)$user[$this->auth_timeout_field];
933  } else {
934  $timeout = $this->sessionTimeout;
935  }
936  // If timeout > 0 (TRUE) and current time has not exceeded the latest sessions-time plus the timeout in seconds then accept user
937  // Use a gracetime-value to avoid updating a session-record too often
938  if ($timeout > 0 && $GLOBALS['EXEC_TIME'] < $user['ses_tstamp'] + $timeout) {
939  $sessionUpdateGracePeriod = 61;
940  if (!$skipSessionUpdate && $GLOBALS['EXEC_TIME'] > ($user['ses_tstamp'] + $sessionUpdateGracePeriod)) {
941  GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable($this->session_table)->update(
942  $this->session_table,
943  ['ses_tstamp' => $GLOBALS['EXEC_TIME']],
944  ['ses_id' => $this->id, 'ses_name' => $this->name]
945  );
946  // Make sure that the timestamp is also updated in the array
947  $user['ses_tstamp'] = $GLOBALS['EXEC_TIME'];
948  }
949  } else {
950  // Delete any user set...
951  $this->logoff();
952  $user = false;
953  }
954  }
955  return $user;
956  }
957 
965  public function logoff()
966  {
967  if ($this->writeDevLog) {
968  GeneralUtility::devLog('logoff: ses_id = ' . $this->id, self::class);
969  }
970  // Release the locked records
972  // Hook for pre-processing the logoff() method, requested and implemented by andreas.otto@dkd.de:
973  if (is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_userauth.php']['logoff_pre_processing'])) {
974  $_params = [];
975  foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_userauth.php']['logoff_pre_processing'] as $_funcRef) {
976  if ($_funcRef) {
977  GeneralUtility::callUserFunction($_funcRef, $_params, $this);
978  }
979  }
980  }
981 
982  GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable($this->session_table)->delete(
983  $this->session_table,
984  ['ses_id' => $this->id, 'ses_name' => $this->name]
985  );
986 
987  $this->user = null;
988  // Hook for post-processing the logoff() method, requested and implemented by andreas.otto@dkd.de:
989  if (is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_userauth.php']['logoff_post_processing'])) {
990  $_params = [];
991  foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_userauth.php']['logoff_post_processing'] as $_funcRef) {
992  if ($_funcRef) {
993  GeneralUtility::callUserFunction($_funcRef, $_params, $this);
994  }
995  }
996  }
997  }
998 
1005  public function removeCookie($cookieName)
1006  {
1007  $cookieDomain = $this->getCookieDomain();
1008  // If no cookie domain is set, use the base path
1009  $cookiePath = $cookieDomain ? '/' : GeneralUtility::getIndpEnv('TYPO3_SITE_PATH');
1010  setcookie($cookieName, null, -1, $cookiePath, $cookieDomain);
1011  }
1012 
1020  public function isExistingSessionRecord($id)
1021  {
1022  $conn = GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable($this->session_table);
1023  $count = $conn->count(
1024  '*',
1025  $this->session_table,
1026  ['ses_id' => $id]
1027  );
1028 
1029  return (bool)$count;
1030  }
1031 
1038  public function isCookieSet()
1039  {
1040  return $this->cookieWasSetOnCurrentRequest || $this->getCookie($this->name);
1041  }
1042 
1043  /*************************
1044  *
1045  * SQL Functions
1046  *
1047  *************************/
1058  protected function fetchUserSessionFromDB()
1059  {
1060  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
1061  ->getQueryBuilderForTable($this->session_table);
1062  $queryBuilder->setRestrictions($this->userConstraints());
1063  $queryBuilder->select('*')
1064  ->from($this->session_table)
1065  ->from($this->user_table)
1066  ->where(
1067  $queryBuilder->expr()->eq(
1068  $this->session_table . '.ses_id',
1069  $queryBuilder->createNamedParameter($this->id, \PDO::PARAM_STR)
1070  ),
1071  $queryBuilder->expr()->eq(
1072  $this->session_table . '.ses_name',
1073  $queryBuilder->createNamedParameter($this->name, \PDO::PARAM_STR)
1074  ),
1075  // Condition on which to join the session and user table
1076  $queryBuilder->expr()->eq(
1077  $this->session_table . '.ses_userid',
1078  $queryBuilder->quoteIdentifier($this->user_table . '.' . $this->userid_column)
1079  ),
1080  $queryBuilder->expr()->eq(
1081  $this->session_table . '.ses_hashlock',
1082  $queryBuilder->createNamedParameter($this->hashLockClause_getHashInt(), \PDO::PARAM_INT)
1083  )
1084  );
1085 
1086  if ($this->lockIP) {
1087  $queryBuilder->andWhere(
1088  $queryBuilder->expr()->in(
1089  $this->session_table . '.ses_iplock',
1090  $queryBuilder->createNamedParameter(
1091  [$this->ipLockClause_remoteIPNumber($this->lockIP), '[DISABLED]'],
1092  Connection::PARAM_STR_ARRAY // Automatically expand the array into multiple named parameters
1093  )
1094  )
1095  );
1096  }
1097 
1098  // Force the fetch mode to ensure we get back an array independently of the default fetch mode.
1099  return $queryBuilder->execute()->fetch(\PDO::FETCH_ASSOC);
1100  }
1101 
1111  {
1112  $restrictionContainer = GeneralUtility::makeInstance(DefaultRestrictionContainer::class);
1113 
1114  if (empty($this->enablecolumns['disabled'])) {
1115  $restrictionContainer->removeByType(HiddenRestriction::class);
1116  }
1117 
1118  if (empty($this->enablecolumns['deleted'])) {
1119  $restrictionContainer->removeByType(DeletedRestriction::class);
1120  }
1121 
1122  if (empty($this->enablecolumns['starttime'])) {
1123  $restrictionContainer->removeByType(StartTimeRestriction::class);
1124  }
1125 
1126  if (empty($this->enablecolumns['endtime'])) {
1127  $restrictionContainer->removeByType(EndTimeRestriction::class);
1128  }
1129 
1130  if (!empty($this->enablecolumns['rootLevel'])) {
1131  $restrictionContainer->add(GeneralUtility::makeInstance(RootLevelRestriction::class, [$this->user_table]));
1132  }
1133 
1134  return $restrictionContainer;
1135  }
1136 
1145  protected function user_where_clause()
1146  {
1148 
1149  $whereClause = '';
1150  if ($this->enablecolumns['rootLevel']) {
1151  $whereClause .= 'AND ' . $this->user_table . '.pid=0 ';
1152  }
1153  if ($this->enablecolumns['disabled']) {
1154  $whereClause .= ' AND ' . $this->user_table . '.' . $this->enablecolumns['disabled'] . '=0';
1155  }
1156  if ($this->enablecolumns['deleted']) {
1157  $whereClause .= ' AND ' . $this->user_table . '.' . $this->enablecolumns['deleted'] . '=0';
1158  }
1159  if ($this->enablecolumns['starttime']) {
1160  $whereClause .= ' AND (' . $this->user_table . '.' . $this->enablecolumns['starttime'] . '<=' . $GLOBALS['EXEC_TIME'] . ')';
1161  }
1162  if ($this->enablecolumns['endtime']) {
1163  $whereClause .= ' AND (' . $this->user_table . '.' . $this->enablecolumns['endtime'] . '=0 OR '
1164  . $this->user_table . '.' . $this->enablecolumns['endtime'] . '>' . $GLOBALS['EXEC_TIME'] . ')';
1165  }
1166  return $whereClause;
1167  }
1168 
1176  protected function ipLockClause()
1177  {
1179  $statementClause = [
1180  'where' => '',
1181  'parameters' => []
1182  ];
1183  if ($this->lockIP) {
1184  $statementClause['where'] = 'AND (
1185  ' . $this->session_table . '.ses_iplock = :ses_iplock
1186  OR ' . $this->session_table . '.ses_iplock=\'[DISABLED]\'
1187  )';
1188  $statementClause['parameters'] = [
1189  ':ses_iplock' => $this->ipLockClause_remoteIPNumber($this->lockIP)
1190  ];
1191  }
1192  return $statementClause;
1193  }
1194 
1202  protected function ipLockClause_remoteIPNumber($parts)
1203  {
1204  $IP = GeneralUtility::getIndpEnv('REMOTE_ADDR');
1205  if ($parts >= 4) {
1206  return $IP;
1207  } else {
1208  $parts = MathUtility::forceIntegerInRange($parts, 1, 3);
1209  $IPparts = explode('.', $IP);
1210  for ($a = 4; $a > $parts; $a--) {
1211  unset($IPparts[$a - 1]);
1212  }
1213  return implode('.', $IPparts);
1214  }
1215  }
1216 
1224  public function veriCode()
1225  {
1226  return substr(md5($this->id . $GLOBALS['TYPO3_CONF_VARS']['SYS']['encryptionKey']), 0, 10);
1227  }
1228 
1235  protected function hashLockClause()
1236  {
1237  return 'AND ' . $this->session_table . '.ses_hashlock=' . $this->hashLockClause_getHashInt();
1238  }
1239 
1246  protected function hashLockClause_getHashInt()
1247  {
1248  $hashStr = '';
1249  if (GeneralUtility::inList($this->lockHashKeyWords, 'useragent')) {
1250  $hashStr .= ':' . GeneralUtility::getIndpEnv('HTTP_USER_AGENT');
1251  }
1252  return GeneralUtility::md5int($hashStr);
1253  }
1254 
1255  /*************************
1256  *
1257  * Session and Configuration Handling
1258  *
1259  *************************/
1268  public function writeUC($variable = '')
1269  {
1270  if (is_array($this->user) && $this->user[$this->userid_column]) {
1271  if (!is_array($variable)) {
1272  $variable = $this->uc;
1273  }
1274  if ($this->writeDevLog) {
1276  'writeUC: ' . $this->userid_column . '=' . (int)$this->user[$this->userid_column],
1277  self::class
1278  );
1279  }
1280  GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable($this->session_table)->update(
1281  $this->user_table,
1282  ['uc' => serialize($variable)],
1283  [$this->userid_column => (int)$this->user[$this->userid_column]]
1284  );
1285  }
1286  }
1287 
1295  public function unpack_uc($theUC = '')
1296  {
1297  if (!$theUC && isset($this->user['uc'])) {
1298  $theUC = unserialize($this->user['uc']);
1299  }
1300  if (is_array($theUC)) {
1301  $this->uc = $theUC;
1302  }
1303  }
1304 
1315  public function pushModuleData($module, $data, $noSave = 0)
1316  {
1317  $this->uc['moduleData'][$module] = $data;
1318  $this->uc['moduleSessionID'][$module] = $this->id;
1319  if (!$noSave) {
1320  $this->writeUC();
1321  }
1322  }
1323 
1331  public function getModuleData($module, $type = '')
1332  {
1333  if ($type != 'ses' || (isset($this->uc['moduleSessionID'][$module]) && $this->uc['moduleSessionID'][$module] == $this->id)) {
1334  return $this->uc['moduleData'][$module];
1335  }
1336  return null;
1337  }
1338 
1346  public function getSessionData($key)
1347  {
1348  $sesDat = unserialize($this->user['ses_data']);
1349  return $sesDat[$key];
1350  }
1351 
1360  public function setAndSaveSessionData($key, $data)
1361  {
1362  $sesDat = unserialize($this->user['ses_data']);
1363  $sesDat[$key] = $data;
1364  $this->user['ses_data'] = serialize($sesDat);
1365  if ($this->writeDevLog) {
1366  GeneralUtility::devLog('setAndSaveSessionData: ses_id = ' . $this->user['ses_id'], self::class);
1367  }
1368  GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable($this->session_table)->update(
1369  $this->session_table,
1370  ['ses_data' => $this->user['ses_data']],
1371  ['ses_id' => $this->user['ses_id']]
1372  );
1373  }
1374 
1375  /*************************
1376  *
1377  * Misc
1378  *
1379  *************************/
1386  public function getLoginFormData()
1387  {
1388  $loginData = [];
1389  $loginData['status'] = GeneralUtility::_GP($this->formfield_status);
1390  if ($this->getMethodEnabled) {
1391  $loginData['uname'] = GeneralUtility::_GP($this->formfield_uname);
1392  $loginData['uident'] = GeneralUtility::_GP($this->formfield_uident);
1393  } else {
1394  $loginData['uname'] = GeneralUtility::_POST($this->formfield_uname);
1395  $loginData['uident'] = GeneralUtility::_POST($this->formfield_uident);
1396  }
1397  // Only process the login data if a login is requested
1398  if ($loginData['status'] === 'login') {
1399  $loginData = $this->processLoginData($loginData);
1400  }
1401  $loginData = array_map('trim', $loginData);
1402  return $loginData;
1403  }
1404 
1414  public function processLoginData($loginData, $passwordTransmissionStrategy = '')
1415  {
1416  $loginSecurityLevel = trim($GLOBALS['TYPO3_CONF_VARS'][$this->loginType]['loginSecurityLevel']) ?: 'normal';
1417  $passwordTransmissionStrategy = $passwordTransmissionStrategy ?: $loginSecurityLevel;
1418  if ($this->writeDevLog) {
1419  GeneralUtility::devLog('Login data before processing: ' . GeneralUtility::arrayToLogString($loginData), self::class);
1420  }
1421  $serviceChain = '';
1422  $subType = 'processLoginData' . $this->loginType;
1423  $authInfo = $this->getAuthInfoArray();
1424  $isLoginDataProcessed = false;
1425  $processedLoginData = $loginData;
1426  while (is_object($serviceObject = GeneralUtility::makeInstanceService('auth', $subType, $serviceChain))) {
1427  $serviceChain .= ',' . $serviceObject->getServiceKey();
1428  $serviceObject->initAuth($subType, $loginData, $authInfo, $this);
1429  $serviceResult = $serviceObject->processLoginData($processedLoginData, $passwordTransmissionStrategy);
1430  if (!empty($serviceResult)) {
1431  $isLoginDataProcessed = true;
1432  // If the service returns >=200 then no more processing is needed
1433  if ((int)$serviceResult >= 200) {
1434  unset($serviceObject);
1435  break;
1436  }
1437  }
1438  unset($serviceObject);
1439  }
1440  if ($isLoginDataProcessed) {
1441  $loginData = $processedLoginData;
1442  if ($this->writeDevLog) {
1443  GeneralUtility::devLog('Processed login data: ' . GeneralUtility::arrayToLogString($processedLoginData), self::class);
1444  }
1445  }
1446  return $loginData;
1447  }
1448 
1455  public function getAuthInfoArray()
1456  {
1457  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($this->user_table);
1458  $expressionBuilder = $queryBuilder->expr();
1459  $authInfo = [];
1460  $authInfo['loginType'] = $this->loginType;
1461  $authInfo['refInfo'] = parse_url(GeneralUtility::getIndpEnv('HTTP_REFERER'));
1462  $authInfo['HTTP_HOST'] = GeneralUtility::getIndpEnv('HTTP_HOST');
1463  $authInfo['REMOTE_ADDR'] = GeneralUtility::getIndpEnv('REMOTE_ADDR');
1464  $authInfo['REMOTE_HOST'] = GeneralUtility::getIndpEnv('REMOTE_HOST');
1465  $authInfo['showHiddenRecords'] = $this->showHiddenRecords;
1466  // Can be overidden in localconf by SVCONF:
1467  $authInfo['db_user']['table'] = $this->user_table;
1468  $authInfo['db_user']['userid_column'] = $this->userid_column;
1469  $authInfo['db_user']['username_column'] = $this->username_column;
1470  $authInfo['db_user']['userident_column'] = $this->userident_column;
1471  $authInfo['db_user']['usergroup_column'] = $this->usergroup_column;
1472  $authInfo['db_user']['enable_clause'] = $this->userConstraints()->buildExpression(
1473  [$this->user_table],
1474  $expressionBuilder
1475  );
1476  if ($this->checkPid && $this->checkPid_value !== null) {
1477  $authInfo['db_user']['checkPidList'] = $this->checkPid_value;
1478  $authInfo['db_user']['check_pid_clause'] = $expressionBuilder->in(
1479  'pid',
1480  GeneralUtility::intExplode(',', $this->checkPid_value)
1481  );
1482  } else {
1483  $authInfo['db_user']['checkPidList'] = '';
1484  $authInfo['db_user']['check_pid_clause'] = '';
1485  }
1486  $authInfo['db_groups']['table'] = $this->usergroup_table;
1487  return $authInfo;
1488  }
1489 
1498  public function compareUident($user, $loginData, $passwordCompareStrategy = '')
1499  {
1500  return (string)$loginData['uident_text'] !== '' && (string)$loginData['uident_text'] === (string)$user[$this->userident_column];
1501  }
1502 
1509  public function gc()
1510  {
1511  $query = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($this->session_table);
1512  $query->delete($this->session_table)
1513  ->where(
1514  $query->expr()->lt(
1515  'ses_tstamp',
1516  $query->createNamedParameter(($GLOBALS['EXEC_TIME'] - $this->gc_time), \PDO::PARAM_INT)
1517  ),
1518  $query->expr()->eq(
1519  'ses_name',
1520  $query->createNamedParameter($this->name, \PDO::PARAM_STR)
1521  )
1522  )
1523  ->execute();
1524  }
1525 
1540  public function writelog($type, $action, $error, $details_nr, $details, $data, $tablename, $recuid, $recpid)
1541  {
1542  }
1543 
1553  public function checkLogFailures($email, $secondsBack, $maxFailures)
1554  {
1555  }
1556 
1570  public function setBeUserByUid($uid)
1571  {
1572  $this->user = $this->getRawUserByUid($uid);
1573  }
1574 
1583  public function setBeUserByName($name)
1584  {
1585  $this->user = $this->getRawUserByName($name);
1586  }
1587 
1595  public function getRawUserByUid($uid)
1596  {
1597  $query = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($this->user_table);
1598  $query->setRestrictions($this->userConstraints());
1599  $query->select('*')
1600  ->from($this->user_table)
1601  ->where($query->expr()->eq('uid', $query->createNamedParameter($uid, \PDO::PARAM_INT)));
1602 
1603  return $query->execute()->fetch();
1604  }
1605 
1614  public function getRawUserByName($name)
1615  {
1616  $query = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($this->user_table);
1617  $query->setRestrictions($this->userConstraints());
1618  $query->select('*')
1619  ->from($this->user_table)
1620  ->where($query->expr()->eq('username', $query->createNamedParameter($name, \PDO::PARAM_STR)));
1621 
1622  return $query->execute()->fetch();
1623  }
1624 
1625  /*************************
1626  *
1627  * Create/update user - EXPERIMENTAL
1628  *
1629  *************************/
1639  public function fetchUserRecord($dbUser, $username, $extraWhere = '')
1640  {
1641  $user = false;
1642  if ($username || $extraWhere) {
1643  $query = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($dbUser['table']);
1644  $query->getRestrictions()->removeAll()
1645  ->add(GeneralUtility::makeInstance(DeletedRestriction::class));
1646 
1647  $constraints = array_filter([
1648  QueryHelper::stripLogicalOperatorPrefix($dbUser['check_pid_clause']),
1649  QueryHelper::stripLogicalOperatorPrefix($dbUser['enable_clause']),
1651  ]);
1652 
1653  if (!empty($username)) {
1654  array_unshift(
1655  $constraints,
1656  $query->expr()->eq(
1657  $dbUser['username_column'],
1658  $query->createNamedParameter($username, \PDO::PARAM_STR)
1659  )
1660  );
1661  }
1662 
1663  $user = $query->select('*')
1664  ->from($dbUser['table'])
1665  ->where(...$constraints)
1666  ->execute()
1667  ->fetch();
1668  }
1669 
1670  return $user;
1671  }
1672 }
writelog($type, $action, $error, $details_nr, $details, $data, $tablename, $recuid, $recpid)
static arrayToLogString(array $arr, $valueList=[], $valueLength=20)
compareUident($user, $loginData, $passwordCompareStrategy= '')
processLoginData($loginData, $passwordTransmissionStrategy= '')
if(TYPO3_MODE=== 'BE') $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tsfebeuserauth.php']['frontendEditingController']['default']
static makeInstance($className,...$constructorArguments)
static forceIntegerInRange($theInt, $min, $max=2000000000, $defaultValue=0)
Definition: MathUtility.php:31
static stripLogicalOperatorPrefix(string $constraint)
static intExplode($delimiter, $string, $removeEmptyValues=false, $limit=0)
static callUserFunction($funcName, &$params, &$ref, $_= '', $errorMode=0)
static makeInstanceService($serviceType, $serviceSubType= '', $excludeServiceKeys=[])
static devLog($msg, $extKey, $severity=0, $dataVar=false)