1<?php 2 3/* 4 * This file is part of the TYPO3 CMS project. 5 * 6 * It is free software; you can redistribute it and/or modify it under 7 * the terms of the GNU General Public License, either version 2 8 * of the License, or any later version. 9 * 10 * For the full copyright and license information, please read the 11 * LICENSE.txt file that was distributed with this source code. 12 * 13 * The TYPO3 project - inspiring people to share! 14 */ 15 16namespace TYPO3\CMS\Core\Authentication; 17 18use Psr\Log\LoggerAwareInterface; 19use Psr\Log\LoggerAwareTrait; 20use Symfony\Component\HttpFoundation\Cookie; 21use TYPO3\CMS\Core\Core\Environment; 22use TYPO3\CMS\Core\Crypto\Random; 23use TYPO3\CMS\Core\Database\Connection; 24use TYPO3\CMS\Core\Database\ConnectionPool; 25use TYPO3\CMS\Core\Database\Query\Restriction\DefaultRestrictionContainer; 26use TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction; 27use TYPO3\CMS\Core\Database\Query\Restriction\EndTimeRestriction; 28use TYPO3\CMS\Core\Database\Query\Restriction\HiddenRestriction; 29use TYPO3\CMS\Core\Database\Query\Restriction\QueryRestrictionContainerInterface; 30use TYPO3\CMS\Core\Database\Query\Restriction\RootLevelRestriction; 31use TYPO3\CMS\Core\Database\Query\Restriction\StartTimeRestriction; 32use TYPO3\CMS\Core\Exception; 33use TYPO3\CMS\Core\Http\CookieHeaderTrait; 34use TYPO3\CMS\Core\Session\Backend\Exception\SessionNotFoundException; 35use TYPO3\CMS\Core\Session\Backend\HashableSessionBackendInterface; 36use TYPO3\CMS\Core\Session\Backend\SessionBackendInterface; 37use TYPO3\CMS\Core\Session\SessionManager; 38use TYPO3\CMS\Core\SysLog\Action\Login as SystemLogLoginAction; 39use TYPO3\CMS\Core\SysLog\Error as SystemLogErrorClassification; 40use TYPO3\CMS\Core\SysLog\Type as SystemLogType; 41use TYPO3\CMS\Core\Utility\GeneralUtility; 42 43/** 44 * Authentication of users in TYPO3 45 * 46 * This class is used to authenticate a login user. 47 * The class is used by both the frontend and backend. 48 * In both cases this class is a parent class to BackendUserAuthentication and FrontendUserAuthentication 49 * 50 * See Inside TYPO3 for more information about the API of the class and internal variables. 51 */ 52abstract class AbstractUserAuthentication implements LoggerAwareInterface 53{ 54 use LoggerAwareTrait; 55 use CookieHeaderTrait; 56 57 /** 58 * Session/Cookie name 59 * @var string 60 */ 61 public $name = ''; 62 63 /** 64 * Table in database with user data 65 * @var string 66 */ 67 public $user_table = ''; 68 69 /** 70 * Table in database with user groups 71 * @var string 72 */ 73 public $usergroup_table = ''; 74 75 /** 76 * Column for login-name 77 * @var string 78 */ 79 public $username_column = ''; 80 81 /** 82 * Column for password 83 * @var string 84 */ 85 public $userident_column = ''; 86 87 /** 88 * Column for user-id 89 * @var string 90 */ 91 public $userid_column = ''; 92 93 /** 94 * Column for user group information 95 * @var string 96 */ 97 public $usergroup_column = ''; 98 99 /** 100 * Column name for last login timestamp 101 * @var string 102 */ 103 public $lastLogin_column = ''; 104 105 /** 106 * Enable field columns of user table 107 * @var array 108 */ 109 public $enablecolumns = [ 110 'rootLevel' => '', 111 // Boolean: If TRUE, 'AND pid=0' will be a part of the query... 112 'disabled' => '', 113 'starttime' => '', 114 'endtime' => '', 115 'deleted' => '', 116 ]; 117 118 /** 119 * @var bool 120 */ 121 public $showHiddenRecords = false; 122 123 /** 124 * Form field with login-name 125 * @var string 126 */ 127 public $formfield_uname = ''; 128 129 /** 130 * Form field with password 131 * @var string 132 */ 133 public $formfield_uident = ''; 134 135 /** 136 * Form field with status: *'login', 'logout'. If empty login is not verified. 137 * @var string 138 */ 139 public $formfield_status = ''; 140 141 /** 142 * Session timeout (on the server) 143 * 144 * If >0: session-timeout in seconds. 145 * If <=0: Instant logout after login. 146 * 147 * @var int 148 */ 149 public $sessionTimeout = 0; 150 151 /** 152 * Name for a field to fetch the server session timeout from. 153 * If not empty this is a field name from the user table where the timeout can be found. 154 * @var string 155 */ 156 public $auth_timeout_field = ''; 157 158 /** 159 * Lifetime for the session-cookie (on the client) 160 * 161 * If >0: permanent cookie with given lifetime 162 * If 0: session-cookie 163 * Session-cookie means the browser will remove it when the browser is closed. 164 * 165 * @var int 166 */ 167 public $lifetime = 0; 168 169 /** 170 * GarbageCollection 171 * Purge all server session data older than $gc_time seconds. 172 * if $this->sessionTimeout > 0, then the session timeout is used instead. 173 * @var int 174 */ 175 public $gc_time = 86400; 176 177 /** 178 * Probability for garbage collection to be run (in percent) 179 * @var int 180 */ 181 public $gc_probability = 1; 182 183 /** 184 * Decides if the writelog() function is called at login and logout 185 * @var bool 186 */ 187 public $writeStdLog = false; 188 189 /** 190 * Log failed login attempts 191 * @var bool 192 */ 193 public $writeAttemptLog = false; 194 195 /** 196 * Send no-cache headers 197 * @var bool 198 */ 199 public $sendNoCacheHeaders = true; 200 201 /** 202 * The ident-hash is normally 32 characters and should be! 203 * But if you are making sites for WAP-devices or other low-bandwidth stuff, 204 * you may shorten the length. 205 * Never let this value drop below 6! 206 * A length of 6 would give you more than 16 mio possibilities. 207 * @var int 208 */ 209 public $hash_length = 32; 210 211 /** 212 * @var string 213 */ 214 public $warningEmail = ''; 215 216 /** 217 * Time span (in seconds) within the number of failed logins are collected 218 * @var int 219 */ 220 public $warningPeriod = 3600; 221 222 /** 223 * The maximum accepted number of warnings before an email to $warningEmail is sent 224 * @var int 225 */ 226 public $warningMax = 3; 227 228 /** 229 * If set, the user-record must be stored at the page defined by $checkPid_value 230 * @var bool 231 */ 232 public $checkPid = true; 233 234 /** 235 * The page id the user record must be stored at 236 * @var int 237 */ 238 public $checkPid_value = 0; 239 240 /** 241 * session_id (MD5-hash) 242 * @var string 243 * @internal 244 */ 245 public $id; 246 247 /** 248 * Indicates if an authentication was started but failed 249 * @var bool 250 */ 251 public $loginFailure = false; 252 253 /** 254 * Will be set to TRUE if the login session is actually written during auth-check. 255 * @var bool 256 */ 257 public $loginSessionStarted = false; 258 259 /** 260 * @var array|null contains user- AND session-data from database (joined tables) 261 * @internal 262 */ 263 public $user; 264 265 /** 266 * Will be set to TRUE if a new session ID was created 267 * @var bool 268 */ 269 public $newSessionID = false; 270 271 /** 272 * Will force the session cookie to be set every time (lifetime must be 0) 273 * @var bool 274 */ 275 public $forceSetCookie = false; 276 277 /** 278 * Will prevent the setting of the session cookie (takes precedence over forceSetCookie) 279 * @var bool 280 */ 281 public $dontSetCookie = false; 282 283 /** 284 * @var bool 285 */ 286 protected $cookieWasSetOnCurrentRequest = false; 287 288 /** 289 * Login type, used for services. 290 * @var string 291 */ 292 public $loginType = ''; 293 294 /** 295 * "auth" services configuration array from $GLOBALS['TYPO3_CONF_VARS']['SVCONF']['auth'] 296 * @var array 297 */ 298 public $svConfig = []; 299 300 /** 301 * @var array 302 */ 303 public $uc; 304 305 /** 306 * @var IpLocker 307 */ 308 protected $ipLocker; 309 310 /** 311 * @var SessionBackendInterface 312 */ 313 protected $sessionBackend; 314 315 /** 316 * Holds deserialized data from session records. 317 * 'Reserved' keys are: 318 * - 'sys': Reserved for TypoScript standard code. 319 * @var array 320 */ 321 protected $sessionData = []; 322 323 /** 324 * Initialize some important variables 325 * 326 * @throws Exception 327 */ 328 public function __construct() 329 { 330 $this->svConfig = $GLOBALS['TYPO3_CONF_VARS']['SVCONF']['auth'] ?? []; 331 // If there is a custom session timeout, use this instead of the 1d default gc time. 332 if ($this->sessionTimeout > 0) { 333 $this->gc_time = $this->sessionTimeout; 334 } 335 // Backend or frontend login - used for auth services 336 if (empty($this->loginType)) { 337 throw new Exception('No loginType defined, must be set explicitly by subclass', 1476045345); 338 } 339 340 $this->ipLocker = GeneralUtility::makeInstance( 341 IpLocker::class, 342 $GLOBALS['TYPO3_CONF_VARS'][$this->loginType]['lockIP'], 343 $GLOBALS['TYPO3_CONF_VARS'][$this->loginType]['lockIPv6'] 344 ); 345 } 346 347 /** 348 * Starts a user session 349 * Typical configurations will: 350 * a) check if session cookie was set and if not, set one, 351 * b) check if a password/username was sent and if so, try to authenticate the user 352 * c) Lookup a session attached to a user and check timeout etc. 353 * d) Garbage collection, setting of no-cache headers. 354 * If a user is authenticated the database record of the user (array) will be set in the ->user internal variable. 355 */ 356 public function start() 357 { 358 $this->logger->debug('## Beginning of auth logging.'); 359 $this->newSessionID = false; 360 // Make certain that NO user is set initially 361 $this->user = null; 362 // sessionID is set to ses_id if cookie is present. Otherwise a new session will start 363 $this->id = $this->getCookie($this->name); 364 365 // If new session or client tries to fix session... 366 if (!$this->isExistingSessionRecord($this->id)) { 367 $this->id = $this->createSessionId(); 368 $this->newSessionID = true; 369 } 370 371 // Set all possible headers that could ensure that the script is not cached on the client-side 372 $this->sendHttpHeaders(); 373 // Load user session, check to see if anyone has submitted login-information and if so authenticate 374 // the user with the session. $this->user[uid] may be used to write log... 375 $this->checkAuthentication(); 376 // Set cookie if generally enabled or if the current session is a non-session cookie (FE permalogin) 377 if (!$this->dontSetCookie || $this->isRefreshTimeBasedCookie()) { 378 $this->setSessionCookie(); 379 } 380 // Hook for alternative ways of filling the $this->user array (is used by the "timtaw" extension) 381 foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_userauth.php']['postUserLookUp'] ?? [] as $funcName) { 382 $_params = [ 383 'pObj' => $this, 384 ]; 385 GeneralUtility::callUserFunction($funcName, $_params, $this); 386 } 387 // If we're lucky we'll get to clean up old sessions 388 if (random_int(0, mt_getrandmax()) % 100 <= $this->gc_probability) { 389 $this->gc(); 390 } 391 } 392 393 /** 394 * Set all possible headers that could ensure that the script 395 * is not cached on the client-side. 396 * 397 * Only do this if $this->sendNoCacheHeaders is set. 398 */ 399 protected function sendHttpHeaders() 400 { 401 // skip sending the "no-cache" headers if it's a CLI request or the no-cache headers should not be sent. 402 if (!$this->sendNoCacheHeaders || Environment::isCli()) { 403 return; 404 } 405 $httpHeaders = $this->getHttpHeaders(); 406 foreach ($httpHeaders as $httpHeaderName => $value) { 407 header($httpHeaderName . ': ' . $value); 408 } 409 } 410 411 /** 412 * Get the http headers to be sent if an authenticated user is available, in order to disallow 413 * browsers to store the response on the client side. 414 * 415 * @return array 416 */ 417 protected function getHttpHeaders(): array 418 { 419 $headers = [ 420 'Expires' => 0, 421 'Last-Modified' => gmdate('D, d M Y H:i:s') . ' GMT' 422 ]; 423 $cacheControlHeader = 'no-cache, must-revalidate'; 424 $pragmaHeader = 'no-cache'; 425 // Prevent error message in IE when using a https connection 426 // see https://forge.typo3.org/issues/24125 427 if (strpos(GeneralUtility::getIndpEnv('HTTP_USER_AGENT'), 'MSIE') !== false 428 && GeneralUtility::getIndpEnv('TYPO3_SSL')) { 429 // Some IEs can not handle no-cache 430 // see http://support.microsoft.com/kb/323308/en-us 431 $cacheControlHeader = 'must-revalidate'; 432 // IE needs "Pragma: private" if SSL connection 433 $pragmaHeader = 'private'; 434 } 435 $headers['Cache-Control'] = $cacheControlHeader; 436 $headers['Pragma'] = $pragmaHeader; 437 return $headers; 438 } 439 440 /** 441 * Sets the session cookie for the current disposal. 442 * 443 * @throws Exception 444 */ 445 protected function setSessionCookie() 446 { 447 $isSetSessionCookie = $this->isSetSessionCookie(); 448 $isRefreshTimeBasedCookie = $this->isRefreshTimeBasedCookie(); 449 if ($isSetSessionCookie || $isRefreshTimeBasedCookie) { 450 $settings = $GLOBALS['TYPO3_CONF_VARS']['SYS']; 451 // Get the domain to be used for the cookie (if any): 452 $cookieDomain = $this->getCookieDomain(); 453 // If no cookie domain is set, use the base path: 454 $cookiePath = $cookieDomain ? '/' : GeneralUtility::getIndpEnv('TYPO3_SITE_PATH'); 455 // If the cookie lifetime is set, use it: 456 $cookieExpire = $isRefreshTimeBasedCookie ? $GLOBALS['EXEC_TIME'] + $this->lifetime : 0; 457 // Use the secure option when the current request is served by a secure connection: 458 $cookieSecure = (bool)$settings['cookieSecure'] && GeneralUtility::getIndpEnv('TYPO3_SSL'); 459 // Valid options are "strict", "lax" or "none", whereas "none" only works in HTTPS requests (default & fallback is "strict") 460 $cookieSameSite = $this->sanitizeSameSiteCookieValue( 461 strtolower($GLOBALS['TYPO3_CONF_VARS'][$this->loginType]['cookieSameSite'] ?? Cookie::SAMESITE_STRICT) 462 ); 463 // SameSite "none" needs the secure option (only allowed on HTTPS) 464 if ($cookieSameSite === Cookie::SAMESITE_NONE) { 465 $cookieSecure = true; 466 } 467 // Do not set cookie if cookieSecure is set to "1" (force HTTPS) and no secure channel is used: 468 if ((int)$settings['cookieSecure'] !== 1 || GeneralUtility::getIndpEnv('TYPO3_SSL')) { 469 $cookie = new Cookie( 470 $this->name, 471 $this->id, 472 $cookieExpire, 473 $cookiePath, 474 $cookieDomain, 475 $cookieSecure, 476 true, 477 false, 478 $cookieSameSite 479 ); 480 header('Set-Cookie: ' . $cookie->__toString(), false); 481 $this->cookieWasSetOnCurrentRequest = true; 482 } else { 483 throw new Exception('Cookie was not set since HTTPS was forced in $TYPO3_CONF_VARS[SYS][cookieSecure].', 1254325546); 484 } 485 $this->logger->debug( 486 ($isRefreshTimeBasedCookie ? 'Updated Cookie: ' : 'Set Cookie: ') 487 . sha1($this->id) . ($cookieDomain ? ', ' . $cookieDomain : '') 488 ); 489 } 490 } 491 492 /** 493 * Gets the domain to be used on setting cookies. 494 * The information is taken from the value in $GLOBALS['TYPO3_CONF_VARS']['SYS']['cookieDomain']. 495 * 496 * @return string The domain to be used on setting cookies 497 */ 498 protected function getCookieDomain() 499 { 500 $result = ''; 501 $cookieDomain = $GLOBALS['TYPO3_CONF_VARS']['SYS']['cookieDomain']; 502 // If a specific cookie domain is defined for a given TYPO3_MODE, 503 // use that domain 504 if (!empty($GLOBALS['TYPO3_CONF_VARS'][$this->loginType]['cookieDomain'])) { 505 $cookieDomain = $GLOBALS['TYPO3_CONF_VARS'][$this->loginType]['cookieDomain']; 506 } 507 if ($cookieDomain) { 508 if ($cookieDomain[0] === '/') { 509 $match = []; 510 $matchCnt = @preg_match($cookieDomain, GeneralUtility::getIndpEnv('TYPO3_HOST_ONLY'), $match); 511 if ($matchCnt === false) { 512 $this->logger->critical('The regular expression for the cookie domain (' . $cookieDomain . ') contains errors. The session is not shared across sub-domains.'); 513 } elseif ($matchCnt) { 514 $result = $match[0]; 515 } 516 } else { 517 $result = $cookieDomain; 518 } 519 } 520 return $result; 521 } 522 523 /** 524 * Get the value of a specified cookie. 525 * 526 * @param string $cookieName The cookie ID 527 * @return string The value stored in the cookie 528 */ 529 protected function getCookie($cookieName) 530 { 531 return isset($_COOKIE[$cookieName]) ? stripslashes($_COOKIE[$cookieName]) : ''; 532 } 533 534 /** 535 * Determine whether a session cookie needs to be set (lifetime=0) 536 * 537 * @return bool 538 * @internal 539 */ 540 public function isSetSessionCookie() 541 { 542 return ($this->newSessionID || $this->forceSetCookie) && $this->lifetime == 0; 543 } 544 545 /** 546 * Determine whether a non-session cookie needs to be set (lifetime>0) 547 * 548 * @return bool 549 * @internal 550 */ 551 public function isRefreshTimeBasedCookie() 552 { 553 return $this->lifetime > 0; 554 } 555 556 /** 557 * Checks if a submission of username and password is present or use other authentication by auth services 558 * 559 * @throws \RuntimeException 560 * @internal 561 */ 562 public function checkAuthentication() 563 { 564 // No user for now - will be searched by service below 565 $tempuserArr = []; 566 $tempuser = false; 567 // User is not authenticated by default 568 $authenticated = false; 569 // User want to login with passed login data (name/password) 570 $activeLogin = false; 571 // Indicates if an active authentication failed (not auto login) 572 $this->loginFailure = false; 573 $this->logger->debug('Login type: ' . $this->loginType); 574 // The info array provide additional information for auth services 575 $authInfo = $this->getAuthInfoArray(); 576 // Get Login/Logout data submitted by a form or params 577 $loginData = $this->getLoginFormData(); 578 $this->logger->debug('Login data', $this->removeSensitiveLoginDataForLoggingInfo($loginData)); 579 // Active logout (eg. with "logout" button) 580 if ($loginData['status'] === LoginType::LOGOUT) { 581 if ($this->writeStdLog) { 582 // $type,$action,$error,$details_nr,$details,$data,$tablename,$recuid,$recpid 583 $this->writelog(SystemLogType::LOGIN, SystemLogLoginAction::LOGOUT, SystemLogErrorClassification::MESSAGE, 2, 'User %s logged out', [$this->user['username']], '', 0, 0); 584 } 585 $this->logger->info('User logged out. Id: ' . sha1($this->id)); 586 $this->logoff(); 587 } 588 // Determine whether we need to skip session update. 589 // This is used mainly for checking session timeout in advance without refreshing the current session's timeout. 590 $skipSessionUpdate = (bool)GeneralUtility::_GP('skipSessionUpdate'); 591 $haveSession = false; 592 $anonymousSession = false; 593 if (!$this->newSessionID) { 594 // Read user session 595 $authInfo['userSession'] = $this->fetchUserSession($skipSessionUpdate); 596 $haveSession = is_array($authInfo['userSession']); 597 if ($haveSession && !empty($authInfo['userSession']['ses_anonymous'])) { 598 $anonymousSession = true; 599 } 600 } 601 602 // Active login (eg. with login form). 603 if (!$haveSession && $loginData['status'] === LoginType::LOGIN) { 604 $activeLogin = true; 605 $this->logger->debug('Active login (eg. with login form)'); 606 // check referrer for submitted login values 607 if ($this->formfield_status && $loginData['uident'] && $loginData['uname']) { 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 (stripos($loginData['uname'], '_CLI_') === 0 && !Environment::isCli()) { 614 throw new \RuntimeException('TYPO3 Fatal Error: You have tried to login using a CLI user. Access prohibited!', 1270853931); 615 } 616 } 617 618 // Cause elevation of privilege, make sure regenerateSessionId is called later on 619 if ($anonymousSession && $loginData['status'] === LoginType::LOGIN) { 620 $activeLogin = true; 621 } 622 623 if ($haveSession) { 624 $this->logger->debug('User session found', [ 625 $this->userid_column => $authInfo['userSession'][$this->userid_column] ?? null, 626 $this->username_column => $authInfo['userSession'][$this->username_column] ?? null, 627 ]); 628 } else { 629 $this->logger->debug('No user session found'); 630 } 631 if (is_array($this->svConfig['setup'] ?? false)) { 632 $this->logger->debug('SV setup', $this->svConfig['setup']); 633 } 634 635 // Fetch user if ... 636 if ( 637 $activeLogin || !empty($this->svConfig['setup'][$this->loginType . '_alwaysFetchUser']) 638 || !$haveSession && !empty($this->svConfig['setup'][$this->loginType . '_fetchUserIfNoSession']) 639 ) { 640 // Use 'auth' service to find the user 641 // First found user will be used 642 $subType = 'getUser' . $this->loginType; 643 /** @var AuthenticationService $serviceObj */ 644 foreach ($this->getAuthServices($subType, $loginData, $authInfo) as $serviceObj) { 645 if ($row = $serviceObj->getUser()) { 646 $tempuserArr[] = $row; 647 $this->logger->debug('User found', [ 648 $this->userid_column => $row[$this->userid_column], 649 $this->username_column => $row[$this->username_column], 650 ]); 651 // User found, just stop to search for more if not configured to go on 652 if (empty($this->svConfig['setup'][$this->loginType . '_fetchAllUsers'])) { 653 break; 654 } 655 } 656 } 657 658 if (!empty($this->svConfig['setup'][$this->loginType . '_alwaysFetchUser'])) { 659 $this->logger->debug($this->loginType . '_alwaysFetchUser option is enabled'); 660 } 661 if (empty($tempuserArr)) { 662 $this->logger->debug('No user found by services'); 663 } else { 664 $this->logger->debug(count($tempuserArr) . ' user records found by services'); 665 } 666 } 667 668 // If no new user was set we use the already found user session 669 if (empty($tempuserArr) && $haveSession && !$anonymousSession) { 670 $tempuserArr[] = $authInfo['userSession']; 671 $tempuser = $authInfo['userSession']; 672 // User is authenticated because we found a user session 673 $authenticated = true; 674 $this->logger->debug('User session used', [ 675 $this->userid_column => $authInfo['userSession'][$this->userid_column], 676 $this->username_column => $authInfo['userSession'][$this->username_column], 677 ]); 678 } 679 // Re-auth user when 'auth'-service option is set 680 if (!empty($this->svConfig['setup'][$this->loginType . '_alwaysAuthUser'])) { 681 $authenticated = false; 682 $this->logger->debug('alwaysAuthUser option is enabled'); 683 } 684 // Authenticate the user if needed 685 if (!empty($tempuserArr) && !$authenticated) { 686 foreach ($tempuserArr as $tempuser) { 687 // Use 'auth' service to authenticate the user 688 // If one service returns FALSE then authentication failed 689 // a service might return 100 which means there's no reason to stop but the user can't be authenticated by that service 690 $this->logger->debug('Auth user', $this->removeSensitiveLoginDataForLoggingInfo($tempuser, true)); 691 $subType = 'authUser' . $this->loginType; 692 693 /** @var AuthenticationService $serviceObj */ 694 foreach ($this->getAuthServices($subType, $loginData, $authInfo) as $serviceObj) { 695 if (($ret = $serviceObj->authUser($tempuser)) > 0) { 696 // If the service returns >=200 then no more checking is needed - useful for IP checking without password 697 if ((int)$ret >= 200) { 698 $authenticated = true; 699 break; 700 } 701 if ((int)$ret >= 100) { 702 } else { 703 $authenticated = true; 704 } 705 } else { 706 $authenticated = false; 707 break; 708 } 709 } 710 711 if ($authenticated) { 712 // Leave foreach() because a user is authenticated 713 break; 714 } 715 } 716 } 717 718 // If user is authenticated a valid user is in $tempuser 719 if ($authenticated) { 720 // Reset failure flag 721 $this->loginFailure = false; 722 // Insert session record if needed: 723 if (!$haveSession || $anonymousSession || $tempuser['ses_id'] != $this->id && $tempuser['uid'] != $authInfo['userSession']['ses_userid']) { 724 $sessionData = $this->createUserSession($tempuser); 725 726 // Preserve session data on login 727 if ($anonymousSession) { 728 $sessionData = $this->getSessionBackend()->update( 729 $this->id, 730 ['ses_data' => $authInfo['userSession']['ses_data']] 731 ); 732 } 733 734 $this->user = array_merge( 735 $tempuser, 736 $sessionData 737 ); 738 // The login session is started. 739 $this->loginSessionStarted = true; 740 if (is_array($this->user)) { 741 $this->logger->debug('User session finally read', [ 742 $this->userid_column => $this->user[$this->userid_column], 743 $this->username_column => $this->user[$this->username_column], 744 ]); 745 } 746 } elseif ($haveSession) { 747 // Validate the session ID and promote it 748 // This check can be removed in TYPO3 v11 749 if ($this->getSessionBackend() instanceof HashableSessionBackendInterface && !empty($authInfo['userSession']['ses_id'] ?? '')) { 750 // The session is stored in plaintext, promote it 751 if ($authInfo['userSession']['ses_id'] === $this->id) { 752 $authInfo['userSession'] = $this->getSessionBackend()->update( 753 $this->id, 754 ['ses_data' => $authInfo['userSession']['ses_data']] 755 ); 756 } 757 } 758 // if we come here the current session is for sure not anonymous as this is a pre-condition for $authenticated = true 759 $this->user = $authInfo['userSession']; 760 } 761 762 if ($activeLogin && !$this->newSessionID) { 763 $this->regenerateSessionId(); 764 } 765 766 // User logged in - write that to the log! 767 if ($this->writeStdLog && $activeLogin) { 768 $this->writelog(SystemLogType::LOGIN, SystemLogLoginAction::LOGIN, SystemLogErrorClassification::MESSAGE, 1, 'User %s logged in from ###IP###', [$tempuser[$this->username_column]], '', '', ''); 769 } 770 if ($activeLogin) { 771 $this->logger->info('User ' . $tempuser[$this->username_column] . ' logged in from ' . GeneralUtility::getIndpEnv('REMOTE_ADDR')); 772 } 773 if (!$activeLogin) { 774 $this->logger->debug('User ' . $tempuser[$this->username_column] . ' authenticated from ' . GeneralUtility::getIndpEnv('REMOTE_ADDR')); 775 } 776 } else { 777 // User was not authenticated, so we should reuse the existing anonymous session 778 if ($anonymousSession) { 779 $this->user = $authInfo['userSession']; 780 } 781 782 // Mark the current login attempt as failed 783 if ($activeLogin || !empty($tempuserArr)) { 784 $this->loginFailure = true; 785 if (empty($tempuserArr) && $activeLogin) { 786 $logData = [ 787 'loginData' => $this->removeSensitiveLoginDataForLoggingInfo($loginData) 788 ]; 789 $this->logger->debug('Login failed', $logData); 790 } 791 if (!empty($tempuserArr)) { 792 $logData = [ 793 $this->userid_column => $tempuser[$this->userid_column], 794 $this->username_column => $tempuser[$this->username_column], 795 ]; 796 $this->logger->debug('Login failed', $logData); 797 } 798 } 799 } 800 801 // If there were a login failure, check to see if a warning email should be sent: 802 if ($this->loginFailure && $activeLogin) { 803 $this->logger->debug( 804 'Call checkLogFailures', 805 [ 806 'warningEmail' => $this->warningEmail, 807 'warningPeriod' => $this->warningPeriod, 808 'warningMax' => $this->warningMax 809 ] 810 ); 811 812 // Hook to implement login failure tracking methods 813 $_params = []; 814 $sleep = true; 815 foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_userauth.php']['postLoginFailureProcessing'] ?? [] as $_funcRef) { 816 GeneralUtility::callUserFunction($_funcRef, $_params, $this); 817 $sleep = false; 818 } 819 820 if ($sleep) { 821 // No hooks were triggered - default login failure behavior is to sleep 5 seconds 822 sleep(5); 823 } 824 825 $this->checkLogFailures($this->warningEmail, $this->warningPeriod, $this->warningMax); 826 } 827 } 828 829 /** 830 * Creates a new session ID. 831 * 832 * @return string The new session ID 833 */ 834 public function createSessionId() 835 { 836 return GeneralUtility::makeInstance(Random::class)->generateRandomHexString($this->hash_length); 837 } 838 839 /** 840 * Initializes authentication services to be used in a foreach loop 841 * 842 * @param string $subType e.g. getUserFE 843 * @param array $loginData 844 * @param array $authInfo 845 * @return \Traversable A generator of service objects 846 */ 847 protected function getAuthServices(string $subType, array $loginData, array $authInfo): \Traversable 848 { 849 $serviceChain = []; 850 while (is_object($serviceObj = GeneralUtility::makeInstanceService('auth', $subType, $serviceChain))) { 851 $serviceChain[] = $serviceObj->getServiceKey(); 852 $serviceObj->initAuth($subType, $loginData, $authInfo, $this); 853 yield $serviceObj; 854 } 855 if (!empty($serviceChain)) { 856 $this->logger->debug($subType . ' auth services called: ' . implode(', ', $serviceChain)); 857 } 858 } 859 860 /** 861 * Regenerate the session ID and transfer the session to new ID 862 * Call this method whenever a user proceeds to a higher authorization level 863 * e.g. when an anonymous session is now authenticated. 864 * 865 * @param array $existingSessionRecord If given, this session record will be used instead of fetching again 866 * @param bool $anonymous If true session will be regenerated as anonymous session 867 */ 868 protected function regenerateSessionId(array $existingSessionRecord = [], bool $anonymous = false) 869 { 870 if (empty($existingSessionRecord)) { 871 $existingSessionRecord = $this->getSessionBackend()->get($this->id); 872 } 873 874 // Update session record with new ID 875 $oldSessionId = $this->id; 876 $this->id = $this->createSessionId(); 877 $updatedSession = $this->getSessionBackend()->set($this->id, $existingSessionRecord); 878 $this->sessionData = unserialize($updatedSession['ses_data']); 879 // Merge new session data into user/session array 880 $this->user = array_merge($this->user ?? [], $updatedSession); 881 $this->getSessionBackend()->remove($oldSessionId); 882 $this->newSessionID = true; 883 } 884 885 /************************* 886 * 887 * User Sessions 888 * 889 *************************/ 890 891 /** 892 * Creates a user session record and returns its values. 893 * 894 * @param array $tempuser User data array 895 * 896 * @return array The session data for the newly created session. 897 */ 898 public function createUserSession($tempuser) 899 { 900 $this->logger->debug('Create session ses_id = ' . sha1($this->id)); 901 // Delete any session entry first 902 $this->getSessionBackend()->remove($this->id); 903 // Re-create session entry 904 $sessionRecord = $this->getNewSessionRecord($tempuser); 905 $sessionRecord = $this->getSessionBackend()->set($this->id, $sessionRecord); 906 // Updating lastLogin_column carrying information about last login. 907 $this->updateLoginTimestamp($tempuser[$this->userid_column]); 908 return $sessionRecord; 909 } 910 911 /** 912 * Updates the last login column in the user with the given id 913 * 914 * @param int $userId 915 */ 916 protected function updateLoginTimestamp(int $userId) 917 { 918 if ($this->lastLogin_column) { 919 $connection = GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable($this->user_table); 920 $connection->update( 921 $this->user_table, 922 [$this->lastLogin_column => $GLOBALS['EXEC_TIME']], 923 [$this->userid_column => $userId] 924 ); 925 } 926 } 927 928 /** 929 * Returns a new session record for the current user for insertion into the DB. 930 * This function is mainly there as a wrapper for inheriting classes to override it. 931 * 932 * @param array $tempuser 933 * @return array User session record 934 */ 935 public function getNewSessionRecord($tempuser) 936 { 937 $sessionIpLock = $this->ipLocker->getSessionIpLock((string)GeneralUtility::getIndpEnv('REMOTE_ADDR'), empty($tempuser['disableIPlock'])); 938 939 return [ 940 'ses_id' => $this->id, 941 'ses_iplock' => $sessionIpLock, 942 'ses_userid' => $tempuser[$this->userid_column] ?? 0, 943 'ses_tstamp' => $GLOBALS['EXEC_TIME'], 944 'ses_data' => '', 945 ]; 946 } 947 948 /** 949 * Read the user session from db. 950 * 951 * @param bool $skipSessionUpdate 952 * @return array|bool User session data, false if $this->id does not represent valid session 953 */ 954 public function fetchUserSession($skipSessionUpdate = false) 955 { 956 $this->logger->debug('Fetch session ses_id = ' . sha1($this->id)); 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 1001 /** 1002 * Regenerates the session ID and sets the cookie again. 1003 * 1004 * @internal 1005 */ 1006 public function enforceNewSessionId() 1007 { 1008 $this->regenerateSessionId(); 1009 $this->setSessionCookie(); 1010 } 1011 1012 /** 1013 * Log out current user! 1014 * Removes the current session record, sets the internal ->user array to a blank string; 1015 * Thereby the current user (if any) is effectively logged out! 1016 */ 1017 public function logoff() 1018 { 1019 $this->logger->debug('logoff: ses_id = ' . sha1($this->id)); 1020 1021 $_params = []; 1022 foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_userauth.php']['logoff_pre_processing'] ?? [] as $_funcRef) { 1023 if ($_funcRef) { 1024 GeneralUtility::callUserFunction($_funcRef, $_params, $this); 1025 } 1026 } 1027 $this->performLogoff(); 1028 1029 foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_userauth.php']['logoff_post_processing'] ?? [] as $_funcRef) { 1030 if ($_funcRef) { 1031 GeneralUtility::callUserFunction($_funcRef, $_params, $this); 1032 } 1033 } 1034 } 1035 1036 /** 1037 * Perform the logoff action. Called from logoff() as a way to allow subclasses to override 1038 * what happens when a user logs off, without needing to reproduce the hook calls and logging 1039 * that happens in the public logoff() API method. 1040 */ 1041 protected function performLogoff() 1042 { 1043 if ($this->id) { 1044 $this->getSessionBackend()->remove($this->id); 1045 } 1046 $this->sessionData = []; 1047 $this->user = null; 1048 if ($this->isCookieSet()) { 1049 $this->removeCookie($this->name); 1050 } 1051 } 1052 1053 /** 1054 * Empty / unset the cookie 1055 * 1056 * @param string $cookieName usually, this is $this->name 1057 */ 1058 public function removeCookie($cookieName) 1059 { 1060 $cookieDomain = $this->getCookieDomain(); 1061 // If no cookie domain is set, use the base path 1062 $cookiePath = $cookieDomain ? '/' : GeneralUtility::getIndpEnv('TYPO3_SITE_PATH'); 1063 setcookie($cookieName, '', -1, $cookiePath, $cookieDomain); 1064 } 1065 1066 /** 1067 * Determine whether there's an according session record to a given session_id. 1068 * Don't care if session record is still valid or not. 1069 * 1070 * @param string $id Claimed Session ID 1071 * @return bool Returns TRUE if a corresponding session was found in the database 1072 */ 1073 public function isExistingSessionRecord($id) 1074 { 1075 if (empty($id)) { 1076 return false; 1077 } 1078 try { 1079 $sessionRecord = $this->getSessionBackend()->get($id); 1080 if (empty($sessionRecord)) { 1081 return false; 1082 } 1083 // If the session does not match the current IP lock, it should be treated as invalid 1084 // and a new session should be created. 1085 return $this->ipLocker->validateRemoteAddressAgainstSessionIpLock((string)GeneralUtility::getIndpEnv('REMOTE_ADDR'), $sessionRecord['ses_iplock']); 1086 } catch (SessionNotFoundException $e) { 1087 return false; 1088 } 1089 } 1090 1091 /** 1092 * Returns whether this request is going to set a cookie 1093 * or a cookie was already found in the system 1094 * 1095 * @return bool Returns TRUE if a cookie is set 1096 */ 1097 public function isCookieSet() 1098 { 1099 return $this->cookieWasSetOnCurrentRequest || $this->getCookie($this->name); 1100 } 1101 1102 /************************* 1103 * 1104 * SQL Functions 1105 * 1106 *************************/ 1107 /** 1108 * This returns the restrictions needed to select the user respecting 1109 * enable columns and flags like deleted, hidden, starttime, endtime 1110 * and rootLevel 1111 * 1112 * @return QueryRestrictionContainerInterface 1113 * @internal 1114 */ 1115 protected function userConstraints(): QueryRestrictionContainerInterface 1116 { 1117 $restrictionContainer = GeneralUtility::makeInstance(DefaultRestrictionContainer::class); 1118 1119 if (empty($this->enablecolumns['disabled'])) { 1120 $restrictionContainer->removeByType(HiddenRestriction::class); 1121 } 1122 1123 if (empty($this->enablecolumns['deleted'])) { 1124 $restrictionContainer->removeByType(DeletedRestriction::class); 1125 } 1126 1127 if (empty($this->enablecolumns['starttime'])) { 1128 $restrictionContainer->removeByType(StartTimeRestriction::class); 1129 } 1130 1131 if (empty($this->enablecolumns['endtime'])) { 1132 $restrictionContainer->removeByType(EndTimeRestriction::class); 1133 } 1134 1135 if (!empty($this->enablecolumns['rootLevel'])) { 1136 $restrictionContainer->add(GeneralUtility::makeInstance(RootLevelRestriction::class, [$this->user_table])); 1137 } 1138 1139 return $restrictionContainer; 1140 } 1141 1142 /************************* 1143 * 1144 * Session and Configuration Handling 1145 * 1146 *************************/ 1147 /** 1148 * This writes $variable to the user-record. This is a way of providing session-data. 1149 * You can fetch the data again through $this->uc in this class! 1150 * If $variable is not an array, $this->uc is saved! 1151 * 1152 * @param array|string $variable An array you want to store for the user as session data. If $variable is not supplied (is null), the internal variable, ->uc, is stored by default 1153 */ 1154 public function writeUC($variable = '') 1155 { 1156 if (is_array($this->user) && $this->user[$this->userid_column]) { 1157 if (!is_array($variable)) { 1158 $variable = $this->uc; 1159 } 1160 $this->logger->debug('writeUC: ' . $this->userid_column . '=' . (int)$this->user[$this->userid_column]); 1161 GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable($this->user_table)->update( 1162 $this->user_table, 1163 ['uc' => serialize($variable)], 1164 [$this->userid_column => (int)$this->user[$this->userid_column]], 1165 ['uc' => Connection::PARAM_LOB] 1166 ); 1167 } 1168 } 1169 1170 /** 1171 * Sets $theUC as the internal variable ->uc IF $theUC is an array. 1172 * If $theUC is FALSE, the 'uc' content from the ->user array will be unserialized and restored in ->uc 1173 * 1174 * @param mixed $theUC If an array, then set as ->uc, otherwise load from user record 1175 */ 1176 public function unpack_uc($theUC = '') 1177 { 1178 if (!$theUC && isset($this->user['uc'])) { 1179 $theUC = unserialize($this->user['uc'], ['allowed_classes' => false]); 1180 } 1181 if (is_array($theUC)) { 1182 $this->uc = $theUC; 1183 } 1184 } 1185 1186 /** 1187 * Stores data for a module. 1188 * The data is stored with the session id so you can even check upon retrieval 1189 * if the module data is from a previous session or from the current session. 1190 * 1191 * @param string $module Is the name of the module ($MCONF['name']) 1192 * @param mixed $data Is the data you want to store for that module (array, string, ...) 1193 * @param bool|int $noSave If $noSave is set, then the ->uc array (which carries all kinds of user data) is NOT written immediately, but must be written by some subsequent call. 1194 */ 1195 public function pushModuleData($module, $data, $noSave = 0) 1196 { 1197 $sessionHash = GeneralUtility::hmac( 1198 $this->id, 1199 'core-session-hash' 1200 ); 1201 $this->uc['moduleData'][$module] = $data; 1202 $this->uc['moduleSessionID'][$module] = $sessionHash; 1203 if (!$noSave) { 1204 $this->writeUC(); 1205 } 1206 } 1207 1208 /** 1209 * Gets module data for a module (from a loaded ->uc array) 1210 * 1211 * @param string $module Is the name of the module ($MCONF['name']) 1212 * @param string $type If $type = 'ses' then module data is returned only if it was stored in the current session, otherwise data from a previous session will be returned (if available). 1213 * @return mixed The module data if available: $this->uc['moduleData'][$module]; 1214 */ 1215 public function getModuleData($module, $type = '') 1216 { 1217 $sessionHash = GeneralUtility::hmac( 1218 $this->id, 1219 'core-session-hash' 1220 ); 1221 $sessionData = $this->uc['moduleData'][$module] ?? null; 1222 $moduleSessionIdHash = $this->uc['moduleSessionID'][$module] ?? null; 1223 if ($type !== 'ses' 1224 || $sessionData !== null && $moduleSessionIdHash === $sessionHash 1225 // @todo Fallback for non-hashed values in `moduleSessionID`, remove for TYPO3 v11.5 LTS 1226 || $sessionData !== null && $moduleSessionIdHash === $this->id 1227 ) { 1228 return $sessionData; 1229 } 1230 return null; 1231 } 1232 1233 /** 1234 * Returns the session data stored for $key. 1235 * The data will last only for this login session since it is stored in the user session. 1236 * 1237 * @param string $key The key associated with the session data 1238 * @return mixed 1239 */ 1240 public function getSessionData($key) 1241 { 1242 return $this->sessionData[$key] ?? null; 1243 } 1244 1245 /** 1246 * Set session data by key. 1247 * The data will last only for this login session since it is stored in the user session. 1248 * 1249 * @param string $key A non empty string to store the data under 1250 * @param mixed $data Data store store in session 1251 */ 1252 public function setSessionData($key, $data) 1253 { 1254 if (empty($key)) { 1255 throw new \InvalidArgumentException('Argument key must not be empty', 1484311516); 1256 } 1257 $this->sessionData[$key] = $data; 1258 } 1259 1260 /** 1261 * Sets the session data ($data) for $key and writes all session data (from ->user['ses_data']) to the database. 1262 * The data will last only for this login session since it is stored in the session table. 1263 * 1264 * @param string $key Pointer to an associative key in the session data array which is stored serialized in the field "ses_data" of the session table. 1265 * @param mixed $data The data to store in index $key 1266 */ 1267 public function setAndSaveSessionData($key, $data) 1268 { 1269 $this->sessionData[$key] = $data; 1270 $this->user['ses_data'] = serialize($this->sessionData); 1271 $this->logger->debug('setAndSaveSessionData: ses_id = ' . sha1($this->id)); 1272 $updatedSession = $this->getSessionBackend()->update( 1273 $this->id, 1274 ['ses_data' => $this->user['ses_data']] 1275 ); 1276 $this->user = array_merge($this->user ?? [], $updatedSession); 1277 } 1278 1279 /************************* 1280 * 1281 * Misc 1282 * 1283 *************************/ 1284 /** 1285 * Returns an info array with Login/Logout data submitted by a form or params 1286 * 1287 * @return array 1288 * @internal 1289 */ 1290 public function getLoginFormData() 1291 { 1292 $loginData = [ 1293 'status' => GeneralUtility::_GP($this->formfield_status), 1294 'uname' => GeneralUtility::_POST($this->formfield_uname), 1295 'uident' => GeneralUtility::_POST($this->formfield_uident) 1296 ]; 1297 // Only process the login data if a login is requested 1298 if ($loginData['status'] === LoginType::LOGIN) { 1299 $loginData = $this->processLoginData($loginData); 1300 } 1301 return $loginData; 1302 } 1303 1304 /** 1305 * Processes Login data submitted by a form or params depending on the 1306 * passwordTransmissionStrategy 1307 * 1308 * @param array $loginData Login data array 1309 * @param string $passwordTransmissionStrategy Alternative passwordTransmissionStrategy. Used when authentication services wants to override the default. 1310 * @return array 1311 * @internal 1312 */ 1313 public function processLoginData($loginData, $passwordTransmissionStrategy = '') 1314 { 1315 $loginSecurityLevel = trim($GLOBALS['TYPO3_CONF_VARS'][$this->loginType]['loginSecurityLevel']) ?: 'normal'; 1316 $passwordTransmissionStrategy = $passwordTransmissionStrategy ?: $loginSecurityLevel; 1317 $this->logger->debug('Login data before processing', $this->removeSensitiveLoginDataForLoggingInfo($loginData)); 1318 $subType = 'processLoginData' . $this->loginType; 1319 $authInfo = $this->getAuthInfoArray(); 1320 $isLoginDataProcessed = false; 1321 $processedLoginData = $loginData; 1322 /** @var AuthenticationService $serviceObject */ 1323 foreach ($this->getAuthServices($subType, $loginData, $authInfo) as $serviceObject) { 1324 $serviceResult = $serviceObject->processLoginData($processedLoginData, $passwordTransmissionStrategy); 1325 if (!empty($serviceResult)) { 1326 $isLoginDataProcessed = true; 1327 // If the service returns >=200 then no more processing is needed 1328 if ((int)$serviceResult >= 200) { 1329 break; 1330 } 1331 } 1332 } 1333 if ($isLoginDataProcessed) { 1334 $loginData = $processedLoginData; 1335 $this->logger->debug('Processed login data', $this->removeSensitiveLoginDataForLoggingInfo($processedLoginData)); 1336 } 1337 return $loginData; 1338 } 1339 1340 /** 1341 * Removes any sensitive data from the incoming data (either from loginData, processedLogin data 1342 * or the user record from the DB). 1343 * 1344 * No type hinting is added because it might be possible that the incoming data is of any other type. 1345 * 1346 * @param mixed|array $data 1347 * @param bool $isUserRecord 1348 * @return mixed 1349 */ 1350 protected function removeSensitiveLoginDataForLoggingInfo($data, bool $isUserRecord = false) 1351 { 1352 if ($isUserRecord && is_array($data)) { 1353 $fieldNames = ['uid', 'pid', 'tstamp', 'crdate', 'cruser_id', 'deleted', 'disabled', 'starttime', 'endtime', 'username', 'admin', 'usergroup', 'db_mountpoints', 'file_mountpoints', 'file_permissions', 'workspace_perms', 'lastlogin', 'workspace_id', 'category_perms']; 1354 $data = array_intersect_key($data, array_combine($fieldNames, $fieldNames)); 1355 } 1356 if (isset($data['uident'])) { 1357 $data['uident'] = '********'; 1358 } 1359 if (isset($data['uident_text'])) { 1360 $data['uident_text'] = '********'; 1361 } 1362 if (isset($data['password'])) { 1363 $data['password'] = '********'; 1364 } 1365 return $data; 1366 } 1367 1368 /** 1369 * Returns an info array which provides additional information for auth services 1370 * 1371 * @return array 1372 * @internal 1373 */ 1374 public function getAuthInfoArray() 1375 { 1376 $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($this->user_table); 1377 $expressionBuilder = $queryBuilder->expr(); 1378 $authInfo = []; 1379 $authInfo['loginType'] = $this->loginType; 1380 $authInfo['refInfo'] = parse_url(GeneralUtility::getIndpEnv('HTTP_REFERER')); 1381 $authInfo['HTTP_HOST'] = GeneralUtility::getIndpEnv('HTTP_HOST'); 1382 $authInfo['REMOTE_ADDR'] = GeneralUtility::getIndpEnv('REMOTE_ADDR'); 1383 $authInfo['REMOTE_HOST'] = GeneralUtility::getIndpEnv('REMOTE_HOST'); 1384 $authInfo['showHiddenRecords'] = $this->showHiddenRecords; 1385 // Can be overridden in localconf by SVCONF: 1386 $authInfo['db_user']['table'] = $this->user_table; 1387 $authInfo['db_user']['userid_column'] = $this->userid_column; 1388 $authInfo['db_user']['username_column'] = $this->username_column; 1389 $authInfo['db_user']['userident_column'] = $this->userident_column; 1390 $authInfo['db_user']['usergroup_column'] = $this->usergroup_column; 1391 $authInfo['db_user']['enable_clause'] = $this->userConstraints()->buildExpression( 1392 [$this->user_table => $this->user_table], 1393 $expressionBuilder 1394 ); 1395 if ($this->checkPid && $this->checkPid_value !== null) { 1396 $authInfo['db_user']['checkPidList'] = $this->checkPid_value; 1397 $authInfo['db_user']['check_pid_clause'] = $expressionBuilder->in( 1398 'pid', 1399 GeneralUtility::intExplode(',', (string)$this->checkPid_value) 1400 ); 1401 } else { 1402 $authInfo['db_user']['checkPidList'] = ''; 1403 $authInfo['db_user']['check_pid_clause'] = ''; 1404 } 1405 $authInfo['db_groups']['table'] = $this->usergroup_table; 1406 return $authInfo; 1407 } 1408 1409 /** 1410 * Garbage collector, removing old expired sessions. 1411 * 1412 * @internal 1413 */ 1414 public function gc() 1415 { 1416 $this->getSessionBackend()->collectGarbage($this->gc_time); 1417 } 1418 1419 /** 1420 * DUMMY: Writes to log database table (in some extension classes) 1421 * 1422 * @param int $type denotes which module that has submitted the entry. This is the current list: 1=tce_db; 2=tce_file; 3=system (eg. sys_history save); 4=modules; 254=Personal settings changed; 255=login / out action: 1=login, 2=logout, 3=failed login (+ errorcode 3), 4=failure_warning_email sent 1423 * @param int $action denotes which specific operation that wrote the entry (eg. 'delete', 'upload', 'update' and so on...). Specific for each $type. Also used to trigger update of the interface. (see the log-module for the meaning of each number !!) 1424 * @param int $error flag. 0 = message, 1 = error (user problem), 2 = System Error (which should not happen), 3 = security notice (admin) 1425 * @param int $details_nr The message number. Specific for each $type and $action. in the future this will make it possible to translate error messages to other languages 1426 * @param string $details Default text that follows the message 1427 * @param array $data Data that follows the log. Might be used to carry special information. If an array the first 5 entries (0-4) will be sprintf'ed the details-text... 1428 * @param string $tablename Special field used by tce_main.php. These ($tablename, $recuid, $recpid) holds the reference to the record which the log-entry is about. (Was used in attic status.php to update the interface.) 1429 * @param int|string $recuid Special field used by tce_main.php. These ($tablename, $recuid, $recpid) holds the reference to the record which the log-entry is about. (Was used in attic status.php to update the interface.) 1430 * @param int|string $recpid Special field used by tce_main.php. These ($tablename, $recuid, $recpid) holds the reference to the record which the log-entry is about. (Was used in attic status.php to update the interface.) 1431 */ 1432 public function writelog($type, $action, $error, $details_nr, $details, $data, $tablename, $recuid, $recpid) 1433 { 1434 } 1435 1436 /** 1437 * DUMMY: Check login failures (in some extension classes) 1438 * 1439 * @param string $email Email address 1440 * @param int $secondsBack Number of sections back in time to check. This is a kind of limit for how many failures an hour for instance 1441 * @param int $maxFailures Max allowed failures before a warning mail is sent 1442 * @ignore 1443 */ 1444 public function checkLogFailures($email, $secondsBack, $maxFailures) 1445 { 1446 } 1447 1448 /** 1449 * Raw initialization of the be_user with uid=$uid 1450 * This will circumvent all login procedures and select a be_users record from the 1451 * database and set the content of ->user to the record selected. 1452 * Thus the BE_USER object will appear like if a user was authenticated - however without 1453 * a session id and the fields from the session table of course. 1454 * Will check the users for disabled, start/endtime, etc. ($this->user_where_clause()) 1455 * 1456 * @param int $uid The UID of the backend user to set in ->user 1457 * @internal 1458 */ 1459 public function setBeUserByUid($uid) 1460 { 1461 $this->user = $this->getRawUserByUid($uid); 1462 } 1463 1464 /** 1465 * Raw initialization of the be_user with username=$name 1466 * 1467 * @param string $name The username to look up. 1468 * @see \TYPO3\CMS\Core\Authentication\AbstractUserAuthentication::setBeUserByUid() 1469 * @internal 1470 */ 1471 public function setBeUserByName($name) 1472 { 1473 $this->user = $this->getRawUserByName($name); 1474 } 1475 1476 /** 1477 * Fetching raw user record with uid=$uid 1478 * 1479 * @param int $uid The UID of the backend user to set in ->user 1480 * @return array user record or FALSE 1481 * @internal 1482 */ 1483 public function getRawUserByUid($uid) 1484 { 1485 $query = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($this->user_table); 1486 $query->setRestrictions($this->userConstraints()); 1487 $query->select('*') 1488 ->from($this->user_table) 1489 ->where($query->expr()->eq('uid', $query->createNamedParameter($uid, \PDO::PARAM_INT))); 1490 1491 return $query->execute()->fetch(); 1492 } 1493 1494 /** 1495 * Fetching raw user record with username=$name 1496 * 1497 * @param string $name The username to look up. 1498 * @return array user record or FALSE 1499 * @see \TYPO3\CMS\Core\Authentication\AbstractUserAuthentication::getUserByUid() 1500 * @internal 1501 */ 1502 public function getRawUserByName($name) 1503 { 1504 $query = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($this->user_table); 1505 $query->setRestrictions($this->userConstraints()); 1506 $query->select('*') 1507 ->from($this->user_table) 1508 ->where($query->expr()->eq('username', $query->createNamedParameter($name, \PDO::PARAM_STR))); 1509 1510 return $query->execute()->fetch(); 1511 } 1512 1513 /** 1514 * @internal 1515 * @return string 1516 */ 1517 public function getSessionId(): string 1518 { 1519 return $this->id; 1520 } 1521 1522 /** 1523 * @internal 1524 * @return string 1525 */ 1526 public function getLoginType(): string 1527 { 1528 return $this->loginType; 1529 } 1530 1531 /** 1532 * Returns initialized session backend. Returns same session backend if called multiple times 1533 * 1534 * @return SessionBackendInterface 1535 */ 1536 protected function getSessionBackend() 1537 { 1538 if (!isset($this->sessionBackend)) { 1539 $this->sessionBackend = GeneralUtility::makeInstance(SessionManager::class)->getSessionBackend($this->loginType); 1540 } 1541 return $this->sessionBackend; 1542 } 1543} 1544