fix: corrections SonarQube - qualité code, accessibilité, complexité cognitive
Propriétés inutilisées supprimées :
- CheckDnsCommand : suppression de $urlGenerator (jamais lu, seulement injecté)
- PurgeEmailTrackingCommand : suppression de $repository (jamais lu, requêtes
via $em->createQueryBuilder directement), suppression import EmailTrackingRepository
Corrections PHPStan / types :
- SyncController : suppression $wh['status'] ?? 'created' redondant, accès direct
à $wh['status'] car le type retour inclut désormais status: string
- StripeWebhookService : PHPDoc createAllWebhooks corrigé de
list<array{type, url, id}> vers list<array{type, url, id, status, secret?}>
pour refléter les clés status et secret effectivement présentes
- DnsReportController : suppression ?? '' sur EXPECTED_MX[$domain] (clé toujours existante)
- CloudflareService : ajout @param array<string, mixed> $query sur request()
- CheckDnsCommand : suppression ?? '' sur EXPECTED_MX[$domain], ajout PHPDoc
@param list<array<string, mixed>> $cfRecords sur checkMailcow
Méthode manquante :
- DnsCheckService : ajout getDkimTxtRecord() qui parcourt les TXT records
et retourne le premier commençant par 'v=DKIM1', appelé dans checkDkim()
Code mort supprimé :
- MailcowService : suppression is_array($data) toujours vrai sur retour
de $response->toArray(false), retour direct
- DnsInfraHelper : suppression getFirstTxtValueRaw() identique à getFirstTxtValue(),
simplification de getActualDnsValue() qui n'appelle plus le fallback
Constantes pour littéraux dupliqués :
- DnsInfraHelper : ajout LABEL_AWS_SES, LABEL_MAILCOW, LABEL_MAILCOW_DNS,
NOT_FOUND, NOT_CONFIGURED — remplace les chaînes 'AWS SES' (10×),
'Non trouve' (4×), 'Non configure' (3×), 'Mailcow' et 'Mailcow DNS'
- Utilisation dans CheckDnsCommand (checkAwsSes, checkSesDomain, checkSesDkim,
checkSesMailFrom, checkSesBounce, checkMailcow)
Réduction complexité cognitive checkAwsSes (61 → ~10 par méthode) :
- Extraction checkSesDomain() : vérifie isDomainVerified, ajoute check + erreur/succès
- Extraction checkSesDkim() : vérifie getDkimStatus (enabled+verified),
parcourt les tokens DKIM CNAME avec enrichLastCheck
- Extraction checkSesMailFrom() : vérifie getMailFromStatus, MAIL FROM MX
(checkMxExists + getMxValues), MAIL FROM TXT (checkTxtContains + getTxtSpfValue)
- Extraction checkSesBounce() : vérifie getNotificationStatus (forwarding ou bounce_topic)
Accessibilité WCAG AA :
- app.scss : contraste sidebar-nav-item augmenté de rgba(255,255,255,0.6)
à rgba(255,255,255,0.75) pour ratio de contraste suffisant sur fond sombre
- tarification/index.html.twig : ajout for/id sur les 5 paires label/input
(title-{id}, priceHt-{id}, monthPrice-{id}, period-{id}, description-{id})
- membres.html.twig : ajout for/id sur les 15 checkboxes de groupes
(group-member, group-admin, group-esy-web, ..., group-esy-ndd),
remplacement du label titre par <span> (n'est pas associé à un contrôle)
- 2fa_google.html.twig : ajout for="trusted-device" et id="trusted-device"
sur le checkbox de confiance appareil
- tarif.html.twig : ajout <thead class="sr-only"> avec <th>Option</th><th>Tarif</th>
sur la table options Esy-Mail (table sans en-têtes)
Ansible :
- vault.yml : ajout discord_webhook pour le déploiement prod
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -19,6 +19,7 @@ cloudflare_key: cfut_xqEEvg5LDezheCI9rWsd4JdfflvLH5vjmeMp7QHO442dd83b
|
||||
mailcow_api_key: DF0E7E-0FD059-16226F-8ECFF1-E558B3
|
||||
docuseal_api: pgAU116mCFmeF7WQSezHqxtZW8V1fgo31u5d2FXoaKe
|
||||
docuseal_webhooks_secret: CRM_COSLAY
|
||||
discord_webhook: https://discord.com/api/webhooks/1419573620602044518/ikAdxWxsrrTqMTb5Gh_8ylcoJHlOnq7aJZvR5udoS_fCK56Jk3qpEnJHVKdD8fwuNJF3
|
||||
smime_private_key: |
|
||||
Bag Attributes
|
||||
localKeyID: 75 15 E3 C2 1D 7B 61 75 99 B9 22 D8 FD A4 19 AC 6B BE 1F 8F
|
||||
|
||||
@@ -228,7 +228,7 @@ body.glass-bg {
|
||||
letter-spacing: 0.08em;
|
||||
border-radius: var(--radius-sm);
|
||||
transition: all 0.2s ease;
|
||||
color: rgba(255, 255, 255, 0.6);
|
||||
color: rgba(255, 255, 255, 0.75);
|
||||
|
||||
&:hover {
|
||||
background: rgba(255, 255, 255, 0.08);
|
||||
|
||||
@@ -13,7 +13,6 @@ use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||
use Symfony\Component\DependencyInjection\Attribute\Autowire;
|
||||
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
|
||||
use Symfony\Contracts\HttpClient\HttpClientInterface;
|
||||
use Twig\Environment;
|
||||
|
||||
@@ -32,7 +31,6 @@ class CheckDnsCommand extends Command
|
||||
private MailerService $mailer,
|
||||
private Environment $twig,
|
||||
private HttpClientInterface $httpClient,
|
||||
private UrlGeneratorInterface $urlGenerator,
|
||||
private DnsInfraHelper $helper,
|
||||
#[Autowire('%kernel.environment%')] private string $appEnv,
|
||||
#[Autowire(env: 'DISCORD_WEBHOOK')] private string $discordWebhook = '',
|
||||
@@ -63,7 +61,7 @@ class CheckDnsCommand extends Command
|
||||
$this->dnsCheck->checkDmarc($domain, $checks, $errors, $successes);
|
||||
$this->helper->enrichWithCloudflare($checks, '_dmarc.'.$domain, 'DMARC', 'TXT', $cfRecords);
|
||||
|
||||
$this->dnsCheck->checkMx($domain, DnsInfraHelper::EXPECTED_MX[$domain] ?? '', $checks, $errors, $successes);
|
||||
$this->dnsCheck->checkMx($domain, DnsInfraHelper::EXPECTED_MX[$domain], $checks, $errors, $successes);
|
||||
$this->helper->enrichWithCloudflare($checks, $domain, 'MX', 'MX', $cfRecords);
|
||||
|
||||
$this->dnsCheck->checkBounce($domain, $checks, $errors, $warnings, $successes);
|
||||
@@ -128,142 +126,121 @@ class CheckDnsCommand extends Command
|
||||
*/
|
||||
private function checkAwsSes(string $domain, array &$checks, array &$errors, array &$successes, array $cfRecords = []): void
|
||||
{
|
||||
$ses = DnsInfraHelper::LABEL_AWS_SES;
|
||||
|
||||
if (!$this->awsSes->isAvailable()) {
|
||||
$checks[] = DnsCheckService::check('AWS SES', 'API', 'warning', 'Cles non configurees', 'Acces API SES', 'N/A');
|
||||
$checks[] = DnsCheckService::check($ses, 'API', 'warning', 'Cles non configurees', 'Acces API SES', 'N/A');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
$verif = $this->awsSes->isDomainVerified($domain);
|
||||
$checks[] = DnsCheckService::check(
|
||||
'AWS SES', 'Domaine', 'Success' === $verif ? 'ok' : 'error',
|
||||
$verif ?? 'Non verifie', 'Success', $verif ?? 'Absent'
|
||||
);
|
||||
if ('Success' === $verif) {
|
||||
$successes[] = "[$domain] AWS SES : domaine verifie";
|
||||
} else {
|
||||
$errors[] = "[$domain] AWS SES : domaine non verifie ($verif)";
|
||||
}
|
||||
|
||||
$dkim = $this->awsSes->getDkimStatus($domain);
|
||||
$dkimOk = $dkim['enabled'] && $dkim['verified'];
|
||||
|
||||
$checks[] = DnsCheckService::check(
|
||||
'AWS SES', 'DKIM statut', $dkimOk ? 'ok' : 'error',
|
||||
$dkimOk ? 'Active et verifiee' : 'Non active ou non verifiee',
|
||||
'Enabled=oui, Verified=oui',
|
||||
'Enabled='.($dkim['enabled'] ? 'oui' : 'non').', Verified='.($dkim['verified'] ? 'oui' : 'non')
|
||||
);
|
||||
|
||||
if ($dkimOk) {
|
||||
$successes[] = "[$domain] AWS SES DKIM : active et verifiee";
|
||||
} else {
|
||||
$errors[] = "[$domain] AWS SES DKIM : non active ou non verifiee";
|
||||
}
|
||||
|
||||
foreach ($dkim['tokens'] as $token) {
|
||||
$expectedCname = $token.'.dkim.amazonses.com';
|
||||
$dkimFqdn = $token.'._domainkey.'.$domain;
|
||||
$actualCname = $this->dnsCheck->getCnameRecord($dkimFqdn);
|
||||
|
||||
$found = null !== $actualCname && str_contains($actualCname, 'dkim.amazonses.com');
|
||||
$checks[] = DnsCheckService::check(
|
||||
'AWS SES', 'DKIM CNAME '.$token, $found ? 'ok' : 'error',
|
||||
$found ? 'Present' : 'Absent',
|
||||
$expectedCname,
|
||||
$actualCname ?? 'Non trouve'
|
||||
);
|
||||
|
||||
$this->helper->enrichLastCheck($checks, $dkimFqdn, 'CNAME', $cfRecords);
|
||||
|
||||
if ($found) {
|
||||
$successes[] = "[$domain] AWS SES DKIM CNAME $token : OK";
|
||||
} else {
|
||||
$errors[] = "[$domain] AWS SES DKIM CNAME $token : absent (attendu: $dkimFqdn CNAME $expectedCname)";
|
||||
}
|
||||
}
|
||||
|
||||
$mailFrom = $this->awsSes->getMailFromStatus($domain);
|
||||
$mailFromDomain = $mailFrom['mail_from_domain'];
|
||||
|
||||
if (null !== $mailFromDomain) {
|
||||
$mailFromStatus = $mailFrom['mail_from_status'];
|
||||
$checks[] = DnsCheckService::check(
|
||||
'AWS SES', 'MAIL FROM', 'Success' === $mailFromStatus ? 'ok' : 'error',
|
||||
$mailFromStatus ?? 'Inconnu',
|
||||
$mailFromDomain.' (statut: Success)',
|
||||
$mailFromDomain.' (statut: '.($mailFromStatus ?? '?').')'
|
||||
);
|
||||
|
||||
if ('Success' === $mailFromStatus) {
|
||||
$successes[] = "[$domain] AWS SES MAIL FROM : $mailFromDomain verifie";
|
||||
} else {
|
||||
$errors[] = "[$domain] AWS SES MAIL FROM : $mailFromDomain statut $mailFromStatus";
|
||||
}
|
||||
|
||||
$mxExpected = $mailFrom['mx_expected'];
|
||||
if (null !== $mxExpected) {
|
||||
$mxFound = $this->helper->checkMxExists($mailFromDomain, $mxExpected);
|
||||
$actualMx = $this->helper->getMxValues($mailFromDomain);
|
||||
$checks[] = DnsCheckService::check(
|
||||
'AWS SES', 'MAIL FROM MX', $mxFound ? 'ok' : 'error',
|
||||
$mxFound ? 'Present' : 'Absent',
|
||||
"$mailFromDomain MX $mxExpected",
|
||||
$actualMx ?: 'Non trouve'
|
||||
);
|
||||
|
||||
$this->helper->enrichLastCheck($checks, $mailFromDomain, 'MX', $cfRecords);
|
||||
|
||||
if ($mxFound) {
|
||||
$successes[] = "[$domain] AWS SES MAIL FROM MX : OK";
|
||||
} else {
|
||||
$errors[] = "[$domain] AWS SES MAIL FROM MX absent (attendu: $mailFromDomain MX $mxExpected)";
|
||||
}
|
||||
}
|
||||
|
||||
$txtExpected = $mailFrom['txt_expected'];
|
||||
if (null !== $txtExpected) {
|
||||
$txtFound = $this->helper->checkTxtContains($mailFromDomain, 'v=spf1');
|
||||
$actualTxt = $this->helper->getTxtSpfValue($mailFromDomain);
|
||||
$checks[] = DnsCheckService::check(
|
||||
'AWS SES', 'MAIL FROM TXT', $txtFound ? 'ok' : 'error',
|
||||
$txtFound ? 'Present' : 'Absent',
|
||||
"$mailFromDomain TXT $txtExpected",
|
||||
$actualTxt ?: 'Non trouve'
|
||||
);
|
||||
$this->helper->enrichLastCheck($checks, $mailFromDomain, 'TXT', $cfRecords);
|
||||
|
||||
if ($txtFound) {
|
||||
$successes[] = "[$domain] AWS SES MAIL FROM SPF : OK";
|
||||
} else {
|
||||
$errors[] = "[$domain] AWS SES MAIL FROM SPF absent (attendu: $mailFromDomain TXT $txtExpected)";
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$checks[] = DnsCheckService::check('AWS SES', 'MAIL FROM', 'warning', 'Non configure', 'bounce.'.$domain, 'N/A');
|
||||
}
|
||||
|
||||
$notif = $this->awsSes->getNotificationStatus($domain);
|
||||
$bounceOk = $notif['forwarding'] || null !== $notif['bounce_topic'];
|
||||
$bounceDetail = $notif['forwarding'] ? 'Forwarding actif' : ($notif['bounce_topic'] ?? 'Non configure');
|
||||
$checks[] = DnsCheckService::check('AWS SES', 'Bounce notif', $bounceOk ? 'ok' : 'warning', $bounceDetail, 'Forwarding ou SNS topic', $bounceDetail);
|
||||
$this->checkSesDomain($domain, $checks, $errors, $successes);
|
||||
$this->checkSesDkim($domain, $checks, $errors, $successes, $cfRecords);
|
||||
$this->checkSesMailFrom($domain, $checks, $errors, $successes, $cfRecords);
|
||||
$this->checkSesBounce($domain, $checks);
|
||||
} catch (\Throwable $e) {
|
||||
$errors[] = "[$domain] AWS SES : ".$e->getMessage();
|
||||
$checks[] = DnsCheckService::check('AWS SES', 'API', 'error', $e->getMessage());
|
||||
$errors[] = "[$domain] $ses : ".$e->getMessage();
|
||||
$checks[] = DnsCheckService::check($ses, 'API', 'error', $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/** @param list<array> $checks @param list<string> $errors @param list<string> $successes */
|
||||
private function checkSesDomain(string $domain, array &$checks, array &$errors, array &$successes): void
|
||||
{
|
||||
$ses = DnsInfraHelper::LABEL_AWS_SES;
|
||||
$verif = $this->awsSes->isDomainVerified($domain);
|
||||
$ok = 'Success' === $verif;
|
||||
|
||||
$checks[] = DnsCheckService::check($ses, 'Domaine', $ok ? 'ok' : 'error', $verif ?? 'Non verifie', 'Success', $verif ?? 'Absent');
|
||||
$ok ? $successes[] = "[$domain] $ses : domaine verifie" : $errors[] = "[$domain] $ses : domaine non verifie ($verif)";
|
||||
}
|
||||
|
||||
/** @param list<array> $checks @param list<string> $errors @param list<string> $successes @param list<array<string, mixed>> $cfRecords */
|
||||
private function checkSesDkim(string $domain, array &$checks, array &$errors, array &$successes, array $cfRecords): void
|
||||
{
|
||||
$ses = DnsInfraHelper::LABEL_AWS_SES;
|
||||
$dkim = $this->awsSes->getDkimStatus($domain);
|
||||
$dkimOk = $dkim['enabled'] && $dkim['verified'];
|
||||
|
||||
$checks[] = DnsCheckService::check(
|
||||
$ses, 'DKIM statut', $dkimOk ? 'ok' : 'error',
|
||||
$dkimOk ? 'Active et verifiee' : 'Non active ou non verifiee',
|
||||
'Enabled=oui, Verified=oui',
|
||||
'Enabled='.($dkim['enabled'] ? 'oui' : 'non').', Verified='.($dkim['verified'] ? 'oui' : 'non')
|
||||
);
|
||||
$dkimOk ? $successes[] = "[$domain] $ses DKIM : active et verifiee" : $errors[] = "[$domain] $ses DKIM : non active ou non verifiee";
|
||||
|
||||
foreach ($dkim['tokens'] as $token) {
|
||||
$expectedCname = $token.'.dkim.amazonses.com';
|
||||
$dkimFqdn = $token.'._domainkey.'.$domain;
|
||||
$actualCname = $this->dnsCheck->getCnameRecord($dkimFqdn);
|
||||
$found = null !== $actualCname && str_contains($actualCname, 'dkim.amazonses.com');
|
||||
|
||||
$checks[] = DnsCheckService::check($ses, 'DKIM CNAME '.$token, $found ? 'ok' : 'error', $found ? 'Present' : 'Absent', $expectedCname, $actualCname ?? DnsInfraHelper::NOT_FOUND);
|
||||
$this->helper->enrichLastCheck($checks, $dkimFqdn, 'CNAME', $cfRecords);
|
||||
$found ? $successes[] = "[$domain] $ses DKIM CNAME $token : OK" : $errors[] = "[$domain] $ses DKIM CNAME $token : absent (attendu: $dkimFqdn CNAME $expectedCname)";
|
||||
}
|
||||
}
|
||||
|
||||
/** @param list<array> $checks @param list<string> $errors @param list<string> $successes @param list<array<string, mixed>> $cfRecords */
|
||||
private function checkSesMailFrom(string $domain, array &$checks, array &$errors, array &$successes, array $cfRecords): void
|
||||
{
|
||||
$ses = DnsInfraHelper::LABEL_AWS_SES;
|
||||
$mailFrom = $this->awsSes->getMailFromStatus($domain);
|
||||
$mfd = $mailFrom['mail_from_domain'];
|
||||
|
||||
if (null === $mfd) {
|
||||
$checks[] = DnsCheckService::check($ses, 'MAIL FROM', 'warning', DnsInfraHelper::NOT_CONFIGURED, 'bounce.'.$domain, 'N/A');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$status = $mailFrom['mail_from_status'];
|
||||
$statusOk = 'Success' === $status;
|
||||
$checks[] = DnsCheckService::check($ses, 'MAIL FROM', $statusOk ? 'ok' : 'error', $status ?? 'Inconnu', $mfd.' (statut: Success)', $mfd.' (statut: '.($status ?? '?').')');
|
||||
$statusOk ? $successes[] = "[$domain] $ses MAIL FROM : $mfd verifie" : $errors[] = "[$domain] $ses MAIL FROM : $mfd statut $status";
|
||||
|
||||
if (null !== $mailFrom['mx_expected']) {
|
||||
$mxFound = $this->helper->checkMxExists($mfd, $mailFrom['mx_expected']);
|
||||
$actualMx = $this->helper->getMxValues($mfd);
|
||||
$checks[] = DnsCheckService::check($ses, 'MAIL FROM MX', $mxFound ? 'ok' : 'error', $mxFound ? 'Present' : 'Absent', "$mfd MX {$mailFrom['mx_expected']}", $actualMx ?: DnsInfraHelper::NOT_FOUND);
|
||||
$this->helper->enrichLastCheck($checks, $mfd, 'MX', $cfRecords);
|
||||
$mxFound ? $successes[] = "[$domain] $ses MAIL FROM MX : OK" : $errors[] = "[$domain] $ses MAIL FROM MX absent (attendu: $mfd MX {$mailFrom['mx_expected']})";
|
||||
}
|
||||
|
||||
if (null !== $mailFrom['txt_expected']) {
|
||||
$txtFound = $this->helper->checkTxtContains($mfd, 'v=spf1');
|
||||
$actualTxt = $this->helper->getTxtSpfValue($mfd);
|
||||
$checks[] = DnsCheckService::check($ses, 'MAIL FROM TXT', $txtFound ? 'ok' : 'error', $txtFound ? 'Present' : 'Absent', "$mfd TXT {$mailFrom['txt_expected']}", $actualTxt ?: DnsInfraHelper::NOT_FOUND);
|
||||
$this->helper->enrichLastCheck($checks, $mfd, 'TXT', $cfRecords);
|
||||
$txtFound ? $successes[] = "[$domain] $ses MAIL FROM SPF : OK" : $errors[] = "[$domain] $ses MAIL FROM SPF absent (attendu: $mfd TXT {$mailFrom['txt_expected']})";
|
||||
}
|
||||
}
|
||||
|
||||
/** @param list<array> $checks */
|
||||
private function checkSesBounce(string $domain, array &$checks): void
|
||||
{
|
||||
$ses = DnsInfraHelper::LABEL_AWS_SES;
|
||||
$notif = $this->awsSes->getNotificationStatus($domain);
|
||||
$bounceOk = $notif['forwarding'] || null !== $notif['bounce_topic'];
|
||||
$detail = $notif['forwarding'] ? 'Forwarding actif' : ($notif['bounce_topic'] ?? DnsInfraHelper::NOT_CONFIGURED);
|
||||
$checks[] = DnsCheckService::check($ses, 'Bounce notif', $bounceOk ? 'ok' : 'warning', $detail, 'Forwarding ou SNS topic', $detail);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param list<array> $checks
|
||||
* @param list<string> $errors
|
||||
* @param list<string> $warnings
|
||||
* @param list<string> $successes
|
||||
* @param list<array<string, mixed>> $cfRecords
|
||||
*/
|
||||
private function checkMailcow(string $domain, array &$checks, array &$errors, array &$warnings, array &$successes, array $cfRecords = []): void
|
||||
{
|
||||
$mc = DnsInfraHelper::LABEL_MAILCOW;
|
||||
|
||||
if (!$this->mailcow->isAvailable()) {
|
||||
$checks[] = DnsCheckService::check('Mailcow', 'API', 'warning', 'Non disponible', 'Acces API Mailcow', 'N/A');
|
||||
$checks[] = DnsCheckService::check($mc, 'API', 'warning', 'Non disponible', 'Acces API Mailcow', 'N/A');
|
||||
|
||||
return;
|
||||
}
|
||||
@@ -271,50 +248,44 @@ class CheckDnsCommand extends Command
|
||||
try {
|
||||
$info = $this->mailcow->getDomainStatus($domain);
|
||||
if (null === $info) {
|
||||
$warnings[] = "[$domain] Mailcow : domaine non configure";
|
||||
$checks[] = DnsCheckService::check('Mailcow', 'Domaine', 'warning', 'Non configure', 'Domaine actif', 'Absent');
|
||||
$warnings[] = "[$domain] $mc : domaine non configure";
|
||||
$checks[] = DnsCheckService::check($mc, 'Domaine', 'warning', DnsInfraHelper::NOT_CONFIGURED, 'Domaine actif', 'Absent');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$checks[] = DnsCheckService::check(
|
||||
'Mailcow', 'Domaine', $info['active'] ? 'ok' : 'error',
|
||||
$mc, 'Domaine', $info['active'] ? 'ok' : 'error',
|
||||
$info['active'] ? "Actif, {$info['mailboxes']} boite(s)" : 'Desactive',
|
||||
'Actif', $info['active'] ? "Actif ({$info['mailboxes']} boites)" : 'Desactive'
|
||||
);
|
||||
$info['active'] ? $successes[] = "[$domain] $mc : actif, {$info['mailboxes']} boite(s)" : $errors[] = "[$domain] $mc : desactive";
|
||||
|
||||
if ($info['active']) {
|
||||
$successes[] = "[$domain] Mailcow : actif, {$info['mailboxes']} boite(s)";
|
||||
} else {
|
||||
$errors[] = "[$domain] Mailcow : desactive";
|
||||
}
|
||||
|
||||
$expectedRecords = $this->mailcow->getExpectedDnsRecords($domain);
|
||||
foreach ($expectedRecords as $expected) {
|
||||
$mcDns = DnsInfraHelper::LABEL_MAILCOW_DNS;
|
||||
foreach ($this->mailcow->getExpectedDnsRecords($domain) as $expected) {
|
||||
$found = $this->helper->checkDnsRecordExists($expected['type'], $expected['name'], $expected['content']);
|
||||
$isOptional = $expected['optional'];
|
||||
$label = $expected['type'].' '.$expected['name'];
|
||||
$digValue = $this->helper->getActualDnsValue($expected['type'], $expected['name']);
|
||||
|
||||
$checks[] = DnsCheckService::check(
|
||||
'Mailcow DNS', $label, $found ? 'ok' : ($isOptional ? 'warning' : 'error'),
|
||||
$mcDns, $label, $found ? 'ok' : ($isOptional ? 'warning' : 'error'),
|
||||
$found ? 'Present' : ($isOptional ? 'Absent (optionnel)' : 'Absent'),
|
||||
$expected['content'], $digValue ?: 'Non trouve'
|
||||
$expected['content'], $digValue ?: DnsInfraHelper::NOT_FOUND
|
||||
);
|
||||
|
||||
$this->helper->enrichLastCheck($checks, $expected['name'], $expected['type'], $cfRecords);
|
||||
|
||||
if ($found) {
|
||||
$successes[] = "[$domain] Mailcow DNS : $label OK";
|
||||
$successes[] = "[$domain] $mcDns : $label OK";
|
||||
} elseif ($isOptional) {
|
||||
$warnings[] = "[$domain] Mailcow DNS : $label absent (optionnel)";
|
||||
$warnings[] = "[$domain] $mcDns : $label absent (optionnel)";
|
||||
} else {
|
||||
$errors[] = "[$domain] Mailcow DNS : $label absent";
|
||||
$errors[] = "[$domain] $mcDns : $label absent";
|
||||
}
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
$errors[] = "[$domain] Mailcow : ".$e->getMessage();
|
||||
$checks[] = DnsCheckService::check('Mailcow', 'API', 'error', $e->getMessage());
|
||||
$errors[] = "[$domain] $mc : ".$e->getMessage();
|
||||
$checks[] = DnsCheckService::check($mc, 'API', 'error', $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
namespace App\Command;
|
||||
|
||||
use App\Repository\EmailTrackingRepository;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Symfony\Component\Console\Attribute\AsCommand;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
@@ -18,7 +17,6 @@ use Symfony\Component\Console\Style\SymfonyStyle;
|
||||
class PurgeEmailTrackingCommand extends Command
|
||||
{
|
||||
public function __construct(
|
||||
private EmailTrackingRepository $repository,
|
||||
private EntityManagerInterface $em,
|
||||
) {
|
||||
parent::__construct();
|
||||
|
||||
@@ -123,8 +123,7 @@ class SyncController extends AbstractController
|
||||
];
|
||||
|
||||
foreach ($result['created'] as $wh) {
|
||||
$status = $wh['status'] ?? 'created';
|
||||
if ('exists' === $status) {
|
||||
if ('exists' === $wh['status']) {
|
||||
$this->addFlash('success', $wh['type'].' : deja configure ('.$wh['id'].')');
|
||||
} else {
|
||||
$this->addFlash('success', $wh['type'].' : cree ('.$wh['id'].')');
|
||||
|
||||
@@ -54,7 +54,7 @@ class DnsReportController extends AbstractController
|
||||
$dnsCheck->checkDmarc($domain, $checks, $errors, $successes);
|
||||
$helper->enrichWithCloudflare($checks, '_dmarc.'.$domain, 'DMARC', 'TXT', $cfRecords);
|
||||
|
||||
$dnsCheck->checkMx($domain, DnsInfraHelper::EXPECTED_MX[$domain] ?? '', $checks, $errors, $successes);
|
||||
$dnsCheck->checkMx($domain, DnsInfraHelper::EXPECTED_MX[$domain], $checks, $errors, $successes);
|
||||
$helper->enrichWithCloudflare($checks, $domain, 'MX', 'MX', $cfRecords);
|
||||
|
||||
$dnsCheck->checkBounce($domain, $checks, $errors, $warnings, $successes);
|
||||
|
||||
@@ -130,6 +130,7 @@ class CloudflareService
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $query
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
private function request(string $method, string $path, array $query = []): array
|
||||
|
||||
@@ -338,6 +338,17 @@ class DnsCheckService
|
||||
return $records;
|
||||
}
|
||||
|
||||
public function getDkimTxtRecord(string $domain): ?string
|
||||
{
|
||||
foreach ($this->getTxtRecords($domain) as $txt) {
|
||||
if (str_starts_with($txt, 'v=DKIM1')) {
|
||||
return $txt;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return list<string>
|
||||
*/
|
||||
|
||||
@@ -11,6 +11,12 @@ class DnsInfraHelper
|
||||
'esy-web.dev' => 'mail.esy-web.dev',
|
||||
];
|
||||
|
||||
public const LABEL_AWS_SES = 'AWS SES';
|
||||
public const LABEL_MAILCOW = 'Mailcow';
|
||||
public const LABEL_MAILCOW_DNS = 'Mailcow DNS';
|
||||
public const NOT_FOUND = 'Non trouve';
|
||||
public const NOT_CONFIGURED = 'Non configure';
|
||||
|
||||
public function __construct(
|
||||
private DnsCheckService $dnsCheck,
|
||||
private CloudflareService $cloudflare,
|
||||
@@ -58,7 +64,7 @@ class DnsInfraHelper
|
||||
}
|
||||
|
||||
if (null === $cfValue) {
|
||||
$cfValue = 'Non trouve';
|
||||
$cfValue = self::NOT_FOUND;
|
||||
$cfStatus = '' === $cfStatus ? '' : 'error';
|
||||
}
|
||||
|
||||
@@ -76,7 +82,7 @@ class DnsInfraHelper
|
||||
*/
|
||||
public function enrichLastCheck(array &$checks, string $recordName, string $dnsType, array $cfRecords): void
|
||||
{
|
||||
$cfValue = 'Non trouve';
|
||||
$cfValue = self::NOT_FOUND;
|
||||
$cfStatus = '';
|
||||
|
||||
foreach ($cfRecords as $r) {
|
||||
@@ -99,7 +105,7 @@ class DnsInfraHelper
|
||||
return match ($type) {
|
||||
'MX' => $this->getMxValues($name),
|
||||
'CNAME' => $this->dnsCheck->getCnameRecord($name) ?? '',
|
||||
'TXT' => $this->getFirstTxtValue($name) ?: $this->getFirstTxtValueRaw($name),
|
||||
'TXT' => $this->getFirstTxtValue($name),
|
||||
'SRV' => $this->getSrvValue($name),
|
||||
default => '',
|
||||
};
|
||||
@@ -128,19 +134,6 @@ class DnsInfraHelper
|
||||
return '';
|
||||
}
|
||||
|
||||
private function getFirstTxtValueRaw(string $domain): string
|
||||
{
|
||||
$output = $this->dnsCheck->dig($domain, 'TXT');
|
||||
|
||||
foreach (explode("\n", $output) as $line) {
|
||||
if (preg_match('/\bIN\s+TXT\s+"(.+)"/', $line, $m)) {
|
||||
return str_replace('" "', '', $m[1]);
|
||||
}
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
public function getSrvValue(string $domain): string
|
||||
{
|
||||
$values = [];
|
||||
|
||||
@@ -137,8 +137,6 @@ class MailcowService
|
||||
],
|
||||
]);
|
||||
|
||||
$data = $response->toArray(false);
|
||||
|
||||
return \is_array($data) ? $data : [];
|
||||
return $response->toArray(false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -74,7 +74,7 @@ class StripeWebhookService
|
||||
/**
|
||||
* Cree les 4 webhooks Stripe (main light/instant + connect light/instant).
|
||||
*
|
||||
* @return array{created: list<array{type: string, url: string, id: string}>, errors: list<string>}
|
||||
* @return array{created: list<array{type: string, url: string, id: string, status: string, secret?: string}>, errors: list<string>}
|
||||
*/
|
||||
public function createAllWebhooks(string $baseUrl): array
|
||||
{
|
||||
|
||||
@@ -41,66 +41,66 @@
|
||||
placeholder="email@exemple.fr">
|
||||
</div>
|
||||
<div>
|
||||
<label for="groups-member" class="block text-xs font-bold uppercase tracking-wider mb-2 text-gray-600">Groupes d'acces</label>
|
||||
<span class="block text-xs font-bold uppercase tracking-wider mb-2 text-gray-600">Groupes d'acces</span>
|
||||
<div class="grid grid-cols-2 md:grid-cols-4 gap-3">
|
||||
<label class="flex items-center gap-2 px-3 py-2 glass rounded-lg cursor-pointer hover:bg-white/80 transition-all" style="border-color: rgba(99,102,241,0.3);">
|
||||
<input type="checkbox" id="groups-member" name="groups[]" value="siteconseil_member" class="accent-indigo-600" checked>
|
||||
<label for="group-member" class="flex items-center gap-2 px-3 py-2 glass rounded-lg cursor-pointer hover:bg-white/80 transition-all" style="border-color: rgba(99,102,241,0.3);">
|
||||
<input type="checkbox" id="group-member" name="groups[]" value="siteconseil_member" class="accent-indigo-600" checked>
|
||||
<span class="text-xs font-bold text-indigo-800">Membre</span>
|
||||
</label>
|
||||
<label class="flex items-center gap-2 px-3 py-2 glass rounded-lg cursor-pointer hover:bg-white/80 transition-all" style="border-color: rgba(220,38,38,0.3);">
|
||||
<input type="checkbox" name="groups[]" value="siteconseil_admin" class="accent-red-600">
|
||||
<label for="group-admin" class="flex items-center gap-2 px-3 py-2 glass rounded-lg cursor-pointer hover:bg-white/80 transition-all" style="border-color: rgba(220,38,38,0.3);">
|
||||
<input type="checkbox" id="group-admin" name="groups[]" value="siteconseil_admin" class="accent-red-600">
|
||||
<span class="text-xs font-bold text-red-800">Super Admin</span>
|
||||
</label>
|
||||
<label class="flex items-center gap-2 px-3 py-2 glass rounded-lg cursor-pointer hover:bg-white/80 transition-all">
|
||||
<input type="checkbox" name="groups[]" value="esy-web" class="accent-[#fabf04]">
|
||||
<label for="group-esy-web" class="flex items-center gap-2 px-3 py-2 glass rounded-lg cursor-pointer hover:bg-white/80 transition-all">
|
||||
<input type="checkbox" id="group-esy-web" name="groups[]" value="esy-web" class="accent-[#fabf04]">
|
||||
<span class="text-xs font-bold">Esy-Web</span>
|
||||
</label>
|
||||
<label class="flex items-center gap-2 px-3 py-2 glass rounded-lg cursor-pointer hover:bg-white/80 transition-all">
|
||||
<input type="checkbox" name="groups[]" value="esy-mail" class="accent-[#fabf04]">
|
||||
<label for="group-esy-mail" class="flex items-center gap-2 px-3 py-2 glass rounded-lg cursor-pointer hover:bg-white/80 transition-all">
|
||||
<input type="checkbox" id="group-esy-mail" name="groups[]" value="esy-mail" class="accent-[#fabf04]">
|
||||
<span class="text-xs font-bold">Esy-Mail</span>
|
||||
</label>
|
||||
<label class="flex items-center gap-2 px-3 py-2 glass rounded-lg cursor-pointer hover:bg-white/80 transition-all">
|
||||
<input type="checkbox" name="groups[]" value="esy-mailer" class="accent-[#fabf04]">
|
||||
<label for="group-esy-mailer" class="flex items-center gap-2 px-3 py-2 glass rounded-lg cursor-pointer hover:bg-white/80 transition-all">
|
||||
<input type="checkbox" id="group-esy-mailer" name="groups[]" value="esy-mailer" class="accent-[#fabf04]">
|
||||
<span class="text-xs font-bold">Esy-Mailer</span>
|
||||
</label>
|
||||
<label class="flex items-center gap-2 px-3 py-2 glass rounded-lg cursor-pointer hover:bg-white/80 transition-all">
|
||||
<input type="checkbox" name="groups[]" value="esy-analytics" class="accent-[#fabf04]">
|
||||
<label for="group-esy-analytics" class="flex items-center gap-2 px-3 py-2 glass rounded-lg cursor-pointer hover:bg-white/80 transition-all">
|
||||
<input type="checkbox" id="group-esy-analytics" name="groups[]" value="esy-analytics" class="accent-[#fabf04]">
|
||||
<span class="text-xs font-bold">Esy-Analytics</span>
|
||||
</label>
|
||||
<label class="flex items-center gap-2 px-3 py-2 glass rounded-lg cursor-pointer hover:bg-white/80 transition-all">
|
||||
<input type="checkbox" name="groups[]" value="esy-monitor" class="accent-[#fabf04]">
|
||||
<label for="group-esy-monitor" class="flex items-center gap-2 px-3 py-2 glass rounded-lg cursor-pointer hover:bg-white/80 transition-all">
|
||||
<input type="checkbox" id="group-esy-monitor" name="groups[]" value="esy-monitor" class="accent-[#fabf04]">
|
||||
<span class="text-xs font-bold">Esy-Monitor</span>
|
||||
</label>
|
||||
<label class="flex items-center gap-2 px-3 py-2 glass rounded-lg cursor-pointer hover:bg-white/80 transition-all">
|
||||
<input type="checkbox" name="groups[]" value="esy-defender" class="accent-[#fabf04]">
|
||||
<label for="group-esy-defender" class="flex items-center gap-2 px-3 py-2 glass rounded-lg cursor-pointer hover:bg-white/80 transition-all">
|
||||
<input type="checkbox" id="group-esy-defender" name="groups[]" value="esy-defender" class="accent-[#fabf04]">
|
||||
<span class="text-xs font-bold">Esy-Defender</span>
|
||||
</label>
|
||||
<label class="flex items-center gap-2 px-3 py-2 glass rounded-lg cursor-pointer hover:bg-white/80 transition-all">
|
||||
<input type="checkbox" name="groups[]" value="esy-translate" class="accent-[#fabf04]">
|
||||
<label for="group-esy-translate" class="flex items-center gap-2 px-3 py-2 glass rounded-lg cursor-pointer hover:bg-white/80 transition-all">
|
||||
<input type="checkbox" id="group-esy-translate" name="groups[]" value="esy-translate" class="accent-[#fabf04]">
|
||||
<span class="text-xs font-bold">Esy-Translate</span>
|
||||
</label>
|
||||
<label class="flex items-center gap-2 px-3 py-2 glass rounded-lg cursor-pointer hover:bg-white/80 transition-all">
|
||||
<input type="checkbox" name="groups[]" value="esy-signature" class="accent-[#fabf04]">
|
||||
<label for="group-esy-signature" class="flex items-center gap-2 px-3 py-2 glass rounded-lg cursor-pointer hover:bg-white/80 transition-all">
|
||||
<input type="checkbox" id="group-esy-signature" name="groups[]" value="esy-signature" class="accent-[#fabf04]">
|
||||
<span class="text-xs font-bold">Esy-Signature</span>
|
||||
</label>
|
||||
<label class="flex items-center gap-2 px-3 py-2 glass rounded-lg cursor-pointer hover:bg-white/80 transition-all">
|
||||
<input type="checkbox" name="groups[]" value="esy-creator" class="accent-[#fabf04]">
|
||||
<label for="group-esy-creator" class="flex items-center gap-2 px-3 py-2 glass rounded-lg cursor-pointer hover:bg-white/80 transition-all">
|
||||
<input type="checkbox" id="group-esy-creator" name="groups[]" value="esy-creator" class="accent-[#fabf04]">
|
||||
<span class="text-xs font-bold">Esy-Creator</span>
|
||||
</label>
|
||||
<label class="flex items-center gap-2 px-3 py-2 glass rounded-lg cursor-pointer hover:bg-white/80 transition-all">
|
||||
<input type="checkbox" name="groups[]" value="esy-aide" class="accent-[#fabf04]">
|
||||
<label for="group-esy-aide" class="flex items-center gap-2 px-3 py-2 glass rounded-lg cursor-pointer hover:bg-white/80 transition-all">
|
||||
<input type="checkbox" id="group-esy-aide" name="groups[]" value="esy-aide" class="accent-[#fabf04]">
|
||||
<span class="text-xs font-bold">Esy-Aide</span>
|
||||
</label>
|
||||
<label class="flex items-center gap-2 px-3 py-2 glass rounded-lg cursor-pointer hover:bg-white/80 transition-all">
|
||||
<input type="checkbox" name="groups[]" value="esy-meet" class="accent-[#fabf04]">
|
||||
<label for="group-esy-meet" class="flex items-center gap-2 px-3 py-2 glass rounded-lg cursor-pointer hover:bg-white/80 transition-all">
|
||||
<input type="checkbox" id="group-esy-meet" name="groups[]" value="esy-meet" class="accent-[#fabf04]">
|
||||
<span class="text-xs font-bold">Esy-Meet</span>
|
||||
</label>
|
||||
<label class="flex items-center gap-2 px-3 py-2 glass rounded-lg cursor-pointer hover:bg-white/80 transition-all">
|
||||
<input type="checkbox" name="groups[]" value="esy-tchat" class="accent-[#fabf04]">
|
||||
<label for="group-esy-tchat" class="flex items-center gap-2 px-3 py-2 glass rounded-lg cursor-pointer hover:bg-white/80 transition-all">
|
||||
<input type="checkbox" id="group-esy-tchat" name="groups[]" value="esy-tchat" class="accent-[#fabf04]">
|
||||
<span class="text-xs font-bold">Esy-Tchat</span>
|
||||
</label>
|
||||
<label class="flex items-center gap-2 px-3 py-2 glass rounded-lg cursor-pointer hover:bg-white/80 transition-all">
|
||||
<input type="checkbox" name="groups[]" value="esy-ndd" class="accent-[#fabf04]">
|
||||
<label for="group-esy-ndd" class="flex items-center gap-2 px-3 py-2 glass rounded-lg cursor-pointer hover:bg-white/80 transition-all">
|
||||
<input type="checkbox" id="group-esy-ndd" name="groups[]" value="esy-ndd" class="accent-[#fabf04]">
|
||||
<span class="text-xs font-bold">Nom de domaine</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
@@ -44,20 +44,20 @@
|
||||
<form method="post" action="{{ path('app_admin_tarification_edit', {id: price.id}) }}" class="p-4">
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||
<div>
|
||||
<label class="block text-xs font-bold uppercase tracking-wider mb-1 text-gray-600">Titre</label>
|
||||
<input type="text" name="title" value="{{ price.title }}" required class="input-glass w-full px-3 py-2 text-sm font-medium">
|
||||
<label for="title-{{ price.id }}" class="block text-xs font-bold uppercase tracking-wider mb-1 text-gray-600">Titre</label>
|
||||
<input type="text" id="title-{{ price.id }}" name="title" value="{{ price.title }}" required class="input-glass w-full px-3 py-2 text-sm font-medium">
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-xs font-bold uppercase tracking-wider mb-1 text-gray-600">Prix unique HT (€)</label>
|
||||
<input type="text" name="priceHt" value="{{ price.priceHt }}" required class="input-glass w-full px-3 py-2 text-sm font-medium">
|
||||
<label for="priceHt-{{ price.id }}" class="block text-xs font-bold uppercase tracking-wider mb-1 text-gray-600">Prix unique HT (€)</label>
|
||||
<input type="text" id="priceHt-{{ price.id }}" name="priceHt" value="{{ price.priceHt }}" required class="input-glass w-full px-3 py-2 text-sm font-medium">
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-xs font-bold uppercase tracking-wider mb-1 text-gray-600">Prix mensuel HT (€)</label>
|
||||
<input type="text" name="monthPrice" value="{{ price.monthPrice }}" required class="input-glass w-full px-3 py-2 text-sm font-medium">
|
||||
<label for="monthPrice-{{ price.id }}" class="block text-xs font-bold uppercase tracking-wider mb-1 text-gray-600">Prix mensuel HT (€)</label>
|
||||
<input type="text" id="monthPrice-{{ price.id }}" name="monthPrice" value="{{ price.monthPrice }}" required class="input-glass w-full px-3 py-2 text-sm font-medium">
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-xs font-bold uppercase tracking-wider mb-1 text-gray-600">Periode (mois)</label>
|
||||
<select name="period" class="input-glass w-full px-3 py-2 text-sm font-medium">
|
||||
<label for="period-{{ price.id }}" class="block text-xs font-bold uppercase tracking-wider mb-1 text-gray-600">Periode (mois)</label>
|
||||
<select id="period-{{ price.id }}" name="period" class="input-glass w-full px-3 py-2 text-sm font-medium">
|
||||
{% for p in [1, 2, 3, 6, 12] %}
|
||||
<option value="{{ p }}" {{ price.period == p ? 'selected' }}>{{ p }} mois</option>
|
||||
{% endfor %}
|
||||
@@ -66,8 +66,8 @@
|
||||
<input type="hidden" name="stripeId" value="{{ price.stripeId }}">
|
||||
<input type="hidden" name="stripeAbonnementId" value="{{ price.stripeAbonnementId }}">
|
||||
<div class="md:col-span-2 lg:col-span-3">
|
||||
<label class="block text-xs font-bold uppercase tracking-wider mb-1 text-gray-600">Description</label>
|
||||
<textarea name="description" rows="2" class="input-glass w-full px-3 py-2 text-sm font-medium">{{ price.description }}</textarea>
|
||||
<label for="description-{{ price.id }}" class="block text-xs font-bold uppercase tracking-wider mb-1 text-gray-600">Description</label>
|
||||
<textarea id="description-{{ price.id }}" name="description" rows="2" class="input-glass w-full px-3 py-2 text-sm font-medium">{{ price.description }}</textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-4 flex justify-end">
|
||||
|
||||
@@ -620,6 +620,9 @@
|
||||
<div class="glass-dark text-white px-4 py-2" style="border-radius: 16px 16px 0 0;"><span class="font-bold uppercase text-xs tracking-widest">Options</span></div>
|
||||
<div class="p-4">
|
||||
<table class="w-full text-sm">
|
||||
<thead class="sr-only">
|
||||
<tr><th>Option</th><th>Tarif</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr class="border-b border-gray-200">
|
||||
<td class="py-2 font-bold">Boite mail supplementaire</td>
|
||||
|
||||
@@ -37,8 +37,8 @@
|
||||
</div>
|
||||
|
||||
{% if displayTrustedOption %}
|
||||
<label class="flex items-center gap-2 cursor-pointer">
|
||||
<input type="checkbox" name="{{ trustedParameterName }}" class="accent-[#fabf04]">
|
||||
<label for="trusted-device" class="flex items-center gap-2 cursor-pointer">
|
||||
<input type="checkbox" id="trusted-device" name="{{ trustedParameterName }}" class="accent-[#fabf04]">
|
||||
<span class="text-xs font-bold">Faire confiance a cet appareil pendant 30 jours</span>
|
||||
</label>
|
||||
{% endif %}
|
||||
|
||||
Reference in New Issue
Block a user