feat: check malformed domain names

This commit is contained in:
Maël Gangloff
2025-10-16 14:16:58 +02:00
parent a20344816c
commit a143039925
8 changed files with 77 additions and 32 deletions

View File

@@ -235,7 +235,7 @@ export function TrackedDomainTable() {
description={t`No tracked domain names were found, please create your first Watchlist`} description={t`No tracked domain names were found, please create your first Watchlist`}
> >
<Link to='/tracking/watchlist'> <Link to='/tracking/watchlist'>
<Button type='primary'>Create Now</Button> <Button type='primary'>{t`Create now`}</Button>
</Link> </Link>
</Empty> </Empty>
: <Skeleton loading={total === undefined}> : <Skeleton loading={total === undefined}>

View File

@@ -6,6 +6,7 @@ use ApiPlatform\Metadata\ApiProperty;
use ApiPlatform\Metadata\ApiResource; use ApiPlatform\Metadata\ApiResource;
use ApiPlatform\Metadata\Get; use ApiPlatform\Metadata\Get;
use App\Config\EventAction; use App\Config\EventAction;
use App\Exception\MalformedDomainException;
use App\Repository\DomainRepository; use App\Repository\DomainRepository;
use App\Service\RDAPService; use App\Service\RDAPService;
use App\State\AutoRegisterDomainProvider; use App\State\AutoRegisterDomainProvider;
@@ -177,6 +178,9 @@ class Domain
return $this->ldhName; return $this->ldhName;
} }
/**
* @throws MalformedDomainException
*/
public function setLdhName(string $ldhName): static public function setLdhName(string $ldhName): static
{ {
$this->ldhName = RDAPService::convertToIdn($ldhName); $this->ldhName = RDAPService::convertToIdn($ldhName);

View File

@@ -6,6 +6,6 @@ class MalformedDomainException extends \Exception
{ {
public static function fromDomain(string $ldhName): self 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)");
} }
} }

View File

@@ -197,9 +197,18 @@ class RDAPService
return $tldEntity; return $tldEntity;
} }
/**
* @throws MalformedDomainException
*/
public static function convertToIdn(string $fqdn): string 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;
} }
/** /**

View File

@@ -52,7 +52,6 @@ readonly class AutoRegisterDomainProvider implements ProviderInterface
if (null !== $domain if (null !== $domain
&& !$domain->getDeleted() && !$domain->getDeleted()
&& !$domain->isToBeUpdated(true, true) && !$domain->isToBeUpdated(true, true)
&& !$this->kernel->isDebug()
&& ($request && !filter_var($request->get('forced', false), FILTER_VALIDATE_BOOLEAN)) && ($request && !filter_var($request->get('forced', false), FILTER_VALIDATE_BOOLEAN))
) { ) {
$this->logger->debug('It is not necessary to update the domain name', [ $this->logger->debug('It is not necessary to update the domain name', [

View File

@@ -7,6 +7,7 @@ namespace App\Tests\Entity;
use App\Entity\Domain; use App\Entity\Domain;
use App\Entity\DomainEvent; use App\Entity\DomainEvent;
use App\Entity\DomainStatus; use App\Entity\DomainStatus;
use App\Exception\MalformedDomainException;
use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\TestCase; 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 * @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 public static function isToBeUpdatedProvider(): array
{ {
$now = new \DateTimeImmutable(); $now = new \DateTimeImmutable();

View File

@@ -5,6 +5,8 @@ namespace App\Tests\State;
use ApiPlatform\Symfony\Bundle\Test\ApiTestCase; use ApiPlatform\Symfony\Bundle\Test\ApiTestCase;
use App\Entity\Domain; use App\Entity\Domain;
use App\Factory\UserFactory; use App\Factory\UserFactory;
use App\Repository\DomainRepository;
use App\Service\RDAPService;
use App\Tests\AuthenticatedUserTrait; use App\Tests\AuthenticatedUserTrait;
use App\Tests\Service\RDAPServiceTest; use App\Tests\Service\RDAPServiceTest;
use PHPUnit\Framework\Attributes\DependsExternal; use PHPUnit\Framework\Attributes\DependsExternal;
@@ -18,11 +20,31 @@ final class AutoRegisterDomainProviderTest extends ApiTestCase
#[DependsExternal(RDAPServiceTest::class, 'testUpdateRdapServers')] #[DependsExternal(RDAPServiceTest::class, 'testUpdateRdapServers')]
public function testRegisterDomain(): void public function testRegisterDomain(): void
{ {
$testUser = UserFactory::createOne(); $client = AutoRegisterDomainProviderTest::createClientWithCredentials(AutoRegisterDomainProviderTest::getToken(UserFactory::createOne()));
$client = AutoRegisterDomainProviderTest::createClientWithCredentials(AutoRegisterDomainProviderTest::getToken($testUser));
$client->request('GET', '/api/domains/example.com'); $client->request('GET', '/api/domains/example.com');
$this->assertResponseIsSuccessful(); $this->assertResponseIsSuccessful();
$this->assertMatchesResourceItemJsonSchema(Domain::class); $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');
}
} }

View File

@@ -44,8 +44,8 @@ msgstr ""
#: assets/components/RegisterForm.tsx:39 #: assets/components/RegisterForm.tsx:39
#: assets/components/RegisterForm.tsx:47 #: assets/components/RegisterForm.tsx:47
#: assets/components/search/DomainSearchBar.tsx:28 #: assets/components/search/DomainSearchBar.tsx:28
#: assets/components/tracking/watchlist/WatchlistForm.tsx:157 #: assets/components/tracking/watchlist/WatchlistForm.tsx:120
#: assets/components/tracking/watchlist/WatchlistForm.tsx:263 #: assets/components/tracking/watchlist/WatchlistForm.tsx:223
#: assets/utils/providers/forms/AutoDnsConnectorForm.tsx:21 #: assets/utils/providers/forms/AutoDnsConnectorForm.tsx:21
#: assets/utils/providers/forms/AutoDnsConnectorForm.tsx:32 #: assets/utils/providers/forms/AutoDnsConnectorForm.tsx:32
#: assets/utils/providers/forms/AutoDnsConnectorForm.tsx:47 #: assets/utils/providers/forms/AutoDnsConnectorForm.tsx:47
@@ -164,7 +164,7 @@ msgid "Entities"
msgstr "" msgstr ""
#: assets/components/search/DomainSearchBar.tsx:31 #: 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" msgid "This domain name does not appear to be valid"
msgstr "" msgstr ""
@@ -291,7 +291,7 @@ msgid "Next"
msgstr "" msgstr ""
#: assets/components/tracking/connector/ConnectorForm.tsx:96 #: assets/components/tracking/connector/ConnectorForm.tsx:96
#: assets/components/tracking/watchlist/WatchlistForm.tsx:310 #: assets/components/tracking/watchlist/WatchlistForm.tsx:270
msgid "Create" msgid "Create"
msgstr "" msgstr ""
@@ -437,6 +437,10 @@ msgstr ""
msgid "No tracked domain names were found, please create your first Watchlist" msgid "No tracked domain names were found, please create your first Watchlist"
msgstr "" msgstr ""
#: assets/components/tracking/watchlist/TrackedDomainTable.tsx:238
msgid "Create now"
msgstr ""
#: assets/components/tracking/watchlist/TrackedDomainTable.tsx:244 #: assets/components/tracking/watchlist/TrackedDomainTable.tsx:244
msgid "" msgid ""
"Please note that this table does not include domain names marked as expired " "Please note that this table does not include domain names marked as expired "
@@ -471,74 +475,74 @@ msgstr ""
msgid "Watchlist" msgid "Watchlist"
msgstr "" msgstr ""
#: assets/components/tracking/watchlist/WatchlistForm.tsx:113 #: assets/components/tracking/watchlist/WatchlistForm.tsx:76
msgid "Name" msgid "Name"
msgstr "" msgstr ""
#: assets/components/tracking/watchlist/WatchlistForm.tsx:125 #: assets/components/tracking/watchlist/WatchlistForm.tsx:88
msgid "Watchlist Name" msgid "Watchlist Name"
msgstr "" 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." msgid "Naming the Watchlist makes it easier to find in the list below."
msgstr "" msgstr ""
#: assets/components/tracking/watchlist/WatchlistForm.tsx:137 #: assets/components/tracking/watchlist/WatchlistForm.tsx:100
msgid "At least one domain name" msgid "At least one domain name"
msgstr "" msgstr ""
#: assets/components/tracking/watchlist/WatchlistForm.tsx:148 #: assets/components/tracking/watchlist/WatchlistForm.tsx:111
msgid "Domain names" msgid "Domain names"
msgstr "" msgstr ""
#: assets/components/tracking/watchlist/WatchlistForm.tsx:166 #: assets/components/tracking/watchlist/WatchlistForm.tsx:129
msgid "Domain name" msgid "Domain name"
msgstr "" msgstr ""
#: assets/components/tracking/watchlist/WatchlistForm.tsx:185 #: assets/components/tracking/watchlist/WatchlistForm.tsx:148
msgid "Add a Domain name" msgid "Add a Domain name"
msgstr "" msgstr ""
#: assets/components/tracking/watchlist/WatchlistForm.tsx:193 #: assets/components/tracking/watchlist/WatchlistForm.tsx:156
msgid "Tracked events" msgid "Tracked events"
msgstr "" msgstr ""
#: assets/components/tracking/watchlist/WatchlistForm.tsx:195 #: assets/components/tracking/watchlist/WatchlistForm.tsx:158
msgid "At least one trigger" msgid "At least one trigger"
msgstr "" msgstr ""
#: assets/components/tracking/watchlist/WatchlistForm.tsx:222 #: assets/components/tracking/watchlist/WatchlistForm.tsx:182
#: assets/components/tracking/watchlist/WatchlistForm.tsx:237 #: assets/components/tracking/watchlist/WatchlistForm.tsx:197
msgid "Connector" msgid "Connector"
msgstr "" msgstr ""
#: assets/components/tracking/watchlist/WatchlistForm.tsx:232 #: assets/components/tracking/watchlist/WatchlistForm.tsx:192
msgid "" msgid ""
"Please make sure the connector information is valid to purchase a domain " "Please make sure the connector information is valid to purchase a domain "
"that may be available soon." "that may be available soon."
msgstr "" msgstr ""
#: assets/components/tracking/watchlist/WatchlistForm.tsx:254 #: assets/components/tracking/watchlist/WatchlistForm.tsx:214
msgid "DSN" msgid "DSN"
msgstr "" msgstr ""
#: assets/components/tracking/watchlist/WatchlistForm.tsx:266 #: assets/components/tracking/watchlist/WatchlistForm.tsx:226
msgid "This DSN does not appear to be valid" msgid "This DSN does not appear to be valid"
msgstr "" 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" msgid "Check out this link to the Symfony documentation to help you build the DSN"
msgstr "" msgstr ""
#: assets/components/tracking/watchlist/WatchlistForm.tsx:300 #: assets/components/tracking/watchlist/WatchlistForm.tsx:260
msgid "Add a Webhook" msgid "Add a Webhook"
msgstr "" msgstr ""
#: assets/components/tracking/watchlist/WatchlistForm.tsx:310 #: assets/components/tracking/watchlist/WatchlistForm.tsx:270
msgid "Update" msgid "Update"
msgstr "" msgstr ""
#: assets/components/tracking/watchlist/WatchlistForm.tsx:313 #: assets/components/tracking/watchlist/WatchlistForm.tsx:273
msgid "Reset" msgid "Reset"
msgstr "" msgstr ""
@@ -719,15 +723,15 @@ msgstr ""
msgid "Create a Connector" msgid "Create a Connector"
msgstr "" msgstr ""
#: assets/pages/tracking/WatchlistPage.tsx:63 #: assets/pages/tracking/WatchlistPage.tsx:43
msgid "Watchlist created !" msgid "Watchlist created !"
msgstr "" msgstr ""
#: assets/pages/tracking/WatchlistPage.tsx:75 #: assets/pages/tracking/WatchlistPage.tsx:55
msgid "Watchlist updated !" msgid "Watchlist updated !"
msgstr "" msgstr ""
#: assets/pages/tracking/WatchlistPage.tsx:99 #: assets/pages/tracking/WatchlistPage.tsx:79
msgid "Create a Watchlist" msgid "Create a Watchlist"
msgstr "" msgstr ""