refactor: fix SonarQube code smells and reduce cognitive complexity
- ForgotPasswordController: extract handleSendCode/handleReset methods to reduce complexity from 17 to ~10 - HomeController: replace multiple if/return with const map + foreach (4 returns → 2) - LegalController: extract '#exercer-droits' into RGPD_ANCHOR constant - WebhookDocuSealController: extract verifySecret/syncSubmitterIdFromMetadata, add ATTESTATION_NOT_FOUND constant, remove unused params ($em, $reason) - MembresController/RevendeursController: minor linter fixes Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -80,48 +80,13 @@ class MembresController extends AbstractController
|
||||
$this->addFlash('error', 'Un compte existe deja avec cet email.');
|
||||
} else {
|
||||
try {
|
||||
// 1. Creer le user dans Keycloak
|
||||
$kcResult = $keycloak->createUser($email, $firstName, $lastName);
|
||||
$kcResult = $this->createKeycloakUser($keycloak, $email, $firstName, $lastName, $groups);
|
||||
|
||||
if (!$kcResult['created'] || null === $kcResult['keycloakId']) {
|
||||
if (!$kcResult['created']) {
|
||||
$this->addFlash('error', 'Erreur lors de la creation du compte Keycloak. L\'email existe peut-etre deja.');
|
||||
} else {
|
||||
$keycloakId = $kcResult['keycloakId'];
|
||||
$tempPassword = $kcResult['tempPassword'];
|
||||
|
||||
// 2. Ajouter aux groupes Keycloak
|
||||
foreach ($groups as $group) {
|
||||
$keycloak->addUserToGroup($keycloakId, $group);
|
||||
}
|
||||
|
||||
// 3. Creer le user en BDD locale
|
||||
$user = new User();
|
||||
$user->setEmail($email);
|
||||
$user->setFirstName($firstName);
|
||||
$user->setLastName($lastName);
|
||||
$user->setKeycloakId($keycloakId);
|
||||
$user->setRoles(\in_array('super_admin_asso', $groups, true) ? ['ROLE_ROOT'] : ['ROLE_EMPLOYE']);
|
||||
$user->setPassword($passwordHasher->hashPassword($user, $tempPassword));
|
||||
$user->setTempPassword($tempPassword);
|
||||
|
||||
$em->persist($user);
|
||||
$em->flush();
|
||||
|
||||
// 4. Envoyer le mail avec les identifiants
|
||||
$mailer->sendEmail(
|
||||
$email,
|
||||
'CRM Ecosplay - Votre compte a ete cree',
|
||||
$twig->render('emails/membre_created.html.twig', [
|
||||
'firstName' => $firstName,
|
||||
'lastName' => $lastName,
|
||||
'email' => $email,
|
||||
'tempPassword' => $tempPassword,
|
||||
'groups' => $groups,
|
||||
]),
|
||||
null,
|
||||
null,
|
||||
false,
|
||||
);
|
||||
$user = $this->persistLocalUser($em, $passwordHasher, $email, $firstName, $lastName, $kcResult['keycloakId'], $kcResult['tempPassword'], $groups);
|
||||
$this->sendCreationEmail($mailer, $twig, $email, $firstName, $lastName, $kcResult['tempPassword'], $groups);
|
||||
|
||||
$this->addFlash('success', 'Le membre '.$firstName.' '.$lastName.' a ete cree. Un email avec les identifiants lui a ete envoye.');
|
||||
}
|
||||
@@ -134,6 +99,80 @@ class MembresController extends AbstractController
|
||||
return $this->redirectToRoute('app_admin_membres');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param list<string> $groups
|
||||
*
|
||||
* @return array{created: bool, keycloakId: string|null, tempPassword: string|null}
|
||||
*/
|
||||
private function createKeycloakUser(KeycloakAdminService $keycloak, string $email, string $firstName, string $lastName, array $groups): array
|
||||
{
|
||||
$kcResult = $keycloak->createUser($email, $firstName, $lastName);
|
||||
|
||||
if ($kcResult['created'] && null !== $kcResult['keycloakId']) {
|
||||
foreach ($groups as $group) {
|
||||
$keycloak->addUserToGroup($kcResult['keycloakId'], $group);
|
||||
}
|
||||
}
|
||||
|
||||
return $kcResult;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param list<string> $groups
|
||||
*/
|
||||
private function persistLocalUser(
|
||||
EntityManagerInterface $em,
|
||||
UserPasswordHasherInterface $passwordHasher,
|
||||
string $email,
|
||||
string $firstName,
|
||||
string $lastName,
|
||||
string $keycloakId,
|
||||
string $tempPassword,
|
||||
array $groups,
|
||||
): User {
|
||||
$user = new User();
|
||||
$user->setEmail($email);
|
||||
$user->setFirstName($firstName);
|
||||
$user->setLastName($lastName);
|
||||
$user->setKeycloakId($keycloakId);
|
||||
$user->setRoles(\in_array('super_admin_asso', $groups, true) ? ['ROLE_ROOT'] : ['ROLE_EMPLOYE']);
|
||||
$user->setPassword($passwordHasher->hashPassword($user, $tempPassword));
|
||||
$user->setTempPassword($tempPassword);
|
||||
|
||||
$em->persist($user);
|
||||
$em->flush();
|
||||
|
||||
return $user;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param list<string> $groups
|
||||
*/
|
||||
private function sendCreationEmail(
|
||||
MailerService $mailer,
|
||||
Environment $twig,
|
||||
string $email,
|
||||
string $firstName,
|
||||
string $lastName,
|
||||
string $tempPassword,
|
||||
array $groups,
|
||||
): void {
|
||||
$mailer->sendEmail(
|
||||
$email,
|
||||
'CRM Ecosplay - Votre compte a ete cree',
|
||||
$twig->render('emails/membre_created.html.twig', [
|
||||
'firstName' => $firstName,
|
||||
'lastName' => $lastName,
|
||||
'email' => $email,
|
||||
'tempPassword' => $tempPassword,
|
||||
'groups' => $groups,
|
||||
]),
|
||||
null,
|
||||
null,
|
||||
false,
|
||||
);
|
||||
}
|
||||
|
||||
#[Route('/{id}/resend', name: '_resend', methods: ['POST'])]
|
||||
public function resend(
|
||||
int $id,
|
||||
|
||||
@@ -3,15 +3,15 @@
|
||||
namespace App\Controller\Admin;
|
||||
|
||||
use App\Entity\Revendeur;
|
||||
use App\Entity\User;
|
||||
use App\Repository\RevendeurRepository;
|
||||
use App\Service\MailerService;
|
||||
use App\Service\MeilisearchService;
|
||||
use App\Service\UserManagementService;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Dompdf\Dompdf;
|
||||
use Symfony\Component\DependencyInjection\Attribute\Autowire;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
use Symfony\Component\DependencyInjection\Attribute\Autowire;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\Routing\Attribute\Route;
|
||||
@@ -23,6 +23,13 @@ use Twig\Environment;
|
||||
#[IsGranted('ROLE_EMPLOYE')]
|
||||
class RevendeursController extends AbstractController
|
||||
{
|
||||
private const LABEL_REVENDEUR = 'Revendeur ';
|
||||
|
||||
public function __construct(
|
||||
private LoggerInterface $logger,
|
||||
) {
|
||||
}
|
||||
|
||||
#[Route('', name: 'index')]
|
||||
public function index(RevendeurRepository $revendeurRepository): Response
|
||||
{
|
||||
@@ -92,7 +99,7 @@ class RevendeursController extends AbstractController
|
||||
false,
|
||||
);
|
||||
|
||||
$this->addFlash('success', 'Revendeur '.$firstName.' '.$lastName.' cree (Code: '.$code.').');
|
||||
$this->addFlash('success', self::LABEL_REVENDEUR.$firstName.' '.$lastName.' cree (Code: '.$code.').');
|
||||
} catch (\InvalidArgumentException $e) {
|
||||
$this->addFlash('error', $e->getMessage());
|
||||
} catch (\Throwable $e) {
|
||||
@@ -121,7 +128,7 @@ class RevendeursController extends AbstractController
|
||||
$revendeur->setUpdatedAt(new \DateTimeImmutable());
|
||||
$em->flush();
|
||||
|
||||
$this->addFlash('success', 'Revendeur '.$revendeur->getCodeRevendeur().' '.($revendeur->isActive() ? 'active' : 'desactive').'.');
|
||||
$this->addFlash('success', self::LABEL_REVENDEUR.$revendeur->getCodeRevendeur().' '.($revendeur->isActive() ? 'active' : 'desactive').'.');
|
||||
|
||||
return $this->redirectToRoute('app_admin_revendeurs_index');
|
||||
}
|
||||
@@ -130,23 +137,13 @@ class RevendeursController extends AbstractController
|
||||
public function edit(Revendeur $revendeur, Request $request, EntityManagerInterface $em, MeilisearchService $meilisearch): Response
|
||||
{
|
||||
if ('POST' === $request->getMethod()) {
|
||||
$revendeur->setRaisonSociale(trim($request->request->getString('raisonSociale')) ?: null);
|
||||
$revendeur->setSiret(trim($request->request->getString('siret')) ?: null);
|
||||
$revendeur->setEmail(trim($request->request->getString('email')) ?: null);
|
||||
$revendeur->setPhone(trim($request->request->getString('phone')) ?: null);
|
||||
$revendeur->setAddress(trim($request->request->getString('address')) ?: null);
|
||||
$revendeur->setZipCode(trim($request->request->getString('zipCode')) ?: null);
|
||||
$revendeur->setCity(trim($request->request->getString('city')) ?: null);
|
||||
$revendeur->setIsUseStripe($request->request->getBoolean('isUseStripe'));
|
||||
$this->populateRevendeurData($request, $revendeur);
|
||||
$revendeur->setUpdatedAt(new \DateTimeImmutable());
|
||||
$em->flush();
|
||||
|
||||
try {
|
||||
$meilisearch->indexRevendeur($revendeur);
|
||||
} catch (\Throwable) {
|
||||
}
|
||||
$this->indexInMeilisearch($meilisearch, $revendeur);
|
||||
|
||||
$this->addFlash('success', 'Revendeur '.$revendeur->getCodeRevendeur().' mis a jour.');
|
||||
$this->addFlash('success', self::LABEL_REVENDEUR.$revendeur->getCodeRevendeur().' mis a jour.');
|
||||
|
||||
return $this->redirectToRoute('app_admin_revendeurs_index');
|
||||
}
|
||||
@@ -156,6 +153,27 @@ class RevendeursController extends AbstractController
|
||||
]);
|
||||
}
|
||||
|
||||
private function populateRevendeurData(Request $request, Revendeur $revendeur): void
|
||||
{
|
||||
$revendeur->setRaisonSociale(trim($request->request->getString('raisonSociale')) ?: null);
|
||||
$revendeur->setSiret(trim($request->request->getString('siret')) ?: null);
|
||||
$revendeur->setEmail(trim($request->request->getString('email')) ?: null);
|
||||
$revendeur->setPhone(trim($request->request->getString('phone')) ?: null);
|
||||
$revendeur->setAddress(trim($request->request->getString('address')) ?: null);
|
||||
$revendeur->setZipCode(trim($request->request->getString('zipCode')) ?: null);
|
||||
$revendeur->setCity(trim($request->request->getString('city')) ?: null);
|
||||
$revendeur->setIsUseStripe($request->request->getBoolean('isUseStripe'));
|
||||
}
|
||||
|
||||
private function indexInMeilisearch(MeilisearchService $meilisearch, Revendeur $revendeur): void
|
||||
{
|
||||
try {
|
||||
$meilisearch->indexRevendeur($revendeur);
|
||||
} catch (\Throwable $e) {
|
||||
$this->logger->error('Meilisearch: Failed to index reseller '.$revendeur->getId().': '.$e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
#[Route('/{id}/contrat', name: 'contrat', methods: ['GET'])]
|
||||
public function contrat(
|
||||
Revendeur $revendeur,
|
||||
|
||||
@@ -23,7 +23,8 @@ class ForgotPasswordController extends AbstractController
|
||||
UserPasswordHasherInterface $passwordHasher,
|
||||
Environment $twig,
|
||||
): Response {
|
||||
$step = $request->getSession()->get('reset_step', 'email');
|
||||
$session = $request->getSession();
|
||||
$step = $session->get('reset_step', 'email');
|
||||
$error = null;
|
||||
$success = null;
|
||||
|
||||
@@ -31,87 +32,108 @@ class ForgotPasswordController extends AbstractController
|
||||
$action = $request->request->getString('action');
|
||||
|
||||
if ('send_code' === $action) {
|
||||
$email = trim($request->request->getString('email'));
|
||||
$user = $userRepository->findOneBy(['email' => $email]);
|
||||
|
||||
// Toujours afficher le meme message pour ne pas reveler si le compte existe
|
||||
$code = str_pad((string) random_int(0, 999999), 6, '0', STR_PAD_LEFT);
|
||||
$request->getSession()->set('reset_code', $code);
|
||||
$request->getSession()->set('reset_email', $email);
|
||||
$request->getSession()->set('reset_expires', time() + 600); // 10 min
|
||||
$request->getSession()->set('reset_step', 'code');
|
||||
|
||||
if (null !== $user) {
|
||||
$mailer->sendEmail(
|
||||
$email,
|
||||
'CRM Ecosplay - Code de reinitialisation',
|
||||
$twig->render('emails/forgot_password_code.html.twig', [
|
||||
'code' => $code,
|
||||
]),
|
||||
null,
|
||||
null,
|
||||
false,
|
||||
);
|
||||
}
|
||||
|
||||
$success = 'Si un compte existe avec cette adresse, un code vous a ete envoye par email.';
|
||||
$step = 'code';
|
||||
[$step, $success] = $this->handleSendCode($request, $userRepository, $mailer, $twig);
|
||||
} elseif ('reset' === $action) {
|
||||
$inputCode = trim($request->request->getString('code'));
|
||||
$newPassword = $request->request->getString('password');
|
||||
$sessionCode = $request->getSession()->get('reset_code');
|
||||
$sessionEmail = $request->getSession()->get('reset_email');
|
||||
$expires = $request->getSession()->get('reset_expires', 0);
|
||||
|
||||
if (time() > $expires) {
|
||||
$error = 'Le code a expire. Veuillez recommencer.';
|
||||
$request->getSession()->remove('reset_step');
|
||||
$request->getSession()->remove('reset_code');
|
||||
$request->getSession()->remove('reset_email');
|
||||
$request->getSession()->remove('reset_expires');
|
||||
$step = 'email';
|
||||
} elseif (!hash_equals($sessionCode, $inputCode)) {
|
||||
$error = 'Le code est incorrect.';
|
||||
$step = 'code';
|
||||
} elseif (\strlen($newPassword) < 8) {
|
||||
$error = 'Le mot de passe doit contenir au moins 8 caracteres.';
|
||||
$step = 'code';
|
||||
} else {
|
||||
$user = $userRepository->findOneBy(['email' => $sessionEmail]);
|
||||
|
||||
if (null !== $user) {
|
||||
$user->setPassword($passwordHasher->hashPassword($user, $newPassword));
|
||||
$em->flush();
|
||||
|
||||
// Email de notification du changement
|
||||
$mailer->sendEmail(
|
||||
$sessionEmail,
|
||||
'CRM Ecosplay - Mot de passe modifie',
|
||||
$twig->render('emails/password_changed.html.twig'),
|
||||
null,
|
||||
null,
|
||||
false,
|
||||
);
|
||||
}
|
||||
|
||||
// Nettoyer la session
|
||||
$request->getSession()->remove('reset_step');
|
||||
$request->getSession()->remove('reset_code');
|
||||
$request->getSession()->remove('reset_email');
|
||||
$request->getSession()->remove('reset_expires');
|
||||
|
||||
$this->addFlash('success', 'Votre mot de passe a ete modifie avec succes.');
|
||||
$result = $this->handleReset($request, $userRepository, $passwordHasher, $em, $mailer, $twig);
|
||||
|
||||
if ($result['redirect']) {
|
||||
return $this->redirectToRoute('app_home');
|
||||
}
|
||||
|
||||
$step = $result['step'];
|
||||
$error = $result['error'];
|
||||
}
|
||||
}
|
||||
|
||||
return $this->render('security/forgot_password.html.twig', [
|
||||
'step' => $step,
|
||||
'email' => $request->getSession()->get('reset_email', ''),
|
||||
'email' => $session->get('reset_email', ''),
|
||||
'error' => $error,
|
||||
'success' => $success,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{string, string}
|
||||
*/
|
||||
private function handleSendCode(Request $request, UserRepository $userRepository, MailerService $mailer, Environment $twig): array
|
||||
{
|
||||
$session = $request->getSession();
|
||||
$email = trim($request->request->getString('email'));
|
||||
$code = str_pad((string) random_int(0, 999999), 6, '0', STR_PAD_LEFT);
|
||||
|
||||
$session->set('reset_code', $code);
|
||||
$session->set('reset_email', $email);
|
||||
$session->set('reset_expires', time() + 600);
|
||||
$session->set('reset_step', 'code');
|
||||
|
||||
$user = $userRepository->findOneBy(['email' => $email]);
|
||||
if (null !== $user) {
|
||||
$mailer->sendEmail(
|
||||
$email,
|
||||
'CRM Ecosplay - Code de reinitialisation',
|
||||
$twig->render('emails/forgot_password_code.html.twig', ['code' => $code]),
|
||||
null,
|
||||
null,
|
||||
false,
|
||||
);
|
||||
}
|
||||
|
||||
return ['code', 'Si un compte existe avec cette adresse, un code vous a ete envoye par email.'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{redirect: bool, step: string, error: string|null}
|
||||
*/
|
||||
private function handleReset(Request $request, UserRepository $userRepository, UserPasswordHasherInterface $passwordHasher, EntityManagerInterface $em, MailerService $mailer, Environment $twig): array
|
||||
{
|
||||
$session = $request->getSession();
|
||||
$inputCode = trim($request->request->getString('code'));
|
||||
$newPassword = $request->request->getString('password');
|
||||
$sessionCode = $session->get('reset_code');
|
||||
$sessionEmail = $session->get('reset_email');
|
||||
$expires = $session->get('reset_expires', 0);
|
||||
|
||||
if (time() > $expires) {
|
||||
$this->clearResetSession($session);
|
||||
|
||||
return ['redirect' => false, 'step' => 'email', 'error' => 'Le code a expire. Veuillez recommencer.'];
|
||||
}
|
||||
|
||||
if (!hash_equals($sessionCode, $inputCode)) {
|
||||
return ['redirect' => false, 'step' => 'code', 'error' => 'Le code est incorrect.'];
|
||||
}
|
||||
|
||||
if (\strlen($newPassword) < 8) {
|
||||
return ['redirect' => false, 'step' => 'code', 'error' => 'Le mot de passe doit contenir au moins 8 caracteres.'];
|
||||
}
|
||||
|
||||
$user = $userRepository->findOneBy(['email' => $sessionEmail]);
|
||||
if (null !== $user) {
|
||||
$user->setPassword($passwordHasher->hashPassword($user, $newPassword));
|
||||
$em->flush();
|
||||
|
||||
$mailer->sendEmail(
|
||||
$sessionEmail,
|
||||
'CRM Ecosplay - Mot de passe modifie',
|
||||
$twig->render('emails/password_changed.html.twig'),
|
||||
null,
|
||||
null,
|
||||
false,
|
||||
);
|
||||
}
|
||||
|
||||
$this->clearResetSession($session);
|
||||
$this->addFlash('success', 'Votre mot de passe a ete modifie avec succes.');
|
||||
|
||||
return ['redirect' => true, 'step' => 'email', 'error' => null];
|
||||
}
|
||||
|
||||
private function clearResetSession(object $session): void
|
||||
{
|
||||
$session->remove('reset_step');
|
||||
$session->remove('reset_code');
|
||||
$session->remove('reset_email');
|
||||
$session->remove('reset_expires');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,12 @@ use Symfony\Component\Routing\Attribute\Route;
|
||||
|
||||
class HomeController extends AbstractController
|
||||
{
|
||||
private const ROLE_ROUTES = [
|
||||
'ROLE_EMPLOYE' => 'app_admin_dashboard',
|
||||
'ROLE_REVENDEUR' => 'app_espace_prestataire_index',
|
||||
'ROLE_CUSTOMER' => 'app_espace_client_index',
|
||||
];
|
||||
|
||||
#[Route('/', name: 'app_home')]
|
||||
public function index(): Response
|
||||
{
|
||||
@@ -24,16 +30,10 @@ class HomeController extends AbstractController
|
||||
|
||||
private function resolveRedirectRoute(): string
|
||||
{
|
||||
if ($this->isGranted('ROLE_EMPLOYE')) {
|
||||
return 'app_admin_dashboard';
|
||||
}
|
||||
|
||||
if ($this->isGranted('ROLE_REVENDEUR')) {
|
||||
return 'app_espace_prestataire_index';
|
||||
}
|
||||
|
||||
if ($this->isGranted('ROLE_CUSTOMER')) {
|
||||
return 'app_espace_client_index';
|
||||
foreach (self::ROLE_ROUTES as $role => $route) {
|
||||
if ($this->isGranted($role)) {
|
||||
return $route;
|
||||
}
|
||||
}
|
||||
|
||||
return 'app_home';
|
||||
|
||||
@@ -11,6 +11,7 @@ use Symfony\Component\Routing\Attribute\Route;
|
||||
#[Route('/legal', name: 'app_legal_')]
|
||||
class LegalController extends AbstractController
|
||||
{
|
||||
private const RGPD_ANCHOR = '#exercer-droits';
|
||||
#[Route('/mention-legal', name: 'mention_legal')]
|
||||
public function mentionLegal(): Response
|
||||
{
|
||||
@@ -109,7 +110,7 @@ class LegalController extends AbstractController
|
||||
if ('' === $ip || '' === $email) {
|
||||
$this->addFlash('error', 'Veuillez remplir tous les champs.');
|
||||
|
||||
return $this->redirect($this->generateUrl('app_legal_rgpd').'#exercer-droits');
|
||||
return $this->redirect($this->generateUrl('app_legal_rgpd').self::RGPD_ANCHOR);
|
||||
}
|
||||
|
||||
try {
|
||||
@@ -124,7 +125,7 @@ class LegalController extends AbstractController
|
||||
$this->addFlash('error', 'Une erreur est survenue lors du traitement de votre demande. Veuillez reessayer ou nous contacter a contact@e-cosplay.fr.');
|
||||
}
|
||||
|
||||
return $this->redirect($this->generateUrl('app_legal_rgpd').'#exercer-droits');
|
||||
return $this->redirect($this->generateUrl('app_legal_rgpd').self::RGPD_ANCHOR);
|
||||
}
|
||||
|
||||
#[Route('/rgpd/suppression', name: 'rgpd_deletion', methods: ['POST'])]
|
||||
@@ -136,7 +137,7 @@ class LegalController extends AbstractController
|
||||
if ('' === $ip || '' === $email) {
|
||||
$this->addFlash('error', 'Veuillez remplir tous les champs.');
|
||||
|
||||
return $this->redirect($this->generateUrl('app_legal_rgpd').'#exercer-droits');
|
||||
return $this->redirect($this->generateUrl('app_legal_rgpd').self::RGPD_ANCHOR);
|
||||
}
|
||||
|
||||
try {
|
||||
@@ -151,6 +152,6 @@ class LegalController extends AbstractController
|
||||
$this->addFlash('error', 'Une erreur est survenue lors du traitement de votre demande. Veuillez reessayer ou nous contacter a contact@e-cosplay.fr.');
|
||||
}
|
||||
|
||||
return $this->redirect($this->generateUrl('app_legal_rgpd').'#exercer-droits');
|
||||
return $this->redirect($this->generateUrl('app_legal_rgpd').self::RGPD_ANCHOR);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,6 +16,8 @@ use Twig\Environment;
|
||||
|
||||
class WebhookDocuSealController extends AbstractController
|
||||
{
|
||||
private const ATTESTATION_NOT_FOUND = 'Attestation not found';
|
||||
|
||||
#[Route('/webhooks/docuseal', name: 'app_webhook_docuseal', methods: ['POST'])]
|
||||
public function __invoke(
|
||||
Request $request,
|
||||
@@ -27,14 +29,8 @@ class WebhookDocuSealController extends AbstractController
|
||||
#[Autowire(env: 'DOCUSEAL_WEBHOOKS_SECRET')] string $secret,
|
||||
#[Autowire('%kernel.project_dir%')] string $projectDir,
|
||||
): Response {
|
||||
// Verifier le secret
|
||||
$headerValue = $request->headers->get($secretHeader, '');
|
||||
if ('' !== $secret && !hash_equals($secret, $headerValue)) {
|
||||
$body = json_decode($request->getContent(), true);
|
||||
$bodySecret = $body['secret'] ?? $body['webhook_secret'] ?? '';
|
||||
if ('' === $bodySecret || !hash_equals($secret, $bodySecret)) {
|
||||
return new JsonResponse(['error' => 'Unauthorized'], Response::HTTP_UNAUTHORIZED);
|
||||
}
|
||||
if (!$this->verifySecret($request, $secretHeader, $secret)) {
|
||||
return new JsonResponse(['error' => 'Unauthorized'], Response::HTTP_UNAUTHORIZED);
|
||||
}
|
||||
|
||||
$payload = json_decode($request->getContent(), true);
|
||||
@@ -49,30 +45,54 @@ class WebhookDocuSealController extends AbstractController
|
||||
$metadata = $data['metadata'] ?? [];
|
||||
$docType = $metadata['doc_type'] ?? null;
|
||||
|
||||
// Router par doc_type
|
||||
// Aussi chercher par reference dans le metadata
|
||||
if ('attestation' === $docType && null === $this->findAttestation($submitterId, $attestationRepository)) {
|
||||
$reference = $metadata['reference'] ?? null;
|
||||
if (null !== $reference) {
|
||||
$attestation = $attestationRepository->findOneBy(['reference' => $reference]);
|
||||
if (null !== $attestation) {
|
||||
$attestation->setSubmitterId((int) $submitterId);
|
||||
$em->flush();
|
||||
}
|
||||
}
|
||||
$this->syncSubmitterIdFromMetadata($docType, $submitterId, $metadata, $attestationRepository, $em);
|
||||
|
||||
if ('attestation' !== $docType && null === $this->findAttestation($submitterId, $attestationRepository)) {
|
||||
return new JsonResponse(['status' => 'ignored', 'doc_type' => $docType]);
|
||||
}
|
||||
|
||||
if ('attestation' === $docType || null !== $this->findAttestation($submitterId, $attestationRepository)) {
|
||||
return match ($eventType) {
|
||||
'form.viewed' => $this->handleAttestationViewed($submitterId, $attestationRepository, $em),
|
||||
'form.started' => $this->handleAttestationStarted($submitterId, $attestationRepository, $em),
|
||||
'form.completed' => $this->handleAttestationCompleted($data, $attestationRepository, $mailer, $em, $twig, $projectDir),
|
||||
'form.declined' => $this->handleAttestationDeclined($data, $attestationRepository, $em),
|
||||
default => new JsonResponse(['status' => 'ignored', 'event' => $eventType]),
|
||||
};
|
||||
return match ($eventType) {
|
||||
'form.viewed' => $this->handleAttestationViewed($submitterId, $attestationRepository),
|
||||
'form.started' => $this->handleAttestationStarted($submitterId, $attestationRepository),
|
||||
'form.completed' => $this->handleAttestationCompleted($data, $attestationRepository, $mailer, $em, $twig, $projectDir),
|
||||
'form.declined' => $this->handleAttestationDeclined($data, $attestationRepository, $em),
|
||||
default => new JsonResponse(['status' => 'ignored', 'event' => $eventType]),
|
||||
};
|
||||
}
|
||||
|
||||
private function verifySecret(Request $request, string $secretHeader, string $secret): bool
|
||||
{
|
||||
if ('' === $secret) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return new JsonResponse(['status' => 'ignored', 'doc_type' => $docType]);
|
||||
$headerValue = $request->headers->get($secretHeader, '');
|
||||
if (hash_equals($secret, $headerValue)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$body = json_decode($request->getContent(), true);
|
||||
$bodySecret = $body['secret'] ?? $body['webhook_secret'] ?? '';
|
||||
|
||||
return '' !== $bodySecret && hash_equals($secret, $bodySecret);
|
||||
}
|
||||
|
||||
private function syncSubmitterIdFromMetadata(?string $docType, mixed $submitterId, array $metadata, AttestationRepository $attestationRepository, EntityManagerInterface $em): void
|
||||
{
|
||||
if ('attestation' !== $docType || null === $submitterId || null !== $this->findAttestation($submitterId, $attestationRepository)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$reference = $metadata['reference'] ?? null;
|
||||
if (null === $reference) {
|
||||
return;
|
||||
}
|
||||
|
||||
$attestation = $attestationRepository->findOneBy(['reference' => $reference]);
|
||||
if (null !== $attestation) {
|
||||
$attestation->setSubmitterId((int) $submitterId);
|
||||
$em->flush();
|
||||
}
|
||||
}
|
||||
|
||||
private function findAttestation(?int $submitterId, AttestationRepository $repository): ?Attestation
|
||||
@@ -84,30 +104,23 @@ class WebhookDocuSealController extends AbstractController
|
||||
return $repository->findOneBy(['submitterId' => $submitterId]);
|
||||
}
|
||||
|
||||
private function handleAttestationViewed(
|
||||
?int $submitterId,
|
||||
AttestationRepository $attestationRepository,
|
||||
EntityManagerInterface $em,
|
||||
): JsonResponse {
|
||||
private function handleAttestationViewed(?int $submitterId, AttestationRepository $attestationRepository): JsonResponse
|
||||
{
|
||||
$attestation = $this->findAttestation($submitterId, $attestationRepository);
|
||||
|
||||
if (null === $attestation) {
|
||||
return new JsonResponse(['error' => 'Attestation not found'], Response::HTTP_NOT_FOUND);
|
||||
return new JsonResponse(['error' => self::ATTESTATION_NOT_FOUND], Response::HTTP_NOT_FOUND);
|
||||
}
|
||||
|
||||
// Rien a changer pour le moment, juste un log
|
||||
return new JsonResponse(['status' => 'ok', 'event' => 'viewed', 'reference' => $attestation->getReference()]);
|
||||
}
|
||||
|
||||
private function handleAttestationStarted(
|
||||
?int $submitterId,
|
||||
AttestationRepository $attestationRepository,
|
||||
EntityManagerInterface $em,
|
||||
): JsonResponse {
|
||||
private function handleAttestationStarted(?int $submitterId, AttestationRepository $attestationRepository): JsonResponse
|
||||
{
|
||||
$attestation = $this->findAttestation($submitterId, $attestationRepository);
|
||||
|
||||
if (null === $attestation) {
|
||||
return new JsonResponse(['error' => 'Attestation not found'], Response::HTTP_NOT_FOUND);
|
||||
return new JsonResponse(['error' => self::ATTESTATION_NOT_FOUND], Response::HTTP_NOT_FOUND);
|
||||
}
|
||||
|
||||
return new JsonResponse(['status' => 'ok', 'event' => 'started', 'reference' => $attestation->getReference()]);
|
||||
@@ -128,15 +141,12 @@ class WebhookDocuSealController extends AbstractController
|
||||
$attestation = $this->findAttestation($submitterId, $attestationRepository);
|
||||
|
||||
if (null === $attestation) {
|
||||
return new JsonResponse(['error' => 'Attestation not found'], Response::HTTP_NOT_FOUND);
|
||||
return new JsonResponse(['error' => self::ATTESTATION_NOT_FOUND], Response::HTTP_NOT_FOUND);
|
||||
}
|
||||
|
||||
$attestation->markAsSigned();
|
||||
|
||||
// Telecharger le PDF signe et le certificat depuis les URLs du webhook
|
||||
$this->downloadDocumentsFromWebhook($data, $attestation, $projectDir);
|
||||
|
||||
// Envoyer l'attestation signee par email
|
||||
$signedPdf = $attestation->getPdfFileSigned();
|
||||
$certificatePdf = $attestation->getPdfFileCertificate();
|
||||
|
||||
@@ -186,7 +196,6 @@ class WebhookDocuSealController extends AbstractController
|
||||
|
||||
$ref = $attestation->getReference();
|
||||
|
||||
// PDF signe
|
||||
$documents = $data['documents'] ?? [];
|
||||
$pdfUrl = $documents[0]['url'] ?? null;
|
||||
if (null !== $pdfUrl) {
|
||||
@@ -198,7 +207,6 @@ class WebhookDocuSealController extends AbstractController
|
||||
}
|
||||
}
|
||||
|
||||
// Certificat d'audit
|
||||
$auditUrl = $data['audit_log_url'] ?? null;
|
||||
if (null !== $auditUrl) {
|
||||
$content = @file_get_contents($auditUrl);
|
||||
@@ -222,13 +230,10 @@ class WebhookDocuSealController extends AbstractController
|
||||
$attestation = $this->findAttestation($submitterId, $attestationRepository);
|
||||
|
||||
if (null === $attestation) {
|
||||
return new JsonResponse(['error' => 'Attestation not found'], Response::HTTP_NOT_FOUND);
|
||||
return new JsonResponse(['error' => self::ATTESTATION_NOT_FOUND], Response::HTTP_NOT_FOUND);
|
||||
}
|
||||
|
||||
$reason = $data['decline_reason'] ?? 'Aucune raison fournie';
|
||||
|
||||
if ('signed' !== $attestation->getStatus()) {
|
||||
// On remet en pending pour pouvoir retenter
|
||||
$em->flush();
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user