TYPO3 CMS  TYPO3_8-7
AbstractUserAuthentication.php
Go to the documentation of this file.
1 <?php
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 
35 
46 {
51  public $name = '';
52 
57  public $get_name = '';
58 
63  public $user_table = '';
64 
69  public $usergroup_table = '';
70 
75  public $username_column = '';
76 
81  public $userident_column = '';
82 
87  public $userid_column = '';
88 
93  public $usergroup_column = '';
94 
99  public $lastLogin_column = '';
100 
105  public $enablecolumns = [
106  'rootLevel' => '',
107  // Boolean: If TRUE, 'AND pid=0' will be a part of the query...
108  'disabled' => '',
109  'starttime' => '',
110  'endtime' => '',
111  'deleted' => '',
112  ];
113 
117  public $showHiddenRecords = false;
118 
123  public $formfield_uname = '';
124 
129  public $formfield_uident = '';
130 
135  public $formfield_status = '';
136 
145  public $sessionTimeout = 0;
146 
152  public $auth_timeout_field = '';
153 
163  public $lifetime = 0;
164 
171  public $gc_time = 0;
172 
177  public $gc_probability = 1;
178 
183  public $writeStdLog = false;
184 
189  public $writeAttemptLog = false;
190 
195  public $sendNoCacheHeaders = true;
196 
203  public $getFallBack = false;
204 
213  public $hash_length = 32;
214 
220  public $getMethodEnabled = false;
221 
227  public $lockIP = 4;
228 
232  public $warningEmail = '';
233 
238  public $warningPeriod = 3600;
239 
244  public $warningMax = 3;
245 
250  public $checkPid = true;
251 
256  public $checkPid_value = 0;
257 
263  public $id;
264 
269  public $loginFailure = false;
270 
275  public $loginSessionStarted = false;
276 
281  public $user = null;
282 
289  public $get_URL_ID = '';
290 
295  public $newSessionID = false;
296 
301  public $forceSetCookie = false;
302 
307  public $dontSetCookie = false;
308 
312  protected $cookieWasSetOnCurrentRequest = false;
313 
318  public $loginType = '';
319 
324  public $svConfig = [];
325 
330  public $writeDevLog = false;
331 
335  public $uc;
336 
340  protected $sessionBackend;
341 
349  protected $sessionData = [];
350 
354  public function __construct()
355  {
356  // This function has to stay even if it's empty
357  // Implementations of that abstract class might call parent::__construct();
358  }
359 
371  public function start()
372  {
373  // Backend or frontend login - used for auth services
374  if (empty($this->loginType)) {
375  throw new Exception('No loginType defined, should be set explicitly by subclass', 1476045345);
376  }
377  // Enable dev logging if set
378  if ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_userauth.php']['writeDevLog']) {
379  $this->writeDevLog = true;
380  }
381  if ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_userauth.php']['writeDevLog' . $this->loginType]) {
382  $this->writeDevLog = true;
383  }
384  if ((bool)$GLOBALS['TYPO3_CONF_VARS']['SYS']['enable_DLOG']) {
385  $this->writeDevLog = true;
386  }
387  if ($this->writeDevLog) {
388  GeneralUtility::devLog('## Beginning of auth logging.', self::class);
389  }
390  // Init vars.
391  $mode = '';
392  $this->newSessionID = false;
393  // $id is set to ses_id if cookie is present. Else set to FALSE, which will start a new session
394  $id = $this->getCookie($this->name);
395  $this->svConfig = $GLOBALS['TYPO3_CONF_VARS']['SVCONF']['auth'] ?? [];
396 
397  // If fallback to get mode....
398  if (!$id && $this->getFallBack && $this->get_name) {
399  $id = isset($_GET[$this->get_name]) ? GeneralUtility::_GET($this->get_name) : '';
400  if (strlen($id) != $this->hash_length) {
401  $id = '';
402  }
403  $mode = 'get';
404  }
405 
406  // If new session or client tries to fix session...
407  if (!$id || !$this->isExistingSessionRecord($id)) {
408  // New random session-$id is made
409  $id = $this->createSessionId();
410  // New session
411  $this->newSessionID = true;
412  }
413  // Internal var 'id' is set
414  $this->id = $id;
415  // If fallback to get mode....
416  if ($mode === 'get' && $this->getFallBack && $this->get_name) {
417  $this->get_URL_ID = '&' . $this->get_name . '=' . $id;
418  }
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 
472  protected function setSessionCookie()
473  {
474  $isSetSessionCookie = $this->isSetSessionCookie();
475  $isRefreshTimeBasedCookie = $this->isRefreshTimeBasedCookie();
476  if ($isSetSessionCookie || $isRefreshTimeBasedCookie) {
477  $settings = $GLOBALS['TYPO3_CONF_VARS']['SYS'];
478  // Get the domain to be used for the cookie (if any):
479  $cookieDomain = $this->getCookieDomain();
480  // If no cookie domain is set, use the base path:
481  $cookiePath = $cookieDomain ? '/' : GeneralUtility::getIndpEnv('TYPO3_SITE_PATH');
482  // If the cookie lifetime is set, use it:
483  $cookieExpire = $isRefreshTimeBasedCookie ? $GLOBALS['EXEC_TIME'] + $this->lifetime : 0;
484  // Use the secure option when the current request is served by a secure connection:
485  $cookieSecure = (bool)$settings['cookieSecure'] && GeneralUtility::getIndpEnv('TYPO3_SSL');
486  // Do not set cookie if cookieSecure is set to "1" (force HTTPS) and no secure channel is used:
487  if ((int)$settings['cookieSecure'] !== 1 || GeneralUtility::getIndpEnv('TYPO3_SSL')) {
488  setcookie($this->name, $this->id, $cookieExpire, $cookiePath, $cookieDomain, $cookieSecure, true);
489  $this->cookieWasSetOnCurrentRequest = true;
490  } else {
491  throw new Exception('Cookie was not set since HTTPS was forced in $TYPO3_CONF_VARS[SYS][cookieSecure].', 1254325546);
492  }
493  if ($this->writeDevLog) {
494  $devLogMessage = ($isRefreshTimeBasedCookie ? 'Updated Cookie: ' : 'Set Cookie: ') . $this->id;
495  GeneralUtility::devLog($devLogMessage . ($cookieDomain ? ', ' . $cookieDomain : ''), self::class);
496  }
497  }
498  }
499 
506  protected function getCookieDomain()
507  {
508  $result = '';
509  $cookieDomain = $GLOBALS['TYPO3_CONF_VARS']['SYS']['cookieDomain'];
510  // If a specific cookie domain is defined for a given TYPO3_MODE,
511  // use that domain
512  if (!empty($GLOBALS['TYPO3_CONF_VARS'][$this->loginType]['cookieDomain'])) {
513  $cookieDomain = $GLOBALS['TYPO3_CONF_VARS'][$this->loginType]['cookieDomain'];
514  }
515  if ($cookieDomain) {
516  if ($cookieDomain[0] === '/') {
517  $match = [];
518  $matchCnt = @preg_match($cookieDomain, GeneralUtility::getIndpEnv('TYPO3_HOST_ONLY'), $match);
519  if ($matchCnt === false) {
520  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);
521  } elseif ($matchCnt) {
522  $result = $match[0];
523  }
524  } else {
525  $result = $cookieDomain;
526  }
527  }
528  return $result;
529  }
530 
537  protected function getCookie($cookieName)
538  {
539  return isset($_COOKIE[$cookieName]) ? stripslashes($_COOKIE[$cookieName]) : '';
540  }
541 
548  public function isSetSessionCookie()
549  {
550  return ($this->newSessionID || $this->forceSetCookie) && $this->lifetime == 0;
551  }
552 
559  public function isRefreshTimeBasedCookie()
560  {
561  return $this->lifetime > 0;
562  }
563 
570  public function checkAuthentication()
571  {
572  // No user for now - will be searched by service below
573  $tempuserArr = [];
574  $tempuser = false;
575  // User is not authenticated by default
576  $authenticated = false;
577  // User want to login with passed login data (name/password)
578  $activeLogin = false;
579  // Indicates if an active authentication failed (not auto login)
580  $this->loginFailure = false;
581  if ($this->writeDevLog) {
582  GeneralUtility::devLog('Login type: ' . $this->loginType, self::class);
583  }
584  // The info array provide additional information for auth services
585  $authInfo = $this->getAuthInfoArray();
586  // Get Login/Logout data submitted by a form or params
587  $loginData = $this->getLoginFormData();
588  if ($this->writeDevLog) {
589  GeneralUtility::devLog('Login data: ' . GeneralUtility::arrayToLogString($loginData), self::class);
590  }
591  // Active logout (eg. with "logout" button)
592  if ($loginData['status'] === 'logout') {
593  if ($this->writeStdLog) {
594  // $type,$action,$error,$details_nr,$details,$data,$tablename,$recuid,$recpid
595  $this->writelog(255, 2, 0, 2, 'User %s logged out', [$this->user['username']], '', 0, 0);
596  }
597  // Logout written to log
598  if ($this->writeDevLog) {
599  GeneralUtility::devLog('User logged out. Id: ' . $this->id, self::class, -1);
600  }
601  $this->logoff();
602  }
603  // Determine whether we need to skip session update.
604  // This is used mainly for checking session timeout in advance without refreshing the current session's timeout.
605  $skipSessionUpdate = (bool)GeneralUtility::_GP('skipSessionUpdate');
606  $haveSession = false;
607  $anonymousSession = false;
608  if (!$this->newSessionID) {
609  // Read user session
610  $authInfo['userSession'] = $this->fetchUserSession($skipSessionUpdate);
611  $haveSession = is_array($authInfo['userSession']);
612  if ($haveSession && !empty($authInfo['userSession']['ses_anonymous'])) {
613  $anonymousSession = true;
614  }
615  }
616 
617  // Active login (eg. with login form).
618  if (!$haveSession && $loginData['status'] === 'login') {
619  $activeLogin = true;
620  if ($this->writeDevLog) {
621  GeneralUtility::devLog('Active login (eg. with login form)', self::class);
622  }
623  // check referrer for submitted login values
624  if ($this->formfield_status && $loginData['uident'] && $loginData['uname']) {
625  // Delete old user session if any
626  $this->logoff();
627  }
628  // Refuse login for _CLI users, if not processing a CLI request type
629  // (although we shouldn't be here in case of a CLI request type)
630  if (strtoupper(substr($loginData['uname'], 0, 5)) === '_CLI_' && !(TYPO3_REQUESTTYPE & TYPO3_REQUESTTYPE_CLI)) {
631  throw new \RuntimeException('TYPO3 Fatal Error: You have tried to login using a CLI user. Access prohibited!', 1270853931);
632  }
633  }
634 
635  // Cause elevation of privilege, make sure regenerateSessionId is called later on
636  if ($anonymousSession && $loginData['status'] === 'login') {
637  $activeLogin = true;
638  }
639 
640  if ($this->writeDevLog) {
641  if ($haveSession) {
642  GeneralUtility::devLog('User session found: ' . GeneralUtility::arrayToLogString($authInfo['userSession'], [$this->userid_column, $this->username_column]), self::class, 0);
643  } else {
644  GeneralUtility::devLog('No user session found.', self::class, 2);
645  }
646  if (is_array($this->svConfig['setup'] ?? false)) {
647  GeneralUtility::devLog('SV setup: ' . GeneralUtility::arrayToLogString($this->svConfig['setup']), self::class, 0);
648  }
649  }
650 
651  // Fetch user if ...
652  if (
653  $activeLogin || !empty($this->svConfig['setup'][$this->loginType . '_alwaysFetchUser'])
654  || !$haveSession && !empty($this->svConfig['setup'][$this->loginType . '_fetchUserIfNoSession'])
655  ) {
656  // Use 'auth' service to find the user
657  // First found user will be used
658  $subType = 'getUser' . $this->loginType;
660  foreach ($this->getAuthServices($subType, $loginData, $authInfo) as $serviceObj) {
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  }
672 
673  if ($this->writeDevLog && $this->svConfig['setup'][$this->loginType . '_alwaysFetchUser']) {
674  GeneralUtility::devLog($this->loginType . '_alwaysFetchUser option is enabled', self::class);
675  }
676  if ($this->writeDevLog && empty($tempuserArr)) {
677  GeneralUtility::devLog('No user found by services', self::class);
678  }
679  if ($this->writeDevLog && !empty($tempuserArr)) {
680  GeneralUtility::devLog(count($tempuserArr) . ' user records found by services', self::class);
681  }
682  }
683 
684  // If no new user was set we use the already found user session
685  if (empty($tempuserArr) && $haveSession && !$anonymousSession) {
686  $tempuserArr[] = $authInfo['userSession'];
687  $tempuser = $authInfo['userSession'];
688  // User is authenticated because we found a user session
689  $authenticated = true;
690  if ($this->writeDevLog) {
691  GeneralUtility::devLog('User session used: ' . GeneralUtility::arrayToLogString($authInfo['userSession'], [$this->userid_column, $this->username_column]), self::class);
692  }
693  }
694  // Re-auth user when 'auth'-service option is set
695  if (!empty($this->svConfig['setup'][$this->loginType . '_alwaysAuthUser'])) {
696  $authenticated = false;
697  if ($this->writeDevLog) {
698  GeneralUtility::devLog('alwaysAuthUser option is enabled', self::class);
699  }
700  }
701  // Authenticate the user if needed
702  if (!empty($tempuserArr) && !$authenticated) {
703  foreach ($tempuserArr as $tempuser) {
704  // Use 'auth' service to authenticate the user
705  // If one service returns FALSE then authentication failed
706  // a service might return 100 which means there's no reason to stop but the user can't be authenticated by that service
707  if ($this->writeDevLog) {
708  GeneralUtility::devLog('Auth user: ' . GeneralUtility::arrayToLogString($tempuser), self::class);
709  }
710  $subType = 'authUser' . $this->loginType;
711 
712  foreach ($this->getAuthServices($subType, $loginData, $authInfo) as $serviceObj) {
713  if (($ret = $serviceObj->authUser($tempuser)) > 0) {
714  // If the service returns >=200 then no more checking is needed - useful for IP checking without password
715  if ((int)$ret >= 200) {
716  $authenticated = true;
717  break;
718  }
719  if ((int)$ret >= 100) {
720  } else {
721  $authenticated = true;
722  }
723  } else {
724  $authenticated = false;
725  break;
726  }
727  }
728 
729  if ($authenticated) {
730  // Leave foreach() because a user is authenticated
731  break;
732  }
733  }
734  }
735 
736  // If user is authenticated a valid user is in $tempuser
737  if ($authenticated) {
738  // Reset failure flag
739  $this->loginFailure = false;
740  // Insert session record if needed:
741  if (!$haveSession || $anonymousSession || $tempuser['ses_id'] != $this->id && $tempuser['uid'] != $authInfo['userSession']['ses_userid']) {
742  $sessionData = $this->createUserSession($tempuser);
743 
744  // Preserve session data on login
745  if ($anonymousSession) {
746  $sessionData = $this->getSessionBackend()->update(
747  $this->id,
748  ['ses_data' => $authInfo['userSession']['ses_data']]
749  );
750  }
751 
752  $this->user = array_merge(
753  $tempuser,
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  // if we come here the current session is for sure not anonymous as this is a pre-condition for $authenticated = true
763  $this->user = $authInfo['userSession'];
764  }
765 
766  if ($activeLogin && !$this->newSessionID) {
767  $this->regenerateSessionId();
768  }
769 
770  // User logged in - write that to the log!
771  if ($this->writeStdLog && $activeLogin) {
772  $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')], '', '', '');
773  }
774  if ($this->writeDevLog && $activeLogin) {
775  GeneralUtility::devLog('User ' . $tempuser[$this->username_column] . ' logged in from ' . GeneralUtility::getIndpEnv('REMOTE_ADDR') . ' (' . GeneralUtility::getIndpEnv('REMOTE_HOST') . ')', self::class, -1);
776  }
777  if ($this->writeDevLog && !$activeLogin) {
778  GeneralUtility::devLog('User ' . $tempuser[$this->username_column] . ' authenticated from ' . GeneralUtility::getIndpEnv('REMOTE_ADDR') . ' (' . GeneralUtility::getIndpEnv('REMOTE_HOST') . ')', self::class, -1);
779  }
780  } else {
781  // User was not authenticated, so we should reuse the existing anonymous session
782  if ($anonymousSession) {
783  $this->user = $authInfo['userSession'];
784  }
785 
786  // Mark the current login attempt as failed
787  if ($activeLogin || !empty($tempuserArr)) {
788  $this->loginFailure = true;
789  if ($this->writeDevLog && empty($tempuserArr) && $activeLogin) {
790  GeneralUtility::devLog('Login failed: ' . GeneralUtility::arrayToLogString($loginData), self::class, 2);
791  }
792  if ($this->writeDevLog && !empty($tempuserArr)) {
793  GeneralUtility::devLog('Login failed: ' . GeneralUtility::arrayToLogString($tempuser, [$this->userid_column, $this->username_column]), self::class, 2);
794  }
795  }
796  }
797 
798  // If there were a login failure, check to see if a warning email should be sent:
799  if ($this->loginFailure && $activeLogin) {
800  if ($this->writeDevLog) {
801  GeneralUtility::devLog('Call checkLogFailures: ' . GeneralUtility::arrayToLogString(['warningEmail' => $this->warningEmail, 'warningPeriod' => $this->warningPeriod, 'warningMax' => $this->warningMax]), self::class, -1);
802  }
803 
804  // Hook to implement login failure tracking methods
805  if (
806  !empty($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_userauth.php']['postLoginFailureProcessing'])
807  && is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_userauth.php']['postLoginFailureProcessing'])
808  ) {
809  $_params = [];
810  foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_userauth.php']['postLoginFailureProcessing'] as $_funcRef) {
811  GeneralUtility::callUserFunction($_funcRef, $_params, $this);
812  }
813  } else {
814  // If no hook is implemented, wait for 5 seconds
815  sleep(5);
816  }
817 
818  $this->checkLogFailures($this->warningEmail, $this->warningPeriod, $this->warningMax);
819  }
820  }
821 
827  public function createSessionId()
828  {
829  return GeneralUtility::makeInstance(Random::class)->generateRandomHexString($this->hash_length);
830  }
831 
840  protected function getAuthServices(string $subType, array $loginData, array $authInfo): \Traversable
841  {
842  $serviceChain = '';
843  while (is_object($serviceObj = GeneralUtility::makeInstanceService('auth', $subType, $serviceChain))) {
844  $serviceChain .= ',' . $serviceObj->getServiceKey();
845  $serviceObj->initAuth($subType, $loginData, $authInfo, $this);
846  yield $serviceObj;
847  }
848  if ($this->writeDevLog && $serviceChain) {
849  GeneralUtility::devLog($subType . ' auth services called: ' . $serviceChain, self::class);
850  }
851  }
852 
861  protected function regenerateSessionId(array $existingSessionRecord = [], bool $anonymous = false)
862  {
863  if (empty($existingSessionRecord)) {
864  $existingSessionRecord = $this->getSessionBackend()->get($this->id);
865  }
866 
867  // Update session record with new ID
868  $oldSessionId = $this->id;
869  $this->id = $this->createSessionId();
870  $updatedSession = $this->getSessionBackend()->set($this->id, $existingSessionRecord);
871  $this->sessionData = unserialize($updatedSession['ses_data']);
872  // Merge new session data into user/session array
873  $this->user = array_merge($this->user ?? [], $updatedSession);
874  $this->getSessionBackend()->remove($oldSessionId);
875  $this->newSessionID = true;
876  }
877 
878  /*************************
879  *
880  * User Sessions
881  *
882  *************************/
883 
891  public function createUserSession($tempuser)
892  {
893  if ($this->writeDevLog) {
894  GeneralUtility::devLog('Create session ses_id = ' . $this->id, self::class);
895  }
896  // Delete any session entry first
897  $this->getSessionBackend()->remove($this->id);
898  // Re-create session entry
899  $sessionRecord = $this->getNewSessionRecord($tempuser);
900  $sessionRecord = $this->getSessionBackend()->set($this->id, $sessionRecord);
901  // Updating lastLogin_column carrying information about last login.
902  $this->updateLoginTimestamp($tempuser[$this->userid_column]);
903  return $sessionRecord;
904  }
905 
911  protected function updateLoginTimestamp(int $userId)
912  {
913  if ($this->lastLogin_column) {
914  $connection = GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable($this->user_table);
915  $connection->update(
916  $this->user_table,
917  [$this->lastLogin_column => $GLOBALS['EXEC_TIME']],
918  [$this->userid_column => $userId]
919  );
920  }
921  }
922 
930  public function getNewSessionRecord($tempuser)
931  {
932  $sessionIpLock = '[DISABLED]';
933  if ($this->lockIP && empty($tempuser['disableIPlock'])) {
934  $sessionIpLock = $this->ipLockClause_remoteIPNumber($this->lockIP);
935  }
936 
937  return [
938  'ses_id' => $this->id,
939  'ses_iplock' => $sessionIpLock,
940  'ses_userid' => $tempuser[$this->userid_column] ?? 0,
941  'ses_tstamp' => $GLOBALS['EXEC_TIME'],
942  'ses_data' => '',
943  ];
944  }
945 
952  public function fetchUserSession($skipSessionUpdate = false)
953  {
954  if ($this->writeDevLog) {
955  GeneralUtility::devLog('Fetch session ses_id = ' . $this->id, self::class);
956  }
957  try {
958  $sessionRecord = $this->getSessionBackend()->get($this->id);
959  } catch (SessionNotFoundException $e) {
960  return false;
961  }
962 
963  $this->sessionData = unserialize($sessionRecord['ses_data']);
964  // Session is anonymous so no need to fetch user
965  if (!empty($sessionRecord['ses_anonymous'])) {
966  return $sessionRecord;
967  }
968 
969  // Fetch the user from the DB
970  $userRecord = $this->getRawUserByUid((int)$sessionRecord['ses_userid']);
971  if ($userRecord) {
972  $userRecord = array_merge($sessionRecord, $userRecord);
973  // A user was found
974  $userRecord['ses_tstamp'] = (int)$userRecord['ses_tstamp'];
975  $userRecord['is_online'] = (int)$userRecord['ses_tstamp'];
976 
977  if (!empty($this->auth_timeout_field)) {
978  // Get timeout-time from usertable
979  $timeout = (int)$userRecord[$this->auth_timeout_field];
980  } else {
981  $timeout = $this->sessionTimeout;
982  }
983  // If timeout > 0 (TRUE) and current time has not exceeded the latest sessions-time plus the timeout in seconds then accept user
984  // Use a gracetime-value to avoid updating a session-record too often
985  if ($timeout > 0 && $GLOBALS['EXEC_TIME'] < $userRecord['ses_tstamp'] + $timeout) {
986  $sessionUpdateGracePeriod = 61;
987  if (!$skipSessionUpdate && $GLOBALS['EXEC_TIME'] > ($userRecord['ses_tstamp'] + $sessionUpdateGracePeriod)) {
988  // Update the session timestamp by writing a dummy update. (Backend will update the timestamp)
989  $updatesSession = $this->getSessionBackend()->update($this->id, []);
990  $userRecord = array_merge($userRecord, $updatesSession);
991  }
992  } else {
993  // Delete any user set...
994  $this->logoff();
995  $userRecord = false;
996  }
997  }
998  return $userRecord;
999  }
1000 
1006  public function enforceNewSessionId()
1007  {
1008  $this->regenerateSessionId();
1009  $this->setSessionCookie();
1010  }
1011 
1017  public function logoff()
1018  {
1019  if ($this->writeDevLog) {
1020  GeneralUtility::devLog('logoff: ses_id = ' . $this->id, self::class);
1021  }
1022  if (is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_userauth.php']['logoff_pre_processing'])) {
1023  $_params = [];
1024  foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_userauth.php']['logoff_pre_processing'] as $_funcRef) {
1025  if ($_funcRef) {
1026  GeneralUtility::callUserFunction($_funcRef, $_params, $this);
1027  }
1028  }
1029  }
1030  $this->performLogoff();
1031  if (is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_userauth.php']['logoff_post_processing'])) {
1032  $_params = [];
1033  foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_userauth.php']['logoff_post_processing'] as $_funcRef) {
1034  if ($_funcRef) {
1035  GeneralUtility::callUserFunction($_funcRef, $_params, $this);
1036  }
1037  }
1038  }
1039  }
1040 
1046  protected function performLogoff()
1047  {
1048  if ($this->id) {
1049  $this->getSessionBackend()->remove($this->id);
1050  }
1051  $this->sessionData = [];
1052  $this->user = null;
1053  }
1054 
1060  public function removeCookie($cookieName)
1061  {
1062  $cookieDomain = $this->getCookieDomain();
1063  // If no cookie domain is set, use the base path
1064  $cookiePath = $cookieDomain ? '/' : GeneralUtility::getIndpEnv('TYPO3_SITE_PATH');
1065  setcookie($cookieName, null, -1, $cookiePath, $cookieDomain);
1066  }
1067 
1075  public function isExistingSessionRecord($id)
1076  {
1077  try {
1078  $sessionRecord = $this->getSessionBackend()->get($id);
1079  if (empty($sessionRecord)) {
1080  return false;
1081  }
1082  // If the session does not match the current IP lock, it should be treated as invalid
1083  // and a new session should be created.
1084  if ($sessionRecord['ses_iplock'] !== $this->ipLockClause_remoteIPNumber($this->lockIP) && $sessionRecord['ses_iplock'] !== '[DISABLED]') {
1085  return false;
1086  }
1087  return true;
1088  } catch (SessionNotFoundException $e) {
1089  return false;
1090  }
1091  }
1092 
1099  public function isCookieSet()
1100  {
1101  return $this->cookieWasSetOnCurrentRequest || $this->getCookie($this->name);
1102  }
1103 
1104  /*************************
1105  *
1106  * SQL Functions
1107  *
1108  *************************/
1118  {
1119  $restrictionContainer = GeneralUtility::makeInstance(DefaultRestrictionContainer::class);
1120 
1121  if (empty($this->enablecolumns['disabled'])) {
1122  $restrictionContainer->removeByType(HiddenRestriction::class);
1123  }
1124 
1125  if (empty($this->enablecolumns['deleted'])) {
1126  $restrictionContainer->removeByType(DeletedRestriction::class);
1127  }
1128 
1129  if (empty($this->enablecolumns['starttime'])) {
1130  $restrictionContainer->removeByType(StartTimeRestriction::class);
1131  }
1132 
1133  if (empty($this->enablecolumns['endtime'])) {
1134  $restrictionContainer->removeByType(EndTimeRestriction::class);
1135  }
1136 
1137  if (!empty($this->enablecolumns['rootLevel'])) {
1138  $restrictionContainer->add(GeneralUtility::makeInstance(RootLevelRestriction::class, [$this->user_table]));
1139  }
1140 
1141  return $restrictionContainer;
1142  }
1143 
1152  protected function user_where_clause()
1153  {
1155 
1156  $whereClause = '';
1157  if ($this->enablecolumns['rootLevel']) {
1158  $whereClause .= ' AND ' . $this->user_table . '.pid=0 ';
1159  }
1160  if ($this->enablecolumns['disabled']) {
1161  $whereClause .= ' AND ' . $this->user_table . '.' . $this->enablecolumns['disabled'] . '=0';
1162  }
1163  if ($this->enablecolumns['deleted']) {
1164  $whereClause .= ' AND ' . $this->user_table . '.' . $this->enablecolumns['deleted'] . '=0';
1165  }
1166  if ($this->enablecolumns['starttime']) {
1167  $whereClause .= ' AND (' . $this->user_table . '.' . $this->enablecolumns['starttime'] . '<=' . $GLOBALS['EXEC_TIME'] . ')';
1168  }
1169  if ($this->enablecolumns['endtime']) {
1170  $whereClause .= ' AND (' . $this->user_table . '.' . $this->enablecolumns['endtime'] . '=0 OR '
1171  . $this->user_table . '.' . $this->enablecolumns['endtime'] . '>' . $GLOBALS['EXEC_TIME'] . ')';
1172  }
1173  return $whereClause;
1174  }
1175 
1183  protected function ipLockClause_remoteIPNumber($parts)
1184  {
1185  $IP = GeneralUtility::getIndpEnv('REMOTE_ADDR');
1186  if ($parts >= 4) {
1187  return $IP;
1188  }
1189  $parts = MathUtility::forceIntegerInRange($parts, 1, 3);
1190  $IPparts = explode('.', $IP);
1191  for ($a = 4; $a > $parts; $a--) {
1192  unset($IPparts[$a - 1]);
1193  }
1194  return implode('.', $IPparts);
1195  }
1196 
1205  public function veriCode()
1206  {
1208  return substr(md5($this->id . $GLOBALS['TYPO3_CONF_VARS']['SYS']['encryptionKey']), 0, 10);
1209  }
1210 
1211  /*************************
1212  *
1213  * Session and Configuration Handling
1214  *
1215  *************************/
1223  public function writeUC($variable = '')
1224  {
1225  if (is_array($this->user) && $this->user[$this->userid_column]) {
1226  if (!is_array($variable)) {
1227  $variable = $this->uc;
1228  }
1229  if ($this->writeDevLog) {
1231  'writeUC: ' . $this->userid_column . '=' . (int)$this->user[$this->userid_column],
1232  self::class
1233  );
1234  }
1235  GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable($this->user_table)->update(
1236  $this->user_table,
1237  ['uc' => serialize($variable)],
1238  [$this->userid_column => (int)$this->user[$this->userid_column]],
1239  ['uc' => Connection::PARAM_LOB]
1240  );
1241  }
1242  }
1243 
1250  public function unpack_uc($theUC = '')
1251  {
1252  if (!$theUC && isset($this->user['uc'])) {
1253  $theUC = unserialize($this->user['uc']);
1254  }
1255  if (is_array($theUC)) {
1256  $this->uc = $theUC;
1257  }
1258  }
1259 
1269  public function pushModuleData($module, $data, $noSave = 0)
1270  {
1271  $this->uc['moduleData'][$module] = $data;
1272  $this->uc['moduleSessionID'][$module] = $this->id;
1273  if (!$noSave) {
1274  $this->writeUC();
1275  }
1276  }
1277 
1285  public function getModuleData($module, $type = '')
1286  {
1287  if ($type !== 'ses' || (isset($this->uc['moduleSessionID'][$module]) && $this->uc['moduleSessionID'][$module] == $this->id)) {
1288  return $this->uc['moduleData'][$module];
1289  }
1290  return null;
1291  }
1292 
1300  public function getSessionData($key)
1301  {
1302  return $this->sessionData[$key] ?? null;
1303  }
1304 
1312  public function setSessionData($key, $data)
1313  {
1314  if (empty($key)) {
1315  throw new \InvalidArgumentException('Argument key must not be empty', 1484311516);
1316  }
1317  $this->sessionData[$key] = $data;
1318  }
1319 
1327  public function setAndSaveSessionData($key, $data)
1328  {
1329  $this->sessionData[$key] = $data;
1330  $this->user['ses_data'] = serialize($this->sessionData);
1331  if ($this->writeDevLog) {
1332  GeneralUtility::devLog('setAndSaveSessionData: ses_id = ' . $this->id, self::class);
1333  }
1334  $updatedSession = $this->getSessionBackend()->update(
1335  $this->id,
1336  ['ses_data' => $this->user['ses_data']]
1337  );
1338  $this->user = array_merge($this->user ?? [], $updatedSession);
1339  }
1340 
1341  /*************************
1342  *
1343  * Misc
1344  *
1345  *************************/
1352  public function getLoginFormData()
1353  {
1354  $loginData = [];
1355  $loginData['status'] = GeneralUtility::_GP($this->formfield_status);
1356  if ($this->getMethodEnabled) {
1357  $loginData['uname'] = GeneralUtility::_GP($this->formfield_uname);
1358  $loginData['uident'] = GeneralUtility::_GP($this->formfield_uident);
1359  } else {
1360  $loginData['uname'] = GeneralUtility::_POST($this->formfield_uname);
1361  $loginData['uident'] = GeneralUtility::_POST($this->formfield_uident);
1362  }
1363  // Only process the login data if a login is requested
1364  if ($loginData['status'] === 'login') {
1365  $loginData = $this->processLoginData($loginData);
1366  }
1367  $loginData = array_map('trim', $loginData);
1368  return $loginData;
1369  }
1370 
1380  public function processLoginData($loginData, $passwordTransmissionStrategy = '')
1381  {
1382  $loginSecurityLevel = trim($GLOBALS['TYPO3_CONF_VARS'][$this->loginType]['loginSecurityLevel']) ?: 'normal';
1383  $passwordTransmissionStrategy = $passwordTransmissionStrategy ?: $loginSecurityLevel;
1384  if ($this->writeDevLog) {
1385  GeneralUtility::devLog('Login data before processing: ' . GeneralUtility::arrayToLogString($loginData), self::class);
1386  }
1387  $serviceChain = '';
1388  $subType = 'processLoginData' . $this->loginType;
1389  $authInfo = $this->getAuthInfoArray();
1390  $isLoginDataProcessed = false;
1391  $processedLoginData = $loginData;
1392  while (is_object($serviceObject = GeneralUtility::makeInstanceService('auth', $subType, $serviceChain))) {
1393  $serviceChain .= ',' . $serviceObject->getServiceKey();
1394  $serviceObject->initAuth($subType, $loginData, $authInfo, $this);
1395  $serviceResult = $serviceObject->processLoginData($processedLoginData, $passwordTransmissionStrategy);
1396  if (!empty($serviceResult)) {
1397  $isLoginDataProcessed = true;
1398  // If the service returns >=200 then no more processing is needed
1399  if ((int)$serviceResult >= 200) {
1400  unset($serviceObject);
1401  break;
1402  }
1403  }
1404  unset($serviceObject);
1405  }
1406  if ($isLoginDataProcessed) {
1407  $loginData = $processedLoginData;
1408  if ($this->writeDevLog) {
1409  GeneralUtility::devLog('Processed login data: ' . GeneralUtility::arrayToLogString($processedLoginData), self::class);
1410  }
1411  }
1412  return $loginData;
1413  }
1414 
1421  public function getAuthInfoArray()
1422  {
1423  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($this->user_table);
1424  $expressionBuilder = $queryBuilder->expr();
1425  $authInfo = [];
1426  $authInfo['loginType'] = $this->loginType;
1427  $authInfo['refInfo'] = parse_url(GeneralUtility::getIndpEnv('HTTP_REFERER'));
1428  $authInfo['HTTP_HOST'] = GeneralUtility::getIndpEnv('HTTP_HOST');
1429  $authInfo['REMOTE_ADDR'] = GeneralUtility::getIndpEnv('REMOTE_ADDR');
1430  $authInfo['REMOTE_HOST'] = GeneralUtility::getIndpEnv('REMOTE_HOST');
1431  $authInfo['showHiddenRecords'] = $this->showHiddenRecords;
1432  // Can be overidden in localconf by SVCONF:
1433  $authInfo['db_user']['table'] = $this->user_table;
1434  $authInfo['db_user']['userid_column'] = $this->userid_column;
1435  $authInfo['db_user']['username_column'] = $this->username_column;
1436  $authInfo['db_user']['userident_column'] = $this->userident_column;
1437  $authInfo['db_user']['usergroup_column'] = $this->usergroup_column;
1438  $authInfo['db_user']['enable_clause'] = $this->userConstraints()->buildExpression(
1439  [$this->user_table => $this->user_table],
1440  $expressionBuilder
1441  );
1442  if ($this->checkPid && $this->checkPid_value !== null) {
1443  $authInfo['db_user']['checkPidList'] = $this->checkPid_value;
1444  $authInfo['db_user']['check_pid_clause'] = $expressionBuilder->in(
1445  'pid',
1446  GeneralUtility::intExplode(',', $this->checkPid_value)
1447  );
1448  } else {
1449  $authInfo['db_user']['checkPidList'] = '';
1450  $authInfo['db_user']['check_pid_clause'] = '';
1451  }
1452  $authInfo['db_groups']['table'] = $this->usergroup_table;
1453  return $authInfo;
1454  }
1455 
1464  public function compareUident($user, $loginData, $passwordCompareStrategy = '')
1465  {
1466  return (string)$loginData['uident_text'] !== '' && (string)$loginData['uident_text'] === (string)$user[$this->userident_column];
1467  }
1468 
1474  public function gc()
1475  {
1476  $this->getSessionBackend()->collectGarbage($this->gc_time);
1477  }
1478 
1492  public function writelog($type, $action, $error, $details_nr, $details, $data, $tablename, $recuid, $recpid)
1493  {
1494  }
1495 
1504  public function checkLogFailures($email, $secondsBack, $maxFailures)
1505  {
1506  }
1507 
1520  public function setBeUserByUid($uid)
1521  {
1522  $this->user = $this->getRawUserByUid($uid);
1523  }
1524 
1532  public function setBeUserByName($name)
1533  {
1534  $this->user = $this->getRawUserByName($name);
1535  }
1536 
1544  public function getRawUserByUid($uid)
1545  {
1546  $query = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($this->user_table);
1547  $query->setRestrictions($this->userConstraints());
1548  $query->select('*')
1549  ->from($this->user_table)
1550  ->where($query->expr()->eq('uid', $query->createNamedParameter($uid, \PDO::PARAM_INT)));
1551 
1552  return $query->execute()->fetch();
1553  }
1554 
1563  public function getRawUserByName($name)
1564  {
1565  $query = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($this->user_table);
1566  $query->setRestrictions($this->userConstraints());
1567  $query->select('*')
1568  ->from($this->user_table)
1569  ->where($query->expr()->eq('username', $query->createNamedParameter($name, \PDO::PARAM_STR)));
1570 
1571  return $query->execute()->fetch();
1572  }
1573 
1583  public function fetchUserRecord($dbUser, $username, $extraWhere = '')
1584  {
1585  $user = false;
1586  if ($username || $extraWhere) {
1587  $query = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($dbUser['table']);
1588  $query->getRestrictions()->removeAll()
1589  ->add(GeneralUtility::makeInstance(DeletedRestriction::class));
1590 
1591  $constraints = array_filter([
1592  QueryHelper::stripLogicalOperatorPrefix($dbUser['check_pid_clause']),
1593  QueryHelper::stripLogicalOperatorPrefix($dbUser['enable_clause']),
1595  ]);
1596 
1597  if (!empty($username)) {
1598  array_unshift(
1599  $constraints,
1600  $query->expr()->eq(
1601  $dbUser['username_column'],
1602  $query->createNamedParameter($username, \PDO::PARAM_STR)
1603  )
1604  );
1605  }
1606 
1607  $user = $query->select('*')
1608  ->from($dbUser['table'])
1609  ->where(...$constraints)
1610  ->execute()
1611  ->fetch();
1612  }
1613 
1614  return $user;
1615  }
1616 
1621  public function getSessionId(): string
1622  {
1623  return $this->id;
1624  }
1625 
1630  public function getLoginType(): string
1631  {
1632  return $this->loginType;
1633  }
1634 
1640  protected function getSessionBackend()
1641  {
1642  if (!isset($this->sessionBackend)) {
1643  $this->sessionBackend = GeneralUtility::makeInstance(SessionManager::class)->getSessionBackend($this->loginType);
1644  }
1645  return $this->sessionBackend;
1646  }
1647 }
static devLog($msg, $extKey, $severity=0, $dataVar=false)
regenerateSessionId(array $existingSessionRecord=[], bool $anonymous=false)
static intExplode($delimiter, $string, $removeEmptyValues=false, $limit=0)
static arrayToLogString(array $arr, $valueList=[], $valueLength=20)
compareUident($user, $loginData, $passwordCompareStrategy='')
static forceIntegerInRange($theInt, $min, $max=2000000000, $defaultValue=0)
Definition: MathUtility.php:31
static callUserFunction($funcName, &$params, &$ref, $_='', $errorMode=0)
processLoginData($loginData, $passwordTransmissionStrategy='')
static makeInstance($className,... $constructorArguments)
static makeInstanceService($serviceType, $serviceSubType='', $excludeServiceKeys=[])
getAuthServices(string $subType, array $loginData, array $authInfo)
static stripLogicalOperatorPrefix(string $constraint)
writelog($type, $action, $error, $details_nr, $details, $data, $tablename, $recuid, $recpid)
if(TYPO3_MODE==='BE') $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tsfebeuserauth.php']['frontendEditingController']['default']