mirror of
https://github.com/maelgangloff/domain-watchdog.git
synced 2025-12-27 15:18:23 +00:00
Merge branch 'feature/namecheap'
This commit is contained in:
commit
f3718bb1dc
5
.env
5
.env
@ -68,6 +68,11 @@ OAUTH_TOKEN_URL=
|
|||||||
OAUTH_USERINFO_URL=
|
OAUTH_USERINFO_URL=
|
||||||
OAUTH_SCOPE=
|
OAUTH_SCOPE=
|
||||||
|
|
||||||
|
# Typically your IP address, this envvar is required for
|
||||||
|
# some connectors that need to be provided with your host's
|
||||||
|
# outgoing IP address.
|
||||||
|
OUTGOING_IP=
|
||||||
|
|
||||||
LIMITED_FEATURES=false
|
LIMITED_FEATURES=false
|
||||||
LIMIT_MAX_WATCHLIST=0
|
LIMIT_MAX_WATCHLIST=0
|
||||||
LIMIT_MAX_WATCHLIST_DOMAINS=0
|
LIMIT_MAX_WATCHLIST_DOMAINS=0
|
||||||
|
|||||||
5
.gitignore
vendored
5
.gitignore
vendored
@ -43,6 +43,11 @@ yarn-error.log
|
|||||||
phpstan.neon
|
phpstan.neon
|
||||||
###< phpstan/phpstan ###
|
###< phpstan/phpstan ###
|
||||||
|
|
||||||
|
# Eclipse
|
||||||
|
.project
|
||||||
|
.buildpath
|
||||||
|
/.settings/
|
||||||
|
|
||||||
public/images/*.png
|
public/images/*.png
|
||||||
public/content/*.md
|
public/content/*.md
|
||||||
public/favicon.ico
|
public/favicon.ico
|
||||||
|
|||||||
@ -53,7 +53,7 @@ The table below lists the supported API connector providers:
|
|||||||
|:---------:|---------------------------------------------------------------|:---------:|
|
|:---------:|---------------------------------------------------------------|:---------:|
|
||||||
| OVH | https://api.ovh.com | **Yes** |
|
| OVH | https://api.ovh.com | **Yes** |
|
||||||
| GANDI | https://api.gandi.net/docs/domains/ | **Yes** |
|
| GANDI | https://api.gandi.net/docs/domains/ | **Yes** |
|
||||||
| NAMECHEAP | https://www.namecheap.com/support/api/methods/domains/create/ | |
|
| NAMECHEAP | https://www.namecheap.com/support/api/methods/domains/create/ | **Yes** |
|
||||||
|
|
||||||
If a domain has expired and a connector is linked to the Watchlist, then Domain Watchdog will try to order it via the
|
If a domain has expired and a connector is linked to the Watchlist, then Domain Watchdog will try to order it via the
|
||||||
connector provider's API.
|
connector provider's API.
|
||||||
|
|||||||
@ -132,6 +132,22 @@ export function ConnectorForm({form, onCreate}: { form: FormInstance, onCreate:
|
|||||||
</Form.Item>
|
</Form.Item>
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
|
{
|
||||||
|
provider === ConnectorProvider.NAMECHEAP && <>
|
||||||
|
<Form.Item
|
||||||
|
label={t`Username`}
|
||||||
|
name={['authData', 'ApiUser']}
|
||||||
|
>
|
||||||
|
<Input autoComplete='off'></Input>
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item
|
||||||
|
label={t`API key`}
|
||||||
|
name={['authData', 'ApiKey']}
|
||||||
|
>
|
||||||
|
<Input autoComplete='off'></Input>
|
||||||
|
</Form.Item>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
provider !== undefined && <>
|
provider !== undefined && <>
|
||||||
|
|||||||
@ -2,7 +2,8 @@ import {request} from "./index";
|
|||||||
|
|
||||||
export enum ConnectorProvider {
|
export enum ConnectorProvider {
|
||||||
OVH = 'ovh',
|
OVH = 'ovh',
|
||||||
GANDI = 'gandi'
|
GANDI = 'gandi',
|
||||||
|
NAMECHEAP = 'namecheap'
|
||||||
}
|
}
|
||||||
|
|
||||||
export type Connector = {
|
export type Connector = {
|
||||||
|
|||||||
@ -15,6 +15,10 @@ export const helpGetTokenLink = (provider?: string) => {
|
|||||||
return <Typography.Link target='_blank' href="https://admin.gandi.net/organizations/account/pat">
|
return <Typography.Link target='_blank' href="https://admin.gandi.net/organizations/account/pat">
|
||||||
{t`Retrieve a Personal Access Token from your customer account on the Provider's website`}
|
{t`Retrieve a Personal Access Token from your customer account on the Provider's website`}
|
||||||
</Typography.Link>
|
</Typography.Link>
|
||||||
|
case ConnectorProvider.NAMECHEAP:
|
||||||
|
return <Typography.Link target='_blank' href="https://ap.www.namecheap.com/settings/tools/apiaccess/">
|
||||||
|
{t`Retreive an API key and whitelist this instance's IP address on Namecheap's website`}
|
||||||
|
</Typography.Link>
|
||||||
default:
|
default:
|
||||||
return <></>
|
return <></>
|
||||||
|
|
||||||
|
|||||||
@ -87,7 +87,8 @@
|
|||||||
"symfony/zulip-notifier": "7.1.*",
|
"symfony/zulip-notifier": "7.1.*",
|
||||||
"symfonycasts/verify-email-bundle": "*",
|
"symfonycasts/verify-email-bundle": "*",
|
||||||
"twig/extra-bundle": "^2.12|^3.0",
|
"twig/extra-bundle": "^2.12|^3.0",
|
||||||
"twig/twig": "^2.12|^3.0"
|
"twig/twig": "^2.12|^3.0",
|
||||||
|
"ext-simplexml": "*"
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"allow-plugins": {
|
"allow-plugins": {
|
||||||
|
|||||||
@ -17,6 +17,8 @@ parameters:
|
|||||||
limit_max_watchlist_domains: '%env(int:LIMIT_MAX_WATCHLIST_DOMAINS)%'
|
limit_max_watchlist_domains: '%env(int:LIMIT_MAX_WATCHLIST_DOMAINS)%'
|
||||||
limit_max_watchlist_webhooks: '%env(int:LIMIT_MAX_WATCHLIST_WEBHOOKS)%'
|
limit_max_watchlist_webhooks: '%env(int:LIMIT_MAX_WATCHLIST_WEBHOOKS)%'
|
||||||
|
|
||||||
|
outgoing_ip: '%env(string:OUTGOING_IP)%'
|
||||||
|
|
||||||
services:
|
services:
|
||||||
# default configuration for services in *this* file
|
# default configuration for services in *this* file
|
||||||
_defaults:
|
_defaults:
|
||||||
@ -25,6 +27,7 @@ services:
|
|||||||
bind:
|
bind:
|
||||||
$mailerSenderEmail: '%mailer_sender_email%'
|
$mailerSenderEmail: '%mailer_sender_email%'
|
||||||
$mailerSenderName: '%mailer_sender_name%'
|
$mailerSenderName: '%mailer_sender_name%'
|
||||||
|
$outgoingIp: '%outgoing_ip%'
|
||||||
|
|
||||||
# makes classes in src/ available to be used as services
|
# makes classes in src/ available to be used as services
|
||||||
# this creates a service per class whose id is the fully-qualified class name
|
# this creates a service per class whose id is the fully-qualified class name
|
||||||
|
|||||||
@ -2,19 +2,22 @@
|
|||||||
|
|
||||||
namespace App\Config;
|
namespace App\Config;
|
||||||
|
|
||||||
use App\Config\Provider\GandiProvider;
|
use App\Service\Connector\GandiProvider;
|
||||||
use App\Config\Provider\OvhProvider;
|
use App\Service\Connector\NamecheapProvider;
|
||||||
|
use App\Service\Connector\OvhProvider;
|
||||||
|
|
||||||
enum ConnectorProvider: string
|
enum ConnectorProvider: string
|
||||||
{
|
{
|
||||||
case OVH = 'ovh';
|
case OVH = 'ovh';
|
||||||
case GANDI = 'gandi';
|
case GANDI = 'gandi';
|
||||||
|
case NAMECHEAP = 'namecheap';
|
||||||
|
|
||||||
public function getConnectorProvider(): string
|
public function getConnectorProvider(): string
|
||||||
{
|
{
|
||||||
return match ($this) {
|
return match ($this) {
|
||||||
ConnectorProvider::OVH => OvhProvider::class,
|
ConnectorProvider::OVH => OvhProvider::class,
|
||||||
ConnectorProvider::GANDI => GandiProvider::class
|
ConnectorProvider::GANDI => GandiProvider::class,
|
||||||
|
ConnectorProvider::NAMECHEAP => NamecheapProvider::class,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,13 +2,15 @@
|
|||||||
|
|
||||||
namespace App\Controller;
|
namespace App\Controller;
|
||||||
|
|
||||||
use App\Config\Provider\AbstractProvider;
|
|
||||||
use App\Entity\Connector;
|
use App\Entity\Connector;
|
||||||
use App\Entity\User;
|
use App\Entity\User;
|
||||||
|
use App\Service\Connector\AbstractProvider;
|
||||||
use Doctrine\Common\Collections\Collection;
|
use Doctrine\Common\Collections\Collection;
|
||||||
use Doctrine\ORM\EntityManagerInterface;
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
use Psr\Log\LoggerInterface;
|
use Psr\Log\LoggerInterface;
|
||||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||||
|
use Symfony\Component\DependencyInjection\Attribute\Autowire;
|
||||||
|
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||||
use Symfony\Component\HttpFoundation\Request;
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
|
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
|
||||||
use Symfony\Component\Routing\Attribute\Route;
|
use Symfony\Component\Routing\Attribute\Route;
|
||||||
@ -20,7 +22,9 @@ class ConnectorController extends AbstractController
|
|||||||
public function __construct(
|
public function __construct(
|
||||||
private readonly SerializerInterface $serializer,
|
private readonly SerializerInterface $serializer,
|
||||||
private readonly EntityManagerInterface $em,
|
private readonly EntityManagerInterface $em,
|
||||||
private readonly LoggerInterface $logger
|
private readonly LoggerInterface $logger,
|
||||||
|
#[Autowire(service: 'service_container')]
|
||||||
|
private ContainerInterface $locator
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -71,10 +75,9 @@ class ConnectorController extends AbstractController
|
|||||||
throw new BadRequestHttpException('Provider not found');
|
throw new BadRequestHttpException('Provider not found');
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @var AbstractProvider $connectorProviderClass */
|
/** @var AbstractProvider $providerClient */
|
||||||
$connectorProviderClass = $provider->getConnectorProvider();
|
$providerClient = $this->locator->get($provider->getConnectorProvider());
|
||||||
|
$authData = $providerClient->verifyAuthData($connector->getAuthData());
|
||||||
$authData = $connectorProviderClass::verifyAuthData($connector->getAuthData(), $client);
|
|
||||||
$connector->setAuthData($authData);
|
$connector->setAuthData($authData);
|
||||||
|
|
||||||
$this->logger->info('User {username} authentication data with the {provider} provider has been validated.', [
|
$this->logger->info('User {username} authentication data with the {provider} provider has been validated.', [
|
||||||
|
|||||||
@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
namespace App\Controller;
|
namespace App\Controller;
|
||||||
|
|
||||||
use App\Config\Provider\AbstractProvider;
|
|
||||||
use App\Entity\Connector;
|
use App\Entity\Connector;
|
||||||
use App\Entity\Domain;
|
use App\Entity\Domain;
|
||||||
use App\Entity\DomainEntity;
|
use App\Entity\DomainEntity;
|
||||||
@ -12,6 +11,7 @@ use App\Entity\WatchList;
|
|||||||
use App\Notifier\TestChatNotification;
|
use App\Notifier\TestChatNotification;
|
||||||
use App\Repository\WatchListRepository;
|
use App\Repository\WatchListRepository;
|
||||||
use App\Service\ChatNotificationService;
|
use App\Service\ChatNotificationService;
|
||||||
|
use App\Service\Connector\AbstractProvider;
|
||||||
use Doctrine\Common\Collections\Collection;
|
use Doctrine\Common\Collections\Collection;
|
||||||
use Doctrine\ORM\EntityManagerInterface;
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
use Doctrine\ORM\Exception\ORMException;
|
use Doctrine\ORM\Exception\ORMException;
|
||||||
@ -27,21 +27,20 @@ use Eluceo\iCal\Domain\ValueObject\Timestamp;
|
|||||||
use Eluceo\iCal\Presentation\Component\Property;
|
use Eluceo\iCal\Presentation\Component\Property;
|
||||||
use Eluceo\iCal\Presentation\Component\Property\Value\TextValue;
|
use Eluceo\iCal\Presentation\Component\Property\Value\TextValue;
|
||||||
use Eluceo\iCal\Presentation\Factory\CalendarFactory;
|
use Eluceo\iCal\Presentation\Factory\CalendarFactory;
|
||||||
use Psr\Cache\CacheItemPoolInterface;
|
|
||||||
use Psr\Log\LoggerInterface;
|
use Psr\Log\LoggerInterface;
|
||||||
use Sabre\VObject\EofException;
|
use Sabre\VObject\EofException;
|
||||||
use Sabre\VObject\InvalidDataException;
|
use Sabre\VObject\InvalidDataException;
|
||||||
use Sabre\VObject\ParseException;
|
use Sabre\VObject\ParseException;
|
||||||
use Sabre\VObject\Reader;
|
use Sabre\VObject\Reader;
|
||||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||||
|
use Symfony\Component\DependencyInjection\Attribute\Autowire;
|
||||||
|
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||||
use Symfony\Component\HttpFoundation\Request;
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
use Symfony\Component\HttpFoundation\Response;
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
|
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
|
||||||
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
|
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
|
||||||
use Symfony\Component\HttpKernel\KernelInterface;
|
|
||||||
use Symfony\Component\Routing\Attribute\Route;
|
use Symfony\Component\Routing\Attribute\Route;
|
||||||
use Symfony\Component\Serializer\SerializerInterface;
|
use Symfony\Component\Serializer\SerializerInterface;
|
||||||
use Symfony\Contracts\HttpClient\HttpClientInterface;
|
|
||||||
|
|
||||||
class WatchListController extends AbstractController
|
class WatchListController extends AbstractController
|
||||||
{
|
{
|
||||||
@ -50,10 +49,9 @@ class WatchListController extends AbstractController
|
|||||||
private readonly EntityManagerInterface $em,
|
private readonly EntityManagerInterface $em,
|
||||||
private readonly WatchListRepository $watchListRepository,
|
private readonly WatchListRepository $watchListRepository,
|
||||||
private readonly LoggerInterface $logger,
|
private readonly LoggerInterface $logger,
|
||||||
private readonly HttpClientInterface $httpClient,
|
private readonly ChatNotificationService $chatNotificationService,
|
||||||
private readonly CacheItemPoolInterface $cacheItemPool,
|
#[Autowire(service: 'service_container')]
|
||||||
private readonly KernelInterface $kernel,
|
private ContainerInterface $locator
|
||||||
private readonly ChatNotificationService $chatNotificationService
|
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -182,9 +180,9 @@ class WatchListController extends AbstractController
|
|||||||
|
|
||||||
$connectorProviderClass = $connector->getProvider()->getConnectorProvider();
|
$connectorProviderClass = $connector->getProvider()->getConnectorProvider();
|
||||||
/** @var AbstractProvider $connectorProvider */
|
/** @var AbstractProvider $connectorProvider */
|
||||||
$connectorProvider = new $connectorProviderClass($connector->getAuthData(), $this->httpClient, $this->cacheItemPool, $this->kernel);
|
$connectorProvider = $this->locator->get($connectorProviderClass);
|
||||||
|
|
||||||
$connectorProvider::verifyAuthData($connector->getAuthData(), $this->httpClient); // We want to check if the tokens are OK
|
$connectorProvider->authenticate($connector->getAuthData());
|
||||||
$supported = $connectorProvider->isSupported(...$watchList->getDomains()->toArray());
|
$supported = $connectorProvider->isSupported(...$watchList->getDomains()->toArray());
|
||||||
|
|
||||||
if (!$supported) {
|
if (!$supported) {
|
||||||
|
|||||||
@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
namespace App\MessageHandler;
|
namespace App\MessageHandler;
|
||||||
|
|
||||||
use App\Config\Provider\AbstractProvider;
|
|
||||||
use App\Entity\Domain;
|
use App\Entity\Domain;
|
||||||
use App\Entity\WatchList;
|
use App\Entity\WatchList;
|
||||||
use App\Message\OrderDomain;
|
use App\Message\OrderDomain;
|
||||||
@ -11,16 +10,17 @@ 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\StatService;
|
use App\Service\StatService;
|
||||||
use Psr\Cache\CacheItemPoolInterface;
|
|
||||||
use Psr\Log\LoggerInterface;
|
use Psr\Log\LoggerInterface;
|
||||||
|
use Symfony\Component\DependencyInjection\Attribute\Autowire;
|
||||||
|
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||||
use Symfony\Component\HttpKernel\KernelInterface;
|
use Symfony\Component\HttpKernel\KernelInterface;
|
||||||
use Symfony\Component\Mailer\Exception\TransportExceptionInterface;
|
use Symfony\Component\Mailer\Exception\TransportExceptionInterface;
|
||||||
use Symfony\Component\Mailer\MailerInterface;
|
use Symfony\Component\Mailer\MailerInterface;
|
||||||
use Symfony\Component\Messenger\Attribute\AsMessageHandler;
|
use Symfony\Component\Messenger\Attribute\AsMessageHandler;
|
||||||
use Symfony\Component\Mime\Address;
|
use Symfony\Component\Mime\Address;
|
||||||
use Symfony\Component\Notifier\Recipient\Recipient;
|
use Symfony\Component\Notifier\Recipient\Recipient;
|
||||||
use Symfony\Contracts\HttpClient\HttpClientInterface;
|
|
||||||
|
|
||||||
#[AsMessageHandler]
|
#[AsMessageHandler]
|
||||||
final readonly class OrderDomainHandler
|
final readonly class OrderDomainHandler
|
||||||
@ -33,12 +33,12 @@ final readonly class OrderDomainHandler
|
|||||||
private WatchListRepository $watchListRepository,
|
private WatchListRepository $watchListRepository,
|
||||||
private DomainRepository $domainRepository,
|
private DomainRepository $domainRepository,
|
||||||
private KernelInterface $kernel,
|
private KernelInterface $kernel,
|
||||||
private HttpClientInterface $client,
|
|
||||||
private CacheItemPoolInterface $cacheItemPool,
|
|
||||||
private MailerInterface $mailer,
|
private MailerInterface $mailer,
|
||||||
private LoggerInterface $logger,
|
private LoggerInterface $logger,
|
||||||
private StatService $statService,
|
private StatService $statService,
|
||||||
private ChatNotificationService $chatNotificationService
|
private ChatNotificationService $chatNotificationService,
|
||||||
|
#[Autowire(service: 'service_container')]
|
||||||
|
private ContainerInterface $locator
|
||||||
) {
|
) {
|
||||||
$this->sender = new Address($mailerSenderEmail, $mailerSenderName);
|
$this->sender = new Address($mailerSenderEmail, $mailerSenderName);
|
||||||
}
|
}
|
||||||
@ -72,7 +72,8 @@ final readonly class OrderDomainHandler
|
|||||||
$connectorProviderClass = $provider->getConnectorProvider();
|
$connectorProviderClass = $provider->getConnectorProvider();
|
||||||
|
|
||||||
/** @var AbstractProvider $connectorProvider */
|
/** @var AbstractProvider $connectorProvider */
|
||||||
$connectorProvider = new $connectorProviderClass($connector->getAuthData(), $this->client, $this->cacheItemPool, $this->kernel);
|
$connectorProvider = $this->locator->get($connectorProviderClass);
|
||||||
|
$connectorProvider->authenticate($connector->getAuthData());
|
||||||
|
|
||||||
$connectorProvider->orderDomain($domain, $this->kernel->isDebug());
|
$connectorProvider->orderDomain($domain, $this->kernel->isDebug());
|
||||||
$this->statService->incrementStat('stats.domain.purchased');
|
$this->statService->incrementStat('stats.domain.purchased');
|
||||||
|
|||||||
@ -1,24 +1,38 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace App\Config\Provider;
|
namespace App\Service\Connector;
|
||||||
|
|
||||||
use App\Entity\Domain;
|
use App\Entity\Domain;
|
||||||
use Psr\Cache\CacheItemInterface;
|
use Psr\Cache\CacheItemInterface;
|
||||||
use Psr\Cache\CacheItemPoolInterface;
|
use Psr\Cache\CacheItemPoolInterface;
|
||||||
use Symfony\Component\HttpKernel\KernelInterface;
|
|
||||||
use Symfony\Contracts\HttpClient\HttpClientInterface;
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The typical flow of a provider will go as follows:
|
||||||
|
*
|
||||||
|
* MyProvider $provider; // gotten from DI
|
||||||
|
* $provider->authenticate($authData);
|
||||||
|
* $provider->orderDomain($domain, $dryRun);
|
||||||
|
*/
|
||||||
abstract class AbstractProvider
|
abstract class AbstractProvider
|
||||||
{
|
{
|
||||||
|
protected array $authData;
|
||||||
|
|
||||||
public function __construct(
|
public function __construct(
|
||||||
protected array $authData,
|
protected CacheItemPoolInterface $cacheItemPool
|
||||||
protected HttpClientInterface $client,
|
|
||||||
protected CacheItemPoolInterface $cacheItemPool,
|
|
||||||
protected KernelInterface $kernel
|
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract public static function verifyAuthData(array $authData, HttpClientInterface $client): array;
|
/**
|
||||||
|
* @param array $authData raw authentication data as supplied by the user
|
||||||
|
*
|
||||||
|
* @return array a cleaned up version of the authentication data
|
||||||
|
*/
|
||||||
|
abstract public function verifyAuthData(array $authData): array;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws \Exception when the registrar denies the authentication
|
||||||
|
*/
|
||||||
|
abstract public function assertAuthentication(): void; // TODO use dedicated exception type
|
||||||
|
|
||||||
abstract public function orderDomain(Domain $domain, bool $dryRun): void;
|
abstract public function orderDomain(Domain $domain, bool $dryRun): void;
|
||||||
|
|
||||||
@ -55,6 +69,15 @@ abstract class AbstractProvider
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws \Exception
|
||||||
|
*/
|
||||||
|
public function authenticate(array $authData): void
|
||||||
|
{
|
||||||
|
$this->authData = $this->verifyAuthData($authData);
|
||||||
|
$this->assertAuthentication();
|
||||||
|
}
|
||||||
|
|
||||||
abstract protected function getCachedTldList(): CacheItemInterface;
|
abstract protected function getCachedTldList(): CacheItemInterface;
|
||||||
|
|
||||||
abstract protected function getSupportedTldList(): array;
|
abstract protected function getSupportedTldList(): array;
|
||||||
@ -1,9 +1,10 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace App\Config\Provider;
|
namespace App\Service\Connector;
|
||||||
|
|
||||||
use App\Entity\Domain;
|
use App\Entity\Domain;
|
||||||
use Psr\Cache\CacheItemInterface;
|
use Psr\Cache\CacheItemInterface;
|
||||||
|
use Psr\Cache\CacheItemPoolInterface;
|
||||||
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\BadRequestHttpException;
|
||||||
@ -19,6 +20,11 @@ class GandiProvider extends AbstractProvider
|
|||||||
{
|
{
|
||||||
private const BASE_URL = 'https://api.gandi.net';
|
private const BASE_URL = 'https://api.gandi.net';
|
||||||
|
|
||||||
|
public function __construct(CacheItemPoolInterface $cacheItemPool, private HttpClientInterface $client)
|
||||||
|
{
|
||||||
|
parent::__construct($cacheItemPool);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Order a domain name with the Gandi API.
|
* Order a domain name with the Gandi API.
|
||||||
*
|
*
|
||||||
@ -37,17 +43,15 @@ class GandiProvider extends AbstractProvider
|
|||||||
throw new \InvalidArgumentException('Domain name cannot be null');
|
throw new \InvalidArgumentException('Domain name cannot be null');
|
||||||
}
|
}
|
||||||
|
|
||||||
$authData = self::verifyAuthData($this->authData, $this->client);
|
|
||||||
|
|
||||||
$user = $this->client->request('GET', '/v5/organization/user-info', (new HttpOptions())
|
$user = $this->client->request('GET', '/v5/organization/user-info', (new HttpOptions())
|
||||||
->setAuthBearer($authData['token'])
|
->setAuthBearer($this->authData['token'])
|
||||||
->setHeader('Accept', 'application/json')
|
->setHeader('Accept', 'application/json')
|
||||||
->setBaseUri(self::BASE_URL)
|
->setBaseUri(self::BASE_URL)
|
||||||
->toArray()
|
->toArray()
|
||||||
)->toArray();
|
)->toArray();
|
||||||
|
|
||||||
$httpOptions = (new HttpOptions())
|
$httpOptions = (new HttpOptions())
|
||||||
->setAuthBearer($authData['token'])
|
->setAuthBearer($this->authData['token'])
|
||||||
->setHeader('Accept', 'application/json')
|
->setHeader('Accept', 'application/json')
|
||||||
->setBaseUri(self::BASE_URL)
|
->setBaseUri(self::BASE_URL)
|
||||||
->setHeader('Dry-Run', $dryRun ? '1' : '0')
|
->setHeader('Dry-Run', $dryRun ? '1' : '0')
|
||||||
@ -68,9 +72,9 @@ class GandiProvider extends AbstractProvider
|
|||||||
'tld_period' => 'golive',
|
'tld_period' => 'golive',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
if (array_key_exists('sharingId', $authData)) {
|
if (array_key_exists('sharingId', $this->authData)) {
|
||||||
$httpOptions->setQuery([
|
$httpOptions->setQuery([
|
||||||
'sharing_id' => $authData['sharingId'],
|
'sharing_id' => $this->authData['sharingId'],
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -78,14 +82,11 @@ 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 HttpException($res->toArray()['message']);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public function verifyAuthData(array $authData): array
|
||||||
* @throws TransportExceptionInterface
|
|
||||||
*/
|
|
||||||
public static function verifyAuthData(array $authData, HttpClientInterface $client): array
|
|
||||||
{
|
{
|
||||||
$token = $authData['token'];
|
$token = $authData['token'];
|
||||||
|
|
||||||
@ -105,17 +106,6 @@ class GandiProvider extends AbstractProvider
|
|||||||
throw new HttpException(451, 'The user has not given explicit consent');
|
throw new HttpException(451, 'The user has not given explicit consent');
|
||||||
}
|
}
|
||||||
|
|
||||||
$response = $client->request('GET', '/v5/organization/user-info', (new HttpOptions())
|
|
||||||
->setAuthBearer($token)
|
|
||||||
->setHeader('Accept', 'application/json')
|
|
||||||
->setBaseUri(self::BASE_URL)
|
|
||||||
->toArray()
|
|
||||||
);
|
|
||||||
|
|
||||||
if (Response::HTTP_OK !== $response->getStatusCode()) {
|
|
||||||
throw new BadRequestHttpException('The status of these credentials is not valid');
|
|
||||||
}
|
|
||||||
|
|
||||||
$authDataReturned = [
|
$authDataReturned = [
|
||||||
'token' => $token,
|
'token' => $token,
|
||||||
'acceptConditions' => $acceptConditions,
|
'acceptConditions' => $acceptConditions,
|
||||||
@ -130,6 +120,23 @@ class GandiProvider extends AbstractProvider
|
|||||||
return $authDataReturned;
|
return $authDataReturned;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws TransportExceptionInterface
|
||||||
|
*/
|
||||||
|
public function assertAuthentication(): void
|
||||||
|
{
|
||||||
|
$response = $this->client->request('GET', '/v5/organization/user-info', (new HttpOptions())
|
||||||
|
->setAuthBearer($this->authData['token'])
|
||||||
|
->setHeader('Accept', 'application/json')
|
||||||
|
->setBaseUri(self::BASE_URL)
|
||||||
|
->toArray()
|
||||||
|
);
|
||||||
|
|
||||||
|
if (Response::HTTP_OK !== $response->getStatusCode()) {
|
||||||
|
throw new BadRequestHttpException('The status of these credentials is not valid');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @throws TransportExceptionInterface
|
* @throws TransportExceptionInterface
|
||||||
* @throws ServerExceptionInterface
|
* @throws ServerExceptionInterface
|
||||||
@ -139,10 +146,8 @@ class GandiProvider extends AbstractProvider
|
|||||||
*/
|
*/
|
||||||
protected function getSupportedTldList(): array
|
protected function getSupportedTldList(): array
|
||||||
{
|
{
|
||||||
$authData = self::verifyAuthData($this->authData, $this->client);
|
|
||||||
|
|
||||||
$response = $this->client->request('GET', '/v5/domain/tlds', (new HttpOptions())
|
$response = $this->client->request('GET', '/v5/domain/tlds', (new HttpOptions())
|
||||||
->setAuthBearer($authData['token'])
|
->setAuthBearer($this->authData['token'])
|
||||||
->setHeader('Accept', 'application/json')
|
->setHeader('Accept', 'application/json')
|
||||||
->setBaseUri(self::BASE_URL)
|
->setBaseUri(self::BASE_URL)
|
||||||
->toArray())->toArray();
|
->toArray())->toArray();
|
||||||
148
src/Service/Connector/NamecheapProvider.php
Normal file
148
src/Service/Connector/NamecheapProvider.php
Normal file
@ -0,0 +1,148 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Service\Connector;
|
||||||
|
|
||||||
|
use App\Entity\Domain;
|
||||||
|
use Exception;
|
||||||
|
use Psr\Cache\CacheItemInterface;
|
||||||
|
use Psr\Cache\CacheItemPoolInterface;
|
||||||
|
use Psr\Cache\InvalidArgumentException;
|
||||||
|
use Symfony\Component\DependencyInjection\Attribute\Autoconfigure;
|
||||||
|
use Symfony\Contracts\HttpClient\Exception\ClientExceptionInterface;
|
||||||
|
use Symfony\Contracts\HttpClient\Exception\RedirectionExceptionInterface;
|
||||||
|
use Symfony\Contracts\HttpClient\Exception\ServerExceptionInterface;
|
||||||
|
use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface;
|
||||||
|
use Symfony\Contracts\HttpClient\HttpClientInterface;
|
||||||
|
|
||||||
|
#[Autoconfigure(public: true)]
|
||||||
|
class NamecheapProvider extends AbstractProvider
|
||||||
|
{
|
||||||
|
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 HttpClientInterface $client, private readonly string $outgoingIp)
|
||||||
|
{
|
||||||
|
parent::__construct($cacheItemPool);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws \Exception
|
||||||
|
* @throws TransportExceptionInterface
|
||||||
|
*/
|
||||||
|
public function orderDomain(Domain $domain, $dryRun): void
|
||||||
|
{
|
||||||
|
$addressesRes = $this->call('namecheap.users.address.getList', [], $dryRun);
|
||||||
|
$addresses = $addressesRes->AddressGetListResult->List;
|
||||||
|
|
||||||
|
if (count($addresses) < 1) {
|
||||||
|
throw new \Exception('Namecheap account requires at least one address to purchase a domain');
|
||||||
|
}
|
||||||
|
|
||||||
|
$addressId = (string) $addresses->attributes()['AddressId'];
|
||||||
|
$address = (array) $this->call('namecheap.users.address.getinfo', ['AddressId' => $addressId], $dryRun)->GetAddressInfoResult;
|
||||||
|
|
||||||
|
if (empty($address['PostalCode'])) {
|
||||||
|
$address['PostalCode'] = $address['Zip'];
|
||||||
|
}
|
||||||
|
|
||||||
|
$domainAddresses = [];
|
||||||
|
|
||||||
|
self::mergePrefixKeys('Registrant', $address, $domainAddresses);
|
||||||
|
self::mergePrefixKeys('Tech', $address, $domainAddresses);
|
||||||
|
self::mergePrefixKeys('Admin', $address, $domainAddresses);
|
||||||
|
self::mergePrefixKeys('AuxBilling', $address, $domainAddresses);
|
||||||
|
|
||||||
|
$this->call('namecheap.domains.create', array_merge([
|
||||||
|
'DomainName' => $domain->getLdhName(),
|
||||||
|
'Years' => 1,
|
||||||
|
'AddFreeWhoisguard' => 'yes',
|
||||||
|
'WGEnabled' => 'yes',
|
||||||
|
], $domainAddresses), $dryRun);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function mergePrefixKeys(string $prefix, array|object $src, array &$dest): void
|
||||||
|
{
|
||||||
|
foreach ($src as $key => $value) {
|
||||||
|
$dest[$prefix.$key] = $value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws TransportExceptionInterface
|
||||||
|
* @throws ServerExceptionInterface
|
||||||
|
* @throws RedirectionExceptionInterface
|
||||||
|
* @throws ClientExceptionInterface
|
||||||
|
* @throws \Exception
|
||||||
|
*/
|
||||||
|
private function call(string $command, array $parameters = [], bool $dryRun = true): object
|
||||||
|
{
|
||||||
|
$actualParams = array_merge([
|
||||||
|
'Command' => $command,
|
||||||
|
'UserName' => $this->authData['ApiUser'],
|
||||||
|
'ApiUser' => $this->authData['ApiUser'],
|
||||||
|
'ApiKey' => $this->authData['ApiKey'],
|
||||||
|
'ClientIp' => $this->outgoingIp,
|
||||||
|
], $parameters);
|
||||||
|
|
||||||
|
$response = $this->client->request('POST', $dryRun ? self::SANDBOX_BASE_URL : self::BASE_URL, [
|
||||||
|
'query' => $actualParams,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$data = new \SimpleXMLElement($response->getContent());
|
||||||
|
|
||||||
|
if ($data->Errors->Error) {
|
||||||
|
throw new \Exception($data->Errors->Error); // FIXME better exception type
|
||||||
|
}
|
||||||
|
|
||||||
|
return $data->CommandResponse;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function verifyAuthData(array $authData): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'ApiUser' => $authData['ApiUser'],
|
||||||
|
'ApiKey' => $authData['ApiKey'],
|
||||||
|
'acceptConditions' => $authData['acceptConditions'],
|
||||||
|
'ownerLegalAge' => $authData['ownerLegalAge'],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws TransportExceptionInterface
|
||||||
|
* @throws ServerExceptionInterface
|
||||||
|
* @throws RedirectionExceptionInterface
|
||||||
|
* @throws ClientExceptionInterface
|
||||||
|
*/
|
||||||
|
public function assertAuthentication(): void
|
||||||
|
{
|
||||||
|
$this->call('namecheap.domains.gettldlist', [], false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws InvalidArgumentException
|
||||||
|
*/
|
||||||
|
protected function getCachedTldList(): CacheItemInterface
|
||||||
|
{
|
||||||
|
return $this->cacheItemPool->getItem('app.provider.namecheap.supported-tld');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws TransportExceptionInterface
|
||||||
|
* @throws ServerExceptionInterface
|
||||||
|
* @throws RedirectionExceptionInterface
|
||||||
|
* @throws ClientExceptionInterface
|
||||||
|
*/
|
||||||
|
protected function getSupportedTldList(): array
|
||||||
|
{
|
||||||
|
$supported = [];
|
||||||
|
|
||||||
|
$tlds = $this->call('namecheap.domains.gettldlist', [], false)->Tlds->Tld;
|
||||||
|
|
||||||
|
for ($i = 0; $i < $tlds->count(); ++$i) {
|
||||||
|
$supported[] = (string) $tlds[$i]['Name'];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $supported;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,16 +1,16 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace App\Config\Provider;
|
namespace App\Service\Connector;
|
||||||
|
|
||||||
use App\Entity\Domain;
|
use App\Entity\Domain;
|
||||||
use GuzzleHttp\Exception\ClientException;
|
use GuzzleHttp\Exception\ClientException;
|
||||||
use Ovh\Api;
|
use Ovh\Api;
|
||||||
use Ovh\Exceptions\InvalidParameterException;
|
use Ovh\Exceptions\InvalidParameterException;
|
||||||
use Psr\Cache\CacheItemInterface;
|
use Psr\Cache\CacheItemInterface;
|
||||||
|
use Psr\Cache\CacheItemPoolInterface;
|
||||||
use Psr\Cache\InvalidArgumentException;
|
use Psr\Cache\InvalidArgumentException;
|
||||||
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
|
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
|
||||||
use Symfony\Component\HttpKernel\Exception\HttpException;
|
use Symfony\Component\HttpKernel\Exception\HttpException;
|
||||||
use Symfony\Contracts\HttpClient\HttpClientInterface;
|
|
||||||
|
|
||||||
class OvhProvider extends AbstractProvider
|
class OvhProvider extends AbstractProvider
|
||||||
{
|
{
|
||||||
@ -41,6 +41,11 @@ class OvhProvider extends AbstractProvider
|
|||||||
],
|
],
|
||||||
];
|
];
|
||||||
|
|
||||||
|
public function __construct(CacheItemPoolInterface $cacheItemPool)
|
||||||
|
{
|
||||||
|
parent::__construct($cacheItemPool);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Order a domain name with the OVH API.
|
* Order a domain name with the OVH API.
|
||||||
*
|
*
|
||||||
@ -57,21 +62,19 @@ class OvhProvider extends AbstractProvider
|
|||||||
throw new \InvalidArgumentException('Domain name cannot be null');
|
throw new \InvalidArgumentException('Domain name cannot be null');
|
||||||
}
|
}
|
||||||
|
|
||||||
$authData = self::verifyAuthData($this->authData, $this->client);
|
$acceptConditions = $this->authData['acceptConditions'];
|
||||||
|
$ownerLegalAge = $this->authData['ownerLegalAge'];
|
||||||
$acceptConditions = $authData['acceptConditions'];
|
$waiveRetractationPeriod = $this->authData['waiveRetractationPeriod'];
|
||||||
$ownerLegalAge = $authData['ownerLegalAge'];
|
|
||||||
$waiveRetractationPeriod = $authData['waiveRetractationPeriod'];
|
|
||||||
|
|
||||||
$conn = new Api(
|
$conn = new Api(
|
||||||
$authData['appKey'],
|
$this->authData['appKey'],
|
||||||
$authData['appSecret'],
|
$this->authData['appSecret'],
|
||||||
$authData['apiEndpoint'],
|
$this->authData['apiEndpoint'],
|
||||||
$authData['consumerKey']
|
$this->authData['consumerKey']
|
||||||
);
|
);
|
||||||
|
|
||||||
$cart = $conn->post('/order/cart', [
|
$cart = $conn->post('/order/cart', [
|
||||||
'ovhSubsidiary' => $authData['ovhSubsidiary'],
|
'ovhSubsidiary' => $this->authData['ovhSubsidiary'],
|
||||||
'description' => 'Domain Watchdog',
|
'description' => 'Domain Watchdog',
|
||||||
]);
|
]);
|
||||||
$cartId = $cart['cartId'];
|
$cartId = $cart['cartId'];
|
||||||
@ -81,8 +84,8 @@ class OvhProvider extends AbstractProvider
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
$pricingModes = ['create-default'];
|
$pricingModes = ['create-default'];
|
||||||
if ('create-default' !== $authData['pricingMode']) {
|
if ('create-default' !== $this->authData['pricingMode']) {
|
||||||
$pricingModes[] = $authData['pricingMode'];
|
$pricingModes[] = $this->authData['pricingMode'];
|
||||||
}
|
}
|
||||||
|
|
||||||
$offer = array_filter($offers, fn ($offer) => 'create' === $offer['action']
|
$offer = array_filter($offers, fn ($offer) => 'create' === $offer['action']
|
||||||
@ -131,7 +134,7 @@ class OvhProvider extends AbstractProvider
|
|||||||
/**
|
/**
|
||||||
* @throws \Exception
|
* @throws \Exception
|
||||||
*/
|
*/
|
||||||
public static function verifyAuthData(array $authData, HttpClientInterface $client): array
|
public function verifyAuthData(array $authData): array
|
||||||
{
|
{
|
||||||
$appKey = $authData['appKey'];
|
$appKey = $authData['appKey'];
|
||||||
$appSecret = $authData['appSecret'];
|
$appSecret = $authData['appSecret'];
|
||||||
@ -160,11 +163,26 @@ class OvhProvider extends AbstractProvider
|
|||||||
throw new HttpException(451, 'The user has not given explicit consent');
|
throw new HttpException(451, 'The user has not given explicit consent');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
'appKey' => $appKey,
|
||||||
|
'appSecret' => $appSecret,
|
||||||
|
'apiEndpoint' => $apiEndpoint,
|
||||||
|
'consumerKey' => $consumerKey,
|
||||||
|
'ovhSubsidiary' => $ovhSubsidiary,
|
||||||
|
'pricingMode' => $pricingMode,
|
||||||
|
'acceptConditions' => $acceptConditions,
|
||||||
|
'ownerLegalAge' => $ownerLegalAge,
|
||||||
|
'waiveRetractationPeriod' => $waiveRetractationPeriod,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function assertAuthentication(): void
|
||||||
|
{
|
||||||
$conn = new Api(
|
$conn = new Api(
|
||||||
$appKey,
|
$this->authData['appKey'],
|
||||||
$appSecret,
|
$this->authData['appSecret'],
|
||||||
$apiEndpoint,
|
$this->authData['apiEndpoint'],
|
||||||
$consumerKey
|
$this->authData['consumerKey'],
|
||||||
);
|
);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -197,18 +215,6 @@ class OvhProvider extends AbstractProvider
|
|||||||
throw new BadRequestHttpException('This Connector does not have enough permissions on the Provider API. Please recreate this Connector.');
|
throw new BadRequestHttpException('This Connector does not have enough permissions on the Provider API. Please recreate this Connector.');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return [
|
|
||||||
'appKey' => $appKey,
|
|
||||||
'appSecret' => $appSecret,
|
|
||||||
'apiEndpoint' => $apiEndpoint,
|
|
||||||
'consumerKey' => $consumerKey,
|
|
||||||
'ovhSubsidiary' => $ovhSubsidiary,
|
|
||||||
'pricingMode' => $pricingMode,
|
|
||||||
'acceptConditions' => $acceptConditions,
|
|
||||||
'ownerLegalAge' => $ownerLegalAge,
|
|
||||||
'waiveRetractationPeriod' => $waiveRetractationPeriod,
|
|
||||||
];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -218,17 +224,15 @@ class OvhProvider extends AbstractProvider
|
|||||||
*/
|
*/
|
||||||
protected function getSupportedTldList(): array
|
protected function getSupportedTldList(): array
|
||||||
{
|
{
|
||||||
$authData = self::verifyAuthData($this->authData, $this->client);
|
|
||||||
|
|
||||||
$conn = new Api(
|
$conn = new Api(
|
||||||
$authData['appKey'],
|
$this->authData['appKey'],
|
||||||
$authData['appSecret'],
|
$this->authData['appSecret'],
|
||||||
$authData['apiEndpoint'],
|
$this->authData['apiEndpoint'],
|
||||||
$authData['consumerKey']
|
$this->authData['consumerKey']
|
||||||
);
|
);
|
||||||
|
|
||||||
return $conn->get('/domain/extensions', [
|
return $conn->get('/domain/extensions', [
|
||||||
'ovhSubsidiary' => $authData['ovhSubsidiary'],
|
'ovhSubsidiary' => $this->authData['ovhSubsidiary'],
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
Loading…
x
Reference in New Issue
Block a user