diff --git a/migrations/Version20260402203207.php b/migrations/Version20260402203207.php new file mode 100644 index 0000000..e6d135e --- /dev/null +++ b/migrations/Version20260402203207.php @@ -0,0 +1,35 @@ +addSql('ALTER TABLE advert ADD hmac VARCHAR(128) NOT NULL'); + $this->addSql('ALTER TABLE devis ADD hmac VARCHAR(128) NOT NULL'); + $this->addSql('ALTER TABLE facture ADD hmac VARCHAR(128) NOT NULL'); + } + + public function down(Schema $schema): void + { + // this down() migration is auto-generated, please modify it to your needs + $this->addSql('ALTER TABLE advert DROP hmac'); + $this->addSql('ALTER TABLE devis DROP hmac'); + $this->addSql('ALTER TABLE facture DROP hmac'); + } +} diff --git a/src/Entity/Advert.php b/src/Entity/Advert.php index 92db721..ca9754b 100644 --- a/src/Entity/Advert.php +++ b/src/Entity/Advert.php @@ -23,6 +23,9 @@ class Advert #[ORM\JoinColumn(nullable: true)] private ?Devis $devis = null; + #[ORM\Column(length: 128)] + private string $hmac; + #[ORM\Column] private \DateTimeImmutable $createdAt; @@ -30,11 +33,12 @@ class Advert #[ORM\OneToMany(targetEntity: Facture::class, mappedBy: 'advert')] private Collection $factures; - public function __construct(OrderNumber $orderNumber) + public function __construct(OrderNumber $orderNumber, string $hmacSecret) { $this->orderNumber = $orderNumber; $this->createdAt = new \DateTimeImmutable(); $this->factures = new ArrayCollection(); + $this->hmac = $this->generateHmac($hmacSecret); } public function getId(): ?int @@ -57,6 +61,11 @@ class Advert $this->devis = $devis; } + public function getHmac(): string + { + return $this->hmac; + } + public function getCreatedAt(): \DateTimeImmutable { return $this->createdAt; @@ -67,4 +76,20 @@ class Advert { return $this->factures; } + + public function verifyHmac(string $hmacSecret): bool + { + return hash_equals($this->hmac, $this->generateHmac($hmacSecret)); + } + + private function generateHmac(string $secret): string + { + $payload = implode('|', [ + 'advert', + $this->orderNumber->getNumOrder(), + $this->createdAt->format('Y-m-d\TH:i:s'), + ]); + + return hash_hmac('sha256', $payload, $secret); + } } diff --git a/src/Entity/Devis.php b/src/Entity/Devis.php index bc94960..94ddcf3 100644 --- a/src/Entity/Devis.php +++ b/src/Entity/Devis.php @@ -19,6 +19,9 @@ class Devis #[ORM\JoinColumn(nullable: false)] private OrderNumber $orderNumber; + #[ORM\Column(length: 128)] + private string $hmac; + #[ORM\Column] private \DateTimeImmutable $createdAt; @@ -26,11 +29,12 @@ class Devis #[ORM\OneToMany(targetEntity: Advert::class, mappedBy: 'devis')] private Collection $adverts; - public function __construct(OrderNumber $orderNumber) + public function __construct(OrderNumber $orderNumber, string $hmacSecret) { $this->orderNumber = $orderNumber; $this->createdAt = new \DateTimeImmutable(); $this->adverts = new ArrayCollection(); + $this->hmac = $this->generateHmac($hmacSecret); } public function getId(): ?int @@ -43,6 +47,11 @@ class Devis return $this->orderNumber; } + public function getHmac(): string + { + return $this->hmac; + } + public function getCreatedAt(): \DateTimeImmutable { return $this->createdAt; @@ -53,4 +62,20 @@ class Devis { return $this->adverts; } + + public function verifyHmac(string $hmacSecret): bool + { + return hash_equals($this->hmac, $this->generateHmac($hmacSecret)); + } + + private function generateHmac(string $secret): string + { + $payload = implode('|', [ + 'devis', + $this->orderNumber->getNumOrder(), + $this->createdAt->format('Y-m-d\TH:i:s'), + ]); + + return hash_hmac('sha256', $payload, $secret); + } } diff --git a/src/Entity/Facture.php b/src/Entity/Facture.php index 0ed4a56..5b94885 100644 --- a/src/Entity/Facture.php +++ b/src/Entity/Facture.php @@ -27,13 +27,17 @@ class Facture #[ORM\Column(type: 'smallint', options: ['default' => 0])] private int $splitIndex = 0; + #[ORM\Column(length: 128)] + private string $hmac; + #[ORM\Column] private \DateTimeImmutable $createdAt; - public function __construct(OrderNumber $orderNumber) + public function __construct(OrderNumber $orderNumber, string $hmacSecret) { $this->orderNumber = $orderNumber; $this->createdAt = new \DateTimeImmutable(); + $this->hmac = $this->generateHmac($hmacSecret); } public function getId(): ?int @@ -77,8 +81,30 @@ class Facture return $this->splitIndex > 0 ? $num.'-'.$this->splitIndex : $num; } + public function getHmac(): string + { + return $this->hmac; + } + public function getCreatedAt(): \DateTimeImmutable { return $this->createdAt; } + + public function verifyHmac(string $hmacSecret): bool + { + return hash_equals($this->hmac, $this->generateHmac($hmacSecret)); + } + + private function generateHmac(string $secret): string + { + $payload = implode('|', [ + 'facture', + $this->orderNumber->getNumOrder(), + (string) $this->splitIndex, + $this->createdAt->format('Y-m-d\TH:i:s'), + ]); + + return hash_hmac('sha256', $payload, $secret); + } } diff --git a/src/Service/AdvertService.php b/src/Service/AdvertService.php index f3224c2..9a57c7d 100644 --- a/src/Service/AdvertService.php +++ b/src/Service/AdvertService.php @@ -5,25 +5,24 @@ namespace App\Service; use App\Entity\Advert; use App\Entity\Devis; use Doctrine\ORM\EntityManagerInterface; +use Symfony\Component\DependencyInjection\Attribute\Autowire; class AdvertService { public function __construct( private OrderNumberService $orderNumberService, private EntityManagerInterface $em, + #[Autowire('%env(APP_SECRET)%')] private string $hmacSecret, ) { } - /** - * Cree un advert avec un nouveau numero de commande. - */ public function create(?Devis $devis = null): Advert { $orderNumber = null !== $devis ? $devis->getOrderNumber() : $this->orderNumberService->generateAndUse(); - $advert = new Advert($orderNumber); + $advert = new Advert($orderNumber, $this->hmacSecret); if (null !== $devis) { $advert->setDevis($devis); @@ -35,9 +34,6 @@ class AdvertService return $advert; } - /** - * Cree un advert a partir d'un devis existant (meme numero). - */ public function createFromDevis(Devis $devis): Advert { return $this->create($devis); diff --git a/src/Service/DevisService.php b/src/Service/DevisService.php index 820ecf4..3e6f25a 100644 --- a/src/Service/DevisService.php +++ b/src/Service/DevisService.php @@ -4,22 +4,21 @@ namespace App\Service; use App\Entity\Devis; use Doctrine\ORM\EntityManagerInterface; +use Symfony\Component\DependencyInjection\Attribute\Autowire; class DevisService { public function __construct( private OrderNumberService $orderNumberService, private EntityManagerInterface $em, + #[Autowire('%env(APP_SECRET)%')] private string $hmacSecret, ) { } - /** - * Cree un devis avec un nouveau numero de commande. - */ public function create(): Devis { $orderNumber = $this->orderNumberService->generateAndUse(); - $devis = new Devis($orderNumber); + $devis = new Devis($orderNumber, $this->hmacSecret); $this->em->persist($devis); $this->em->flush(); diff --git a/src/Service/FactureService.php b/src/Service/FactureService.php index 6dba0b9..f56c68e 100644 --- a/src/Service/FactureService.php +++ b/src/Service/FactureService.php @@ -5,18 +5,17 @@ namespace App\Service; use App\Entity\Advert; use App\Entity\Facture; use Doctrine\ORM\EntityManagerInterface; +use Symfony\Component\DependencyInjection\Attribute\Autowire; class FactureService { public function __construct( private OrderNumberService $orderNumberService, private EntityManagerInterface $em, + #[Autowire('%env(APP_SECRET)%')] private string $hmacSecret, ) { } - /** - * Cree une facture avec un nouveau numero de commande (facture seule). - */ public function create(?Advert $advert = null): Facture { if (null !== $advert) { @@ -24,7 +23,7 @@ class FactureService } $orderNumber = $this->orderNumberService->generateAndUse(); - $facture = new Facture($orderNumber); + $facture = new Facture($orderNumber, $this->hmacSecret); $this->em->persist($facture); $this->em->flush(); @@ -32,20 +31,16 @@ class FactureService return $facture; } - /** - * Cree une facture a partir d'un advert (meme numero + splitIndex si plusieurs). - */ public function createFromAdvert(Advert $advert): Facture { $existingCount = $advert->getFactures()->count(); - $facture = new Facture($advert->getOrderNumber()); + $facture = new Facture($advert->getOrderNumber(), $this->hmacSecret); $facture->setAdvert($advert); if ($existingCount > 0) { $facture->setSplitIndex($existingCount + 1); - // La premiere facture existante doit aussi avoir un splitIndex si elle n'en a pas $firstFacture = $advert->getFactures()->first(); if (false !== $firstFacture && 0 === $firstFacture->getSplitIndex()) { $firstFacture->setSplitIndex(1);