63 lines
2.0 KiB
PHP
63 lines
2.0 KiB
PHP
<?php
|
|
|
|
/*
|
|
* This file is part of the Kimai time-tracking app.
|
|
*
|
|
* For the full copyright and license information, please view the LICENSE
|
|
* file that was distributed with this source code.
|
|
*/
|
|
|
|
namespace App\Security;
|
|
|
|
use Doctrine\DBAL\Connection;
|
|
use Symfony\Component\HttpFoundation\RequestStack;
|
|
use Symfony\Component\HttpFoundation\Session\Storage\Handler\PdoSessionHandler;
|
|
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
|
|
use Symfony\Component\RateLimiter\RateLimiterFactory;
|
|
|
|
final class SessionHandler extends PdoSessionHandler
|
|
{
|
|
public function __construct(
|
|
Connection $connection,
|
|
private readonly RateLimiterFactory $sessionPredictionLimiter,
|
|
private readonly RequestStack $requestStack
|
|
)
|
|
{
|
|
parent::__construct($connection->getNativeConnection(), [
|
|
'db_table' => 'kimai2_sessions',
|
|
'db_id_col' => 'id',
|
|
'db_data_col' => 'data',
|
|
'db_lifetime_col' => 'lifetime',
|
|
'db_time_col' => 'time',
|
|
'lock_mode' => PdoSessionHandler::LOCK_ADVISORY,
|
|
]);
|
|
}
|
|
|
|
public function garbageCollection(): void
|
|
{
|
|
$connection = $this->getConnection();
|
|
$sql = 'DELETE FROM kimai2_sessions WHERE lifetime < :time';
|
|
$stmt = $connection->prepare($sql);
|
|
$stmt->bindValue(':time', time(), \PDO::PARAM_INT);
|
|
$stmt->execute();
|
|
}
|
|
|
|
public function validateId(#[\SensitiveParameter] string $sessionId): bool
|
|
{
|
|
$result = parent::validateId($sessionId);
|
|
|
|
if ($result === false) {
|
|
$limiter = $this->sessionPredictionLimiter->create($this->requestStack->getMainRequest()?->getClientIp());
|
|
$limit = $limiter->consume();
|
|
|
|
if (false === $limit->isAccepted()) {
|
|
throw new BadRequestHttpException('Too many requests with invalid Session ID. Prediction attack?');
|
|
}
|
|
|
|
usleep(250000); // slow down potential attacks
|
|
}
|
|
|
|
return $result;
|
|
}
|
|
}
|