TYPO3 v15 dev-main snapshot ()
This commit is contained in:
@@ -0,0 +1,238 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Core\Session\Backend;
|
||||
|
||||
use Doctrine\DBAL\Exception as DBALException;
|
||||
use Symfony\Component\DependencyInjection\Attribute\Autoconfigure;
|
||||
use TYPO3\CMS\Core\Crypto\HashAlgo;
|
||||
use TYPO3\CMS\Core\Crypto\HashService;
|
||||
use TYPO3\CMS\Core\Database\Connection;
|
||||
use TYPO3\CMS\Core\Database\ConnectionPool;
|
||||
use TYPO3\CMS\Core\Database\Query\QueryBuilder;
|
||||
use TYPO3\CMS\Core\Session\Backend\Exception\SessionNotCreatedException;
|
||||
use TYPO3\CMS\Core\Session\Backend\Exception\SessionNotFoundException;
|
||||
use TYPO3\CMS\Core\Session\Backend\Exception\SessionNotUpdatedException;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
|
||||
/**
|
||||
* This session backend requires the 'table' configuration option. If the backend is used to holds non-authenticated
|
||||
* sessions (default in frontend application), the 'ses_userid' configuration option must be set to `0`.
|
||||
*/
|
||||
#[Autoconfigure(public: true, shared: false)]
|
||||
class DatabaseSessionBackend implements SessionBackendInterface, HashableSessionBackendInterface
|
||||
{
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $configuration = [];
|
||||
|
||||
/**
|
||||
* @var bool Indicates whether the ses_userid is set to `0` in the sessions table
|
||||
*/
|
||||
protected $hasAnonymousSessions = false;
|
||||
|
||||
public function __construct(
|
||||
private readonly ConnectionPool $connectionPool,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Initializes the session backend
|
||||
*
|
||||
* @param string $identifier Name of the session type, e.g. FE or BE
|
||||
* @internal To be used only by SessionManager
|
||||
*/
|
||||
public function initialize(string $identifier, array $configuration)
|
||||
{
|
||||
$this->hasAnonymousSessions = (bool)($configuration['has_anonymous'] ?? false);
|
||||
$this->configuration = $configuration;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the configuration is valid
|
||||
*
|
||||
* @throws \InvalidArgumentException
|
||||
* @internal To be used only by SessionManager
|
||||
*/
|
||||
public function validateConfiguration(): bool
|
||||
{
|
||||
if (empty($this->configuration['table'])) {
|
||||
throw new \InvalidArgumentException(
|
||||
'The session backend "' . static::class . '" needs a "table" configuration.',
|
||||
1442996707
|
||||
);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public function hash(string $sessionId): string
|
||||
{
|
||||
return GeneralUtility::makeInstance(HashService::class)
|
||||
->hmac($sessionId, 'core-session-backend', HashAlgo::SHA3_256);
|
||||
}
|
||||
|
||||
/**
|
||||
* Read session data
|
||||
*
|
||||
* @return array Returns the session data
|
||||
* @throws SessionNotFoundException
|
||||
*/
|
||||
public function get(string $sessionId): array
|
||||
{
|
||||
$query = $this->getQueryBuilder();
|
||||
$query->select('*')
|
||||
->from($this->configuration['table'])
|
||||
->where($query->expr()->eq('ses_id', $query->createNamedParameter($this->hash($sessionId))));
|
||||
$result = $query->executeQuery()->fetchAssociative();
|
||||
if (!is_array($result)) {
|
||||
throw new SessionNotFoundException(
|
||||
'The session with identifier ' . $sessionId . ' was not found ',
|
||||
1481885483
|
||||
);
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a session record
|
||||
*
|
||||
* @return bool true if the session was deleted, false it session could not be found
|
||||
*/
|
||||
public function remove(string $sessionId): bool
|
||||
{
|
||||
$query = $this->getQueryBuilder();
|
||||
$query->delete($this->configuration['table'])
|
||||
->where(
|
||||
$query->expr()->or(
|
||||
$query->expr()->eq('ses_id', $query->createNamedParameter($this->hash($sessionId))),
|
||||
$query->expr()->eq('ses_id', $query->createNamedParameter($sessionId))
|
||||
)
|
||||
);
|
||||
|
||||
return (bool)$query->executeStatement();
|
||||
}
|
||||
|
||||
/**
|
||||
* Write session data. This method prevents overriding existing session data.
|
||||
* ses_id will always be set to $sessionId and overwritten if existing in $sessionData
|
||||
* This method updates ses_tstamp automatically
|
||||
*
|
||||
* @return array The newly created session record.
|
||||
* @throws SessionNotCreatedException
|
||||
*/
|
||||
public function set(string $sessionId, array $sessionData): array
|
||||
{
|
||||
$sessionId = $this->hash($sessionId);
|
||||
$sessionData['ses_id'] = $sessionId;
|
||||
$sessionData['ses_tstamp'] = $GLOBALS['EXEC_TIME'] ?? time();
|
||||
|
||||
try {
|
||||
$this->getConnection()->insert(
|
||||
$this->configuration['table'],
|
||||
$sessionData,
|
||||
['ses_data' => Connection::PARAM_LOB]
|
||||
);
|
||||
} catch (DBALException $e) {
|
||||
throw new SessionNotCreatedException(
|
||||
'Session could not be written to database: ' . $e->getMessage(),
|
||||
1481895005,
|
||||
$e
|
||||
);
|
||||
}
|
||||
|
||||
return $sessionData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the session data.
|
||||
* ses_id will always be set to $sessionId and overwritten if existing in $sessionData
|
||||
* This method updates ses_tstamp automatically
|
||||
*
|
||||
* @param array $sessionData The session data to update. Data may be partial.
|
||||
* @return array $sessionData The newly updated session record.
|
||||
* @throws SessionNotUpdatedException
|
||||
*/
|
||||
public function update(string $sessionId, array $sessionData): array
|
||||
{
|
||||
$hashedSessionId = $this->hash($sessionId);
|
||||
$sessionData['ses_id'] = $hashedSessionId;
|
||||
$sessionData['ses_tstamp'] = $GLOBALS['EXEC_TIME'] ?? time();
|
||||
|
||||
try {
|
||||
// allow 0 records to be affected, happens when no columns where changed
|
||||
$this->getConnection()->update(
|
||||
$this->configuration['table'],
|
||||
$sessionData,
|
||||
['ses_id' => $hashedSessionId],
|
||||
['ses_data' => Connection::PARAM_LOB]
|
||||
);
|
||||
} catch (DBALException $e) {
|
||||
throw new SessionNotUpdatedException(
|
||||
'Session with id ' . $sessionId . ' could not be updated: ' . $e->getMessage(),
|
||||
1481889220,
|
||||
$e
|
||||
);
|
||||
}
|
||||
return $sessionData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Garbage Collection
|
||||
*
|
||||
* @param int $maximumLifetime maximum lifetime of authenticated user sessions, in seconds.
|
||||
* @param int $maximumAnonymousLifetime maximum lifetime of non-authenticated user sessions, in seconds. If set to 0, non-authenticated sessions are ignored.
|
||||
*/
|
||||
public function collectGarbage(int $maximumLifetime, int $maximumAnonymousLifetime = 0)
|
||||
{
|
||||
$query = $this->getQueryBuilder();
|
||||
|
||||
$query->delete($this->configuration['table'])
|
||||
->where($query->expr()->lt('ses_tstamp', (int)($GLOBALS['EXEC_TIME'] - (int)$maximumLifetime)))
|
||||
->andWhere($this->hasAnonymousSessions ? $query->expr()->neq('ses_userid', 0) : ' 1 = 1');
|
||||
$query->executeStatement();
|
||||
|
||||
if ($maximumAnonymousLifetime > 0 && $this->hasAnonymousSessions) {
|
||||
$query = $this->getQueryBuilder();
|
||||
$query->delete($this->configuration['table'])
|
||||
->where($query->expr()->lt('ses_tstamp', (int)($GLOBALS['EXEC_TIME'] - (int)$maximumAnonymousLifetime)))
|
||||
->andWhere($query->expr()->eq('ses_userid', 0));
|
||||
$query->executeStatement();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* List all sessions
|
||||
*
|
||||
* @return array Return a list of all user sessions. The list may be empty
|
||||
*/
|
||||
public function getAll(): array
|
||||
{
|
||||
$query = $this->getQueryBuilder();
|
||||
$query->select('*')->from($this->configuration['table']);
|
||||
return $query->executeQuery()->fetchAllAssociative();
|
||||
}
|
||||
|
||||
protected function getQueryBuilder(): QueryBuilder
|
||||
{
|
||||
return $this->getConnection()->createQueryBuilder();
|
||||
}
|
||||
|
||||
protected function getConnection(): Connection
|
||||
{
|
||||
return $this->connectionPool->getConnectionForTable($this->configuration['table']);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user