feat: ajout signature HMAC SHA-256 sur Devis, Advert et Facture
src/Entity/Devis.php:
- Nouveau champ hmac (string 128), genere dans le constructeur
- Constructeur prend maintenant le hmacSecret en 2eme parametre
- generateHmac(): payload = "devis|numOrder|createdAt" signe avec APP_SECRET
- verifyHmac(): verification par hash_equals (timing-safe)
src/Entity/Advert.php:
- Nouveau champ hmac (string 128), genere dans le constructeur
- Constructeur prend maintenant le hmacSecret en 2eme parametre
- generateHmac(): payload = "advert|numOrder|createdAt" signe avec APP_SECRET
- verifyHmac(): verification par hash_equals
src/Entity/Facture.php:
- Nouveau champ hmac (string 128), genere dans le constructeur
- Constructeur prend maintenant le hmacSecret en 2eme parametre
- generateHmac(): payload = "facture|numOrder|splitIndex|createdAt"
signe avec APP_SECRET (inclut splitIndex pour differencier les splits)
- verifyHmac(): verification par hash_equals
src/Service/DevisService.php, AdvertService.php, FactureService.php:
- Injection de APP_SECRET via #[Autowire('%env(APP_SECRET)%')]
- Passage du hmacSecret aux constructeurs des entites
migrations/Version20260402203207.php:
- Ajout colonne hmac VARCHAR(128) NOT NULL sur devis, advert, facture
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
35
migrations/Version20260402203207.php
Normal file
35
migrations/Version20260402203207.php
Normal file
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace DoctrineMigrations;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
/**
|
||||
* Auto-generated Migration: Please modify to your needs!
|
||||
*/
|
||||
final class Version20260402203207 extends AbstractMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
// this up() migration is auto-generated, please modify it to your needs
|
||||
$this->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');
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user