refactor: change log level and add exceptions

This commit is contained in:
Maël Gangloff
2025-10-13 13:51:51 +02:00
parent efa56055d0
commit 0af22ff989
36 changed files with 253 additions and 135 deletions

View File

@@ -70,6 +70,8 @@ jobs:
platforms: ${{ matrix.platform }} platforms: ${{ matrix.platform }}
labels: ${{ steps.meta.outputs.labels }} labels: ${{ steps.meta.outputs.labels }}
outputs: type=image,name=${{ github.repository }},name-canonical=true,push=true outputs: type=image,name=${{ github.repository }},name-canonical=true,push=true
cache-from: type=gha
cache-to: type=gha,mode=max
- name: Export digest - name: Export digest
run: | run: |

View File

@@ -41,3 +41,8 @@ api_platform:
App\Exception\MalformedDomainException: 400 App\Exception\MalformedDomainException: 400
App\Exception\TldNotSupportedException: 400 App\Exception\TldNotSupportedException: 400
App\Exception\UnknownRdapServerException: 400 App\Exception\UnknownRdapServerException: 400
App\Exception\UnsupportedDsnScheme: 400
# Provider exception
App\Exception\Provider\UserNoExplicitConsentException: 451
App\Exception\Provider\AbstractProviderException: 400

View File

@@ -2,12 +2,12 @@
namespace App\Config; namespace App\Config;
use App\Service\Connector\AutodnsProvider; use App\Service\Provider\AutodnsProvider;
use App\Service\Connector\EppClientProvider; use App\Service\Provider\EppClientProvider;
use App\Service\Connector\GandiProvider; use App\Service\Provider\GandiProvider;
use App\Service\Connector\NamecheapProvider; use App\Service\Provider\NamecheapProvider;
use App\Service\Connector\NameComProvider; use App\Service\Provider\NameComProvider;
use App\Service\Connector\OvhProvider; use App\Service\Provider\OvhProvider;
enum ConnectorProvider: string enum ConnectorProvider: string
{ {

View File

@@ -58,7 +58,7 @@ class DomainRefreshController extends AbstractController
&& !$this->kernel->isDebug() && !$this->kernel->isDebug()
&& true !== filter_var($request->get('forced', false), FILTER_VALIDATE_BOOLEAN) && true !== filter_var($request->get('forced', false), FILTER_VALIDATE_BOOLEAN)
) { ) {
$this->logger->info('It is not necessary to update the domain name', [ $this->logger->debug('It is not necessary to update the domain name', [
'ldhName' => $idnDomain, 'ldhName' => $idnDomain,
'updatedAt' => $domain->getUpdatedAt()->format(\DateTimeInterface::ATOM), 'updatedAt' => $domain->getUpdatedAt()->format(\DateTimeInterface::ATOM),
]); ]);

View File

@@ -4,8 +4,8 @@ namespace App\Exception;
class DomainNotFoundException extends \Exception class DomainNotFoundException extends \Exception
{ {
public static function fromDomain(string $ldhName): DomainNotFoundException public static function fromDomain(string $ldhName): self
{ {
return new DomainNotFoundException("The domain name $ldhName is not present in the WHOIS database"); return new self("The domain name $ldhName is not present in the WHOIS database");
} }
} }

View File

@@ -4,8 +4,8 @@ namespace App\Exception;
class MalformedDomainException extends \Exception class MalformedDomainException extends \Exception
{ {
public static function fromDomain(string $ldhName): MalformedDomainException public static function fromDomain(string $ldhName): self
{ {
return new MalformedDomainException("Domain name ($ldhName) must contain at least one dot"); return new self("Domain name ($ldhName) must contain at least one dot");
} }
} }

View File

@@ -0,0 +1,7 @@
<?php
namespace App\Exception\Provider;
abstract class AbstractProviderException extends \Exception
{
}

View File

@@ -0,0 +1,7 @@
<?php
namespace App\Exception\Provider;
class DomainOrderFailedExeption extends AbstractProviderException
{
}

View File

@@ -0,0 +1,11 @@
<?php
namespace App\Exception\Provider;
class EppContactIsAvailableException extends AbstractProviderException
{
public static function fromContact(string $handle): self
{
return new self("At least one of the entered contacts cannot be used because it is indicated as available ($handle)");
}
}

View File

@@ -0,0 +1,11 @@
<?php
namespace App\Exception\Provider;
class ExpiredLoginException extends AbstractProviderException
{
public static function fromIdentifier(string $identifier): self
{
return new self("Expired login for identifier $identifier");
}
}

View File

@@ -0,0 +1,16 @@
<?php
namespace App\Exception\Provider;
class InvalidLoginException extends AbstractProviderException
{
public function __construct(string $message = '')
{
parent::__construct('' === $message ? 'The status of these credentials is not valid' : $message);
}
public static function fromIdentifier(string $identifier): self
{
return new self("Invalid login for identifier $identifier");
}
}

View File

@@ -0,0 +1,11 @@
<?php
namespace App\Exception\Provider;
class InvalidLoginStatusException extends AbstractProviderException
{
public static function fromStatus(string $status): self
{
return new self("The status of these credentials is not valid ($status)");
}
}

View File

@@ -0,0 +1,11 @@
<?php
namespace App\Exception\Provider;
class NamecheapRequiresAddressException extends AbstractProviderException
{
public function __construct()
{
parent::__construct('Namecheap account requires at least one address to purchase a domain');
}
}

View File

@@ -0,0 +1,11 @@
<?php
namespace App\Exception\Provider;
class PermissionErrorException extends AbstractProviderException
{
public static function fromIdentifier(string $identifier): self
{
return new self("Not enough permissions for identifier $identifier");
}
}

View File

@@ -0,0 +1,11 @@
<?php
namespace App\Exception\Provider;
class ProviderGenericErrorException extends AbstractProviderException
{
public function __construct(string $message)
{
parent::__construct($message);
}
}

View File

@@ -0,0 +1,11 @@
<?php
namespace App\Exception\Provider;
class UserNoExplicitConsentException extends AbstractProviderException
{
public function __construct()
{
parent::__construct('The user has not given explicit consent');
}
}

View File

@@ -4,8 +4,8 @@ namespace App\Exception;
class TldNotSupportedException extends \Exception class TldNotSupportedException extends \Exception
{ {
public static function fromTld(string $tld): TldNotSupportedException public static function fromTld(string $tld): self
{ {
return new TldNotSupportedException("The requested TLD $tld is not yet supported, please try again with another one"); return new self("The requested TLD $tld is not yet supported, please try again with another one");
} }
} }

View File

@@ -4,8 +4,8 @@ namespace App\Exception;
class UnknownRdapServerException extends \Exception class UnknownRdapServerException extends \Exception
{ {
public static function fromTld(string $tld): UnknownRdapServerException public static function fromTld(string $tld): self
{ {
return new UnknownRdapServerException("TLD $tld: Unable to determine which RDAP server to contact"); return new self("TLD $tld: Unable to determine which RDAP server to contact");
} }
} }

View File

@@ -0,0 +1,11 @@
<?php
namespace App\Exception;
class UnsupportedDsnSchemeException extends \Exception
{
public static function fromScheme(string $scheme): UnsupportedDsnSchemeException
{
return new UnsupportedDsnSchemeException("The DSN scheme ($scheme) is not supported");
}
}

View File

@@ -10,8 +10,8 @@ use App\Notifier\DomainOrderNotification;
use App\Repository\DomainRepository; use App\Repository\DomainRepository;
use App\Repository\WatchListRepository; use App\Repository\WatchListRepository;
use App\Service\ChatNotificationService; use App\Service\ChatNotificationService;
use App\Service\Connector\AbstractProvider;
use App\Service\InfluxdbService; use App\Service\InfluxdbService;
use App\Service\Provider\AbstractProvider;
use App\Service\StatService; use App\Service\StatService;
use Psr\Log\LoggerInterface; use Psr\Log\LoggerInterface;
use Symfony\Component\DependencyInjection\Attribute\Autowire; use Symfony\Component\DependencyInjection\Attribute\Autowire;
@@ -118,7 +118,7 @@ final readonly class OrderDomainHandler
* The purchase was not successful (for several possible reasons that we have not determined). * The purchase was not successful (for several possible reasons that we have not determined).
* The user is informed and the exception is raised, which may allow you to try again. * The user is informed and the exception is raised, which may allow you to try again.
*/ */
$this->logger->warning('Unable to complete purchase : an error message is sent to user', [ $this->logger->warning('Unable to complete purchase : an error message is sent to the user', [
'watchlist' => $message->watchListToken, 'watchlist' => $message->watchListToken,
'connector' => $connector->getId(), 'connector' => $connector->getId(),
'ldhName' => $message->ldhName, 'ldhName' => $message->ldhName,

View File

@@ -73,22 +73,29 @@ final readonly class SendDomainEventNotifHandler
/** @var WatchListTrigger $watchListTrigger */ /** @var WatchListTrigger $watchListTrigger */
foreach ($watchListTriggers->getIterator() as $watchListTrigger) { foreach ($watchListTriggers->getIterator() as $watchListTrigger) {
$this->logger->info('New action has been detected on this domain name : a notification is sent to user', [
'event' => $event->getAction(),
'ldhName' => $message->ldhName,
'username' => $watchList->getUser()->getUserIdentifier(),
]);
$recipient = new Recipient($watchList->getUser()->getEmail()); $recipient = new Recipient($watchList->getUser()->getEmail());
$notification = new DomainUpdateNotification($this->sender, $event); $notification = new DomainUpdateNotification($this->sender, $event);
if (TriggerAction::SendEmail == $watchListTrigger->getAction()) { if (TriggerAction::SendEmail == $watchListTrigger->getAction()) {
$this->logger->info('New action has been detected on this domain name : an email is sent to user', [
'event' => $event->getAction(),
'ldhName' => $message->ldhName,
'username' => $watchList->getUser()->getUserIdentifier(),
]);
$this->mailer->send($notification->asEmailMessage($recipient)->getMessage()); $this->mailer->send($notification->asEmailMessage($recipient)->getMessage());
} elseif (TriggerAction::SendChat == $watchListTrigger->getAction()) { } elseif (TriggerAction::SendChat == $watchListTrigger->getAction()) {
$webhookDsn = $watchList->getWebhookDsn(); $webhookDsn = $watchList->getWebhookDsn();
if (null === $webhookDsn || 0 === count($webhookDsn)) { if (null === $webhookDsn || 0 === count($webhookDsn)) {
continue; continue;
} }
$this->logger->info('New action has been detected on this domain name : a notification is sent to user', [
'event' => $event->getAction(),
'ldhName' => $message->ldhName,
'username' => $watchList->getUser()->getUserIdentifier(),
]);
$this->chatNotificationService->sendChatNotification($watchList, $notification); $this->chatNotificationService->sendChatNotification($watchList, $notification);
} }

View File

@@ -14,8 +14,8 @@ use App\Notifier\DomainDeletedNotification;
use App\Repository\DomainRepository; use App\Repository\DomainRepository;
use App\Repository\WatchListRepository; use App\Repository\WatchListRepository;
use App\Service\ChatNotificationService; use App\Service\ChatNotificationService;
use App\Service\Connector\AbstractProvider; use App\Service\Provider\AbstractProvider;
use App\Service\Connector\CheckDomainProviderInterface; use App\Service\Provider\CheckDomainProviderInterface;
use App\Service\RDAPService; use App\Service\RDAPService;
use Psr\Log\LoggerInterface; use Psr\Log\LoggerInterface;
use Symfony\Component\DependencyInjection\Attribute\Autowire; use Symfony\Component\DependencyInjection\Attribute\Autowire;
@@ -55,7 +55,7 @@ final readonly class UpdateDomainsFromWatchlistHandler
/** @var WatchList $watchList */ /** @var WatchList $watchList */
$watchList = $this->watchListRepository->findOneBy(['token' => $message->watchListToken]); $watchList = $this->watchListRepository->findOneBy(['token' => $message->watchListToken]);
$this->logger->info('Domain names listed in the Watchlist will be updated', [ $this->logger->debug('Domain names listed in the Watchlist will be updated', [
'watchlist' => $message->watchListToken, 'watchlist' => $message->watchListToken,
]); ]);
@@ -63,18 +63,19 @@ final readonly class UpdateDomainsFromWatchlistHandler
$connectorProvider = $this->getConnectorProvider($watchList); $connectorProvider = $this->getConnectorProvider($watchList);
if ($connectorProvider instanceof CheckDomainProviderInterface) { if ($connectorProvider instanceof CheckDomainProviderInterface) {
$this->logger->notice('Watchlist is linked to a connector', [ $this->logger->debug('Watchlist is linked to a connector', [
'watchlist' => $watchList->getToken(), 'watchlist' => $watchList->getToken(),
'connector' => $watchList->getConnector()->getId(), 'connector' => $watchList->getConnector()->getId(),
]); ]);
$domainList = array_unique(array_map(fn (Domain $d) => $d->getLdhName(), $watchList->getDomains()->toArray()));
try { try {
$checkedDomains = $connectorProvider->checkDomains( $checkedDomains = $connectorProvider->checkDomains(...$domainList);
...array_unique(array_map(fn (Domain $d) => $d->getLdhName(), $watchList->getDomains()->toArray()))
);
} catch (\Throwable $exception) { } catch (\Throwable $exception) {
$this->logger->warning('Unable to check domain names availability with this connector', [ $this->logger->warning('Unable to check domain names availability with this connector', [
'connector' => $watchList->getConnector()->getId(), 'connector' => $watchList->getConnector()->getId(),
'ldhName' => $domainList,
]); ]);
throw $exception; throw $exception;

View File

@@ -5,7 +5,7 @@ namespace App\MessageHandler;
use App\Message\ValidateConnectorCredentials; use App\Message\ValidateConnectorCredentials;
use App\Notifier\ValidateConnectorCredentialsErrorNotification; use App\Notifier\ValidateConnectorCredentialsErrorNotification;
use App\Repository\ConnectorRepository; use App\Repository\ConnectorRepository;
use App\Service\Connector\AbstractProvider; use App\Service\Provider\AbstractProvider;
use Symfony\Component\DependencyInjection\Attribute\Autowire; use Symfony\Component\DependencyInjection\Attribute\Autowire;
use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\Mailer\Exception\TransportExceptionInterface; use Symfony\Component\Mailer\Exception\TransportExceptionInterface;

View File

@@ -4,10 +4,9 @@ namespace App\Service;
use App\Config\WebhookScheme; use App\Config\WebhookScheme;
use App\Entity\WatchList; use App\Entity\WatchList;
use App\Exception\UnsupportedDsnSchemeException;
use App\Notifier\DomainWatchdogNotification; use App\Notifier\DomainWatchdogNotification;
use Psr\Log\LoggerInterface; use Symfony\Component\Notifier\Exception\TransportExceptionInterface;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
use Symfony\Component\Notifier\Exception\InvalidArgumentException;
use Symfony\Component\Notifier\Recipient\NoRecipient; use Symfony\Component\Notifier\Recipient\NoRecipient;
use Symfony\Component\Notifier\Transport\AbstractTransportFactory; use Symfony\Component\Notifier\Transport\AbstractTransportFactory;
use Symfony\Component\Notifier\Transport\Dsn; use Symfony\Component\Notifier\Transport\Dsn;
@@ -15,10 +14,13 @@ use Symfony\Component\Notifier\Transport\Dsn;
readonly class ChatNotificationService readonly class ChatNotificationService
{ {
public function __construct( public function __construct(
private LoggerInterface $logger,
) { ) {
} }
/**
* @throws UnsupportedDsnSchemeException
* @throws TransportExceptionInterface
*/
public function sendChatNotification(WatchList $watchList, DomainWatchdogNotification $notification): void public function sendChatNotification(WatchList $watchList, DomainWatchdogNotification $notification): void
{ {
$webhookDsn = $watchList->getWebhookDsn(); $webhookDsn = $watchList->getWebhookDsn();
@@ -28,17 +30,13 @@ readonly class ChatNotificationService
} }
foreach ($webhookDsn as $dsnString) { foreach ($webhookDsn as $dsnString) {
try { $dsn = new Dsn($dsnString);
$dsn = new Dsn($dsnString);
} catch (InvalidArgumentException $exception) {
throw new BadRequestHttpException($exception->getMessage());
}
$scheme = $dsn->getScheme(); $scheme = $dsn->getScheme();
$webhookScheme = WebhookScheme::tryFrom($scheme); $webhookScheme = WebhookScheme::tryFrom($scheme);
if (null === $webhookScheme) { if (null === $webhookScheme) {
throw new BadRequestHttpException("The DSN scheme ($scheme) is not supported"); throw new UnsupportedDsnSchemeException($scheme);
} }
$transportFactoryClass = $webhookScheme->getChatTransportFactory(); $transportFactoryClass = $webhookScheme->getChatTransportFactory();
@@ -48,29 +46,14 @@ readonly class ChatNotificationService
$push = $notification->asPushMessage(new NoRecipient()); $push = $notification->asPushMessage(new NoRecipient());
$chat = $notification->asChatMessage(new NoRecipient(), $webhookScheme->value); $chat = $notification->asChatMessage(new NoRecipient(), $webhookScheme->value);
try { $factory = $transportFactory->create($dsn);
$factory = $transportFactory->create($dsn);
if ($factory->supports($push)) { if ($factory->supports($push)) {
$factory->send($push); $factory->send($push);
} elseif ($factory->supports($chat)) { } elseif ($factory->supports($chat)) {
$factory->send($chat); $factory->send($chat);
} else { } else {
throw new BadRequestHttpException('Unsupported message type'); throw new \InvalidArgumentException('Unsupported message type');
}
$this->logger->info('Chat message sent', [
'username' => $watchList->getUser()->getUserIdentifier(),
'scheme' => $webhookScheme->name,
'watchlist' => $watchList->getToken(),
]);
} catch (\Throwable $exception) {
$this->logger->error('Unable to send a chat message', [
'username' => $watchList->getUser()->getUserIdentifier(),
'scheme' => $webhookScheme->name,
'watchlist' => $watchList->getToken(),
]);
throw new BadRequestHttpException($exception->getMessage());
} }
} }
} }

View File

@@ -1,9 +1,10 @@
<?php <?php
namespace App\Service\Connector; namespace App\Service\Provider;
use App\Dto\Connector\DefaultProviderDto; use App\Dto\Connector\DefaultProviderDto;
use App\Entity\Domain; use App\Entity\Domain;
use App\Exception\Provider\UserNoExplicitConsentException;
use Psr\Cache\CacheItemInterface; use Psr\Cache\CacheItemInterface;
use Psr\Cache\CacheItemPoolInterface; use Psr\Cache\CacheItemPoolInterface;
use Symfony\Component\DependencyInjection\Attribute\Autoconfigure; use Symfony\Component\DependencyInjection\Attribute\Autoconfigure;
@@ -65,7 +66,7 @@ abstract class AbstractProvider
* *
* @return array raw authentication data as supplied by the user * @return array raw authentication data as supplied by the user
* *
* @throws HttpException when the user does not accept the necessary conditions * @throws UserNoExplicitConsentException when the user does not accept the necessary conditions
*/ */
private function verifyLegalAuthData(array $authData): array private function verifyLegalAuthData(array $authData): array
{ {
@@ -76,7 +77,7 @@ abstract class AbstractProvider
if (true !== $acceptConditions if (true !== $acceptConditions
|| true !== $ownerLegalAge || true !== $ownerLegalAge
|| true !== $waiveRetractationPeriod) { || true !== $waiveRetractationPeriod) {
throw new HttpException(451, 'The user has not given explicit consent'); throw new UserNoExplicitConsentException();
} }
return $authData; return $authData;

View File

@@ -1,17 +1,17 @@
<?php <?php
namespace App\Service\Connector; namespace App\Service\Provider;
use App\Dto\Connector\AutodnsProviderDto; use App\Dto\Connector\AutodnsProviderDto;
use App\Dto\Connector\DefaultProviderDto; use App\Dto\Connector\DefaultProviderDto;
use App\Entity\Domain; use App\Entity\Domain;
use App\Exception\Provider\InvalidLoginException;
use Psr\Cache\CacheItemInterface; use Psr\Cache\CacheItemInterface;
use Psr\Cache\CacheItemPoolInterface; use Psr\Cache\CacheItemPoolInterface;
use Psr\Cache\InvalidArgumentException; use Psr\Cache\InvalidArgumentException;
use Symfony\Component\DependencyInjection\Attribute\Autoconfigure; use Symfony\Component\DependencyInjection\Attribute\Autoconfigure;
use Symfony\Component\HttpClient\HttpOptions; use Symfony\Component\HttpClient\HttpOptions;
use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
use Symfony\Component\Serializer\Normalizer\DenormalizerInterface; use Symfony\Component\Serializer\Normalizer\DenormalizerInterface;
use Symfony\Component\Serializer\Normalizer\NormalizerInterface; use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
use Symfony\Component\Validator\Validator\ValidatorInterface; use Symfony\Component\Validator\Validator\ValidatorInterface;
@@ -201,26 +201,23 @@ class AutodnsProvider extends AbstractProvider
/** /**
* @throws TransportExceptionInterface * @throws TransportExceptionInterface
* @throws InvalidLoginException
*/ */
protected function assertAuthentication(): void protected function assertAuthentication(): void
{ {
try { $response = $this->client->request(
$response = $this->client->request( 'GET',
'GET', '/v1/hello',
'/v1/hello', (new HttpOptions())
(new HttpOptions()) ->setAuthBasic($this->authData->username, $this->authData->password)
->setAuthBasic($this->authData->username, $this->authData->password) ->setHeader('Accept', 'application/json')
->setHeader('Accept', 'application/json') ->setHeader('X-Domainrobot-Context', (string) $this->authData->context)
->setHeader('X-Domainrobot-Context', (string) $this->authData->context) ->setBaseUri(self::BASE_URL)
->setBaseUri(self::BASE_URL) ->toArray()
->toArray() );
);
} catch (\Exception) {
throw new BadRequestHttpException('Invalid Login');
}
if (Response::HTTP_OK !== $response->getStatusCode()) { if (Response::HTTP_OK !== $response->getStatusCode()) {
throw new BadRequestHttpException('The status of these credentials is not valid'); throw InvalidLoginException::fromIdentifier($this->authData->username);
} }
} }
} }

View File

@@ -1,6 +1,6 @@
<?php <?php
namespace App\Service\Connector; namespace App\Service\Provider;
interface CheckDomainProviderInterface interface CheckDomainProviderInterface
{ {

View File

@@ -1,10 +1,11 @@
<?php <?php
namespace App\Service\Connector; namespace App\Service\Provider;
use App\Dto\Connector\DefaultProviderDto; use App\Dto\Connector\DefaultProviderDto;
use App\Dto\Connector\EppClientProviderDto; use App\Dto\Connector\EppClientProviderDto;
use App\Entity\Domain; use App\Entity\Domain;
use App\Exception\Provider\EppContactIsAvailableException;
use Metaregistrar\EPP\eppCheckContactRequest; use Metaregistrar\EPP\eppCheckContactRequest;
use Metaregistrar\EPP\eppCheckContactResponse; use Metaregistrar\EPP\eppCheckContactResponse;
use Metaregistrar\EPP\eppCheckDomainRequest; use Metaregistrar\EPP\eppCheckDomainRequest;
@@ -18,7 +19,6 @@ use Metaregistrar\EPP\eppHelloRequest;
use Psr\Cache\CacheItemInterface; use Psr\Cache\CacheItemInterface;
use Psr\Cache\CacheItemPoolInterface; use Psr\Cache\CacheItemPoolInterface;
use Psr\Cache\InvalidArgumentException; use Psr\Cache\InvalidArgumentException;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
use Symfony\Component\Serializer\Exception\ExceptionInterface; use Symfony\Component\Serializer\Exception\ExceptionInterface;
use Symfony\Component\Serializer\Normalizer\DenormalizerInterface; use Symfony\Component\Serializer\Normalizer\DenormalizerInterface;
use Symfony\Component\Serializer\Normalizer\NormalizerInterface; use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
@@ -57,7 +57,7 @@ class EppClientProvider extends AbstractProvider implements CheckDomainProviderI
$resp = $this->eppClient->request(new eppCheckContactRequest($contacts)); $resp = $this->eppClient->request(new eppCheckContactRequest($contacts));
foreach ($resp->getCheckedContacts() as $contact => $available) { foreach ($resp->getCheckedContacts() as $contact => $available) {
if ($available) { if ($available) {
throw new BadRequestHttpException("At least one of the entered contacts cannot be used because it is indicated as available ($contact)."); throw EppContactIsAvailableException::fromContact($contact);
} }
} }

View File

@@ -1,17 +1,17 @@
<?php <?php
namespace App\Service\Connector; namespace App\Service\Provider;
use App\Dto\Connector\DefaultProviderDto; use App\Dto\Connector\DefaultProviderDto;
use App\Dto\Connector\GandiProviderDto; use App\Dto\Connector\GandiProviderDto;
use App\Entity\Domain; use App\Entity\Domain;
use App\Exception\Provider\DomainOrderFailedExeption;
use App\Exception\Provider\InvalidLoginException;
use Psr\Cache\CacheItemInterface; use Psr\Cache\CacheItemInterface;
use Psr\Cache\CacheItemPoolInterface; use Psr\Cache\CacheItemPoolInterface;
use Symfony\Component\DependencyInjection\Attribute\Autoconfigure; use Symfony\Component\DependencyInjection\Attribute\Autoconfigure;
use Symfony\Component\HttpClient\HttpOptions; use Symfony\Component\HttpClient\HttpOptions;
use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
use Symfony\Component\HttpKernel\Exception\HttpException;
use Symfony\Component\Serializer\Normalizer\DenormalizerInterface; use Symfony\Component\Serializer\Normalizer\DenormalizerInterface;
use Symfony\Component\Serializer\Normalizer\NormalizerInterface; use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
use Symfony\Component\Validator\Validator\ValidatorInterface; use Symfony\Component\Validator\Validator\ValidatorInterface;
@@ -94,12 +94,13 @@ class GandiProvider extends AbstractProvider
if ((!$dryRun && Response::HTTP_ACCEPTED !== $res->getStatusCode()) if ((!$dryRun && Response::HTTP_ACCEPTED !== $res->getStatusCode())
|| ($dryRun && Response::HTTP_OK !== $res->getStatusCode())) { || ($dryRun && Response::HTTP_OK !== $res->getStatusCode())) {
throw new HttpException($res->toArray()['message']); throw new DomainOrderFailedExeption($res->toArray()['message']);
} }
} }
/** /**
* @throws TransportExceptionInterface * @throws TransportExceptionInterface
* @throws InvalidLoginException
*/ */
protected function assertAuthentication(): void protected function assertAuthentication(): void
{ {
@@ -111,7 +112,7 @@ class GandiProvider extends AbstractProvider
); );
if (Response::HTTP_OK !== $response->getStatusCode()) { if (Response::HTTP_OK !== $response->getStatusCode()) {
throw new BadRequestHttpException('The status of these credentials is not valid'); throw new InvalidLoginException();
} }
} }

View File

@@ -1,17 +1,17 @@
<?php <?php
namespace App\Service\Connector; namespace App\Service\Provider;
use App\Dto\Connector\DefaultProviderDto; use App\Dto\Connector\DefaultProviderDto;
use App\Dto\Connector\NameComProviderDto; use App\Dto\Connector\NameComProviderDto;
use App\Entity\Domain; use App\Entity\Domain;
use App\Exception\Provider\InvalidLoginException;
use Psr\Cache\CacheItemInterface; use Psr\Cache\CacheItemInterface;
use Psr\Cache\CacheItemPoolInterface; use Psr\Cache\CacheItemPoolInterface;
use Psr\Cache\InvalidArgumentException; use Psr\Cache\InvalidArgumentException;
use Symfony\Component\DependencyInjection\Attribute\Autoconfigure; use Symfony\Component\DependencyInjection\Attribute\Autoconfigure;
use Symfony\Component\HttpClient\HttpOptions; use Symfony\Component\HttpClient\HttpOptions;
use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
use Symfony\Component\HttpKernel\KernelInterface; use Symfony\Component\HttpKernel\KernelInterface;
use Symfony\Component\Serializer\Normalizer\DenormalizerInterface; use Symfony\Component\Serializer\Normalizer\DenormalizerInterface;
use Symfony\Component\Serializer\Normalizer\NormalizerInterface; use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
@@ -98,25 +98,22 @@ class NameComProvider extends AbstractProvider
/** /**
* @throws TransportExceptionInterface * @throws TransportExceptionInterface
* @throws InvalidLoginException
*/ */
protected function assertAuthentication(): void protected function assertAuthentication(): void
{ {
try { $response = $this->client->request(
$response = $this->client->request( 'GET',
'GET', '/v4/hello',
'/v4/hello', (new HttpOptions())
(new HttpOptions()) ->setHeader('Accept', 'application/json')
->setHeader('Accept', 'application/json') ->setAuthBasic($this->authData->username, $this->authData->token)
->setAuthBasic($this->authData->username, $this->authData->token) ->setBaseUri($this->kernel->isDebug() ? self::DEV_BASE_URL : self::BASE_URL)
->setBaseUri($this->kernel->isDebug() ? self::DEV_BASE_URL : self::BASE_URL) ->toArray()
->toArray() );
);
} catch (\Exception) {
throw new BadRequestHttpException('Invalid Login');
}
if (Response::HTTP_OK !== $response->getStatusCode()) { if (Response::HTTP_OK !== $response->getStatusCode()) {
throw new BadRequestHttpException('The status of these credentials is not valid'); throw InvalidLoginException::fromIdentifier($this->authData->username);
} }
} }
} }

View File

@@ -1,15 +1,16 @@
<?php <?php
namespace App\Service\Connector; namespace App\Service\Provider;
use App\Dto\Connector\DefaultProviderDto; use App\Dto\Connector\DefaultProviderDto;
use App\Dto\Connector\NamecheapProviderDto; use App\Dto\Connector\NamecheapProviderDto;
use App\Entity\Domain; use App\Entity\Domain;
use App\Exception\Provider\NamecheapRequiresAddressException;
use App\Exception\Provider\ProviderGenericErrorException;
use Psr\Cache\CacheItemInterface; use Psr\Cache\CacheItemInterface;
use Psr\Cache\CacheItemPoolInterface; use Psr\Cache\CacheItemPoolInterface;
use Psr\Cache\InvalidArgumentException; use Psr\Cache\InvalidArgumentException;
use Symfony\Component\DependencyInjection\Attribute\Autoconfigure; use Symfony\Component\DependencyInjection\Attribute\Autoconfigure;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
use Symfony\Component\Serializer\Normalizer\DenormalizerInterface; use Symfony\Component\Serializer\Normalizer\DenormalizerInterface;
use Symfony\Component\Serializer\Normalizer\NormalizerInterface; use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
use Symfony\Component\Validator\Validator\ValidatorInterface; use Symfony\Component\Validator\Validator\ValidatorInterface;
@@ -49,7 +50,7 @@ class NamecheapProvider extends AbstractProvider
$addresses = $this->call('namecheap.users.address.getList', [], $dryRun)->AddressGetListResult->List; $addresses = $this->call('namecheap.users.address.getList', [], $dryRun)->AddressGetListResult->List;
if (count($addresses) < 1) { if (count($addresses) < 1) {
throw new BadRequestHttpException('Namecheap account requires at least one address to purchase a domain'); throw new NamecheapRequiresAddressException();
} }
$addressId = (string) $addresses->attributes()['AddressId']; $addressId = (string) $addresses->attributes()['AddressId'];
@@ -105,7 +106,7 @@ class NamecheapProvider extends AbstractProvider
$data = new \SimpleXMLElement($response->getContent()); $data = new \SimpleXMLElement($response->getContent());
if ($data->Errors->Error) { if ($data->Errors->Error) {
throw new BadRequestHttpException($data->Errors->Error); throw new ProviderGenericErrorException($data->Errors->Error);
} }
return $data->CommandResponse; return $data->CommandResponse;
@@ -116,13 +117,14 @@ class NamecheapProvider extends AbstractProvider
* @throws ServerExceptionInterface * @throws ServerExceptionInterface
* @throws RedirectionExceptionInterface * @throws RedirectionExceptionInterface
* @throws ClientExceptionInterface * @throws ClientExceptionInterface
* @throws NamecheapRequiresAddressException
*/ */
protected function assertAuthentication(): void protected function assertAuthentication(): void
{ {
$addresses = $this->call('namecheap.users.address.getList', [], false)->AddressGetListResult->List; $addresses = $this->call('namecheap.users.address.getList', [], false)->AddressGetListResult->List;
if (count($addresses) < 1) { if (count($addresses) < 1) {
throw new BadRequestHttpException('Namecheap account requires at least one address to purchase a domain'); throw new NamecheapRequiresAddressException();
} }
} }

View File

@@ -1,10 +1,15 @@
<?php <?php
namespace App\Service\Connector; namespace App\Service\Provider;
use App\Dto\Connector\DefaultProviderDto; use App\Dto\Connector\DefaultProviderDto;
use App\Dto\Connector\OvhProviderDto; use App\Dto\Connector\OvhProviderDto;
use App\Entity\Domain; use App\Entity\Domain;
use App\Exception\Provider\DomainOrderFailedExeption;
use App\Exception\Provider\ExpiredLoginException;
use App\Exception\Provider\InvalidLoginStatusException;
use App\Exception\Provider\PermissionErrorException;
use App\Exception\Provider\ProviderGenericErrorException;
use GuzzleHttp\Exception\ClientException; use GuzzleHttp\Exception\ClientException;
use Ovh\Api; use Ovh\Api;
use Ovh\Exceptions\InvalidParameterException; use Ovh\Exceptions\InvalidParameterException;
@@ -12,7 +17,6 @@ use Psr\Cache\CacheItemInterface;
use Psr\Cache\CacheItemPoolInterface; use Psr\Cache\CacheItemPoolInterface;
use Psr\Cache\InvalidArgumentException; use Psr\Cache\InvalidArgumentException;
use Symfony\Component\DependencyInjection\Attribute\Autoconfigure; use Symfony\Component\DependencyInjection\Attribute\Autoconfigure;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
use Symfony\Component\Serializer\Normalizer\DenormalizerInterface; use Symfony\Component\Serializer\Normalizer\DenormalizerInterface;
use Symfony\Component\Serializer\Normalizer\NormalizerInterface; use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
use Symfony\Component\Validator\Validator\ValidatorInterface; use Symfony\Component\Validator\Validator\ValidatorInterface;
@@ -104,7 +108,7 @@ class OvhProvider extends AbstractProvider
); );
if (empty($offer)) { if (empty($offer)) {
$conn->delete("/order/cart/{$cartId}"); $conn->delete("/order/cart/{$cartId}");
throw new \InvalidArgumentException('Cannot buy this domain name'); throw new DomainOrderFailedExeption();
} }
$item = $conn->post("/order/cart/{$cartId}/domain", [ $item = $conn->post("/order/cart/{$cartId}/domain", [
@@ -153,15 +157,15 @@ class OvhProvider extends AbstractProvider
try { try {
$res = $conn->get('/auth/currentCredential'); $res = $conn->get('/auth/currentCredential');
if (null !== $res['expiration'] && new \DateTimeImmutable($res['expiration']) < new \DateTimeImmutable()) { if (null !== $res['expiration'] && new \DateTimeImmutable($res['expiration']) < new \DateTimeImmutable()) {
throw new BadRequestHttpException('These credentials have expired'); throw ExpiredLoginException::fromIdentifier($this->authData->appKey);
} }
$status = $res['status']; $status = $res['status'];
if ('validated' !== $status) { if ('validated' !== $status) {
throw new BadRequestHttpException("The status of these credentials is not valid ($status)"); throw InvalidLoginStatusException::fromStatus($status);
} }
} catch (ClientException $exception) { } catch (ClientException $exception) {
throw new BadRequestHttpException($exception->getMessage()); throw new ProviderGenericErrorException($exception->getMessage());
} }
foreach (self::REQUIRED_ROUTES as $requiredRoute) { foreach (self::REQUIRED_ROUTES as $requiredRoute) {
@@ -177,7 +181,7 @@ class OvhProvider extends AbstractProvider
} }
if (!$ok) { if (!$ok) {
throw new BadRequestHttpException('This Connector does not have enough permissions on the Provider API. Please recreate this Connector.'); throw PermissionErrorException::fromIdentifier($this->authData->appKey);
} }
} }
} }

View File

@@ -119,7 +119,7 @@ class RDAPService
$idnDomain = RDAPService::convertToIdn($fqdn); $idnDomain = RDAPService::convertToIdn($fqdn);
$tld = $this->getTld($idnDomain); $tld = $this->getTld($idnDomain);
$this->logger->info('Update request for a domain name is requested', [ $this->logger->debug('Update request for a domain name is requested', [
'ldhName' => $idnDomain, 'ldhName' => $idnDomain,
]); ]);
@@ -187,7 +187,7 @@ class RDAPService
$tldEntity = $this->tldRepository->findOneBy(['tld' => $tld, 'deletedAt' => null]); $tldEntity = $this->tldRepository->findOneBy(['tld' => $tld, 'deletedAt' => null]);
if (null === $tldEntity) { if (null === $tldEntity) {
$this->logger->warning('Domain name cannot be updated because the TLD is not supported', [ $this->logger->debug('Domain name cannot be updated because the TLD is not supported', [
'ldhName' => $domain, 'ldhName' => $domain,
]); ]);
throw TldNotSupportedException::fromTld($tld); throw TldNotSupportedException::fromTld($tld);
@@ -210,7 +210,7 @@ class RDAPService
$rdapServer = $this->rdapServerRepository->findOneBy(['tld' => $tldString], ['updatedAt' => 'DESC']); $rdapServer = $this->rdapServerRepository->findOneBy(['tld' => $tldString], ['updatedAt' => 'DESC']);
if (null === $rdapServer) { if (null === $rdapServer) {
$this->logger->warning('Unable to determine which RDAP server to contact', [ $this->logger->debug('Unable to determine which RDAP server to contact', [
'tld' => $tldString, 'tld' => $tldString,
]); ]);
@@ -231,7 +231,7 @@ class RDAPService
private function fetchRdapResponse(RdapServer $rdapServer, string $idnDomain, ?Domain $domain): array private function fetchRdapResponse(RdapServer $rdapServer, string $idnDomain, ?Domain $domain): array
{ {
$rdapServerUrl = $rdapServer->getUrl(); $rdapServerUrl = $rdapServer->getUrl();
$this->logger->notice('An RDAP query to update this domain name will be made', [ $this->logger->info('An RDAP query to update this domain name will be made', [
'ldhName' => $idnDomain, 'ldhName' => $idnDomain,
'server' => $rdapServerUrl, 'server' => $rdapServerUrl,
]); ]);
@@ -261,7 +261,7 @@ class RDAPService
|| ($e instanceof TransportExceptionInterface && null !== $response && !in_array('content-length', $response->getHeaders(false)) && 404 === $response->getStatusCode()) || ($e instanceof TransportExceptionInterface && null !== $response && !in_array('content-length', $response->getHeaders(false)) && 404 === $response->getStatusCode())
) { ) {
if (null !== $domain) { if (null !== $domain) {
$this->logger->notice('Domain name has been deleted from the WHOIS database', [ $this->logger->info('Domain name has been deleted from the WHOIS database', [
'ldhName' => $idnDomain, 'ldhName' => $idnDomain,
]); ]);
@@ -295,7 +295,7 @@ class RDAPService
{ {
$domain = new Domain(); $domain = new Domain();
$this->logger->info('Domain name was not known to this instance', [ $this->logger->debug('Domain name was not known to this instance', [
'ldhName' => $idnDomain, 'ldhName' => $idnDomain,
]); ]);
@@ -304,7 +304,7 @@ class RDAPService
private function updateDomainStatus(Domain $domain, array $rdapData): void private function updateDomainStatus(Domain $domain, array $rdapData): void
{ {
if (isset($rdapData['status'])) { if (isset($rdapData['status']) && is_array($rdapData['status'])) {
$status = array_unique($rdapData['status']); $status = array_unique($rdapData['status']);
$addedStatus = array_diff($status, $domain->getStatus()); $addedStatus = array_diff($status, $domain->getStatus());
$deletedStatus = array_diff($domain->getStatus(), $status); $deletedStatus = array_diff($domain->getStatus(), $status);
@@ -429,7 +429,7 @@ class RDAPService
*/ */
private function updateDomainNameservers(Domain $domain, array $rdapData): void private function updateDomainNameservers(Domain $domain, array $rdapData): void
{ {
if (array_key_exists('nameservers', $rdapData) && is_array($rdapData['nameservers'])) { if (isset($rdapData['nameservers']) && is_array($rdapData['nameservers'])) {
$domain->getNameservers()->clear(); $domain->getNameservers()->clear();
$this->em->persist($domain); $this->em->persist($domain);
@@ -549,7 +549,7 @@ class RDAPService
if (null === $entity) { if (null === $entity) {
$entity = (new Entity())->setTld($tld); $entity = (new Entity())->setTld($tld);
$this->logger->info('Entity was not known to this instance', [ $this->logger->debug('Entity was not known to this instance', [
'handle' => $rdapEntity['handle'], 'handle' => $rdapEntity['handle'],
'ldhName' => $domain, 'ldhName' => $domain,
]); ]);

View File

@@ -7,8 +7,8 @@ use ApiPlatform\State\ProcessorInterface;
use App\Config\ConnectorProvider; use App\Config\ConnectorProvider;
use App\Entity\Connector; use App\Entity\Connector;
use App\Entity\User; use App\Entity\User;
use App\Service\Connector\AbstractProvider; use App\Service\Provider\AbstractProvider;
use App\Service\Connector\EppClientProvider; use App\Service\Provider\EppClientProvider;
use Psr\Log\LoggerInterface; use Psr\Log\LoggerInterface;
use Symfony\Bundle\SecurityBundle\Security; use Symfony\Bundle\SecurityBundle\Security;
use Symfony\Component\DependencyInjection\Attribute\Autowire; use Symfony\Component\DependencyInjection\Attribute\Autowire;

View File

@@ -6,7 +6,7 @@ use ApiPlatform\Metadata\Operation;
use ApiPlatform\State\ProcessorInterface; use ApiPlatform\State\ProcessorInterface;
use App\Config\ConnectorProvider; use App\Config\ConnectorProvider;
use App\Entity\Connector; use App\Entity\Connector;
use App\Service\Connector\EppClientProvider; use App\Service\Provider\EppClientProvider;
use Symfony\Component\DependencyInjection\Attribute\Autowire; use Symfony\Component\DependencyInjection\Attribute\Autowire;
use Symfony\Component\Filesystem\Filesystem; use Symfony\Component\Filesystem\Filesystem;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException; use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;

View File

@@ -9,7 +9,7 @@ use App\Entity\User;
use App\Entity\WatchList; use App\Entity\WatchList;
use App\Notifier\TestChatNotification; use App\Notifier\TestChatNotification;
use App\Service\ChatNotificationService; use App\Service\ChatNotificationService;
use App\Service\Connector\AbstractProvider; use App\Service\Provider\AbstractProvider;
use Psr\Log\LoggerInterface; use Psr\Log\LoggerInterface;
use Symfony\Bundle\SecurityBundle\Security; use Symfony\Bundle\SecurityBundle\Security;
use Symfony\Component\DependencyInjection\Attribute\Autowire; use Symfony\Component\DependencyInjection\Attribute\Autowire;
@@ -68,7 +68,7 @@ readonly class WatchListUpdateProcessor implements ProcessorInterface
foreach ($data->getDomains()->getIterator() as $domain) { foreach ($data->getDomains()->getIterator() as $domain) {
if (in_array($domain, $trackedDomains)) { if (in_array($domain, $trackedDomains)) {
$ldhName = $domain->getLdhName(); $ldhName = $domain->getLdhName();
$this->logger->notice('User tried to update a watchlist : it is forbidden to register the same domain name twice with limited mode', [ $this->logger->notice('User tried to update a Watchlist : it is forbidden to register the same domain name twice with limited mode', [
'username' => $user->getUserIdentifier(), 'username' => $user->getUserIdentifier(),
'watchlist' => $data->getToken(), 'watchlist' => $data->getToken(),
'ldhName' => $ldhName, 'ldhName' => $ldhName,
@@ -117,7 +117,7 @@ readonly class WatchListUpdateProcessor implements ProcessorInterface
$supported = $connectorProvider->isSupported(...$data->getDomains()->toArray()); $supported = $connectorProvider->isSupported(...$data->getDomains()->toArray());
if (!$supported) { if (!$supported) {
$this->logger->notice('Connector does not support all TLDs in this Watchlist', [ $this->logger->debug('Connector does not support all TLDs in this Watchlist', [
'username' => $user->getUserIdentifier(), 'username' => $user->getUserIdentifier(),
'connector' => $connector->getId(), 'connector' => $connector->getId(),
'provider' => $connector->getProvider()->value, 'provider' => $connector->getProvider()->value,