diff --git a/migrations/Version20250910171544.php b/migrations/Version20250910171544.php new file mode 100644 index 0000000..a1050d4 --- /dev/null +++ b/migrations/Version20250910171544.php @@ -0,0 +1,48 @@ +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'); + } +} diff --git a/src/Config/RegistrarStatus.php b/src/Config/RegistrarStatus.php new file mode 100644 index 0000000..9cc9ace --- /dev/null +++ b/src/Config/RegistrarStatus.php @@ -0,0 +1,10 @@ +domainEntities = new ArrayCollection(); @@ -242,4 +263,64 @@ class Entity return $this; } + + public function getRegistrarNameIANA(): ?string + { + return $this->registrarNameIANA; + } + + public function setRegistrarNameIANA(?string $registrarNameIANA): static + { + $this->registrarNameIANA = $registrarNameIANA; + + return $this; + } + + public function getRdapBaseUrlIANA(): ?string + { + return $this->rdapBaseUrlIANA; + } + + public function setRdapBaseUrlIANA(?string $rdapBaseUrlIANA): static + { + $this->rdapBaseUrlIANA = $rdapBaseUrlIANA; + + return $this; + } + + public function getStatusIANA(): ?RegistrarStatus + { + return $this->statusIANA; + } + + public function setStatusIANA(?RegistrarStatus $statusIANA): static + { + $this->statusIANA = $statusIANA; + + return $this; + } + + public function getUpdatedIANA(): ?\DateTimeImmutable + { + return $this->updatedIANA; + } + + public function setUpdatedIANA(?\DateTimeImmutable $updatedIANA): static + { + $this->updatedIANA = $updatedIANA; + + return $this; + } + + public function getDateIANA(): ?\DateTimeImmutable + { + return $this->dateIANA; + } + + public function setDateIANA(?\DateTimeImmutable $dateIANA): static + { + $this->dateIANA = $dateIANA; + + return $this; + } } diff --git a/src/Entity/Tld.php b/src/Entity/Tld.php index c39f127..8bda5b5 100644 --- a/src/Entity/Tld.php +++ b/src/Entity/Tld.php @@ -115,12 +115,13 @@ 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; } diff --git a/src/MessageHandler/UpdateRdapServersHandler.php b/src/MessageHandler/UpdateRdapServersHandler.php index 5ccc811..49b1d9e 100644 --- a/src/MessageHandler/UpdateRdapServersHandler.php +++ b/src/MessageHandler/UpdateRdapServersHandler.php @@ -65,6 +65,13 @@ final readonly class UpdateRdapServersHandler $throws[] = $throwable; } + + try { + $this->RDAPService->updateRegistrarListIANA(); + } catch (\Throwable $throwable) { + $throws[] = $throwable; + } + if (!empty($throws)) { throw $throws[0]; } diff --git a/src/Service/RDAPService.php b/src/Service/RDAPService.php index f9deb11..b6520af 100644 --- a/src/Service/RDAPService.php +++ b/src/Service/RDAPService.php @@ -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 = $this->entityRepository->findOneBy([ + 'handle' => $rdapEntity['handle'], + 'tld' => null, + ]); + } + if (null === $entity) { - $entity = new 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']) && $entity->getStatusIANA() === null) { $entity->setRemarks($rdapEntity['remarks']); } - if (isset($rdapEntity['vcardArray']) && !in_array($rdapEntity['handle'], self::IANA_RESERVED_IDS)) { + if (isset($rdapEntity['vcardArray']) && $entity->getStatusIANA() === null) { 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']) || $entity->getStatusIANA() !== null) { 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($tldEntity === null) { + $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,41 @@ 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($entity === null) $entity = new Entity(); + $entity + ->setHandle(strval($registrar->value))->setTld(null) + ->setRegistrarNameIANA(strval($registrar->name)) + ->setStatusIANA(RegistrarStatus::from(strval($registrar->status))) + ->setRdapBaseUrlIANA($registrar->rdapurl->count() ? strval($registrar->rdapurl->server) : null) + ->setUpdatedIANA($registrar->attributes()->updated !== null ? new \DateTimeImmutable(strval($registrar->attributes()->updated)) : null) + ->setDateIANA($registrar->attributes()->date !== null ? new \DateTimeImmutable(strval($registrar->attributes()->date)) : null) + ->setJCard(["vcard", [["version", [], "text", "4.0"], ["fn", [], "text", $entity->getRegistrarNameIANA()]]]) + ->setRemarks(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 +877,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'], ]);