<?phpnamespace App\Voter;use App\Controller\Front\AccessTokenController;use App\Entity\Trace;use DateInterval;use DateTime;use Doctrine\ORM\EntityManagerInterface;use Symfony\Component\HttpFoundation\RequestStack;use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;use Symfony\Component\Security\Core\Authorization\Voter\Voter;class AccessTokenVoter extends Voter{ public const TRY_ACCESS_TOKEN = 'try_access_token'; public const MAX_TRY_PER_TIMESPAN = 5; public const TIMESPAN_SECONDS = 3600; private $traceRepo; private $requestStack; public function __construct( EntityManagerInterface $em, RequestStack $requestStack ) { $this->traceRepo = $em->getRepository(Trace::class); $this->requestStack = $requestStack; } protected function supports($attribute, $entity): bool { // if the attribute isn't one we support, return false if ($attribute === self::TRY_ACCESS_TOKEN) { return true; } return false; } protected function voteOnAttribute($attribute, $entity, TokenInterface $token): bool { if ($attribute === self::TRY_ACCESS_TOKEN) { return $this->canTry(); } return false; } private function canTry(): bool { /* * Count all trace with same ip and same type in the timespan */ $request = $this->requestStack->getCurrentRequest(); $ip = $request->getClientIp(); $after = $this->buildTryAfterDT(); $similarTriesCount = $this->traceRepo->searchCount([ 'type' => AccessTokenController::ACCESS_ROUTE_HIT, 'ip' => $ip, 'after' => $after ]); return ($similarTriesCount <= self::MAX_TRY_PER_TIMESPAN); } private function buildTryInterval(): DateInterval { return new DateInterval('PT' . self::TIMESPAN_SECONDS . 'S'); } private function buildTryAfterDT(): DateTime { $interval = $this->buildTryInterval(); $after = new DateTime(); $after->sub($interval); return $after; }}