From a143039925b82439876651807ddb0cc88e0cd4c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Gangloff?= Date: Thu, 16 Oct 2025 14:16:58 +0200 Subject: [PATCH] feat: check malformed domain names --- .../tracking/watchlist/TrackedDomainTable.tsx | 2 +- src/Entity/Domain.php | 4 ++ src/Exception/MalformedDomainException.php | 2 +- src/Service/RDAPService.php | 11 +++- src/State/AutoRegisterDomainProvider.php | 1 - tests/Entity/DomainTest.php | 9 +++- .../State/AutoRegisterDomainProviderTest.php | 26 ++++++++- translations/translations.pot | 54 ++++++++++--------- 8 files changed, 77 insertions(+), 32 deletions(-) diff --git a/assets/components/tracking/watchlist/TrackedDomainTable.tsx b/assets/components/tracking/watchlist/TrackedDomainTable.tsx index be5ba1a..89c1adb 100644 --- a/assets/components/tracking/watchlist/TrackedDomainTable.tsx +++ b/assets/components/tracking/watchlist/TrackedDomainTable.tsx @@ -235,7 +235,7 @@ export function TrackedDomainTable() { description={t`No tracked domain names were found, please create your first Watchlist`} > - + : diff --git a/src/Entity/Domain.php b/src/Entity/Domain.php index cfe4ad8..d116910 100644 --- a/src/Entity/Domain.php +++ b/src/Entity/Domain.php @@ -6,6 +6,7 @@ use ApiPlatform\Metadata\ApiProperty; use ApiPlatform\Metadata\ApiResource; use ApiPlatform\Metadata\Get; use App\Config\EventAction; +use App\Exception\MalformedDomainException; use App\Repository\DomainRepository; use App\Service\RDAPService; use App\State\AutoRegisterDomainProvider; @@ -177,6 +178,9 @@ class Domain return $this->ldhName; } + /** + * @throws MalformedDomainException + */ public function setLdhName(string $ldhName): static { $this->ldhName = RDAPService::convertToIdn($ldhName); diff --git a/src/Exception/MalformedDomainException.php b/src/Exception/MalformedDomainException.php index 5843e50..d322f0b 100644 --- a/src/Exception/MalformedDomainException.php +++ b/src/Exception/MalformedDomainException.php @@ -6,6 +6,6 @@ class MalformedDomainException extends \Exception { public static function fromDomain(string $ldhName): self { - return new self("Domain name ($ldhName) must contain at least one dot"); + return new self("Malformed domain name ($ldhName)"); } } diff --git a/src/Service/RDAPService.php b/src/Service/RDAPService.php index 2cfb068..ac68527 100644 --- a/src/Service/RDAPService.php +++ b/src/Service/RDAPService.php @@ -197,9 +197,18 @@ class RDAPService return $tldEntity; } + /** + * @throws MalformedDomainException + */ public static function convertToIdn(string $fqdn): string { - return strtolower(idn_to_ascii($fqdn)); + $ascii = strtolower(idn_to_ascii($fqdn)); + + if (OfficialDataService::DOMAIN_DOT !== $fqdn && !preg_match('/^(xn--)?[a-z0-9-]+(\.[a-z0-9-]+)*$/', $ascii)) { + throw MalformedDomainException::fromDomain($fqdn); + } + + return $ascii; } /** diff --git a/src/State/AutoRegisterDomainProvider.php b/src/State/AutoRegisterDomainProvider.php index dc061ee..80337e5 100644 --- a/src/State/AutoRegisterDomainProvider.php +++ b/src/State/AutoRegisterDomainProvider.php @@ -52,7 +52,6 @@ readonly class AutoRegisterDomainProvider implements ProviderInterface if (null !== $domain && !$domain->getDeleted() && !$domain->isToBeUpdated(true, true) - && !$this->kernel->isDebug() && ($request && !filter_var($request->get('forced', false), FILTER_VALIDATE_BOOLEAN)) ) { $this->logger->debug('It is not necessary to update the domain name', [ diff --git a/tests/Entity/DomainTest.php b/tests/Entity/DomainTest.php index 60a849f..3d5fa06 100644 --- a/tests/Entity/DomainTest.php +++ b/tests/Entity/DomainTest.php @@ -7,6 +7,7 @@ namespace App\Tests\Entity; use App\Entity\Domain; use App\Entity\DomainEvent; use App\Entity\DomainStatus; +use App\Exception\MalformedDomainException; use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\TestCase; @@ -159,7 +160,7 @@ final class DomainTest extends TestCase ); } - public function testSetLdhName(): void + public function testIdnDomainName(): void { /* * @see https://en.wikipedia.org/wiki/IDN_Test_TLDs @@ -180,6 +181,12 @@ final class DomainTest extends TestCase ); } + public function testInvalidDomainName() + { + $this->expectException(MalformedDomainException::class); + (new Domain())->setLdhName('*'); + } + public static function isToBeUpdatedProvider(): array { $now = new \DateTimeImmutable(); diff --git a/tests/State/AutoRegisterDomainProviderTest.php b/tests/State/AutoRegisterDomainProviderTest.php index b230b5e..b3d0e53 100644 --- a/tests/State/AutoRegisterDomainProviderTest.php +++ b/tests/State/AutoRegisterDomainProviderTest.php @@ -5,6 +5,8 @@ namespace App\Tests\State; use ApiPlatform\Symfony\Bundle\Test\ApiTestCase; use App\Entity\Domain; use App\Factory\UserFactory; +use App\Repository\DomainRepository; +use App\Service\RDAPService; use App\Tests\AuthenticatedUserTrait; use App\Tests\Service\RDAPServiceTest; use PHPUnit\Framework\Attributes\DependsExternal; @@ -18,11 +20,31 @@ final class AutoRegisterDomainProviderTest extends ApiTestCase #[DependsExternal(RDAPServiceTest::class, 'testUpdateRdapServers')] public function testRegisterDomain(): void { - $testUser = UserFactory::createOne(); - $client = AutoRegisterDomainProviderTest::createClientWithCredentials(AutoRegisterDomainProviderTest::getToken($testUser)); + $client = AutoRegisterDomainProviderTest::createClientWithCredentials(AutoRegisterDomainProviderTest::getToken(UserFactory::createOne())); $client->request('GET', '/api/domains/example.com'); $this->assertResponseIsSuccessful(); $this->assertMatchesResourceItemJsonSchema(Domain::class); } + + #[DependsExternal(RDAPServiceTest::class, 'testUpdateRdapServers')] + public function testRegisterDomainAlreadyUpdated(): void + { + $client = AutoRegisterDomainProviderTest::createClientWithCredentials(AutoRegisterDomainProviderTest::getToken(UserFactory::createOne())); + + $mockedDomain = $this->getMockBuilder(Domain::class)->getMock(); + $mockedDomain->method('isToBeUpdated')->willReturn(false); + + $mockedDomainRepository = $this->createMock(DomainRepository::class); + $mockedDomainRepository->method('findOneBy')->willReturn($mockedDomain); + + $rdapServiceMocked = $this->createMock(RDAPService::class); + $rdapServiceMocked->expects(self::never())->method('registerDomain'); + + $container = static::getContainer(); + $container->set(DomainRepository::class, $mockedDomainRepository); + $container->set(RDAPService::class, $rdapServiceMocked); + + $client->request('GET', '/api/domains/example.com'); + } } diff --git a/translations/translations.pot b/translations/translations.pot index ee03e2c..b5d7fc3 100644 --- a/translations/translations.pot +++ b/translations/translations.pot @@ -44,8 +44,8 @@ msgstr "" #: assets/components/RegisterForm.tsx:39 #: assets/components/RegisterForm.tsx:47 #: assets/components/search/DomainSearchBar.tsx:28 -#: assets/components/tracking/watchlist/WatchlistForm.tsx:157 -#: assets/components/tracking/watchlist/WatchlistForm.tsx:263 +#: assets/components/tracking/watchlist/WatchlistForm.tsx:120 +#: assets/components/tracking/watchlist/WatchlistForm.tsx:223 #: assets/utils/providers/forms/AutoDnsConnectorForm.tsx:21 #: assets/utils/providers/forms/AutoDnsConnectorForm.tsx:32 #: assets/utils/providers/forms/AutoDnsConnectorForm.tsx:47 @@ -164,7 +164,7 @@ msgid "Entities" msgstr "" #: assets/components/search/DomainSearchBar.tsx:31 -#: assets/components/tracking/watchlist/WatchlistForm.tsx:160 +#: assets/components/tracking/watchlist/WatchlistForm.tsx:123 msgid "This domain name does not appear to be valid" msgstr "" @@ -291,7 +291,7 @@ msgid "Next" msgstr "" #: assets/components/tracking/connector/ConnectorForm.tsx:96 -#: assets/components/tracking/watchlist/WatchlistForm.tsx:310 +#: assets/components/tracking/watchlist/WatchlistForm.tsx:270 msgid "Create" msgstr "" @@ -437,6 +437,10 @@ msgstr "" msgid "No tracked domain names were found, please create your first Watchlist" msgstr "" +#: assets/components/tracking/watchlist/TrackedDomainTable.tsx:238 +msgid "Create now" +msgstr "" + #: assets/components/tracking/watchlist/TrackedDomainTable.tsx:244 msgid "" "Please note that this table does not include domain names marked as expired " @@ -471,74 +475,74 @@ msgstr "" msgid "Watchlist" msgstr "" -#: assets/components/tracking/watchlist/WatchlistForm.tsx:113 +#: assets/components/tracking/watchlist/WatchlistForm.tsx:76 msgid "Name" msgstr "" -#: assets/components/tracking/watchlist/WatchlistForm.tsx:125 +#: assets/components/tracking/watchlist/WatchlistForm.tsx:88 msgid "Watchlist Name" msgstr "" -#: assets/components/tracking/watchlist/WatchlistForm.tsx:126 +#: assets/components/tracking/watchlist/WatchlistForm.tsx:89 msgid "Naming the Watchlist makes it easier to find in the list below." msgstr "" -#: assets/components/tracking/watchlist/WatchlistForm.tsx:137 +#: assets/components/tracking/watchlist/WatchlistForm.tsx:100 msgid "At least one domain name" msgstr "" -#: assets/components/tracking/watchlist/WatchlistForm.tsx:148 +#: assets/components/tracking/watchlist/WatchlistForm.tsx:111 msgid "Domain names" msgstr "" -#: assets/components/tracking/watchlist/WatchlistForm.tsx:166 +#: assets/components/tracking/watchlist/WatchlistForm.tsx:129 msgid "Domain name" msgstr "" -#: assets/components/tracking/watchlist/WatchlistForm.tsx:185 +#: assets/components/tracking/watchlist/WatchlistForm.tsx:148 msgid "Add a Domain name" msgstr "" -#: assets/components/tracking/watchlist/WatchlistForm.tsx:193 +#: assets/components/tracking/watchlist/WatchlistForm.tsx:156 msgid "Tracked events" msgstr "" -#: assets/components/tracking/watchlist/WatchlistForm.tsx:195 +#: assets/components/tracking/watchlist/WatchlistForm.tsx:158 msgid "At least one trigger" msgstr "" -#: assets/components/tracking/watchlist/WatchlistForm.tsx:222 -#: assets/components/tracking/watchlist/WatchlistForm.tsx:237 +#: assets/components/tracking/watchlist/WatchlistForm.tsx:182 +#: assets/components/tracking/watchlist/WatchlistForm.tsx:197 msgid "Connector" msgstr "" -#: assets/components/tracking/watchlist/WatchlistForm.tsx:232 +#: assets/components/tracking/watchlist/WatchlistForm.tsx:192 msgid "" "Please make sure the connector information is valid to purchase a domain " "that may be available soon." msgstr "" -#: assets/components/tracking/watchlist/WatchlistForm.tsx:254 +#: assets/components/tracking/watchlist/WatchlistForm.tsx:214 msgid "DSN" msgstr "" -#: assets/components/tracking/watchlist/WatchlistForm.tsx:266 +#: assets/components/tracking/watchlist/WatchlistForm.tsx:226 msgid "This DSN does not appear to be valid" msgstr "" -#: assets/components/tracking/watchlist/WatchlistForm.tsx:290 +#: assets/components/tracking/watchlist/WatchlistForm.tsx:250 msgid "Check out this link to the Symfony documentation to help you build the DSN" msgstr "" -#: assets/components/tracking/watchlist/WatchlistForm.tsx:300 +#: assets/components/tracking/watchlist/WatchlistForm.tsx:260 msgid "Add a Webhook" msgstr "" -#: assets/components/tracking/watchlist/WatchlistForm.tsx:310 +#: assets/components/tracking/watchlist/WatchlistForm.tsx:270 msgid "Update" msgstr "" -#: assets/components/tracking/watchlist/WatchlistForm.tsx:313 +#: assets/components/tracking/watchlist/WatchlistForm.tsx:273 msgid "Reset" msgstr "" @@ -719,15 +723,15 @@ msgstr "" msgid "Create a Connector" msgstr "" -#: assets/pages/tracking/WatchlistPage.tsx:63 +#: assets/pages/tracking/WatchlistPage.tsx:43 msgid "Watchlist created !" msgstr "" -#: assets/pages/tracking/WatchlistPage.tsx:75 +#: assets/pages/tracking/WatchlistPage.tsx:55 msgid "Watchlist updated !" msgstr "" -#: assets/pages/tracking/WatchlistPage.tsx:99 +#: assets/pages/tracking/WatchlistPage.tsx:79 msgid "Create a Watchlist" msgstr ""