vendor/ibexa/corporate-account/src/bundle/EventSubscriber/ApplicationRateLimitSubscriber.php line 60

Open in your IDE?
  1. <?php
  2. /**
  3. * @copyright Copyright (C) Ibexa AS. All rights reserved.
  4. * @license For full copyright and license information view LICENSE file distributed with this source code.
  5. */
  6. declare(strict_types=1);
  7. namespace Ibexa\Bundle\CorporateAccount\EventSubscriber;
  8. use Ibexa\Contracts\Core\Repository\Events\Content\BeforeCreateContentEvent;
  9. use Ibexa\Contracts\Core\Repository\PermissionResolver;
  10. use Ibexa\Contracts\Core\Repository\Values\Content\ContentCreateStruct;
  11. use Ibexa\Contracts\Core\Repository\Values\User\UserReference;
  12. use Ibexa\Contracts\Core\SiteAccess\ConfigResolverInterface;
  13. use Ibexa\Contracts\CorporateAccount\Exception\ApplicationRateLimitExceededException;
  14. use Ibexa\CorporateAccount\Configuration\CorporateAccountConfiguration;
  15. use Symfony\Component\EventDispatcher\EventSubscriberInterface;
  16. use Symfony\Component\HttpFoundation\RequestStack;
  17. use Symfony\Component\RateLimiter\RateLimiterFactory;
  18. /**
  19. * @internal
  20. */
  21. final class ApplicationRateLimitSubscriber implements EventSubscriberInterface
  22. {
  23. private const RATE_LIMITER_KEY_PATTERN = 'user_%d_ip_%s';
  24. private RequestStack $requestStack;
  25. private PermissionResolver $permissionResolver;
  26. private CorporateAccountConfiguration $configuration;
  27. private ConfigResolverInterface $configResolver;
  28. private RateLimiterFactory $corporateAccountApplicationLimiter;
  29. public function __construct(
  30. RequestStack $requestStack,
  31. PermissionResolver $permissionResolver,
  32. ConfigResolverInterface $configResolver,
  33. CorporateAccountConfiguration $configuration,
  34. RateLimiterFactory $corporateAccountApplicationLimiter
  35. ) {
  36. $this->requestStack = $requestStack;
  37. $this->permissionResolver = $permissionResolver;
  38. $this->configResolver = $configResolver;
  39. $this->configuration = $configuration;
  40. $this->corporateAccountApplicationLimiter = $corporateAccountApplicationLimiter;
  41. }
  42. public static function getSubscribedEvents(): array
  43. {
  44. return [
  45. BeforeCreateContentEvent::class => ['onBeforeCreateContent', 100],
  46. ];
  47. }
  48. public function onBeforeCreateContent(BeforeCreateContentEvent $event): void
  49. {
  50. $contentCreateStruct = $event->getContentCreateStruct();
  51. $currentUser = $this->permissionResolver->getCurrentUserReference();
  52. if ($this->isApplicationCreateStruct($contentCreateStruct) && $this->isAnonymousUser($currentUser)) {
  53. $mainRequest = $this->requestStack->getMainRequest();
  54. if ($mainRequest !== null) {
  55. $limiter = $this->corporateAccountApplicationLimiter->create(
  56. sprintf(
  57. self::RATE_LIMITER_KEY_PATTERN,
  58. $currentUser->getUserId(),
  59. $mainRequest->getClientIp()
  60. )
  61. );
  62. if (!$limiter->consume()->isAccepted()) {
  63. throw new ApplicationRateLimitExceededException($limiter);
  64. }
  65. }
  66. }
  67. }
  68. private function isApplicationCreateStruct(ContentCreateStruct $contentCreateStruct): bool
  69. {
  70. return $contentCreateStruct->contentType->identifier
  71. === $this->configuration->getApplicationContentTypeIdentifier();
  72. }
  73. private function isAnonymousUser(UserReference $userReference): bool
  74. {
  75. return $userReference->getUserId()
  76. === $this->configResolver->getParameter('anonymous_user_id');
  77. }
  78. }