feat: implement OpenProvider

This commit is contained in:
Maël Gangloff 2025-10-27 23:36:48 +01:00
parent aff37f7a81
commit fee3f3af44
No known key found for this signature in database
GPG Key ID: 11FDC81C24A7F629
8 changed files with 150 additions and 22 deletions

View File

@ -53,7 +53,7 @@ export function ConnectorForm({form, onCreate}: { form: FormInstance, onCreate:
<h2>{t`Choose a registrar`}</h2>
<Row gutter={[16, 16]} justify='center'>
{Object.keys(providersConfig).map((provider: string) => (
<Col key={provider as ConnectorProvider} xs={24} sm={12} md={8} lg={6} xl={4}>
<Col key={provider as ConnectorProvider} xs={24} sm={12} md={8} lg={6} xl={3}>
<Card
hoverable
style={{textAlign: "center"}}

View File

@ -7,6 +7,7 @@ export enum ConnectorProvider {
AutoDNS = 'autodns',
Namecheap = 'namecheap',
'Name.com' = 'namecom',
OpenProvider = 'openprovider',
EPP = 'epp'
}

View File

@ -10,7 +10,7 @@ export default function NamecheapConnectorForm() {
label={t`Username`}
name={['authData', 'ApiUser']}
help={<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`}
{t`Retrieve an API key and whitelist this instance's IP address on Namecheap's website`}
</Typography.Link>}
>
<Input prefix={<UserOutlined/>} autoComplete='off'/>

View File

@ -0,0 +1,100 @@
import {Alert, Col, Form, Input, InputNumber, Row, Typography} from 'antd'
import React from 'react'
import {t} from 'ttag'
import {
DollarOutlined,
FieldTimeOutlined,
IdcardOutlined,
ShoppingOutlined,
SignatureOutlined,
ToolOutlined,
UserOutlined
} from "@ant-design/icons"
export default function OpenProviderConnectorForm() {
return (
<>
<Alert
message={t`This provider does not provide a list of supported TLD. Please double check if the domain you want to register is supported.`}
type='warning'
style={{marginBottom: '2em'}}
/>
<Form.Item
label={t`Token`}
name={['authData', 'token']}
help={<Typography.Link target='_blank' href='https://docs.openprovider.com/doc/all#tag/Auth'>
{t`Obtain an API key by following the provider's instructions`}
</Typography.Link>}
rules={[{required: true, message: t`Required`}]}
>
<Input prefix={<UserOutlined/>} autoComplete='off' required/>
</Form.Item>
<Form.Item label={t`NIC Handle`}>
<Row gutter={16}>
<Col span={4}>
<Form.Item
hasFeedback
required
rules={[{required: true, message: t`Required`}]}
name={['authData', 'ownerHandle']}>
<Input prefix={<SignatureOutlined/>} placeholder={t`Registrant`} required/>
</Form.Item>
</Col>
<Col span={4}>
<Form.Item hasFeedback
required
rules={[{required: true, message: t`Required`}]} name={['authData', 'adminHandle']}>
<Input prefix={<IdcardOutlined/>} placeholder={t`Administrative`}/>
</Form.Item>
</Col>
<Col span={4}>
<Form.Item hasFeedback
required
rules={[{required: true, message: t`Required`}]} name={['authData', 'techHandle']}>
<Input prefix={<ToolOutlined/>} placeholder={t`Technical`}/>
</Form.Item>
</Col>
<Col span={4}>
<Form.Item hasFeedback
required
rules={[{required: true, message: t`Required`}]}
name={['authData', 'billingHandle']}>
<Input prefix={<DollarOutlined/>} placeholder={t`Billing`}/>
</Form.Item>
</Col>
<Col span={4}>
<Form.Item name={['authData', 'resellerHandle']}>
<Input prefix={<ShoppingOutlined/>} placeholder={t`Reseller`}/>
</Form.Item>
</Col>
</Row>
</Form.Item>
<Form.Item
label={t`Registration period`}
initialValue={1}
hasFeedback
rules={[{
required: true,
message: t`Required`,
validator: (_, v: number) => v > 0 && v < 100 ? Promise.resolve() : Promise.reject()
}]}
name={['authData', 'period']}
>
<InputNumber prefix={<FieldTimeOutlined/>} required/>
</Form.Item>
<Form.Item
label={t`Nameserver group`}
name={['authData', 'nsGroup']}
help={<Typography.Link target='_blank'
href='https://cp.openprovider.eu/nameserver/nsgroup-overview.php'>
{t`Create an NS group and write the group name here`}
</Typography.Link>}
rules={[{required: true, message: t`Required`}]}
>
<Input prefix={<UserOutlined/>} autoComplete='off' required/>
</Form.Item>
</>
)
}

View File

@ -7,6 +7,7 @@ import NamecheapConnectorForm from "./forms/NamecheapConnectorForm"
import AutoDnsConnectorForm from "./forms/AutoDnsConnectorForm"
import NamecomConnectorForm from "./forms/NamecomConnectorForm"
import EppConnectorForm from "./forms/EppConnectorForm"
import OpenProviderConnectorForm from "./forms/OpenProviderConnectorForm"
export const formItemLayoutWithOutLabel = {
wrapperCol: {
@ -41,7 +42,11 @@ export const providersConfig: Record<ConnectorProvider, ProviderConfig> = {
tosLink: 'https://www.name.com/policies/',
form: NamecomConnectorForm
},
[ConnectorProvider.OpenProvider]: {
tosLink: 'https://www.openprovider.com/legal/terms-conditions',
form: OpenProviderConnectorForm
},
[ConnectorProvider.EPP]: {
form: EppConnectorForm
}
},
}

View File

@ -7,6 +7,7 @@ use App\Service\Provider\EppClientProvider;
use App\Service\Provider\GandiProvider;
use App\Service\Provider\NamecheapProvider;
use App\Service\Provider\NameComProvider;
use App\Service\Provider\OpenProviderProvider;
use App\Service\Provider\OvhProvider;
enum ConnectorProvider: string
@ -16,6 +17,7 @@ enum ConnectorProvider: string
case AUTODNS = 'autodns';
case NAMECHEAP = 'namecheap';
case NAMECOM = 'namecom';
case OPENPROVIDER = 'openprovider';
case EPP = 'epp';
public function getConnectorProvider(): string
@ -27,6 +29,7 @@ enum ConnectorProvider: string
ConnectorProvider::NAMECHEAP => NamecheapProvider::class,
ConnectorProvider::NAMECOM => NameComProvider::class,
ConnectorProvider::EPP => EppClientProvider::class,
ConnectorProvider::OPENPROVIDER => OpenProviderProvider::class,
};
}
}

View File

@ -27,8 +27,4 @@ final class OpenProviderProviderDto extends DefaultProviderDto
public int $period = 1;
public string $nsGroup;
#[Assert\Choice(['off', 'on', 'default'])]
#[Assert\NotBlank]
public string $autoRenew = 'default';
}

View File

@ -5,14 +5,13 @@ namespace App\Service\Provider;
use App\Dto\Connector\DefaultProviderDto;
use App\Dto\Connector\OpenProviderProviderDto;
use App\Entity\Domain;
use App\Service\Provider\AbstractProvider;
use App\Exception\Provider\DomainOrderFailedExeption;
use App\Exception\Provider\InvalidLoginException;
use Psr\Cache\CacheItemInterface;
use Psr\Cache\CacheItemPoolInterface;
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\HttpKernel\Exception\HttpException;
use Symfony\Component\Serializer\Normalizer\DenormalizerInterface;
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
use Symfony\Component\Validator\Validator\ValidatorInterface;
@ -28,7 +27,7 @@ class OpenProviderProvider extends AbstractProvider
/** @var OpenProviderProviderDto */
protected DefaultProviderDto $authData;
private const string BASE_URL = 'https://api.openprovider.eu/v1beta';
private const string BASE_URL = 'https://api.openprovider.eu';
public function __construct(
CacheItemPoolInterface $cacheItemPool,
@ -54,49 +53,73 @@ class OpenProviderProvider extends AbstractProvider
}
$payload = [
'accept_eap_fee' => 0,
'accept_premium_fee' => 0,
// additional_data
'admin_handle' => $this->authData->adminHandle,
// application_mode
// application_notice_id
// application_smd
// auth_code
'autorenew' => 'default',
'billing_handle' => $this->authData->billingHandle,
'owner_handle' => $this->authData->ownerHandle,
'tech_handle' => $this->authData->techHandle,
'comments' => 'Ordered with Domain Watchdog',
// dnssec_keys
'domain' => [
'name' => explode('.', $domain->getLdhName(), 2)[0],
'extension' => explode('.', $domain->getLdhName(), 2)[1],
],
'period' => '1',
// is_dnssec_enabled
// is_easy_dmarc_enabled
// is_private_whois_enabled
// is_sectigo_dns_enabled
// is_spamexperts_enabled
// name_servers
'ns_group' => $this->authData->nsGroup,
'autorenew' => 'default',
// ns_template_id
// ns_template_name
'owner_handle' => $this->authData->ownerHandle,
'period' => $this->authData->period,
// promo_code
// provider
'tech_handle' => $this->authData->techHandle,
// unit
// use_domicile
];
if (null !== $this->authData->resellerHandle) {
$payload['resellerHandle'] = $this->authData->resellerHandle;
}
$res = $this->client->request('POST', '/domain', (new HttpOptions())
if ($dryRun) {
return;
}
$res = $this->client->request('POST', '/v1beta/domain', (new HttpOptions())
->setAuthBearer($this->authData->token)
->setHeader('Accept', 'application/json')
->setBaseUri(self::BASE_URL)
->setJson($payload)->toArray());
if ((!$dryRun && Response::HTTP_ACCEPTED !== $res->getStatusCode())
|| ($dryRun && Response::HTTP_OK !== $res->getStatusCode())) {
throw new HttpException($res->toArray()['message']);
if (Response::HTTP_OK !== $res->getStatusCode()) {
throw new DomainOrderFailedExeption($res->toArray()['message']);
}
}
/**
* @throws TransportExceptionInterface
* @throws InvalidLoginException
*/
protected function assertAuthentication(): void
{
$response = $this->client->request('GET', '/customers', (new HttpOptions())
$response = $this->client->request('GET', '/v1beta/customers', (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');
throw new InvalidLoginException();
}
}