vendor/can/rest/src/Can/RestBundle/EventListener/RateLimitListener.php line 92

Open in your IDE?
  1. <?php
  2. namespace Can\RestBundle\EventListener;
  3. use Can\RestBundle\CanRestBundle;
  4. use Can\RestBundle\Incident\IncidentReporterInterface;
  5. use Can\RestBundle\Mapping\MetadataStoreInterface;
  6. use Can\RestBundle\RateLimit\RateLimit;
  7. use Can\RestBundle\RateLimit\RateLimitAbusedException;
  8. use Can\RestBundle\RateLimit\RateLimitConfiguration;
  9. use Can\RestBundle\RateLimit\RateLimitConstraint;
  10. use Can\RestBundle\RateLimit\RateLimitEntry;
  11. use Can\RestBundle\RateLimit\RateLimitEventDispatcher;
  12. use Can\RestBundle\RateLimit\RateLimitExceededException;
  13. use Can\RestBundle\RateLimit\RateLimitHandler;
  14. use Can\RestBundle\RateLimit\RateLimitPolicy;
  15. use Can\RestBundle\RateLimit\RateLimitRegistry;
  16. use Can\RestBundle\URI\ServiceNode;
  17. use Symfony\Component\HttpFoundation\Request;
  18. use Symfony\Component\HttpKernel\Event\RequestEvent;
  19. /**
  20.  * @group ratelimit
  21.  *
  22.  * @package can/rest-bundle
  23.  * @author lechecacharro <lechecacharro@gmail.com>
  24.  */
  25. class RateLimitListener
  26. {
  27.     /**
  28.      * @var RateLimitConfiguration
  29.      */
  30.     private $configuration;
  31.     /**
  32.      * @var RateLimitHandler
  33.      */
  34.     private $handler;
  35.     /**
  36.      * @var RateLimitEventDispatcher
  37.      */
  38.     private $dispatcher;
  39.     /**
  40.      * @var RateLimitRegistry
  41.      */
  42.     private $registry;
  43.     /**
  44.      * @var MetadataStoreInterface
  45.      */
  46.     private $metadataStore;
  47.     /**
  48.      * @var IncidentReporterInterface
  49.      */
  50.     private $reporter;
  51.     /**
  52.      * RateLimitListener constructor.
  53.      *
  54.      * @param RateLimitConfiguration    $configuration
  55.      * @param RateLimitHandler          $handler
  56.      * @param RateLimitEventDispatcher  $dispatcher
  57.      * @param RateLimitRegistry         $registry
  58.      * @param MetadataStoreInterface    $metadataStore
  59.      * @param IncidentReporterInterface $reporter
  60.      */
  61.     public function __construct(
  62.         RateLimitConfiguration $configuration,
  63.         RateLimitHandler $handler,
  64.         RateLimitEventDispatcher $dispatcher,
  65.         RateLimitRegistry $registry,
  66.         MetadataStoreInterface $metadataStore,
  67.         IncidentReporterInterface $reporter
  68.     )
  69.     {
  70.         $this->configuration $configuration;
  71.         $this->handler $handler;
  72.         $this->dispatcher $dispatcher;
  73.         $this->registry $registry;
  74.         $this->metadataStore $metadataStore;
  75.         $this->reporter $reporter;
  76.     }
  77.     /**
  78.      * @param RequestEvent $event
  79.      */
  80.     public function onRequest(RequestEvent $event): void
  81.     {
  82.         if (! $this->configuration->isEnabled()) {
  83.             return;
  84.         }
  85.         // Do process only the master request
  86.         if (! $event->isMasterRequest()) {
  87.             return;
  88.         }
  89.         $request $event->getRequest();
  90.         if (! $request->attributes->get(CanRestBundle::ATTR_SERVICE_ZONE)) {
  91.             return;
  92.         }
  93.         // Determine the applicable rate limit, according to the registry
  94.         // and the current configuration, but allow third-parties to modify
  95.         // the determined rate limit
  96.         $rateLimit $this->dispatcher->dispatchGetRateLimit($request$this->getRateLimit($request))->getRateLimit();
  97.         // Skip if the endpoint is not rate-limited
  98.         if (null === $rateLimit || $rateLimit->isUnspecified() || $rateLimit->isUnlimited()) {
  99.             $request->attributes->set(CanRestBundle::ATTR_RATE_LIMIT_CONSTRAINTRateLimitConstraint::NONE);
  100.             return;
  101.         }
  102.         // Handle current rate limit
  103.         $rateLimitEntry $this->handler->handle($request$rateLimit);
  104.         $rateLimitInfo $rateLimitEntry->getRateLimitInfo();
  105.         $request->attributes->set(CanRestBundle::ATTR_RATE_LIMIT_INFO$rateLimitInfo);
  106.         $request->attributes->set(CanRestBundle::ATTR_RATE_LIMIT_KEY$rateLimitEntry->getKey());
  107.         if ($rateLimitInfo->isRateLimitExceeded()) {
  108.             // Notify listeners *first*
  109.             $this->dispatcher->dispatchRateLimitsExceeded($request$rateLimitEntry);
  110.             switch ($this->configuration->getPolicy()) {
  111.                 case RateLimitPolicy::REPORT:
  112.                     $this->reportRateLimitExceeded($request$rateLimitEntry);
  113.                     break;
  114.                 case RateLimitPolicy::REDIRECT:
  115.                     $this->redirectRateLimitExceeded($request$rateLimitEntry);
  116.                     break;
  117.                 case RateLimitPolicy::THROW:
  118.                     $this->throwRateLimitExceeded($request$rateLimitEntry);
  119.                     break;
  120.                 case RateLimitPolicy::BLOCK:
  121.                     $this->blockRateLimitExceeded($request$rateLimitEntry);
  122.                     break;
  123.             }
  124.         }
  125.     }
  126.     /**
  127.      * @param Request        $request
  128.      * @param RateLimitEntry $rateLimitEntry
  129.      *
  130.      * @return RateLimit
  131.      */
  132.     protected function redirectRateLimitExceeded(Request $requestRateLimitEntry $rateLimitEntry): RateLimit
  133.     {
  134.     }
  135.     /**
  136.      * @param Request        $request
  137.      * @param RateLimitEntry $rateLimitEntry
  138.      *
  139.      * @return RateLimit
  140.      */
  141.     protected function reportRateLimitExceeded(Request $requestRateLimitEntry $rateLimitEntry): RateLimit
  142.     {
  143.     }
  144.     /**
  145.      * Throws a {@link RateLimitExceededHttpException} and maybe blocks the
  146.      * consumer, if it already exceeded the {@link blockAfter} counter.
  147.      *
  148.      * @param Request        $request
  149.      * @param RateLimitEntry $rateLimitEntry
  150.      *
  151.      * @return RateLimit
  152.      */
  153.     protected function throwRateLimitExceeded(Request $requestRateLimitEntry $rateLimitEntry): RateLimit
  154.     {
  155.         $blockAfter $this->configuration->getBlockAfter();
  156.         if ($blockAfter && $rateLimitEntry->getRateLimitInfo()->isRateLimitAbused($blockAfter)) {
  157.             // Dispatch a rate limit abused event *first*
  158.             $this->dispatcher->dispatchRateLimitsAbused($request$rateLimitEntry$blockAfter);
  159.             throw new RateLimitAbusedException($rateLimitEntry->getRateLimitInfo(), $blockAfter);
  160.         } else {
  161.             throw new RateLimitExceededException($rateLimitEntry->getRateLimitInfo());
  162.         }
  163.     }
  164.     /**
  165.      * Throws a {@link RateLimitAbusedException} and blocks the consumer.
  166.      *
  167.      * @param Request        $request
  168.      * @param RateLimitEntry $rateLimitEntry
  169.      *
  170.      * @return RateLimit
  171.      */
  172.     protected function blockRateLimitExceeded(Request $requestRateLimitEntry $rateLimitEntry): RateLimit
  173.     {
  174.         $blockAfter $this->configuration->getBlockAfter();
  175.         // Dispatch a rate limit abused event *first*
  176.         $this->dispatcher->dispatchRateLimitsAbused($request$rateLimitEntry$blockAfter);
  177.         throw new RateLimitAbusedException($rateLimitEntry->getRateLimitInfo(), $blockAfter);
  178.     }
  179.     /**
  180.      * @param Request $request
  181.      *
  182.      * @return RateLimit
  183.      */
  184.     protected function getRateLimit(Request $request): RateLimit
  185.     {
  186.         // If someone has already determined the rate limit for the
  187.         // current request, then use that value
  188.         if (null !== $rateLimit $request->attributes->get(CanRestBundle::ATTR_RATE_LIMIT)) {
  189.             $request->attributes->set(CanRestBundle::ATTR_RATE_LIMIT_CONSTRAINTRateLimitConstraint::EXTERNAL);
  190.             return $rateLimit;
  191.         }
  192.         // Else, try to figure out the rate limit from the resource
  193.         // metadata which corresponds to the requested service node
  194.         // Start with the service default limit, and if, there's an
  195.         // entry which is more specific then override
  196.         $rateLimit $this->configuration->getDefault();
  197.         $request->attributes->set(CanRestBundle::ATTR_RATE_LIMIT_CONSTRAINTRateLimitConstraint::SERVICE);
  198.         /** @var ServiceNode $serviceNode */
  199.         $serviceNode $request->attributes->get(CanRestBundle::ATTR_SERVICE_NODE);
  200.         $versionNumber $serviceNode->getVersionNumber();
  201.         if (! empty($versionNumber) && $this->metadataStore->hasVersionMetadata($versionNumber)) {
  202.             $resourceName $serviceNode->getResourceName();
  203.             $metadata $this->metadataStore->getVersionMetadata($versionNumber);
  204.             $versionRateLimit = new RateLimit(
  205.                 $metadata->getRateLimit(),
  206.                 $metadata->getRateLimitWindow()
  207.             );
  208.             if (! $versionRateLimit->isUnspecified()) {
  209.                 $rateLimit $versionRateLimit;
  210.                 $request->attributes->set(CanRestBundle::ATTR_RATE_LIMIT_CONSTRAINTRateLimitConstraint::VERSION);
  211.             }
  212.             if (! empty($resourceName) && $metadata->getResources()->hasItem($resourceName)) {
  213.                 $metadata $metadata->getResources()->getItem($resourceName);
  214.                 $resourceRateLimit = new RateLimit(
  215.                     $metadata->getRateLimit(),
  216.                     $metadata->getRateLimitWindow()
  217.                 );
  218.                 if (! $resourceRateLimit->isUnspecified()) {
  219.                     $rateLimit $resourceRateLimit;
  220.                     $request->attributes->set(CanRestBundle::ATTR_RATE_LIMIT_CONSTRAINTRateLimitConstraint::RESOURCE);
  221.                 }
  222.             }
  223.             return $rateLimit;
  224.         }
  225.         // As fallback, check the registry
  226.         $request->attributes->set(CanRestBundle::ATTR_RATE_LIMIT_CONSTRAINTRateLimitConstraint::FALLBACK);
  227.         return $this->registry->getRateLimit($request);
  228.     }
  229. }