feat: add app:register-domain command

This commit is contained in:
Maël Gangloff
2024-12-07 20:08:29 +01:00
parent c3915556a5
commit fbcecff982
5 changed files with 125 additions and 53 deletions

View File

@@ -0,0 +1,61 @@
<?php
namespace App\Command;
use App\Repository\DomainRepository;
use App\Service\RDAPService;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
#[AsCommand(
name: 'app:register-domain',
description: 'Register a domain name in the database',
)]
class RegisterDomainCommand extends Command
{
public function __construct(
private readonly DomainRepository $domainRepository,
private readonly RDAPService $rdapService,
) {
parent::__construct();
}
protected function configure(): void
{
$this
->addArgument('domain', InputArgument::REQUIRED, 'The domain name to register')
->addOption('force', 'f', InputOption::VALUE_NEGATABLE, 'Do not check the freshness of the data and still make the query', false);
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
$io = new SymfonyStyle($input, $output);
$ldhName = strtolower(idn_to_ascii($input->getArgument('domain')));
$force = (bool) $input->getOption('force');
$domain = $this->domainRepository->findOneBy(['ldhName' => $ldhName]);
try {
if (null !== $domain && !$force) {
if (!$domain->isToBeUpdated()) {
$io->warning('The domain name is already present in the database and does not need to be updated at this time.');
return Command::SUCCESS;
}
}
$this->rdapService->registerDomain($ldhName);
} catch (\Throwable $e) {
$io->error($e->getMessage());
return Command::FAILURE;
}
$io->success('The domain name has been successfully registered in the database.');
return Command::SUCCESS;
}
}

View File

@@ -53,8 +53,7 @@ class DomainRefreshController extends AbstractController
// If the domain name exists in the database, recently updated and not important, we return the stored Domain
if (null !== $domain
&& !$domain->getDeleted()
&& ($domain->getUpdatedAt()->diff(new \DateTimeImmutable('now'))->days < 7)
&& !$this->RDAPService::isToBeWatchClosely($domain)
&& !$domain->isToBeUpdated()
&& !$this->kernel->isDebug()
) {
$this->logger->info('It is not necessary to update the information of the domain name {idnDomain} with the RDAP protocol.', [

View File

@@ -4,6 +4,7 @@ namespace App\Entity;
use ApiPlatform\Metadata\ApiResource;
use ApiPlatform\Metadata\Get;
use App\Config\EventAction;
use App\Controller\DomainRefreshController;
use App\Repository\DomainRepository;
use Doctrine\Common\Collections\ArrayCollection;
@@ -105,6 +106,20 @@ class Domain
#[Groups(['domain:item', 'domain:list'])]
private ?bool $deleted;
private const IMPORTANT_EVENTS = [EventAction::Deletion->value, EventAction::Expiration->value];
private const IMPORTANT_STATUS = [
'redemption period',
'pending delete',
'pending create',
'pending renew',
'pending restore',
'pending transfer',
'pending update',
'add period',
'client hold',
'server hold',
];
public function __construct()
{
$this->events = new ArrayCollection();
@@ -317,4 +332,49 @@ class Domain
return $this;
}
/**
* Determines if a domain name needs special attention.
* These domain names are those whose last event was expiration or deletion.
*
* @throws \Exception
*/
private function isToBeWatchClosely(): bool
{
$status = $this->getStatus();
if ((!empty($status) && count(array_intersect($status, self::IMPORTANT_STATUS))) || $this->getDeleted()) {
return true;
}
/** @var DomainEvent[] $events */
$events = $this->getEvents()
->filter(fn (DomainEvent $e) => $e->getDate() <= new \DateTimeImmutable('now'))
->toArray();
usort($events, fn (DomainEvent $e1, DomainEvent $e2) => $e2->getDate() <=> $e1->getDate());
return !empty($events) && in_array($events[0]->getAction(), self::IMPORTANT_EVENTS);
}
/**
* Returns true if one or more of these conditions are met:
* - It has been more than 7 days since the domain name was last updated
* - It has been more than 1 hour and the domain name has statuses that suggest it is not stable
* - It has been more than 1 day and the domain name is blocked in DNS
*
* @throws \Exception
*/
public function isToBeUpdated(bool $fromUser = true): bool
{
return $this->getUpdatedAt()
->diff(new \DateTimeImmutable())->days >= 7
|| (
($fromUser || ($this->getUpdatedAt()
->diff(new \DateTimeImmutable())->h * 60 + $this->getUpdatedAt()
->diff(new \DateTimeImmutable())->i) >= 50)
&& $this->isToBeWatchClosely()
)
|| (count(array_intersect($this->getStatus(), ['auto renew period', 'client hold', 'server hold'])) > 0
&& $this->getUpdatedAt()->diff(new \DateTimeImmutable())->days >= 1);
}
}

View File

@@ -68,19 +68,7 @@ final readonly class UpdateDomainsFromWatchlistHandler
*/
/** @var Domain $domain */
foreach ($watchList->getDomains()
->filter(fn ($domain) => $domain->getUpdatedAt()
->diff(new \DateTimeImmutable())->days >= 7
|| (
($domain->getUpdatedAt()
->diff(new \DateTimeImmutable())->h * 60 + $domain->getUpdatedAt()
->diff(new \DateTimeImmutable())->i) >= 50
&& $this->RDAPService::isToBeWatchClosely($domain)
)
|| (count(array_intersect($domain->getStatus(), ['auto renew period', 'client hold', 'server hold'])) > 0
&& $domain->getUpdatedAt()->diff(new \DateTimeImmutable())->days >= 1
)
) as $domain
foreach ($watchList->getDomains()->filter(fn ($domain) => $domain->isToBeUpdated(false)) as $domain
) {
$updatedAt = $domain->getUpdatedAt();

View File

@@ -74,18 +74,6 @@ readonly class RDAPService
'xn--hlcj6aya9esc7a',
];
private const IMPORTANT_EVENTS = [EventAction::Deletion->value, EventAction::Expiration->value];
private const IMPORTANT_STATUS = [
'redemption period',
'pending delete',
'pending create',
'pending renew',
'pending restore',
'pending transfer',
'pending update',
'add period',
];
public function __construct(private HttpClientInterface $client,
private EntityRepository $entityRepository,
private DomainRepository $domainRepository,
@@ -102,34 +90,10 @@ readonly class RDAPService
) {
}
/**
* Determines if a domain name needs special attention.
* These domain names are those whose last event was expiration or deletion.
*
* @throws \Exception
*/
public static function isToBeWatchClosely(Domain $domain): bool
{
$status = $domain->getStatus();
if ((!empty($status) && count(array_intersect($status, self::IMPORTANT_STATUS))) || $domain->getDeleted()) {
return true;
}
/** @var DomainEvent[] $events */
$events = $domain->getEvents()
->filter(fn (DomainEvent $e) => $e->getDate() <= new \DateTimeImmutable('now'))
->toArray();
usort($events, fn (DomainEvent $e1, DomainEvent $e2) => $e2->getDate() <=> $e1->getDate());
return !empty($events) && in_array($events[0]->getAction(), self::IMPORTANT_EVENTS);
}
/**
* @throws HttpExceptionInterface
* @throws TransportExceptionInterface
* @throws DecodingExceptionInterface
* @throws \Throwable
*/
public function registerDomains(array $domains): void
{
@@ -144,7 +108,7 @@ readonly class RDAPService
* @throws RedirectionExceptionInterface
* @throws DecodingExceptionInterface
* @throws ClientExceptionInterface
* @throws \Throwable
* @throws \Exception
*/
public function registerDomain(string $fqdn): Domain
{
@@ -179,7 +143,7 @@ readonly class RDAPService
$res = $this->client->request(
'GET', $rdapServerUrl.'domain/'.$idnDomain
)->toArray();
} catch (\Throwable $e) {
} catch (\Exception $e) {
if ($e instanceof ClientException && 404 === $e->getResponse()->getStatusCode()) {
if (null !== $domain) {
$this->logger->notice('The domain name {idnDomain} has been deleted from the WHOIS database.', [