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`}
>
<Link to='/tracking/watchlist'>
<Button type='primary'>Create Now</Button>
<Button type='primary'>{t`Create now`}</Button>
</Link>
</Empty>
: <Skeleton loading={total === undefined}>

View File

@@ -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);

View File

@@ -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)");
}
}

View File

@@ -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;
}
/**

View File

@@ -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', [

View File

@@ -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();

View File

@@ -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');
}
}

View File

@@ -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 ""