From c93037663f35bb9e8da614fa0da52fdd39d66706 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Gangloff?= Date: Sat, 13 Sep 2025 13:09:28 +0200 Subject: [PATCH] feat: add propose domain endpoint --- config/packages/messenger.yaml | 1 + config/packages/rate_limiter.yaml | 7 ++- config/packages/security.yaml | 1 + src/Controller/DomainRefreshController.php | 37 +------------- src/Controller/ProposeDomainController.php | 48 ++++++++++++++++++ src/Entity/Domain.php | 10 ++++ src/Message/ProposeDomainMessage.php | 11 +++++ .../ProposeDomainMessageHandler.php | 35 +++++++++++++ src/Service/RDAPService.php | 49 +++++++++++++++++++ 9 files changed, 163 insertions(+), 36 deletions(-) create mode 100644 src/Controller/ProposeDomainController.php create mode 100644 src/Message/ProposeDomainMessage.php create mode 100644 src/MessageHandler/ProposeDomainMessageHandler.php diff --git a/config/packages/messenger.yaml b/config/packages/messenger.yaml index 8a72323..8954951 100644 --- a/config/packages/messenger.yaml +++ b/config/packages/messenger.yaml @@ -28,6 +28,7 @@ framework: App\Message\UpdateDomainsFromWatchlist: async App\Message\UpdateRdapServers: async App\Message\ValidateConnectorCredentials: async + App\Message\ProposeDomainMessage: async # Route your messages to the transports # 'App\Message\YourMessage': async diff --git a/config/packages/rate_limiter.yaml b/config/packages/rate_limiter.yaml index 06085c5..f55d464 100644 --- a/config/packages/rate_limiter.yaml +++ b/config/packages/rate_limiter.yaml @@ -23,4 +23,9 @@ framework: rdap_requests: policy: sliding_window limit: 10 - interval: '1 hour' \ No newline at end of file + interval: '1 hour' + + propose_domain: + policy: sliding_window + limit: 10 + interval: '1 hour' diff --git a/config/packages/security.yaml b/config/packages/security.yaml index 07fe14e..c01f841 100644 --- a/config/packages/security.yaml +++ b/config/packages/security.yaml @@ -63,6 +63,7 @@ security: - { path: "^/api/watchlists/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/calendar$", roles: PUBLIC_ACCESS } - { path: "^/api/watchlists/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/rss", roles: PUBLIC_ACCESS } - { path: "^/api/config$", roles: PUBLIC_ACCESS } + - { path: "^/api/propose-domain", roles: PUBLIC_ACCESS } - { path: ^/api, roles: IS_AUTHENTICATED_FULLY } when@test: diff --git a/src/Controller/DomainRefreshController.php b/src/Controller/DomainRefreshController.php index a434f40..e50d762 100644 --- a/src/Controller/DomainRefreshController.php +++ b/src/Controller/DomainRefreshController.php @@ -3,18 +3,13 @@ namespace App\Controller; use App\Entity\Domain; -use App\Entity\WatchList; -use App\Message\SendDomainEventNotif; -use App\Repository\DomainRepository; use App\Service\RDAPService; use Psr\Log\LoggerInterface; -use Random\Randomizer; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpKernel\Exception\TooManyRequestsHttpException; use Symfony\Component\HttpKernel\KernelInterface; use Symfony\Component\Messenger\Exception\ExceptionInterface; -use Symfony\Component\Messenger\MessageBusInterface; use Symfony\Component\RateLimiter\RateLimiterFactory; use Symfony\Contracts\HttpClient\Exception\DecodingExceptionInterface; use Symfony\Contracts\HttpClient\Exception\HttpExceptionInterface; @@ -22,10 +17,9 @@ use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface; class DomainRefreshController extends AbstractController { - public function __construct(private readonly DomainRepository $domainRepository, + public function __construct( private readonly RDAPService $RDAPService, private readonly RateLimiterFactory $rdapRequestsLimiter, - private readonly MessageBusInterface $bus, private readonly LoggerInterface $logger, private readonly KernelInterface $kernel, ) { @@ -49,22 +43,6 @@ class DomainRefreshController extends AbstractController 'idnDomain' => $idnDomain, ]); - /** @var ?Domain $domain */ - $domain = $this->domainRepository->findOneBy(['ldhName' => $idnDomain]); - // If the domain name exists in the database, recently updated and not important, we return the stored Domain - if (null !== $domain - && !$domain->getDeleted() - && !$domain->isToBeUpdated(true, true) - && !$this->kernel->isDebug() - && true !== filter_var($request->get('forced', false), FILTER_VALIDATE_BOOLEAN) - ) { - $this->logger->info('It is not necessary to update the information of the domain name {idnDomain} with the RDAP protocol.', [ - 'idnDomain' => $idnDomain, - ]); - - return $domain; - } - if (false === $this->kernel->isDebug() && true === $this->getParameter('limited_features')) { $limiter = $this->rdapRequestsLimiter->create($userId); $limit = $limiter->consume(); @@ -74,17 +52,6 @@ class DomainRefreshController extends AbstractController } } - $updatedAt = null === $domain ? new \DateTimeImmutable('now') : $domain->getUpdatedAt(); - $domain = $this->RDAPService->registerDomain($idnDomain); - - $randomizer = new Randomizer(); - $watchLists = $randomizer->shuffleArray($domain->getWatchLists()->toArray()); - - /** @var WatchList $watchList */ - foreach ($watchLists as $watchList) { - $this->bus->dispatch(new SendDomainEventNotif($watchList->getToken(), $domain->getLdhName(), $updatedAt)); - } - - return $domain; + return $this->RDAPService->updateDomain($idnDomain, $request->get('forced', false)); } } diff --git a/src/Controller/ProposeDomainController.php b/src/Controller/ProposeDomainController.php new file mode 100644 index 0000000..6e011a4 --- /dev/null +++ b/src/Controller/ProposeDomainController.php @@ -0,0 +1,48 @@ +kernel->isDebug()) { + $limiter = $this->rdapRequestsLimiter->create($request->getClientIp()); + $limit = $limiter->consume(); + + if (!$limit->isAccepted()) { + throw new TooManyRequestsHttpException($limit->getRetryAfter()->getTimestamp() - time()); + } + } + + $this->bus->dispatch(new ProposeDomainMessage($ldhName)); + + return new Response(null, 204); + } +} diff --git a/src/Entity/Domain.php b/src/Entity/Domain.php index bbb9430..4215a96 100644 --- a/src/Entity/Domain.php +++ b/src/Entity/Domain.php @@ -4,8 +4,10 @@ namespace App\Entity; use ApiPlatform\Metadata\ApiResource; use ApiPlatform\Metadata\Get; +use ApiPlatform\Metadata\Post; use App\Config\EventAction; use App\Controller\DomainRefreshController; +use App\Controller\ProposeDomainController; use App\Repository\DomainRepository; use App\Service\RDAPService; use App\State\AutoRegisterDomainProvider; @@ -56,6 +58,14 @@ use Symfony\Component\Serializer\Attribute\SerializedName; ], read: false ), + new Post( + uriTemplate: '/propose-domain/{ldhName}', + controller: ProposeDomainController::class, + shortName: 'Propose Domain', + input: false, + read: false, + write: false, + ), ], provider: AutoRegisterDomainProvider::class )] diff --git a/src/Message/ProposeDomainMessage.php b/src/Message/ProposeDomainMessage.php new file mode 100644 index 0000000..4551fa9 --- /dev/null +++ b/src/Message/ProposeDomainMessage.php @@ -0,0 +1,11 @@ +RDAPService->updateDomain($message->ldhName); + } +} diff --git a/src/Service/RDAPService.php b/src/Service/RDAPService.php index a1a3bf8..50979be 100644 --- a/src/Service/RDAPService.php +++ b/src/Service/RDAPService.php @@ -18,6 +18,8 @@ use App\Entity\Nameserver; use App\Entity\NameserverEntity; use App\Entity\RdapServer; use App\Entity\Tld; +use App\Entity\WatchList; +use App\Message\SendDomainEventNotif; use App\Repository\DomainEntityRepository; use App\Repository\DomainEventRepository; use App\Repository\DomainRepository; @@ -31,10 +33,14 @@ use Doctrine\DBAL\LockMode; use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\Exception\ORMException; use Psr\Log\LoggerInterface; +use Random\Randomizer; use Symfony\Component\DependencyInjection\Attribute\Autowire; use Symfony\Component\HttpClient\Exception\ClientException; use Symfony\Component\HttpFoundation\Exception\BadRequestException; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; +use Symfony\Component\HttpKernel\KernelInterface; +use Symfony\Component\Messenger\Exception\ExceptionInterface; +use Symfony\Component\Messenger\MessageBusInterface; use Symfony\Component\Yaml\Yaml; use Symfony\Contracts\HttpClient\Exception\ClientExceptionInterface; use Symfony\Contracts\HttpClient\Exception\DecodingExceptionInterface; @@ -110,6 +116,8 @@ class RDAPService private InfluxdbService $influxService, #[Autowire(param: 'influxdb_enabled')] private bool $influxdbEnabled, + private readonly KernelInterface $kernel, + private readonly MessageBusInterface $bus, ) { } @@ -936,4 +944,45 @@ class RDAPService $this->em->flush(); } + + /** + * @throws TransportExceptionInterface + * @throws ServerExceptionInterface + * @throws RedirectionExceptionInterface + * @throws DecodingExceptionInterface + * @throws ClientExceptionInterface + * @throws ExceptionInterface + * @throws \Exception + */ + public function updateDomain(string $idnDomain, bool $forced = false): ?Domain + { + /** @var ?Domain $domain */ + $domain = $this->domainRepository->findOneBy(['ldhName' => $idnDomain]); + // If the domain name exists in the database, recently updated and not important, we return the stored Domain + if (null !== $domain + && !$domain->getDeleted() + && !$domain->isToBeUpdated(true, true) + && !$this->kernel->isDebug() + && true !== filter_var($forced, FILTER_VALIDATE_BOOLEAN) + ) { + $this->logger->info('It is not necessary to update the information of the domain name {idnDomain} with the RDAP protocol.', [ + 'idnDomain' => $idnDomain, + ]); + + return $domain; + } + + $updatedAt = null === $domain ? new \DateTimeImmutable('now') : $domain->getUpdatedAt(); + $domain = $this->registerDomain($idnDomain); + + $randomizer = new Randomizer(); + $watchLists = $randomizer->shuffleArray($domain->getWatchLists()->toArray()); + + /** @var WatchList $watchList */ + foreach ($watchLists as $watchList) { + $this->bus->dispatch(new SendDomainEventNotif($watchList->getToken(), $domain->getLdhName(), $updatedAt)); + } + + return $domain; + } }