mirror of
https://github.com/maelgangloff/domain-watchdog.git
synced 2025-12-29 16:15:04 +00:00
Merge branch 'feat/iana-registrar-id'
This commit is contained in:
46
migrations/Version20250910171544.php
Normal file
46
migrations/Version20250910171544.php
Normal file
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace DoctrineMigrations;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
/**
|
||||
* Auto-generated Migration: Please modify to your needs!
|
||||
*/
|
||||
final class Version20250910171544 extends AbstractMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return 'Add columns for IANA fields in the entity table';
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
// this up() migration is auto-generated, please modify it to your needs
|
||||
$this->addSql('ALTER TABLE entity ADD registrar_name_iana VARCHAR(255) DEFAULT NULL');
|
||||
$this->addSql('ALTER TABLE entity ADD rdap_base_url_iana VARCHAR(255) DEFAULT NULL');
|
||||
$this->addSql('ALTER TABLE entity ADD status_iana VARCHAR(255) DEFAULT NULL');
|
||||
$this->addSql('ALTER TABLE entity ADD updated_iana DATE DEFAULT NULL');
|
||||
$this->addSql('ALTER TABLE entity ADD date_iana DATE DEFAULT NULL');
|
||||
$this->addSql('COMMENT ON COLUMN entity.updated_iana IS \'(DC2Type:date_immutable)\'');
|
||||
$this->addSql('COMMENT ON COLUMN entity.date_iana IS \'(DC2Type:date_immutable)\'');
|
||||
|
||||
$this->addSql("DELETE FROM domain_entity de USING entity e WHERE de.entity_uid = e.id AND e.handle ~ '^[0-9]+$'");
|
||||
$this->addSql("DELETE FROM entity_event ev USING entity e WHERE ev.entity_uid = e.id AND e.handle ~ '^[0-9]+$'");
|
||||
$this->addSql("DELETE FROM nameserver_entity ne USING entity e WHERE ne.entity_uid = e.id AND e.handle ~ '^[0-9]+$'");
|
||||
$this->addSql("DELETE FROM entity WHERE handle ~ '^[0-9]+$'");
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
// this down() migration is auto-generated, please modify it to your needs
|
||||
$this->addSql('ALTER TABLE entity DROP registrar_name_iana');
|
||||
$this->addSql('ALTER TABLE entity DROP rdap_base_url_iana');
|
||||
$this->addSql('ALTER TABLE entity DROP status_iana');
|
||||
$this->addSql('ALTER TABLE entity DROP updated_iana');
|
||||
$this->addSql('ALTER TABLE entity DROP date_iana');
|
||||
}
|
||||
}
|
||||
54
migrations/Version20250910201456.php
Normal file
54
migrations/Version20250910201456.php
Normal file
@@ -0,0 +1,54 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace DoctrineMigrations;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
/**
|
||||
* Auto-generated Migration: Please modify to your needs!
|
||||
*/
|
||||
final class Version20250910201456 extends AbstractMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return 'Rename IANA columns';
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
// this up() migration is auto-generated, please modify it to your needs
|
||||
$this->addSql('ALTER TABLE entity ADD iana_registrar_name VARCHAR(255) DEFAULT NULL');
|
||||
$this->addSql('ALTER TABLE entity ADD iana_rdap_base_url VARCHAR(255) DEFAULT NULL');
|
||||
$this->addSql('ALTER TABLE entity ADD iana_status VARCHAR(255) DEFAULT NULL');
|
||||
$this->addSql('ALTER TABLE entity ADD iana_updated DATE DEFAULT NULL');
|
||||
$this->addSql('ALTER TABLE entity ADD iana_date DATE DEFAULT NULL');
|
||||
$this->addSql('ALTER TABLE entity DROP registrar_name_iana');
|
||||
$this->addSql('ALTER TABLE entity DROP rdap_base_url_iana');
|
||||
$this->addSql('ALTER TABLE entity DROP status_iana');
|
||||
$this->addSql('ALTER TABLE entity DROP updated_iana');
|
||||
$this->addSql('ALTER TABLE entity DROP date_iana');
|
||||
$this->addSql('COMMENT ON COLUMN entity.iana_updated IS \'(DC2Type:date_immutable)\'');
|
||||
$this->addSql('COMMENT ON COLUMN entity.iana_date IS \'(DC2Type:date_immutable)\'');
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
// this down() migration is auto-generated, please modify it to your needs
|
||||
$this->addSql('CREATE SCHEMA public');
|
||||
$this->addSql('ALTER TABLE entity ADD registrar_name_iana VARCHAR(255) DEFAULT NULL');
|
||||
$this->addSql('ALTER TABLE entity ADD rdap_base_url_iana VARCHAR(255) DEFAULT NULL');
|
||||
$this->addSql('ALTER TABLE entity ADD status_iana VARCHAR(255) DEFAULT NULL');
|
||||
$this->addSql('ALTER TABLE entity ADD updated_iana DATE DEFAULT NULL');
|
||||
$this->addSql('ALTER TABLE entity ADD date_iana DATE DEFAULT NULL');
|
||||
$this->addSql('ALTER TABLE entity DROP iana_registrar_name');
|
||||
$this->addSql('ALTER TABLE entity DROP iana_rdap_base_url');
|
||||
$this->addSql('ALTER TABLE entity DROP iana_status');
|
||||
$this->addSql('ALTER TABLE entity DROP iana_updated');
|
||||
$this->addSql('ALTER TABLE entity DROP iana_date');
|
||||
$this->addSql('COMMENT ON COLUMN entity.updated_iana IS \'(DC2Type:date_immutable)\'');
|
||||
$this->addSql('COMMENT ON COLUMN entity.date_iana IS \'(DC2Type:date_immutable)\'');
|
||||
}
|
||||
}
|
||||
10
src/Config/RegistrarStatus.php
Normal file
10
src/Config/RegistrarStatus.php
Normal file
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace App\Config;
|
||||
|
||||
enum RegistrarStatus: string
|
||||
{
|
||||
case Reserved = 'Reserved';
|
||||
case Accredited = 'Accredited';
|
||||
case Terminated = 'Terminated';
|
||||
}
|
||||
@@ -3,11 +3,11 @@
|
||||
namespace App\Entity;
|
||||
|
||||
use ApiPlatform\Metadata\ApiResource;
|
||||
use ApiPlatform\Metadata\Get;
|
||||
use App\Repository\EntityRepository;
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
use Doctrine\Common\Collections\Collection;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use Doctrine\ORM\Mapping\Embedded;
|
||||
use Symfony\Component\Serializer\Attribute\Groups;
|
||||
use Symfony\Component\Serializer\Attribute\SerializedName;
|
||||
|
||||
@@ -86,11 +86,16 @@ class Entity
|
||||
#[Groups(['entity:item', 'domain:item'])]
|
||||
private ?array $remarks = null;
|
||||
|
||||
#[Embedded(class: IanaAccreditation::class, columnPrefix: 'iana_')]
|
||||
#[Groups(['entity:item', 'domain:item'])]
|
||||
private IanaAccreditation $ianaAccreditation;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->domainEntities = new ArrayCollection();
|
||||
$this->nameserverEntities = new ArrayCollection();
|
||||
$this->events = new ArrayCollection();
|
||||
$this->ianaAccreditation = new IanaAccreditation();
|
||||
}
|
||||
|
||||
public function getHandle(): ?string
|
||||
@@ -242,4 +247,14 @@ class Entity
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getIanaAccreditation(): IanaAccreditation
|
||||
{
|
||||
return $this->ianaAccreditation;
|
||||
}
|
||||
|
||||
public function setIanaAccreditation(IanaAccreditation $ianaAccreditation): void
|
||||
{
|
||||
$this->ianaAccreditation = $ianaAccreditation;
|
||||
}
|
||||
}
|
||||
|
||||
93
src/Entity/IanaAccreditation.php
Normal file
93
src/Entity/IanaAccreditation.php
Normal file
@@ -0,0 +1,93 @@
|
||||
<?php
|
||||
|
||||
namespace App\Entity;
|
||||
|
||||
use App\Config\RegistrarStatus;
|
||||
use Doctrine\DBAL\Types\Types;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use Doctrine\ORM\Mapping\Embeddable;
|
||||
use Symfony\Component\Serializer\Attribute\Groups;
|
||||
|
||||
#[Embeddable]
|
||||
class IanaAccreditation
|
||||
{
|
||||
#[ORM\Column(length: 255, nullable: true)]
|
||||
#[Groups(['entity:item', 'domain:item'])]
|
||||
private ?string $registrarName = null;
|
||||
|
||||
#[ORM\Column(length: 255, nullable: true)]
|
||||
#[Groups(['entity:item', 'domain:item'])]
|
||||
private ?string $rdapBaseUrl = null;
|
||||
|
||||
#[ORM\Column(nullable: true, enumType: RegistrarStatus::class)]
|
||||
#[Groups(['entity:item', 'domain:item'])]
|
||||
private ?RegistrarStatus $status = null;
|
||||
|
||||
#[ORM\Column(type: Types::DATE_IMMUTABLE, nullable: true)]
|
||||
#[Groups(['entity:item', 'domain:item'])]
|
||||
private ?\DateTimeImmutable $updated = null;
|
||||
|
||||
#[ORM\Column(type: Types::DATE_IMMUTABLE, nullable: true)]
|
||||
#[Groups(['entity:item', 'domain:item'])]
|
||||
private ?\DateTimeImmutable $date = null;
|
||||
|
||||
public function getRegistrarName(): ?string
|
||||
{
|
||||
return $this->registrarName;
|
||||
}
|
||||
|
||||
public function setRegistrarName(?string $registrarName): static
|
||||
{
|
||||
$this->registrarName = $registrarName;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getRdapBaseUrl(): ?string
|
||||
{
|
||||
return $this->rdapBaseUrl;
|
||||
}
|
||||
|
||||
public function setRdapBaseUrl(?string $rdapBaseUrl): static
|
||||
{
|
||||
$this->rdapBaseUrl = $rdapBaseUrl;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getStatus(): ?RegistrarStatus
|
||||
{
|
||||
return $this->status;
|
||||
}
|
||||
|
||||
public function setStatus(?RegistrarStatus $status): static
|
||||
{
|
||||
$this->status = $status;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getUpdated(): ?\DateTimeImmutable
|
||||
{
|
||||
return $this->updated;
|
||||
}
|
||||
|
||||
public function setUpdated(?\DateTimeImmutable $updated): static
|
||||
{
|
||||
$this->updated = $updated;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getDate(): ?\DateTimeImmutable
|
||||
{
|
||||
return $this->date;
|
||||
}
|
||||
|
||||
public function setDate(?\DateTimeImmutable $date): static
|
||||
{
|
||||
$this->date = $date;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
@@ -115,12 +115,15 @@ class Tld
|
||||
|
||||
public function getTld(): ?string
|
||||
{
|
||||
return '' === $this->tld ? '.' : $this->tld;
|
||||
return $this->tld;
|
||||
}
|
||||
|
||||
public function setTld(string $tld): static
|
||||
{
|
||||
$this->tld = RDAPService::convertToIdn($tld);
|
||||
if ('' === $this->tld) {
|
||||
$this->tld = '.';
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
@@ -65,6 +65,12 @@ final readonly class UpdateRdapServersHandler
|
||||
$throws[] = $throwable;
|
||||
}
|
||||
|
||||
try {
|
||||
$this->RDAPService->updateRegistrarListIANA();
|
||||
} catch (\Throwable $throwable) {
|
||||
$throws[] = $throwable;
|
||||
}
|
||||
|
||||
if (!empty($throws)) {
|
||||
throw $throws[0];
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ namespace App\Service;
|
||||
use App\Config\DnsKey\Algorithm;
|
||||
use App\Config\DnsKey\DigestType;
|
||||
use App\Config\EventAction;
|
||||
use App\Config\RegistrarStatus;
|
||||
use App\Config\TldType;
|
||||
use App\Entity\DnsKey;
|
||||
use App\Entity\Domain;
|
||||
@@ -94,11 +95,6 @@ readonly class RDAPService
|
||||
'Private',
|
||||
];
|
||||
|
||||
/* @see https://www.iana.org/assignments/registrar-ids/registrar-ids.xhtml */
|
||||
public const IANA_RESERVED_IDS = [
|
||||
1, 3, 8, 119, 365, 376, 9994, 9995, 9996, 9997, 9998, 9999, 10009, 4000001, 8888888,
|
||||
];
|
||||
|
||||
public function __construct(private HttpClientInterface $client,
|
||||
private EntityRepository $entityRepository,
|
||||
private DomainRepository $domainRepository,
|
||||
@@ -551,24 +547,31 @@ readonly class RDAPService
|
||||
|
||||
$entity = $this->entityRepository->findOneBy([
|
||||
'handle' => $rdapEntity['handle'],
|
||||
'tld' => is_numeric($rdapEntity['handle']) ? null : $tld,
|
||||
'tld' => $tld,
|
||||
]);
|
||||
|
||||
if (null === $entity) {
|
||||
$entity = new Entity();
|
||||
$entity = $this->entityRepository->findOneBy([
|
||||
'handle' => $rdapEntity['handle'],
|
||||
'tld' => null,
|
||||
]);
|
||||
}
|
||||
|
||||
if (null === $entity) {
|
||||
$entity = (new Entity())->setTld($tld);
|
||||
|
||||
$this->logger->info('The entity {handle} was not known to this Domain Watchdog instance.', [
|
||||
'handle' => $rdapEntity['handle'],
|
||||
]);
|
||||
}
|
||||
|
||||
$entity->setHandle($rdapEntity['handle'])->setTld(is_numeric($rdapEntity['handle']) ? null : $tld);
|
||||
$entity->setHandle($rdapEntity['handle']);
|
||||
|
||||
if (isset($rdapEntity['remarks']) && is_array($rdapEntity['remarks']) && !is_numeric($rdapEntity['handle'])) {
|
||||
if (isset($rdapEntity['remarks']) && is_array($rdapEntity['remarks']) && null === $entity->getIanaAccreditation()->getStatus()) {
|
||||
$entity->setRemarks($rdapEntity['remarks']);
|
||||
}
|
||||
|
||||
if (isset($rdapEntity['vcardArray']) && !in_array($rdapEntity['handle'], self::IANA_RESERVED_IDS)) {
|
||||
if (isset($rdapEntity['vcardArray']) && null === $entity->getIanaAccreditation()->getStatus()) {
|
||||
if (empty($entity->getJCard())) {
|
||||
if (!array_key_exists('elements', $rdapEntity['vcardArray'])) {
|
||||
$entity->setJCard($rdapEntity['vcardArray']);
|
||||
@@ -602,7 +605,7 @@ readonly class RDAPService
|
||||
}
|
||||
}
|
||||
|
||||
if ($isIANAid || !isset($rdapEntity['events']) || in_array($rdapEntity['handle'], self::IANA_RESERVED_IDS)) {
|
||||
if ($isIANAid || !isset($rdapEntity['events']) || null !== $entity->getIanaAccreditation()->getStatus()) {
|
||||
return $entity;
|
||||
}
|
||||
|
||||
@@ -704,22 +707,26 @@ readonly class RDAPService
|
||||
{
|
||||
foreach ($dnsRoot['services'] as $service) {
|
||||
foreach ($service[0] as $tld) {
|
||||
if ('' === $tld && null === $this->tldRepository->findOneBy(['tld' => $tld])) {
|
||||
$this->em->persist((new Tld())->setTld('')->setType(TldType::root));
|
||||
if ('.' === $tld && null === $this->tldRepository->findOneBy(['tld' => $tld])) {
|
||||
$this->em->persist((new Tld())->setTld('.')->setType(TldType::root));
|
||||
$this->em->flush();
|
||||
}
|
||||
|
||||
$tldReference = $this->em->getReference(Tld::class, $tld);
|
||||
$tldEntity = $this->tldRepository->findOneBy(['tld' => $tld]);
|
||||
if (null === $tldEntity) {
|
||||
$tldEntity = (new Tld())->setTld($tld)->setType(TldType::gTLD);
|
||||
$this->em->persist($tldEntity);
|
||||
}
|
||||
|
||||
foreach ($service[1] as $rdapServerUrl) {
|
||||
$server = $this->rdapServerRepository->findOneBy(['tld' => $tldReference, 'url' => $rdapServerUrl]);
|
||||
$server = $this->rdapServerRepository->findOneBy(['tld' => $tldEntity->getTld(), 'url' => $rdapServerUrl]);
|
||||
|
||||
if (null === $server) {
|
||||
$server = new RdapServer();
|
||||
}
|
||||
|
||||
$server
|
||||
->setTld($tldReference)
|
||||
->setTld($tldEntity)
|
||||
->setUrl($rdapServerUrl)
|
||||
->setUpdatedAt(new \DateTimeImmutable($dnsRoot['publication'] ?? 'now'));
|
||||
|
||||
@@ -792,6 +799,45 @@ readonly class RDAPService
|
||||
$this->em->flush();
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws TransportExceptionInterface
|
||||
* @throws ServerExceptionInterface
|
||||
* @throws RedirectionExceptionInterface
|
||||
* @throws DecodingExceptionInterface
|
||||
* @throws ClientExceptionInterface
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function updateRegistrarListIANA(): void
|
||||
{
|
||||
$this->logger->info('Start of retrieval of the list of Registrar IDs according to IANA.');
|
||||
$registrarList = $this->client->request(
|
||||
'GET', 'https://www.iana.org/assignments/registrar-ids/registrar-ids.xml'
|
||||
);
|
||||
|
||||
$data = new \SimpleXMLElement($registrarList->getContent());
|
||||
|
||||
foreach ($data->registry->record as $registrar) {
|
||||
$entity = $this->entityRepository->findOneBy(['handle' => $registrar->value, 'tld' => null]);
|
||||
if (null === $entity) {
|
||||
$entity = new Entity();
|
||||
}
|
||||
$entity
|
||||
->setHandle($registrar->value)
|
||||
->setTld(null)
|
||||
->setJCard(['vcard', [['version', [], 'text', '4.0'], ['fn', [], 'text', $registrar->name]]])
|
||||
->setRemarks(null)
|
||||
->getIanaAccreditation()
|
||||
->setRegistrarName($registrar->name)
|
||||
->setStatus(RegistrarStatus::from($registrar->status))
|
||||
->setRdapBaseUrl($registrar->rdapurl->count() ? ($registrar->rdapurl->server) : null)
|
||||
->setUpdated(null !== $registrar->attributes()->updated ? new \DateTimeImmutable($registrar->attributes()->updated) : null)
|
||||
->setDate(null !== $registrar->attributes()->date ? new \DateTimeImmutable($registrar->attributes()->date) : null);
|
||||
|
||||
$this->em->persist($entity);
|
||||
}
|
||||
$this->em->flush();
|
||||
}
|
||||
|
||||
private function getTldType(string $tld): ?TldType
|
||||
{
|
||||
if (in_array(strtolower($tld), self::ISO_TLD_EXCEPTION)) {
|
||||
@@ -835,7 +881,7 @@ readonly class RDAPService
|
||||
|
||||
if (null === $gtTldEntity) {
|
||||
$gtTldEntity = new Tld();
|
||||
$gtTldEntity->setTld($gTld['gTLD']);
|
||||
$gtTldEntity->setTld($gTld['gTLD'])->setType(TldType::gTLD);
|
||||
$this->logger->notice('New gTLD detected according to ICANN ({tld}).', [
|
||||
'tld' => $gTld['gTLD'],
|
||||
]);
|
||||
|
||||
Reference in New Issue
Block a user