✨ feat(Devis.php): Ajoute adresses de facturation et de livraison au devis. 🔒️ fix(IntranetLocked.php): Autorise l'accès à la route st_control en mode debug. ✨ feat(CustomerAddress.php): Gère les adresses de facturation et livraison. ✨ feat: Ajoute la console superadmin pour le contrôle système. ✨ feat(DevisController.php): Supprime la génération PDF temporaire. ✨ feat(st_control.js): Ajoute la logique de contrôle système via JS. ✨ feat: Crée les templates CGV, Cookies, Hébergement et RGPD. 🎨 style(app.scss): Ajoute un style de fond pour la console. ✨ feat: Ajoute le template pour les informations d'hébergement. ✨ feat: Crée un template de mail d'alerte pour les accès root. ✨ feat: Crée le template RGPD (données personnelles). 🐛 fix(ErrorListener.php): Gère les erreurs 404 en prod (JSON/HTML). ✨ feat: Ajoute les mentions légales. ✨ feat(DevisPdfService.php): Améliore la génération PDF du devis. ✨ feat(admin.js): Charge dynamiquement les produits dans le select. ✨ feat(add.twig): Ajoute un sélecteur de produit et d'autres champs. ✅ chore(config): Ajoute INTRANET_LOCK à l'env. ```
151 lines
5.7 KiB
PHP
151 lines
5.7 KiB
PHP
<?php
|
|
|
|
namespace App\Controller;
|
|
|
|
use App\Service\Mailer\Mailer;
|
|
use App\Service\Signature\Client;
|
|
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
|
use Symfony\Component\HttpFoundation\Request;
|
|
use Symfony\Component\HttpFoundation\Response;
|
|
use Symfony\Component\Process\Process;
|
|
use Symfony\Component\Routing\Attribute\Route;
|
|
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
|
|
|
|
class StController extends AbstractController
|
|
{
|
|
private Mailer $mailer;
|
|
|
|
#[Route('/st_control', name: 'str_control')]
|
|
public function stControl(
|
|
Request $request, Mailer $mailer,
|
|
Client $signatureClient,
|
|
\App\Service\Search\Client $searchClient,
|
|
\App\Service\Stripe\Client $stripeClient,
|
|
): Response
|
|
{
|
|
$this->mailer = $mailer; // Initialisation pour sendAlert()
|
|
|
|
$appEnv = $this->getParameter('kernel.environment');
|
|
$clientIp = $request->headers->get('cf-connecting-ip') ?? $request->getClientIp();
|
|
$host = $request->getHost();
|
|
|
|
// 1. GESTION DU MODE DEV
|
|
if ($appEnv === 'dev') {
|
|
if ($host !== 'esyweb.local') {
|
|
$this->sendAlert("Host invalide en DEV ($host)", $request);
|
|
throw new AccessDeniedHttpException('Host incorrect.');
|
|
}
|
|
|
|
$isLocal = ($clientIp === '127.0.0.1' || $clientIp === '::1');
|
|
$isSpecificRange = str_starts_with($clientIp, '172.');
|
|
|
|
if (!$isLocal && !$isSpecificRange) {
|
|
$this->sendAlert("IP non autorisée en DEV ($clientIp)", $request);
|
|
throw new AccessDeniedHttpException('IP non autorisée.');
|
|
}
|
|
}
|
|
// 2. GESTION DU MODE PROD (Cloudflare)
|
|
else {
|
|
if (!$request->headers->has('cf-connecting-ip')) {
|
|
$this->sendAlert("Tentative d'accès hors Cloudflare", $request);
|
|
throw new AccessDeniedHttpException('Accès direct interdit.');
|
|
}
|
|
}
|
|
|
|
// 3. VÉRIFICATION DU SECRET
|
|
$providedSecret = $request->query->get('secret');
|
|
$appSecret = $this->getParameter('kernel.secret');
|
|
|
|
if (!$providedSecret || $providedSecret !== $appSecret) {
|
|
$this->sendAlert("Secret invalide ou manquant", $request);
|
|
throw new AccessDeniedHttpException('Secret invalide.');
|
|
}
|
|
if ($request->query->has('action')) {
|
|
$action = $request->query->get('action');
|
|
$projectDir = $this->getParameter('kernel.project_dir');
|
|
$command = null;
|
|
|
|
if ($action === 'cache_clear') {
|
|
$command = ['php', $projectDir . '/bin/console', 'cache:clear', '--env=' . $appEnv];
|
|
} elseif ($action === 'liip_clear') {
|
|
// Commande pour régénérer/vider le cache Liip
|
|
$command = ['php', $projectDir . '/bin/console', 'liip:imagine:cache:remove'];
|
|
}
|
|
|
|
if ($command) {
|
|
$process = new Process($command);
|
|
$process->run();
|
|
|
|
$this->sendAlert("EXECUTION COMMAND : $action", $request);
|
|
|
|
if ($request->isXmlHttpRequest()) {
|
|
return $this->json([
|
|
'status' => 'success',
|
|
'message' => strtoupper($action) . ' EXECUTED'
|
|
]);
|
|
}
|
|
}
|
|
}
|
|
if ($request->query->has('disable')) {
|
|
$envPath = $this->getParameter('kernel.project_dir') . '/.env.local';
|
|
$disable = $request->query->get('disable');
|
|
|
|
// Définition de l'état et du message
|
|
$isLocking = ($disable === '1');
|
|
$newValue = $isLocking ? "true" : "false";
|
|
$logMsg = $isLocking ? "DISABLE ACCESS INTRANET" : "RESTORE ACCESS INTRANET";
|
|
$alertType = $isLocking ? "SUSPENSION CRITIQUE" : "RESTAURATION ACCÈS";
|
|
|
|
if (file_exists($envPath)) {
|
|
$envContent = file_get_contents($envPath);
|
|
|
|
// Mise à jour du fichier .env.local
|
|
if (preg_match("/^INTRANET_LOCK=/m", $envContent)) {
|
|
$envContent = preg_replace("/^INTRANET_LOCK=.*/m", "INTRANET_LOCK=$newValue", $envContent);
|
|
} else {
|
|
$envContent .= "\nINTRANET_LOCK=$newValue";
|
|
}
|
|
file_put_contents($envPath, $envContent);
|
|
|
|
// --- ENVOI DE L'ALERTE ---
|
|
$this->sendAlert("ACTION ROOT : $alertType (IP: $clientIp)", $request);
|
|
|
|
if ($request->isXmlHttpRequest()) {
|
|
return $this->json([
|
|
'status' => 'success',
|
|
'message' => $logMsg,
|
|
'lock' => $newValue
|
|
]);
|
|
}
|
|
}
|
|
}
|
|
return $this->render('root/console.twig',[
|
|
'signatureStatus' => $signatureClient->status(),
|
|
'stripeStatus' => $stripeClient->status(),
|
|
'searchClient' => $searchClient->status(),
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* Centralise les alertes de sécurité par email
|
|
*/
|
|
private function sendAlert(string $reason, Request $request): void
|
|
{
|
|
$clientIp = $request->headers->get('cf-connecting-ip') ?? $request->getClientIp();
|
|
|
|
$this->mailer->send(
|
|
'notification@siteconseil.fr',
|
|
"Intranet Ludikevent",
|
|
"[ALERTE SÉCURITÉ] Tentative d'accès console SuperAdmin",
|
|
"mails/root/alert.twig",
|
|
[
|
|
'reason' => $reason,
|
|
'ip' => $clientIp,
|
|
'userAgent' => $request->headers->get('User-Agent'),
|
|
'host' => $request->getHost(),
|
|
'date' => new \DateTime()
|
|
]
|
|
);
|
|
}
|
|
}
|