Merge branch 'master' into feat/epp-protocol

This commit is contained in:
Maël Gangloff 2025-02-23 00:00:01 +01:00
commit 81147d2069
No known key found for this signature in database
GPG Key ID: 11FDC81C24A7F629
12 changed files with 186 additions and 129 deletions

View File

@ -0,0 +1,19 @@
<?php
namespace App\Dto\Connector;
use Symfony\Component\Validator\Constraints as Assert;
final class AutodnsProviderDto extends DefaultProviderDto
{
#[Assert\NotBlank]
public string $username;
#[Assert\NotBlank]
public string $password;
#[Assert\IsTrue]
public bool $ownerConfirm;
public int $context = 4;
}

View File

@ -0,0 +1,17 @@
<?php
namespace App\Dto\Connector;
use Symfony\Component\Validator\Constraints as Assert;
class DefaultProviderDto
{
#[Assert\IsTrue]
public bool $ownerLegalAge;
#[Assert\IsTrue]
public bool $acceptConditions;
#[Assert\IsTrue]
public bool $waiveRetractationPeriod;
}

View File

@ -0,0 +1,13 @@
<?php
namespace App\Dto\Connector;
use Symfony\Component\Validator\Constraints as Assert;
final class GandiProviderDto extends DefaultProviderDto
{
#[Assert\NotBlank]
public string $token;
public ?string $sharingId = null;
}

View File

@ -0,0 +1,14 @@
<?php
namespace App\Dto\Connector;
use Symfony\Component\Validator\Constraints as Assert;
final class NameComProviderDto extends DefaultProviderDto
{
#[Assert\NotBlank]
public string $username;
#[Assert\NotBlank]
public string $token;
}

View File

@ -0,0 +1,14 @@
<?php
namespace App\Dto\Connector;
use Symfony\Component\Validator\Constraints as Assert;
final class NamecheapProviderDto extends DefaultProviderDto
{
#[Assert\NotBlank]
public string $ApiUser;
#[Assert\NotBlank]
public string $ApiKey;
}

View File

@ -0,0 +1,26 @@
<?php
namespace App\Dto\Connector;
use Symfony\Component\Validator\Constraints as Assert;
final class OvhProviderDto extends DefaultProviderDto
{
#[Assert\NotBlank]
public string $appKey;
#[Assert\NotBlank]
public string $appSecret;
#[Assert\NotBlank]
public string $apiEndpoint;
#[Assert\NotBlank]
public string $consumerKey;
#[Assert\Choice(['create-default', 'create-premium'])]
public string $pricingMode;
#[Assert\NotBlank]
public string $ovhSubsidiary;
}

View File

@ -2,12 +2,19 @@
namespace App\Service\Connector;
use App\Dto\Connector\DefaultProviderDto;
use App\Entity\Domain;
use Exception;
use Psr\Cache\CacheItemInterface;
use Psr\Cache\CacheItemPoolInterface;
use Symfony\Component\DependencyInjection\Attribute\Autoconfigure;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
use Symfony\Component\HttpKernel\Exception\HttpException;
use Symfony\Component\Serializer\Exception\ExceptionInterface;
use Symfony\Component\Serializer\Normalizer\DenormalizerInterface;
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
use Symfony\Component\Validator\ConstraintViolationInterface;
use Symfony\Component\Validator\Validator\ValidatorInterface;
/**
* The typical flow of a provider will go as follows:
@ -19,10 +26,17 @@ use Symfony\Component\HttpKernel\Exception\HttpException;
#[Autoconfigure(public: true)]
abstract class AbstractProvider
{
/**
* @var class-string
*/
protected string $dtoClass = DefaultProviderDto::class;
protected array $authData;
public function __construct(
protected CacheItemPoolInterface $cacheItemPool,
private readonly DenormalizerInterface&NormalizerInterface $serializer,
private readonly ValidatorInterface $validator,
) {
}
@ -35,25 +49,27 @@ abstract class AbstractProvider
*
* @return array a cleaned up version of the authentication data
*
* @throws HttpException when the user does not accept the necessary conditions
* @throws HttpException when the user does not accept the necessary conditions
* @throws ExceptionInterface
*/
public function verifyAuthData(array $authData): array
{
/** @var DefaultProviderDto $data */
$data = $this->serializer->denormalize($this->verifyLegalAuthData($authData), $this->dtoClass);
$violations = $this->validator->validate($data);
if ($violations->count()) {
throw new BadRequestHttpException(implode('\n', array_map(fn (ConstraintViolationInterface $v) => $v->getPropertyPath().': '.$v->getMessage(), iterator_to_array($violations))));
}
return [
...$this->verifySpecificAuthData($this->verifyLegalAuthData($authData)),
...$this->serializer->normalize($data),
'acceptConditions' => $authData['acceptConditions'],
'ownerLegalAge' => $authData['ownerLegalAge'],
'waiveRetractationPeriod' => $authData['waiveRetractationPeriod'],
];
}
/**
* @param array $authData raw authentication data as supplied by the user
*
* @return array specific authentication data
*/
abstract protected function verifySpecificAuthData(array $authData): array;
/**
* @param array $authData raw authentication data as supplied by the user
*
@ -117,6 +133,7 @@ abstract class AbstractProvider
}
/**
* @throws ExceptionInterface
* @throws \Exception
*/
public function authenticate(array $authData): void

View File

@ -2,6 +2,7 @@
namespace App\Service\Connector;
use App\Dto\Connector\AutodnsProviderDto;
use App\Entity\Domain;
use Psr\Cache\CacheItemInterface;
use Psr\Cache\CacheItemPoolInterface;
@ -10,6 +11,9 @@ use Symfony\Component\DependencyInjection\Attribute\Autoconfigure;
use Symfony\Component\HttpClient\HttpOptions;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
use Symfony\Component\Serializer\Normalizer\DenormalizerInterface;
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
use Symfony\Component\Validator\Validator\ValidatorInterface;
use Symfony\Contracts\HttpClient\Exception\ClientExceptionInterface;
use Symfony\Contracts\HttpClient\Exception\DecodingExceptionInterface;
use Symfony\Contracts\HttpClient\Exception\RedirectionExceptionInterface;
@ -20,9 +24,15 @@ use Symfony\Contracts\HttpClient\HttpClientInterface;
#[Autoconfigure(public: true)]
class AutodnsProvider extends AbstractProvider
{
public function __construct(CacheItemPoolInterface $cacheItemPool, private readonly HttpClientInterface $client)
{
parent::__construct($cacheItemPool);
protected string $dtoClass = AutodnsProviderDto::class;
public function __construct(
CacheItemPoolInterface $cacheItemPool,
DenormalizerInterface&NormalizerInterface $serializer,
private readonly HttpClientInterface $client,
ValidatorInterface $validator,
) {
parent::__construct($cacheItemPool, $serializer, $validator);
}
private const BASE_URL = 'https://api.autodns.com';
@ -167,31 +177,6 @@ class AutodnsProvider extends AbstractProvider
}
}
public function verifySpecificAuthData(array $authData): array
{
$username = $authData['username'];
$password = $authData['password'];
if (empty($authData['context'])) {
$authData['context'] = 4;
}
if (
!is_string($username) || empty($username)
|| !is_string($password) || empty($password)
|| true !== $authData['ownerConfirm']
) {
throw new BadRequestHttpException('Bad authData schema');
}
return [
'username' => $authData['username'],
'password' => $authData['password'],
'ownerConfirm' => $authData['ownerConfirm'],
'context' => $authData['context'],
];
}
public function isSupported(Domain ...$domainList): bool
{
return true;

View File

@ -2,6 +2,7 @@
namespace App\Service\Connector;
use App\Dto\Connector\GandiProviderDto;
use App\Entity\Domain;
use Psr\Cache\CacheItemInterface;
use Psr\Cache\CacheItemPoolInterface;
@ -10,6 +11,9 @@ use Symfony\Component\HttpClient\HttpOptions;
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\NormalizerInterface;
use Symfony\Component\Validator\Validator\ValidatorInterface;
use Symfony\Contracts\HttpClient\Exception\ClientExceptionInterface;
use Symfony\Contracts\HttpClient\Exception\DecodingExceptionInterface;
use Symfony\Contracts\HttpClient\Exception\RedirectionExceptionInterface;
@ -20,11 +24,17 @@ use Symfony\Contracts\HttpClient\HttpClientInterface;
#[Autoconfigure(public: true)]
class GandiProvider extends AbstractProvider
{
protected string $dtoClass = GandiProviderDto::class;
private const BASE_URL = 'https://api.gandi.net';
public function __construct(CacheItemPoolInterface $cacheItemPool, private readonly HttpClientInterface $client)
{
parent::__construct($cacheItemPool);
public function __construct(
CacheItemPoolInterface $cacheItemPool,
private readonly HttpClientInterface $client,
DenormalizerInterface&NormalizerInterface $serializer,
ValidatorInterface $validator,
) {
parent::__construct($cacheItemPool, $serializer, $validator);
}
/**
@ -84,27 +94,6 @@ class GandiProvider extends AbstractProvider
}
}
public function verifySpecificAuthData(array $authData): array
{
$token = $authData['token'];
if (!is_string($token) || empty($token)
|| (array_key_exists('sharingId', $authData) && !is_string($authData['sharingId']))
) {
throw new BadRequestHttpException('Bad authData schema');
}
$authDataReturned = [
'token' => $token,
];
if (array_key_exists('sharingId', $authData)) {
$authDataReturned['sharingId'] = $authData['sharingId'];
}
return $authDataReturned;
}
/**
* @throws TransportExceptionInterface
*/

View File

@ -11,6 +11,9 @@ use Symfony\Component\HttpClient\HttpOptions;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
use Symfony\Component\HttpKernel\KernelInterface;
use Symfony\Component\Serializer\Normalizer\DenormalizerInterface;
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
use Symfony\Component\Validator\Validator\ValidatorInterface;
use Symfony\Contracts\HttpClient\Exception\DecodingExceptionInterface;
use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface;
use Symfony\Contracts\HttpClient\HttpClientInterface;
@ -18,11 +21,15 @@ use Symfony\Contracts\HttpClient\HttpClientInterface;
#[Autoconfigure(public: true)]
class NameComProvider extends AbstractProvider
{
protected string $dtoClass = NameComProvider::class;
public function __construct(CacheItemPoolInterface $cacheItemPool,
private readonly HttpClientInterface $client,
private readonly KernelInterface $kernel)
{
parent::__construct($cacheItemPool);
private readonly KernelInterface $kernel,
DenormalizerInterface&NormalizerInterface $serializer,
ValidatorInterface $validator,
) {
parent::__construct($cacheItemPool, $serializer, $validator);
}
private const BASE_URL = 'https://api.name.com';
@ -65,24 +72,6 @@ class NameComProvider extends AbstractProvider
)->toArray();
}
public function verifySpecificAuthData(array $authData): array
{
$username = $authData['username'];
$token = $authData['token'];
if (
!is_string($username) || empty($username)
|| !is_string($token) || empty($token)
) {
throw new BadRequestHttpException('Bad authData schema');
}
return [
'username' => $authData['username'],
'token' => $authData['token'],
];
}
public function isSupported(Domain ...$domainList): bool
{
return true;

View File

@ -2,12 +2,16 @@
namespace App\Service\Connector;
use App\Dto\Connector\NamecheapProviderDto;
use App\Entity\Domain;
use Psr\Cache\CacheItemInterface;
use Psr\Cache\CacheItemPoolInterface;
use Psr\Cache\InvalidArgumentException;
use Symfony\Component\DependencyInjection\Attribute\Autoconfigure;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
use Symfony\Component\Serializer\Normalizer\DenormalizerInterface;
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
use Symfony\Component\Validator\Validator\ValidatorInterface;
use Symfony\Contracts\HttpClient\Exception\ClientExceptionInterface;
use Symfony\Contracts\HttpClient\Exception\RedirectionExceptionInterface;
use Symfony\Contracts\HttpClient\Exception\ServerExceptionInterface;
@ -17,16 +21,19 @@ use Symfony\Contracts\HttpClient\HttpClientInterface;
#[Autoconfigure(public: true)]
class NamecheapProvider extends AbstractProvider
{
public const BASE_URL = 'https://api.namecheap.com/xml.response';
protected string $dtoClass = NamecheapProviderDto::class;
public const BASE_URL = 'https://api.namecheap.com/xml.response';
public const SANDBOX_BASE_URL = 'https://api.sandbox.namecheap.com/xml.response';
public function __construct(
CacheItemPoolInterface $cacheItemPool,
private readonly HttpClientInterface $client,
private readonly string $outgoingIp,
DenormalizerInterface&NormalizerInterface $serializer,
ValidatorInterface $validator,
) {
parent::__construct($cacheItemPool);
parent::__construct($cacheItemPool, $serializer, $validator);
}
/**
@ -100,20 +107,6 @@ class NamecheapProvider extends AbstractProvider
return $data->CommandResponse;
}
public function verifySpecificAuthData(array $authData): array
{
foreach (['ApiUser', 'ApiKey'] as $key) {
if (empty($authData[$key]) || !is_string($authData[$key])) {
throw new BadRequestHttpException("Bad authData schema: missing or invalid '$key'");
}
}
return [
'ApiUser' => $authData['ApiUser'],
'ApiKey' => $authData['ApiKey'],
];
}
/**
* @throws TransportExceptionInterface
* @throws ServerExceptionInterface

View File

@ -2,6 +2,7 @@
namespace App\Service\Connector;
use App\Dto\Connector\OvhProviderDto;
use App\Entity\Domain;
use GuzzleHttp\Exception\ClientException;
use Ovh\Api;
@ -11,10 +12,15 @@ use Psr\Cache\CacheItemPoolInterface;
use Psr\Cache\InvalidArgumentException;
use Symfony\Component\DependencyInjection\Attribute\Autoconfigure;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
use Symfony\Component\Serializer\Normalizer\DenormalizerInterface;
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
use Symfony\Component\Validator\Validator\ValidatorInterface;
#[Autoconfigure(public: true)]
class OvhProvider extends AbstractProvider
{
protected string $dtoClass = OvhProviderDto::class;
public const REQUIRED_ROUTES = [
[
'method' => 'GET',
@ -42,9 +48,12 @@ class OvhProvider extends AbstractProvider
],
];
public function __construct(CacheItemPoolInterface $cacheItemPool)
{
parent::__construct($cacheItemPool);
public function __construct(
CacheItemPoolInterface $cacheItemPool,
DenormalizerInterface&NormalizerInterface $serializer,
ValidatorInterface $validator,
) {
parent::__construct($cacheItemPool, $serializer, $validator);
}
/**
@ -128,34 +137,6 @@ class OvhProvider extends AbstractProvider
]);
}
/**
* @throws \Exception
*/
public function verifySpecificAuthData(array $authData): array
{
foreach ([
'appKey',
'appSecret',
'apiEndpoint',
'consumerKey',
'ovhSubsidiary',
'pricingMode',
] as $key) {
if (empty($authData[$key]) || !is_string($authData[$key])) {
throw new BadRequestHttpException("Bad authData schema: missing or invalid '$key'");
}
}
return [
'appKey' => $authData['appKey'],
'appSecret' => $authData['appSecret'],
'apiEndpoint' => $authData['apiEndpoint'],
'consumerKey' => $authData['consumerKey'],
'ovhSubsidiary' => $authData['ovhSubsidiary'],
'pricingMode' => $authData['pricingMode'],
];
}
protected function assertAuthentication(): void
{
$conn = new Api(