```
✨ feat(EsyWeb): Ajoute gestion des licences et clés DMA pour sites web
Ajoute la gestion des licences pour les sites web EsyWeb, incluant
la génération, le renouvellement et la validation. Intègre aussi
la création et l'utilisation de clés DMA.
```
This commit is contained in:
2
.env
2
.env
@@ -72,7 +72,7 @@ AMAZON_SES_SECRET=BD63dADmgFJJPnjlT9utRDlvcOh8pRH3eOZXsyhNL/F3
|
||||
# MAILER_DSN=ses://ACCESS_KEY:SECRET_KEY@default?region=eu-west-1
|
||||
# MAILER_DSN=ses+smtp://ACCESS_KEY:SECRET_KEY@default?region=eu-west-1
|
||||
###< symfony/amazon-mailer ###
|
||||
CLOUDFLARE_TOKEN=4mqx9d7ynvoeCaXonJA07U19rH8gGhctqp7j2Lch
|
||||
CLOUDFLARE_TOKEN=oSpqBIuiKc3waClbo3si4Y8dXZSVt8anijQiHY9N
|
||||
MAILCOW_KEY=DF0E7E-0FD059-16226F-8ECFF1-E558B3
|
||||
|
||||
DEV_URL=https://086e682e904b.ngrok-free.app
|
||||
|
||||
37
migrations/Version20251112130852.php
Normal file
37
migrations/Version20251112130852.php
Normal file
@@ -0,0 +1,37 @@
|
||||
<?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 Version20251112130852 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('CREATE TABLE website_license (id SERIAL NOT NULL, website_id INT DEFAULT NULL, type VARCHAR(255) NOT NULL, license_number VARCHAR(255) NOT NULL, public_key TEXT NOT NULL, private_key TEXT NOT NULL, create_at TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, expires_at TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, PRIMARY KEY(id))');
|
||||
$this->addSql('CREATE INDEX IDX_FB4688BD18F45C82 ON website_license (website_id)');
|
||||
$this->addSql('COMMENT ON COLUMN website_license.create_at IS \'(DC2Type:datetime_immutable)\'');
|
||||
$this->addSql('COMMENT ON COLUMN website_license.expires_at IS \'(DC2Type:datetime_immutable)\'');
|
||||
$this->addSql('ALTER TABLE website_license ADD CONSTRAINT FK_FB4688BD18F45C82 FOREIGN KEY (website_id) REFERENCES website (id) NOT DEFERRABLE INITIALLY IMMEDIATE');
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
// this down() migration is auto-generated, please modify it to your needs
|
||||
$this->addSql('CREATE SCHEMA public');
|
||||
$this->addSql('ALTER TABLE website_license DROP CONSTRAINT FK_FB4688BD18F45C82');
|
||||
$this->addSql('DROP TABLE website_license');
|
||||
}
|
||||
}
|
||||
@@ -5,9 +5,12 @@ namespace App\Controller\Artemis\EsyWeb;
|
||||
use App\Entity\EsyWeb\Website;
|
||||
use App\Entity\EsyWeb\WebsiteDns;
|
||||
use App\Entity\EsyWeb\WebsiteKey;
|
||||
use App\Entity\EsyWeb\WebsiteLicense;
|
||||
use App\Form\Artemis\EsyWeb\WebsiteType;
|
||||
use App\Repository\EsyWeb\WebsiteRepository;
|
||||
use App\Repository\EsyWebTutoRepository;
|
||||
use App\Service\Cloudflare\Client;
|
||||
use App\Service\License\LicenseManager;
|
||||
use App\Service\Logger\LoggerService;
|
||||
use App\Service\Website\EventCancelWebsite;
|
||||
use App\Service\Website\EventCreatedWebsite;
|
||||
@@ -24,7 +27,10 @@ use Vich\UploaderBundle\Templating\Helper\UploaderHelper;
|
||||
class EsyWebController extends AbstractController
|
||||
{
|
||||
#[Route(path: '/artemis/esyweb/website', name: 'artemis_esyweb', methods: ['GET', 'POST'])]
|
||||
public function websites(LoggerService $loggerService,EventDispatcherInterface $eventDispatcher,Request $request,EntityManagerInterface $entityManager,WebsiteRepository $websiteRepository)
|
||||
public function websites(
|
||||
\App\Service\Dma\Client $clientDma,
|
||||
LicenseManager $licenseManager,
|
||||
Client $client,LoggerService $loggerService,EventDispatcherInterface $eventDispatcher,Request $request,EntityManagerInterface $entityManager,WebsiteRepository $websiteRepository)
|
||||
{
|
||||
$loggerService->log("VIEW","Affiche la page de site internet",$this->getUser());
|
||||
|
||||
@@ -47,7 +53,20 @@ class EsyWebController extends AbstractController
|
||||
$website = $websiteRepository->find($request->query->get('idValidate'));
|
||||
$website->setState("validate");
|
||||
$entityManager->persist($website);
|
||||
|
||||
$slug = new Slugify();
|
||||
$client->createEsyWebDev($slug->slugify($website->getTitle()).".esy-web.dev",$website->getServer()->getExternalIp());
|
||||
|
||||
$websiteKey = new WebsiteKey();
|
||||
$websiteKey->setType("dma_key");
|
||||
$websiteKey->setApiKey($clientDma->createDma($slug->slugify($website->getTitle())));
|
||||
$websiteKey->setWebsitre($website);
|
||||
$entityManager->persist($websiteKey);
|
||||
|
||||
$vd =$licenseManager->generateAndSaveLicense($website,'main_license');
|
||||
$entityManager->persist($vd);
|
||||
$entityManager->flush();
|
||||
|
||||
$loggerService->log("VALIDATE","Validation du site internet",$this->getUser());
|
||||
return $this->redirectToRoute('artemis_esyweb');
|
||||
}
|
||||
@@ -56,13 +75,12 @@ class EsyWebController extends AbstractController
|
||||
]);
|
||||
}
|
||||
#[Route(path: '/artemis/esyweb/website/{id}', name: 'artemis_esyweb_view', methods: ['GET', 'POST'])]
|
||||
public function websiteView(?Website $website,LoggerService $loggerService,Request $request,EntityManagerInterface $entityManager,WebsiteRepository $websiteRepository)
|
||||
public function websiteView(?Website $website,LicenseManager $licenseManager,LoggerService $loggerService,Request $request,EntityManagerInterface $entityManager,WebsiteRepository $websiteRepository)
|
||||
{
|
||||
if(is_null($website)) {
|
||||
return $this->redirectToRoute('artemis_esyweb');
|
||||
}
|
||||
$loggerService->log("VIEW","Affiche la page de site internet - ".$website->getTitle(),$this->getUser());
|
||||
|
||||
return $this->render('artemis/esyweb/website_view.twig', [
|
||||
'current' => $request->get('current','main'),
|
||||
'website' => $website
|
||||
|
||||
@@ -56,10 +56,17 @@ class Website
|
||||
#[ORM\ManyToOne(inversedBy: 'websites')]
|
||||
private ?Compute $server = null;
|
||||
|
||||
/**
|
||||
* @var Collection<int, WebsiteLicense>
|
||||
*/
|
||||
#[ORM\OneToMany(targetEntity: WebsiteLicense::class, mappedBy: 'website')]
|
||||
private Collection $websiteLicenses;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->websiteDns = new ArrayCollection();
|
||||
$this->websiteKeys = new ArrayCollection();
|
||||
$this->websiteLicenses = new ArrayCollection();
|
||||
}
|
||||
|
||||
public function getId(): ?int
|
||||
@@ -228,4 +235,34 @@ class Website
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Collection<int, WebsiteLicense>
|
||||
*/
|
||||
public function getWebsiteLicenses(): Collection
|
||||
{
|
||||
return $this->websiteLicenses;
|
||||
}
|
||||
|
||||
public function addWebsiteLicense(WebsiteLicense $websiteLicense): static
|
||||
{
|
||||
if (!$this->websiteLicenses->contains($websiteLicense)) {
|
||||
$this->websiteLicenses->add($websiteLicense);
|
||||
$websiteLicense->setWebsite($this);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function removeWebsiteLicense(WebsiteLicense $websiteLicense): static
|
||||
{
|
||||
if ($this->websiteLicenses->removeElement($websiteLicense)) {
|
||||
// set the owning side to null (unless already changed)
|
||||
if ($websiteLicense->getWebsite() === $this) {
|
||||
$websiteLicense->setWebsite(null);
|
||||
}
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
||||
126
src/Entity/EsyWeb/WebsiteLicense.php
Normal file
126
src/Entity/EsyWeb/WebsiteLicense.php
Normal file
@@ -0,0 +1,126 @@
|
||||
<?php
|
||||
|
||||
namespace App\Entity\EsyWeb;
|
||||
|
||||
use App\Repository\EsyWeb\WebsiteLicenseRepository;
|
||||
use Doctrine\DBAL\Types\Types;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
|
||||
#[ORM\Entity(repositoryClass: WebsiteLicenseRepository::class)]
|
||||
class WebsiteLicense
|
||||
{
|
||||
#[ORM\Id]
|
||||
#[ORM\GeneratedValue]
|
||||
#[ORM\Column]
|
||||
private ?int $id = null;
|
||||
|
||||
#[ORM\ManyToOne(inversedBy: 'websiteLicenses')]
|
||||
private ?Website $website = null;
|
||||
|
||||
#[ORM\Column(length: 255)]
|
||||
private ?string $type = null;
|
||||
|
||||
#[ORM\Column(length: 255)]
|
||||
private ?string $licenseNumber = null;
|
||||
|
||||
#[ORM\Column(type: Types::TEXT)]
|
||||
private ?string $publicKey = null;
|
||||
|
||||
#[ORM\Column(type: Types::TEXT)]
|
||||
private ?string $privateKey = null;
|
||||
|
||||
#[ORM\Column]
|
||||
private ?\DateTimeImmutable $createAt = null;
|
||||
|
||||
#[ORM\Column]
|
||||
private ?\DateTimeImmutable $expiresAt = null;
|
||||
|
||||
public function getId(): ?int
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
public function getWebsite(): ?Website
|
||||
{
|
||||
return $this->website;
|
||||
}
|
||||
|
||||
public function setWebsite(?Website $website): static
|
||||
{
|
||||
$this->website = $website;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getType(): ?string
|
||||
{
|
||||
return $this->type;
|
||||
}
|
||||
|
||||
public function setType(string $type): static
|
||||
{
|
||||
$this->type = $type;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getLicenseNumber(): ?string
|
||||
{
|
||||
return $this->licenseNumber;
|
||||
}
|
||||
|
||||
public function setLicenseNumber(string $licenseNumber): static
|
||||
{
|
||||
$this->licenseNumber = $licenseNumber;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getPublicKey(): ?string
|
||||
{
|
||||
return $this->publicKey;
|
||||
}
|
||||
|
||||
public function setPublicKey(string $publicKey): static
|
||||
{
|
||||
$this->publicKey = $publicKey;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getPrivateKey(): ?string
|
||||
{
|
||||
return $this->privateKey;
|
||||
}
|
||||
|
||||
public function setPrivateKey(string $privateKey): static
|
||||
{
|
||||
$this->privateKey = $privateKey;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getCreateAt(): ?\DateTimeImmutable
|
||||
{
|
||||
return $this->createAt;
|
||||
}
|
||||
|
||||
public function setCreateAt(\DateTimeImmutable $createAt): static
|
||||
{
|
||||
$this->createAt = $createAt;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getExpiresAt(): ?\DateTimeImmutable
|
||||
{
|
||||
return $this->expiresAt;
|
||||
}
|
||||
|
||||
public function setExpiresAt(\DateTimeImmutable $expiresAt): static
|
||||
{
|
||||
$this->expiresAt = $expiresAt;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
43
src/Repository/EsyWeb/WebsiteLicenseRepository.php
Normal file
43
src/Repository/EsyWeb/WebsiteLicenseRepository.php
Normal file
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
namespace App\Repository\EsyWeb;
|
||||
|
||||
use App\Entity\EsyWeb\WebsiteLicense;
|
||||
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
|
||||
use Doctrine\Persistence\ManagerRegistry;
|
||||
|
||||
/**
|
||||
* @extends ServiceEntityRepository<WebsiteLicense>
|
||||
*/
|
||||
class WebsiteLicenseRepository extends ServiceEntityRepository
|
||||
{
|
||||
public function __construct(ManagerRegistry $registry)
|
||||
{
|
||||
parent::__construct($registry, WebsiteLicense::class);
|
||||
}
|
||||
|
||||
// /**
|
||||
// * @return WebsiteLicense[] Returns an array of WebsiteLicense objects
|
||||
// */
|
||||
// public function findByExampleField($value): array
|
||||
// {
|
||||
// return $this->createQueryBuilder('w')
|
||||
// ->andWhere('w.exampleField = :val')
|
||||
// ->setParameter('val', $value)
|
||||
// ->orderBy('w.id', 'ASC')
|
||||
// ->setMaxResults(10)
|
||||
// ->getQuery()
|
||||
// ->getResult()
|
||||
// ;
|
||||
// }
|
||||
|
||||
// public function findOneBySomeField($value): ?WebsiteLicense
|
||||
// {
|
||||
// return $this->createQueryBuilder('w')
|
||||
// ->andWhere('w.exampleField = :val')
|
||||
// ->setParameter('val', $value)
|
||||
// ->getQuery()
|
||||
// ->getOneOrNullResult()
|
||||
// ;
|
||||
// }
|
||||
}
|
||||
35
src/Service/Cloudflare/Client.php
Normal file
35
src/Service/Cloudflare/Client.php
Normal file
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
namespace App\Service\Cloudflare;
|
||||
|
||||
use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface;
|
||||
use Symfony\Contracts\HttpClient\HttpClientInterface;
|
||||
|
||||
class Client
|
||||
{
|
||||
public function __construct(private readonly HttpClientInterface $httpClient)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public function createEsyWebDev(string $entry,string $ip)
|
||||
{
|
||||
try {
|
||||
$this->httpClient->request('POST', 'https://api.cloudflare.com/client/v4/zones/1032d39d4e3231e080db2b294b0065d7/dns_records', [
|
||||
'headers' => [
|
||||
'Content-Type' => 'application/json',
|
||||
'Authorization' => 'Bearer '.$_ENV['CLOUDFLARE_TOKEN'],
|
||||
],
|
||||
'body' => json_encode([
|
||||
'name' => $entry,
|
||||
'ttl' => 3600,
|
||||
'type' => 'A',
|
||||
'content' => $ip,
|
||||
'proxied' => true,
|
||||
])
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
31
src/Service/Dma/Client.php
Normal file
31
src/Service/Dma/Client.php
Normal file
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
namespace App\Service\Dma;
|
||||
|
||||
use Symfony\Contracts\HttpClient\HttpClientInterface;
|
||||
|
||||
class Client
|
||||
{
|
||||
public function __construct(private readonly HttpClientInterface $httpClient)
|
||||
{
|
||||
}
|
||||
|
||||
public function createDma(string $title) : string
|
||||
{
|
||||
$response = $this->httpClient->request('POST','https://esydma.esy-web.dev/admin/apikey',[
|
||||
'headers' => [
|
||||
'Content-Type' => 'application/json',
|
||||
'EsyDMA-ApiKey' => '8cfd81d231bdffeaf0648f52f70d92bd',
|
||||
],
|
||||
'body' => json_encode([
|
||||
'owner' => $title,
|
||||
'roles' => ['ROLE_ADMIN','ROLE_OWNER'],
|
||||
])
|
||||
]);
|
||||
$content = json_encode($response->getContent());
|
||||
$content = json_decode($content,true);
|
||||
$content = json_decode($content,true);
|
||||
return $content['apiKey'];
|
||||
}
|
||||
|
||||
}
|
||||
175
src/Service/License/LicenseManager.php
Normal file
175
src/Service/License/LicenseManager.php
Normal file
@@ -0,0 +1,175 @@
|
||||
<?php
|
||||
|
||||
namespace App\Service\License;
|
||||
|
||||
use App\Entity\EsyWeb\Website;
|
||||
use App\Entity\EsyWeb\WebsiteLicense;
|
||||
use App\Repository\EsyWeb\WebsiteLicenseRepository;
|
||||
use App\Service\Vault\VaultClient;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use DateTimeImmutable;
|
||||
|
||||
class LicenseManager
|
||||
{
|
||||
public function __construct(
|
||||
private WebsiteLicenseRepository $websiteLicenseRepository,
|
||||
private EntityManagerInterface $entityManager,
|
||||
private VaultClient $vaultClient
|
||||
) {
|
||||
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// 1. GÉNÉRATION ET SAUVEGARDE
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Génère une nouvelle paire de clés RSA, chiffre la clé privée et sauvegarde la licence.
|
||||
*/
|
||||
public function generateAndSaveLicense(Website $website,string $type): WebsiteLicense
|
||||
{
|
||||
// Génération des clés RSA
|
||||
$config = ["private_key_bits" => 2048, "private_key_type" => OPENSSL_KEYTYPE_RSA];
|
||||
$res = openssl_pkey_new($config);
|
||||
openssl_pkey_export($res, $rawPrivateKey); // Clé privée brute
|
||||
$details = openssl_pkey_get_details($res);
|
||||
$publicKey = $details['key'];
|
||||
|
||||
// Chiffrement de la clé privée avec Vault
|
||||
$encryptedPrivateKey = $this->vaultClient->encrypt("lc_private_key",$rawPrivateKey );
|
||||
|
||||
// Définition des dates
|
||||
$createdAt = new DateTimeImmutable();
|
||||
$expiresAt = $createdAt->modify('+1 year');
|
||||
|
||||
// Création de l'entité
|
||||
$license = new WebsiteLicense();
|
||||
$license->setLicenseNumber(uniqid('LIC-', true));
|
||||
$license->setPublicKey($publicKey);
|
||||
$license->setPrivateKey($encryptedPrivateKey); // Clé CHIFRÉE
|
||||
$license->setCreateAt($createdAt);
|
||||
$license->setWebsite($website);
|
||||
$license->setExpiresAt($expiresAt);
|
||||
$license->setType($type);
|
||||
|
||||
return $license;
|
||||
}
|
||||
|
||||
/**
|
||||
* Récupère et déchiffre la clé privée stockée pour l'utiliser dans la signature.
|
||||
*/
|
||||
public function getDecryptedPrivateKey(WebsiteLicense $license): string
|
||||
{
|
||||
return $this->vaultClient->decrypt($license->getPrivateKey(), "lc_private_key");
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// 2. VALIDATION (Expiration)
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Valide qu'une licence existe et n'est pas expirée.
|
||||
*/
|
||||
public function isLicenseValidAndNotExpired(string $licenseNumber): bool
|
||||
{
|
||||
/** @var WebsiteLicense|null $license */
|
||||
$license = $this->websiteLicenseRepository->findOneBy(['licenseNumber' => $licenseNumber]);
|
||||
|
||||
if (!$license) {
|
||||
return false; // Licence non trouvée
|
||||
}
|
||||
|
||||
$currentDate = new DateTimeImmutable();
|
||||
$expiresAt = $license->getExpiresAt();
|
||||
|
||||
// Si la date actuelle est APRÈS la date d'expiration, elle est expirée.
|
||||
return $currentDate < $expiresAt;
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// 3. RENOUVELLEMENT
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Renouvelle une licence en prolongeant sa date d'expiration d'un an.
|
||||
*/
|
||||
public function renewLicense(string $licenseNumber): ?WebsiteLicense
|
||||
{
|
||||
/** @var WebsiteLicense|null $license */
|
||||
$license = $this->websiteLicenseRepository->findOneBy(['licenseNumber' => $licenseNumber]);
|
||||
|
||||
if (!$license) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$currentDate = new DateTimeImmutable();
|
||||
$expiresAt = $license->getExpiresAt();
|
||||
|
||||
// Le renouvellement se base sur la date d'expiration actuelle si elle est future,
|
||||
// sinon il se base sur la date du jour (si la licence est déjà expirée).
|
||||
$startDate = ($currentDate > $expiresAt) ? $currentDate : $expiresAt;
|
||||
|
||||
// Calculer la nouvelle date (+1 an)
|
||||
$newExpiresAt = $startDate->modify('+1 year');
|
||||
|
||||
// Mise à jour de l'entité et sauvegarde
|
||||
$license->setExpiresAt($newExpiresAt);
|
||||
$this->entityManager->flush();
|
||||
|
||||
return $license;
|
||||
}
|
||||
|
||||
public function licenseValidAndVerified(string $licenseNumber, string $publicKey, string $signedToken): bool
|
||||
{
|
||||
/** @var WebsiteLicense|null $license */
|
||||
$license = $this->websiteLicenseRepository->findOneBy(['licenseNumber' => $licenseNumber]);
|
||||
|
||||
if (!$license) {
|
||||
// 1. La licence n'existe pas
|
||||
return false;
|
||||
}
|
||||
|
||||
// 2. Vérification de l'Expiration
|
||||
if (!$this->isLicenseValidAndNotExpired($licenseNumber)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 3. Vérification de la concordance de la clé publique
|
||||
// La clé publique fournie doit correspondre à celle stockée (générée par vous).
|
||||
$normalizedStoredKey = trim($license->getPublicKey());
|
||||
$normalizedProvidedKey = trim($publicKey);
|
||||
|
||||
if ($normalizedStoredKey !== $normalizedProvidedKey) {
|
||||
// Clé publique incorrecte. Tentative de falsification de la clé elle-même.
|
||||
return false;
|
||||
}
|
||||
|
||||
// 4. Vérification de la Signature (openssl_verify)
|
||||
|
||||
// Le contenu original signé (le payload) doit être récupéré.
|
||||
// Ici, nous utilisons uniquement le numéro de licence comme "payload"
|
||||
// pour simplifier l'exemple. Dans un système réel, ce serait un JSON
|
||||
// encodé contenant toutes les données (numéro, date, etc.).
|
||||
$payloadToVerify = $licenseNumber;
|
||||
|
||||
// Décoder le token de signature du Base64 (tel que transmis par le client)
|
||||
$signatureBinary = base64_decode($signedToken);
|
||||
|
||||
// Exécuter openssl_verify
|
||||
$verificationResult = openssl_verify(
|
||||
$payloadToVerify,
|
||||
$signatureBinary,
|
||||
$publicKey,
|
||||
OPENSSL_ALGO_SHA256
|
||||
);
|
||||
|
||||
// openssl_verify retourne 1 pour succès, 0 pour échec (invalide), -1 pour erreur.
|
||||
if ($verificationResult !== 1) {
|
||||
// Signature invalide. Le token a été falsifié.
|
||||
return false;
|
||||
}
|
||||
|
||||
// Si tout est passé : non expiré, clé correcte, et signature valide.
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -13,6 +13,7 @@ class VaultClient
|
||||
private const KEYS = [
|
||||
'mainframe_logger',
|
||||
'mainframe_customer',
|
||||
'lc_private_key',
|
||||
];
|
||||
|
||||
public function __construct(private readonly HttpClientInterface $httpClient)
|
||||
@@ -41,7 +42,9 @@ class VaultClient
|
||||
$data = $response->toArray(false);
|
||||
|
||||
return $data['data']['ciphertext'] ?? null;
|
||||
} catch (\Exception $exception) { return null;
|
||||
} catch (\Exception $exception) {
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ use App\Entity\CustomerOrder;
|
||||
use App\Entity\CustomerSplit;
|
||||
use App\Entity\EsyWeb\Website;
|
||||
use App\Entity\EsyWeb\WebsiteKey;
|
||||
use App\Entity\EsyWeb\WebsiteLicense;
|
||||
use Cocur\Slugify\Slugify;
|
||||
use Twig\Extension\AbstractExtension;
|
||||
use Twig\TwigFilter;
|
||||
@@ -26,10 +27,30 @@ class TwigOrderExtensions extends AbstractExtension
|
||||
new TwigFilter('skFormat',[$this,'skFormat']),
|
||||
new TwigFilter('noCompletedEch',[$this,'noCompletedEch']),
|
||||
new TwigFilter('slugify',[$this,'slugify']),
|
||||
new TwigFilter('dmaKey',[$this,'dmaKey']),
|
||||
new TwigFilter('pubKey',[$this,'pubKey']),
|
||||
new TwigFilter('licNumber',[$this,'licNumber']),
|
||||
new TwigFilter('mainKey',[$this,'mainKey']),
|
||||
];
|
||||
}
|
||||
|
||||
public function licNumber(Website $website): ?string
|
||||
{
|
||||
/** @var WebsiteLicense $apiKey */
|
||||
$apiKey = $website->getWebsiteLicenses()->filter(function (WebsiteLicense $websiteKey) {
|
||||
return $websiteKey->getType() == "main_license";
|
||||
})->first();
|
||||
return $apiKey->getLicenseNumber();
|
||||
}
|
||||
|
||||
public function pubKey(Website $website): ?string
|
||||
{
|
||||
/** @var WebsiteLicense $apiKey */
|
||||
$apiKey = $website->getWebsiteLicenses()->filter(function (WebsiteLicense $websiteKey) {
|
||||
return $websiteKey->getType() == "main_license";
|
||||
})->first();
|
||||
return base64_encode($apiKey->getPublicKey());
|
||||
}
|
||||
public function mainKey(Website $website): ?string
|
||||
{
|
||||
/** @var WebsiteKey $apiKey */
|
||||
@@ -38,6 +59,15 @@ class TwigOrderExtensions extends AbstractExtension
|
||||
})->first();
|
||||
return $apiKey->getApiKey();
|
||||
}
|
||||
|
||||
public function dmaKey(Website $website): ?string
|
||||
{
|
||||
/** @var WebsiteKey $apiKey */
|
||||
$apiKey = $website->getWebsiteKeys()->filter(function (WebsiteKey $websiteKey) {
|
||||
return $websiteKey->getType() == "dma_key";
|
||||
})->first();
|
||||
return $apiKey->getApiKey();
|
||||
}
|
||||
public function slugify(string $title): string
|
||||
{
|
||||
$s = new Slugify();
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
[website_deploy]
|
||||
{% for website in websites %}
|
||||
{{ website.mainDns}} ansible_connection=ssh ansible_user=bot ansible_python_interpreter=/usr/bin/python3 path=/var/www/{{ website.title|slugify }} website_id={{ website.id }} api_key={{ website|mainKey }}
|
||||
{{ website.mainDns}} ansible_connection=ssh ansible_user=bot ansible_python_interpreter=/usr/bin/python3 path=/var/www/{{ website.title|slugify }} website_id={{ website.id }} api_key={{ website|mainKey }} dma_key={{ website|dmaKey }} public_key={{ website|pubKey }} license_number={{ website|licNumber }}
|
||||
{% endfor %}
|
||||
|
||||
@@ -91,7 +91,7 @@
|
||||
<tr>
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-white">
|
||||
<span>{{ website.title }}</span><br/>
|
||||
<span>{{ website.uuid }}</span>
|
||||
<span>{{ website.server.name }}</span>
|
||||
</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-400">
|
||||
<span>{{ website.customer.raisonSocial }}</span><br>
|
||||
|
||||
@@ -24,6 +24,10 @@
|
||||
<i class="fad fa-home"></i>
|
||||
Options du site
|
||||
</a>
|
||||
<a href="{{ path('artemis_esyweb_view',{id:website.id,current:'key'}) }}" class="px-4 py-2 font-semibold {% if current == "ndd" %}{{ active }}{% else %}{{ desactive }}{% endif %}">
|
||||
<i class="fad fa-home"></i>
|
||||
Clé du site internet
|
||||
</a>
|
||||
</div>
|
||||
{% include 'artemis/esyweb/website/'~current~".twig" %}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user