<?php
namespace Bidcoz\Bundle\CoreBundle\Security\Csrf;
use Bidcoz\Bundle\CoreBundle\Entity\User;
use Psr\Log\LoggerInterface;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
use Symfony\Component\Security\Csrf\CsrfToken;
use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface;
/**
* Decorator that accepts failed CSRF tokens for authenticated ManageBundle routes.
*
* SameSite=Lax cookies already prevent cross-site form submissions, so CSRF
* validation in the management area is defense-in-depth. When session issues
* (deploy-triggered GC, save_path problems) cause token mismatches, this
* decorator prevents authenticated users from being blocked.
*
* Public forms (login, registration, donations) remain strictly validated.
*/
class ResilientCsrfTokenManager implements CsrfTokenManagerInterface
{
private CsrfTokenManagerInterface $inner;
private TokenStorageInterface $tokenStorage;
private RequestStack $requestStack;
private LoggerInterface $logger;
public function __construct(
CsrfTokenManagerInterface $inner,
TokenStorageInterface $tokenStorage,
RequestStack $requestStack,
LoggerInterface $logger
) {
$this->inner = $inner;
$this->tokenStorage = $tokenStorage;
$this->requestStack = $requestStack;
$this->logger = $logger;
}
public function getToken(string $tokenId): CsrfToken
{
return $this->inner->getToken($tokenId);
}
public function refreshToken(string $tokenId): CsrfToken
{
return $this->inner->refreshToken($tokenId);
}
public function removeToken(string $tokenId): ?string
{
return $this->inner->removeToken($tokenId);
}
public function isTokenValid(CsrfToken $token): bool
{
if ($this->inner->isTokenValid($token)) {
return true;
}
if ($this->shouldBypassValidation()) {
$request = $this->requestStack->getCurrentRequest();
$this->logger->warning('CSRF bypass: accepting failed token for authenticated management route', [
'route' => $request ? $request->attributes->get('_route') : 'unknown',
'controller' => $request ? $request->attributes->get('_controller', '') : 'unknown',
'token_id' => $token->getId(),
]);
return true;
}
return false;
}
private function shouldBypassValidation(): bool
{
$request = $this->requestStack->getCurrentRequest();
if (!$request) {
return false;
}
// Check controller belongs to ManageBundle
$controller = $request->attributes->get('_controller', '');
if (strpos($controller, 'ManageBundle\\Controller') === false) {
return false;
}
// Check user is authenticated
$token = $this->tokenStorage->getToken();
return $token && $token->getUser() instanceof User;
}
}