‪TYPO3CMS  11.5
UserSessionManager.php
Go to the documentation of this file.
1 <?php
2 
3 declare(strict_types=1);
4 
5 /*
6  * This file is part of the TYPO3 CMS project.
7  *
8  * It is free software; you can redistribute it and/or modify it under
9  * the terms of the GNU General Public License, either version 2
10  * of the License, or any later version.
11  *
12  * For the full copyright and license information, please read the
13  * LICENSE.txt file that was distributed with this source code.
14  *
15  * The TYPO3 project - inspiring people to share!
16  */
17 
19 
20 use Psr\Http\Message\ServerRequestInterface;
21 use Psr\Log\LoggerAwareInterface;
22 use Psr\Log\LoggerAwareTrait;
26 use TYPO3\CMS\Core\Http\CookieScopeTrait;
31 
37 class ‪UserSessionManager implements LoggerAwareInterface
38 {
39  use LoggerAwareTrait;
40  use CookieScopeTrait;
41 
42  protected const ‪SESSION_ID_LENGTH = 32;
43  protected const ‪GARBAGE_COLLECTION_LIFETIME = 86400;
44  protected const ‪LIFETIME_OF_ANONYMOUS_SESSION_DATA = 86400;
45 
54  protected int ‪$sessionLifetime;
55 
59  protected string ‪$loginType;
60 
70  {
71  $this->sessionBackend = ‪$sessionBackend;
72  $this->sessionLifetime = ‪$sessionLifetime;
73  $this->ipLocker = ‪$ipLocker;
74  $this->loginType = ‪$loginType;
75  }
76 
78  {
80  $this->garbageCollectionForAnonymousSessions = ‪$garbageCollectionForAnonymousSessions;
81  }
82  }
83 
92  public function ‪createFromRequestOrAnonymous(ServerRequestInterface $request, string $cookieName): ‪UserSession
93  {
94  $sessionId = (string)($request->getCookieParams()[$cookieName] ?? '');
95  return $this->‪getSessionFromSessionId($sessionId) ?? $this->‪createAnonymousSession();
96  }
97 
105  public function ‪createFromGlobalCookieOrAnonymous(string $cookieName): ‪UserSession
106  {
107  $sessionId = isset($_COOKIE[$cookieName]) ? stripslashes((string)$_COOKIE[$cookieName]) : '';
108  return $this->‪getSessionFromSessionId($sessionId) ?? $this->‪createAnonymousSession();
109  }
110 
117  {
118  $randomSessionId = $this->‪createSessionId();
119  return ‪UserSession::createNonFixated($randomSessionId);
120  }
121 
129  public function ‪createSessionFromStorage(string $sessionId): ‪UserSession
130  {
131  $this->logger->debug('Fetch session with identifier {session}', ['session' => sha1($sessionId)]);
132  $sessionRecord = $this->sessionBackend->‪get($sessionId);
133  return ‪UserSession::createFromRecord($sessionId, $sessionRecord);
134  }
135 
143  public function ‪hasExpired(‪UserSession $session): bool
144  {
145  return $this->sessionLifetime === 0 || ‪$GLOBALS['EXEC_TIME'] > $session->‪getLastUpdated() + ‪$this->sessionLifetime;
146  }
147 
155  public function ‪willExpire(‪UserSession $session, int $gracePeriod): bool
156  {
157  return ‪$GLOBALS['EXEC_TIME'] >= ($session->‪getLastUpdated() + ‪$this->sessionLifetime) - $gracePeriod;
158  }
159 
170  public function ‪fixateAnonymousSession(‪UserSession $session, bool $isPermanent = false): ‪UserSession
171  {
172  $sessionIpLock = $this->ipLocker->getSessionIpLock((string)GeneralUtility::getIndpEnv('REMOTE_ADDR'));
173  $sessionRecord = $session->‪toArray();
174  $sessionRecord['ses_iplock'] = $sessionIpLock;
175  // Ensure the user is not set, as this is always an anonymous session (see elevateToFixatedUserSession)
176  $sessionRecord['ses_userid'] = 0;
177  if ($isPermanent) {
178  $sessionRecord['ses_permanent'] = 1;
179  }
180  // The updated session record now also contains an updated timestamp (ses_tstamp)
181  $updatedSessionRecord = $this->sessionBackend->set($session->‪getIdentifier(), $sessionRecord);
182  return $this->‪recreateUserSession($session, $updatedSessionRecord);
183  }
184 
195  public function ‪elevateToFixatedUserSession(‪UserSession $session, int $userId, bool $isPermanent = false): ‪UserSession
196  {
197  $sessionId = $session->‪getIdentifier();
198  $this->logger->debug('Create session ses_id = {session}', ['session' => sha1($sessionId)]);
199  // Delete any session entry first
200  $this->sessionBackend->remove($sessionId);
201  // Re-create session entry
202  $sessionIpLock = $this->ipLocker->getSessionIpLock((string)GeneralUtility::getIndpEnv('REMOTE_ADDR'));
203  $sessionRecord = [
204  'ses_iplock' => $sessionIpLock,
205  'ses_userid' => $userId,
206  'ses_tstamp' => ‪$GLOBALS['EXEC_TIME'],
207  'ses_data' => '',
208  ];
209  if ($isPermanent) {
210  $sessionRecord['ses_permanent'] = 1;
211  }
212  $sessionRecord = $this->sessionBackend->set($sessionId, $sessionRecord);
213  return ‪UserSession::createFromRecord($sessionId, $sessionRecord, true);
214  }
215 
229  public function ‪regenerateSession(
230  string $sessionId,
231  array $existingSessionRecord = [],
232  bool $anonymous = false
233  ): ‪UserSession {
234  if (empty($existingSessionRecord)) {
235  $existingSessionRecord = $this->sessionBackend->‪get($sessionId);
236  }
237  if ($anonymous) {
238  $existingSessionRecord['ses_userid'] = 0;
239  }
240  // Update session record with new ID
241  $newSessionId = $this->‪createSessionId();
242  $this->sessionBackend->set($newSessionId, $existingSessionRecord);
243  $this->sessionBackend->remove($sessionId);
244  return ‪UserSession::createFromRecord($newSessionId, $existingSessionRecord, true);
245  }
246 
257  {
258  if ($session->‪needsUpdate()) {
259  // Update the session timestamp by writing a dummy update. (Backend will update the timestamp)
260  $this->sessionBackend->update($session->‪getIdentifier(), []);
261  $session = $this->‪recreateUserSession($session);
262  }
263  return $session;
264  }
265 
266  public function ‪isSessionPersisted(‪UserSession $session): bool
267  {
268  return $this->‪getSessionFromSessionId($session->‪getIdentifier()) !== null;
269  }
270 
271  public function ‪removeSession(‪UserSession $session): void
272  {
273  $this->sessionBackend->remove($session->‪getIdentifier());
274  }
275 
276  public function ‪updateSession(‪UserSession $session): ‪UserSession
277  {
278  $sessionRecord = $this->sessionBackend->update($session->‪getIdentifier(), $session->‪toArray());
279  return $this->‪recreateUserSession($session, $sessionRecord);
280  }
281 
282  public function ‪collectGarbage(int $garbageCollectionProbability = 1): void
283  {
284  // If we're lucky we'll get to clean up old sessions
285  if (random_int(0, mt_getrandmax()) % 100 <= $garbageCollectionProbability) {
286  $this->sessionBackend->collectGarbage(
287  $this->sessionLifetime > 0 ? $this->sessionLifetime : self::GARBAGE_COLLECTION_LIFETIME,
288  $this->garbageCollectionForAnonymousSessions
289  );
290  }
291  }
292 
298  protected function ‪createSessionId(): string
299  {
300  $normalizedParams = $this->‪getNormalizedParams();
301  $cookieScope = $this->getCookieScope($normalizedParams);
302  $key = sha1(‪$GLOBALS['TYPO3_CONF_VARS']['SYS']['encryptionKey'] . '/' . UserSession::class . '/' . $cookieScope['domain']);
303  $random = GeneralUtility::makeInstance(Random::class)->generateRandomHexString(self::SESSION_ID_LENGTH);
304  $signature = hash_hmac('sha256', $random, $key);
305 
306  return $random . '.' . $signature;
307  }
308 
313  {
314  $normalizedParams = null;
315  $serverParams = ‪Environment::isCli() ? ['HTTP_HOST' => 'localhost'] : $_SERVER;
316  if (isset(‪$GLOBALS['TYPO3_REQUEST'])) {
317  $normalizedParams = ‪$GLOBALS['TYPO3_REQUEST']->getAttribute('normalizedParams');
318  $serverParams = ‪$GLOBALS['TYPO3_REQUEST']->getServerParams();
319  }
320 
321  if (!$normalizedParams instanceof ‪NormalizedParams) {
322  $normalizedParams = ‪NormalizedParams::createFromServerParams($serverParams);
323  }
324 
325  return $normalizedParams;
326  }
327 
335  protected function ‪getSessionFromSessionId(string $id): ?‪UserSession
336  {
337  if ($id === '') {
338  return null;
339  }
340 
341  $sessionsParts = explode('.', $id, 2);
342  // Verify if session id is signed with cookie domain.
343  // Note that we allow possibly unsiged session IDs (used for testing framework or 3rd party authenticators)
344  if (count($sessionsParts) === 2) {
345  $random = $sessionsParts[0];
346  $signature = $sessionsParts[1];
347  $normalizedParams = $this->‪getNormalizedParams();
348  $cookieScope = $this->getCookieScope($normalizedParams);
349  $key = sha1(‪$GLOBALS['TYPO3_CONF_VARS']['SYS']['encryptionKey'] . '/' . UserSession::class . '/' . $cookieScope['domain']);
350  $validHash = hash_hmac('sha256', $random, $key);
351  if (!hash_equals($validHash, $signature)) {
352  $this->logger->notice('User Session rejected because of invalid signature', ['session' => substr(sha1($id), 0, 12)]);
353  return null;
354  }
355  } elseif ($this->logger !== null) {
356  $this->logger->notice('Unsigned session id has been used', ['session' => substr(sha1($id), 0, 12)]);
357  }
358 
359  try {
360  $sessionRecord = $this->sessionBackend->get($id);
361  if ($sessionRecord === []) {
362  return null;
363  }
364  // If the session does not match the current IP lock, it should be treated as invalid
365  // and a new session should be created.
366  if ($this->ipLocker->validateRemoteAddressAgainstSessionIpLock(
367  (string)GeneralUtility::getIndpEnv('REMOTE_ADDR'),
368  $sessionRecord['ses_iplock']
369  )) {
370  return ‪UserSession::createFromRecord($id, $sessionRecord);
371  }
372  } catch (‪SessionNotFoundException $e) {
373  return null;
374  }
375 
376  return null;
377  }
378 
392  public static function ‪create(string ‪$loginType, int ‪$sessionLifetime = null, ‪SessionManager $sessionManager = null, ‪IpLocker ‪$ipLocker = null): self
393  {
394  $sessionManager = $sessionManager ?? GeneralUtility::makeInstance(SessionManager::class);
395  ‪$ipLocker = ‪$ipLocker ?? GeneralUtility::makeInstance(
396  IpLocker::class,
397  (int)(‪$GLOBALS['TYPO3_CONF_VARS'][‪$loginType]['lockIP'] ?? 0),
398  (int)(‪$GLOBALS['TYPO3_CONF_VARS'][‪$loginType]['lockIPv6'] ?? 0)
399  );
400  $lifetime = (int)(‪$GLOBALS['TYPO3_CONF_VARS'][‪$loginType]['lifetime'] ?? 0);
401  ‪$sessionLifetime = ‪$sessionLifetime ?? (int)‪$GLOBALS['TYPO3_CONF_VARS'][‪$loginType]['sessionTimeout'];
402  if (‪$sessionLifetime > 0 && $sessionLifetime < $lifetime && $lifetime > 0) {
403  // If server session timeout is non-zero but less than client session timeout: Copy this value instead.
404  ‪$sessionLifetime = $lifetime;
405  }
406  $object = GeneralUtility::makeInstance(
407  self::class,
408  $sessionManager->getSessionBackend(‪$loginType),
410  ‪$ipLocker,
412  );
413  if (‪$loginType === 'FE') {
414  $object->setGarbageCollectionTimeoutForAnonymousSessions((int)(‪$GLOBALS['TYPO3_CONF_VARS']['FE']['sessionDataLifetime'] ?? 0));
415  }
416  return $object;
417  }
418 
428  protected function ‪recreateUserSession(‪UserSession $session, array $sessionRecord = null): ‪UserSession
429  {
431  $session->‪getIdentifier(),
432  $sessionRecord ?? $this->sessionBackend->get($session->‪getIdentifier()),
433  $session->‪isNew() // keep state (required to emit e.g. cookies)
434  );
435  }
436 }
‪TYPO3\CMS\Core\Session\UserSessionManager\$sessionLifetime
‪int $sessionLifetime
Definition: UserSessionManager.php:54
‪TYPO3\CMS\Core\Session\UserSessionManager\recreateUserSession
‪UserSession recreateUserSession(UserSession $session, array $sessionRecord=null)
Definition: UserSessionManager.php:428
‪TYPO3\CMS\Core\Session\UserSession\getIdentifier
‪string getIdentifier()
Definition: UserSession.php:63
‪TYPO3\CMS\Core\Session\UserSession\needsUpdate
‪bool needsUpdate()
Definition: UserSession.php:211
‪TYPO3\CMS\Core\Session\UserSession\toArray
‪array toArray()
Definition: UserSession.php:260
‪TYPO3\CMS\Core\Session\UserSession\createFromRecord
‪static UserSession createFromRecord(string $id, array $record, bool $markAsNew=false)
Definition: UserSession.php:224
‪TYPO3\CMS\Core\Session\UserSession
Definition: UserSession.php:39
‪TYPO3\CMS\Core\Session\SessionManager
Definition: SessionManager.php:39
‪TYPO3\CMS\Core\Session\UserSessionManager\fixateAnonymousSession
‪UserSession fixateAnonymousSession(UserSession $session, bool $isPermanent=false)
Definition: UserSessionManager.php:170
‪TYPO3\CMS\Core\Session\UserSessionManager\elevateToFixatedUserSession
‪UserSession elevateToFixatedUserSession(UserSession $session, int $userId, bool $isPermanent=false)
Definition: UserSessionManager.php:195
‪TYPO3\CMS\Core\Session\UserSessionManager\createFromGlobalCookieOrAnonymous
‪UserSession createFromGlobalCookieOrAnonymous(string $cookieName)
Definition: UserSessionManager.php:105
‪TYPO3\CMS\Core\Session\UserSession\isNew
‪bool isNew()
Definition: UserSession.php:191
‪TYPO3\CMS\Core\Session\UserSessionManager\LIFETIME_OF_ANONYMOUS_SESSION_DATA
‪const LIFETIME_OF_ANONYMOUS_SESSION_DATA
Definition: UserSessionManager.php:44
‪TYPO3\CMS\Core\Session
‪TYPO3\CMS\Core\Session\UserSessionManager\SESSION_ID_LENGTH
‪const SESSION_ID_LENGTH
Definition: UserSessionManager.php:42
‪TYPO3\CMS\Core\Session\UserSessionManager\hasExpired
‪bool hasExpired(UserSession $session)
Definition: UserSessionManager.php:143
‪TYPO3\CMS\Core\Session\UserSessionManager\$garbageCollectionForAnonymousSessions
‪int $garbageCollectionForAnonymousSessions
Definition: UserSessionManager.php:56
‪TYPO3\CMS\Core\Session\UserSession\createNonFixated
‪static UserSession createNonFixated(string $identifier)
Definition: UserSession.php:247
‪TYPO3\CMS\Core\Session\UserSessionManager\__construct
‪__construct(SessionBackendInterface $sessionBackend, int $sessionLifetime, IpLocker $ipLocker, string $loginType)
Definition: UserSessionManager.php:69
‪TYPO3\CMS\Core\Session\UserSessionManager\removeSession
‪removeSession(UserSession $session)
Definition: UserSessionManager.php:271
‪TYPO3\CMS\Core\Session\UserSessionManager\getSessionFromSessionId
‪UserSession null getSessionFromSessionId(string $id)
Definition: UserSessionManager.php:335
‪TYPO3\CMS\Core\Http\NormalizedParams\createFromServerParams
‪static static createFromServerParams(array $serverParams, array $systemConfiguration=null)
Definition: NormalizedParams.php:826
‪TYPO3\CMS\Core\Session\Backend\SessionBackendInterface
Definition: SessionBackendInterface.php:28
‪TYPO3\CMS\Core\Session\UserSessionManager\$sessionBackend
‪SessionBackendInterface $sessionBackend
Definition: UserSessionManager.php:57
‪TYPO3\CMS\Core\Session\UserSessionManager\create
‪static static create(string $loginType, int $sessionLifetime=null, SessionManager $sessionManager=null, IpLocker $ipLocker=null)
Definition: UserSessionManager.php:392
‪TYPO3\CMS\Core\Session\UserSessionManager\updateSessionTimestamp
‪UserSession updateSessionTimestamp(UserSession $session)
Definition: UserSessionManager.php:256
‪TYPO3\CMS\Core\Session\UserSessionManager\GARBAGE_COLLECTION_LIFETIME
‪const GARBAGE_COLLECTION_LIFETIME
Definition: UserSessionManager.php:43
‪TYPO3\CMS\Core\Session\UserSession\get
‪mixed get(string $key)
Definition: UserSession.php:124
‪TYPO3\CMS\Core\Session\UserSessionManager\getNormalizedParams
‪getNormalizedParams()
Definition: UserSessionManager.php:312
‪TYPO3\CMS\Core\Session\UserSessionManager\regenerateSession
‪UserSession regenerateSession(string $sessionId, array $existingSessionRecord=[], bool $anonymous=false)
Definition: UserSessionManager.php:229
‪TYPO3\CMS\Core\Session\UserSessionManager\updateSession
‪updateSession(UserSession $session)
Definition: UserSessionManager.php:276
‪$GLOBALS
‪$GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['adminpanel']['modules']
Definition: ext_localconf.php:25
‪TYPO3\CMS\Core\Session\UserSessionManager\createSessionId
‪string createSessionId()
Definition: UserSessionManager.php:298
‪TYPO3\CMS\Core\Session\UserSessionManager\$ipLocker
‪IpLocker $ipLocker
Definition: UserSessionManager.php:58
‪TYPO3\CMS\Core\Core\Environment
Definition: Environment.php:43
‪TYPO3\CMS\Core\Crypto\Random
Definition: Random.php:24
‪TYPO3\CMS\Core\Authentication\IpLocker
Definition: IpLocker.php:26
‪TYPO3\CMS\Core\Session\UserSession\getLastUpdated
‪int getLastUpdated()
Definition: UserSession.php:83
‪TYPO3\CMS\Core\Utility\GeneralUtility
Definition: GeneralUtility.php:50
‪TYPO3\CMS\Core\Session\UserSessionManager\isSessionPersisted
‪isSessionPersisted(UserSession $session)
Definition: UserSessionManager.php:266
‪TYPO3\CMS\Core\Session\UserSessionManager\createFromRequestOrAnonymous
‪UserSession createFromRequestOrAnonymous(ServerRequestInterface $request, string $cookieName)
Definition: UserSessionManager.php:92
‪TYPO3\CMS\Core\Session\UserSessionManager\createAnonymousSession
‪UserSession createAnonymousSession()
Definition: UserSessionManager.php:116
‪TYPO3\CMS\Core\Session\UserSessionManager\setGarbageCollectionTimeoutForAnonymousSessions
‪setGarbageCollectionTimeoutForAnonymousSessions(int $garbageCollectionForAnonymousSessions=0)
Definition: UserSessionManager.php:77
‪TYPO3\CMS\Core\Session\UserSessionManager\collectGarbage
‪collectGarbage(int $garbageCollectionProbability=1)
Definition: UserSessionManager.php:282
‪TYPO3\CMS\Core\Session\UserSessionManager\$loginType
‪string $loginType
Definition: UserSessionManager.php:59
‪TYPO3\CMS\Core\Session\UserSessionManager\willExpire
‪bool willExpire(UserSession $session, int $gracePeriod)
Definition: UserSessionManager.php:155
‪TYPO3\CMS\Core\Core\Environment\isCli
‪static bool isCli()
Definition: Environment.php:162
‪TYPO3\CMS\Core\Session\UserSessionManager\createSessionFromStorage
‪UserSession createSessionFromStorage(string $sessionId)
Definition: UserSessionManager.php:129
‪TYPO3\CMS\Core\Session\Backend\Exception\SessionNotFoundException
Definition: SessionNotFoundException.php:23
‪TYPO3\CMS\Core\Session\UserSessionManager
Definition: UserSessionManager.php:38
‪TYPO3\CMS\Core\Http\NormalizedParams
Definition: NormalizedParams.php:35