diff --git a/README.md b/README.md index d594f18..58ea540 100644 --- a/README.md +++ b/README.md @@ -54,6 +54,7 @@ The table below lists the supported API connector providers: | OVH | https://api.ovh.com | **Yes** | | GANDI | https://api.gandi.net/docs/domains/ | **Yes** | | NAMECHEAP | https://www.namecheap.com/support/api/methods/domains/create/ | **Yes** | +| AUTODNS | https://cloud.autodns.com/ | **Yes** | 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. diff --git a/assets/components/tracking/connector/ConnectorForm.tsx b/assets/components/tracking/connector/ConnectorForm.tsx index 953ff9a..af697e6 100644 --- a/assets/components/tracking/connector/ConnectorForm.tsx +++ b/assets/components/tracking/connector/ConnectorForm.tsx @@ -1,4 +1,4 @@ -import {Button, Checkbox, Form, FormInstance, Input, Popconfirm, Select, Space, Typography} from "antd"; +import {Alert, Button, Checkbox, Form, FormInstance, Input, Popconfirm, Select, Space, Typography} from "antd"; import React, {useState} from "react"; import {Connector, ConnectorProvider} from "../../../utils/api/connectors"; import {t} from "ttag"; @@ -132,6 +132,61 @@ export function ConnectorForm({form, onCreate}: { form: FormInstance, onCreate: } + { + provider === ConnectorProvider.AUTODNS && <> + +
+ {t`Attention: AutoDNS do not support 2-Factor Authentication on API Users for automated systems`}} + rules={[{required: true, message: t`Required`}]}> + + + + + + {t`The Contact ID for ownership of registered Domains. `}{t`You got from this page`}} + rules={[{required: true, message: t`Required`}]} + required={true}> + + + + {t`If you not sure, use the default value 4`}} + + required={false}> + + + + + {t`Owner confirms his consent of domain order jobs`} + + + + } { provider === ConnectorProvider.NAMECHEAP && <> { return {t`Retreive an API key and whitelist this instance's IP address on Namecheap's website`} + case ConnectorProvider.AUTODNS: + return + {t`Because of some limitations in API of AutoDNS, we suggest to create an dedicated user for API with limited rights.`} + default: return <> @@ -31,6 +35,8 @@ export const tosHyperlink = (provider?: string) => { return 'https://www.ovhcloud.com/fr/terms-and-conditions/contracts/' case ConnectorProvider.GANDI: return 'https://www.gandi.net/en/contracts/terms-of-service' + case ConnectorProvider.AUTODNS: + return 'https://www.internetx.com/agb/' default: return '' } diff --git a/src/Config/ConnectorProvider.php b/src/Config/ConnectorProvider.php index 42ea0b0..6051556 100644 --- a/src/Config/ConnectorProvider.php +++ b/src/Config/ConnectorProvider.php @@ -2,6 +2,7 @@ namespace App\Config; +use App\Service\Connector\AutodnsProvider; use App\Service\Connector\GandiProvider; use App\Service\Connector\NamecheapProvider; use App\Service\Connector\OvhProvider; @@ -10,6 +11,7 @@ enum ConnectorProvider: string { case OVH = 'ovh'; case GANDI = 'gandi'; + case AUTODNS = 'autodns'; case NAMECHEAP = 'namecheap'; public function getConnectorProvider(): string @@ -17,6 +19,7 @@ enum ConnectorProvider: string return match ($this) { ConnectorProvider::OVH => OvhProvider::class, ConnectorProvider::GANDI => GandiProvider::class, + ConnectorProvider::AUTODNS => AutodnsProvider::class, ConnectorProvider::NAMECHEAP => NamecheapProvider::class, }; } diff --git a/src/Service/Connector/AutodnsProvider.php b/src/Service/Connector/AutodnsProvider.php new file mode 100644 index 0000000..d97f94f --- /dev/null +++ b/src/Service/Connector/AutodnsProvider.php @@ -0,0 +1,254 @@ +getDeleted()) { + throw new \InvalidArgumentException('The domain name still appears in the WHOIS database'); + } + + $ldhName = $domain->getLdhName(); + if (!$ldhName) { + throw new \InvalidArgumentException('Domain name cannot be null'); + } + + if ($dryRun) { + return; + } + + $this->client->request( + 'POST', + '/v1/domain', + (new HttpOptions()) + ->setAuthBasic($this->authData['username'], $this->authData['password']) + ->setHeader('Accept', 'application/json') + ->setHeader('X-Domainrobot-Context', $this->authData['context']) + ->setBaseUri(self::BASE_URL) + ->setJson([ + 'name' => $ldhName, + 'ownerc' => [ + 'id' => $this->authData['contactid'], + ], + 'adminc' => [ + 'id' => $this->authData['contactid'], + ], + 'techc' => [ + 'id' => $this->authData['contactid'], + ], + 'confirmOrder' => $this->authData['ownerConfirm'], + 'nameServers' => [ + [ + 'name' => 'a.ns14.net', + ], + [ + 'name' => 'b.ns14.net', + ], + [ + 'name' => 'c.ns14.net', + ], + [ + 'name' => 'd.ns14.net', + ], + ], + ]) + ->toArray() + )->toArray(); + } + + /** + * @throws RedirectionExceptionInterface + * @throws DecodingExceptionInterface + * @throws ClientExceptionInterface + * @throws TransportExceptionInterface + * @throws ServerExceptionInterface + */ + public function registerZone(Domain $domain, bool $dryRun = false): void + { + $authData = $this->authData; + + $ldhName = $domain->getLdhName(); + + if ($dryRun) { + return; + } + + $zoneCheck = $this->client->request( + 'POST', + '/v1/zone/_search?keys=name', + (new HttpOptions()) + ->setAuthBasic($authData['username'], $authData['password']) + ->setHeader('Accept', 'application/json') + ->setHeader('X-Domainrobot-Context', $authData['context']) + ->setBaseUri(self::BASE_URL) + ->setJson([ + 'filters' => [ + [ + 'key' => 'name', + 'value' => $ldhName, + 'operator' => 'EQUAL', + ], + ], + ]) + ->toArray() + )->toArray(); + + $responseDataIsEmpty = empty($zoneCheck['data']); + + if ($responseDataIsEmpty) { + // The domain not yet exists in DNS Server, we create them + + $this->client->request( + 'POST', + '/v1/zone', + (new HttpOptions()) + ->setAuthBasic($authData['username'], $authData['password']) + ->setHeader('Accept', 'application/json') + ->setHeader('X-Domainrobot-Context', $authData['context']) + ->setBaseUri(self::BASE_URL) + ->setJson([ + 'origin' => $ldhName, + 'main' => [ + 'address' => $authData['dns_ip'], + ], + 'soa' => [ + 'refresh' => 3600, + 'retry' => 7200, + 'expire' => 604800, + 'ttl' => 600, + ], + 'action' => 'COMPLETE', + 'wwwInclude' => true, + 'nameServers' => [ + [ + 'name' => 'a.ns14.net', + ], + [ + 'name' => 'b.ns14.net', + ], + [ + 'name' => 'c.ns14.net', + ], + [ + 'name' => 'd.ns14.net', + ], + ], + ]) + ->toArray() + )->toArray(); + } + } + + public function verifyAuthData(array $authData): array + { + $username = $authData['username']; + $password = $authData['password']; + + $acceptConditions = $authData['acceptConditions']; + $ownerLegalAge = $authData['ownerLegalAge']; + $waiveRetractationPeriod = $authData['waiveRetractationPeriod']; + + if (empty($authData['context'])) { + $authData['context'] = 4; + } + + if ( + empty($username) || empty($password) + ) { + throw new BadRequestHttpException('Bad authData schema'); + } + + if ( + true !== $acceptConditions + || true !== $authData['ownerConfirm'] + || true !== $ownerLegalAge + || true !== $waiveRetractationPeriod + ) { + throw new HttpException(451, 'The user has not given explicit consent'); + } + + return [ + 'username' => $authData['username'], + 'password' => $authData['password'], + 'acceptConditions' => $authData['acceptConditions'], + 'ownerLegalAge' => $authData['ownerLegalAge'], + 'ownerConfirm' => $authData['ownerConfirm'], + 'waiveRetractationPeriod' => $authData['waiveRetractationPeriod'], + 'context' => $authData['context'], + ]; + } + + public function isSupported(Domain ...$domainList): bool + { + return true; + } + + protected function getSupportedTldList(): array + { + return []; + } + + /** + * @throws InvalidArgumentException + */ + protected function getCachedTldList(): CacheItemInterface + { + return $this->cacheItemPool->getItem('app.provider.autodns.supported-tld'); + } + + /** + * @throws TransportExceptionInterface + */ + public function assertAuthentication(): void + { + try { + $response = $this->client->request( + 'GET', + '/v1/hello', + (new HttpOptions()) + ->setAuthBasic($this->authData['username'], $this->authData['password']) + ->setHeader('Accept', 'application/json') + ->setHeader('X-Domainrobot-Context', $this->authData['context']) + ->setBaseUri(self::BASE_URL) + ->toArray() + ); + } catch (\Exception) { + throw new BadRequestHttpException('Invalid Login'); + } + + if (Response::HTTP_OK !== $response->getStatusCode()) { + throw new BadRequestHttpException('The status of these credentials is not valid'); + } + } +} diff --git a/src/Service/Connector/GandiProvider.php b/src/Service/Connector/GandiProvider.php index 35205aa..348c134 100644 --- a/src/Service/Connector/GandiProvider.php +++ b/src/Service/Connector/GandiProvider.php @@ -20,7 +20,7 @@ class GandiProvider extends AbstractProvider { private const BASE_URL = 'https://api.gandi.net'; - public function __construct(CacheItemPoolInterface $cacheItemPool, private HttpClientInterface $client) + public function __construct(CacheItemPoolInterface $cacheItemPool, private readonly HttpClientInterface $client) { parent::__construct($cacheItemPool); } diff --git a/translations/translations.pot b/translations/translations.pot index e3f252a..30df91e 100644 --- a/translations/translations.pot +++ b/translations/translations.pot @@ -42,9 +42,13 @@ msgstr "" #: assets/components/tracking/connector/ConnectorForm.tsx:84 #: assets/components/tracking/connector/ConnectorForm.tsx:92 #: assets/components/tracking/connector/ConnectorForm.tsx:122 -#: assets/components/tracking/connector/ConnectorForm.tsx:158 -#: assets/components/tracking/connector/ConnectorForm.tsx:172 -#: assets/components/tracking/connector/ConnectorForm.tsx:181 +#: assets/components/tracking/connector/ConnectorForm.tsx:146 +#: assets/components/tracking/connector/ConnectorForm.tsx:152 +#: assets/components/tracking/connector/ConnectorForm.tsx:162 +#: assets/components/tracking/connector/ConnectorForm.tsx:182 +#: assets/components/tracking/connector/ConnectorForm.tsx:213 +#: assets/components/tracking/connector/ConnectorForm.tsx:227 +#: assets/components/tracking/connector/ConnectorForm.tsx:236 #: assets/components/tracking/watchlist/WatchlistForm.tsx:115 #: assets/components/tracking/watchlist/WatchlistForm.tsx:212 msgid "Required" @@ -218,48 +222,96 @@ msgid "It indicates the organization that will pay for the ordered product" msgstr "" #: assets/components/tracking/connector/ConnectorForm.tsx:138 +msgid "" +"This provider does not provide a list of supported TLD. Please double check " +"if the domain you want to register is supported." +msgstr "" + +#: assets/components/tracking/connector/ConnectorForm.tsx:142 +msgid "AutoDNS Username" +msgstr "" + +#: assets/components/tracking/connector/ConnectorForm.tsx:145 +msgid "" +"Attention: AutoDNS do not support 2-Factor Authentication on API Users for " +"automated systems" +msgstr "" + +#: assets/components/tracking/connector/ConnectorForm.tsx:150 +msgid "AutoDNS Password" +msgstr "" + +#: assets/components/tracking/connector/ConnectorForm.tsx:157 +msgid "Domain Contact Handle ID" +msgstr "" + +#: assets/components/tracking/connector/ConnectorForm.tsx:160 +msgid "The Contact ID for ownership of registered Domains. " +msgstr "" + +#: assets/components/tracking/connector/ConnectorForm.tsx:161 +msgid "You got from this page" +msgstr "" + +#: assets/components/tracking/connector/ConnectorForm.tsx:168 +msgid "Context Value" +msgstr "" + +#: assets/components/tracking/connector/ConnectorForm.tsx:171 +msgid "If you not sure, use the default value 4" +msgstr "" + +#: assets/components/tracking/connector/ConnectorForm.tsx:179 +msgid "Owner confirmation" +msgstr "" + +#: assets/components/tracking/connector/ConnectorForm.tsx:185 +msgid "Owner confirms his consent of domain order jobs" +msgstr "" + +#: assets/components/tracking/connector/ConnectorForm.tsx:193 #: assets/pages/UserPage.tsx:18 msgid "Username" msgstr "" -#: assets/components/tracking/connector/ConnectorForm.tsx:144 +#: assets/components/tracking/connector/ConnectorForm.tsx:199 msgid "API key" msgstr "" -#: assets/components/tracking/connector/ConnectorForm.tsx:156 +#: assets/components/tracking/connector/ConnectorForm.tsx:211 msgid "API Terms of Service" msgstr "" -#: assets/components/tracking/connector/ConnectorForm.tsx:164 +#: assets/components/tracking/connector/ConnectorForm.tsx:219 msgid "" "I have read and accepted the conditions of use of the Provider API, " "accessible from this hyperlink" msgstr "" -#: assets/components/tracking/connector/ConnectorForm.tsx:170 +#: assets/components/tracking/connector/ConnectorForm.tsx:225 msgid "Legal age" msgstr "" -#: assets/components/tracking/connector/ConnectorForm.tsx:175 +#: assets/components/tracking/connector/ConnectorForm.tsx:230 msgid "I am of the minimum age required to consent to these conditions" msgstr "" -#: assets/components/tracking/connector/ConnectorForm.tsx:179 +#: assets/components/tracking/connector/ConnectorForm.tsx:234 msgid "Withdrawal period" msgstr "" -#: assets/components/tracking/connector/ConnectorForm.tsx:184 +#: assets/components/tracking/connector/ConnectorForm.tsx:239 msgid "" "I waive my right of withdrawal regarding the purchase of domain names via " "the Provider's API" msgstr "" -#: assets/components/tracking/connector/ConnectorForm.tsx:192 +#: assets/components/tracking/connector/ConnectorForm.tsx:247 #: assets/components/tracking/watchlist/WatchlistForm.tsx:252 msgid "Create" msgstr "" -#: assets/components/tracking/connector/ConnectorForm.tsx:195 +#: assets/components/tracking/connector/ConnectorForm.tsx:250 #: assets/components/tracking/watchlist/WatchlistForm.tsx:255 msgid "Reset" msgstr "" @@ -1047,6 +1099,12 @@ msgid "" "website" msgstr "" +#: assets/utils/providers/index.tsx:24 +msgid "" +"Because of some limitations in API of AutoDNS, we suggest to create an " +"dedicated user for API with limited rights." +msgstr "" + #: assets/utils/providers/ovh.tsx:5 msgid "Application key" msgstr ""