diff --git a/app/public/index.php b/app/public/index.php index ed558e5..a94d88d 100644 --- a/app/public/index.php +++ b/app/public/index.php @@ -149,6 +149,7 @@ $router->addRoute('GET', '/certificates', 'CertificateController@index', true); $router->addRoute('GET', '/certificates/create', 'CertificateController@showCreateForm', true); $router->addRoute('POST', '/certificates/create', 'CertificateController@create', true); $router->addRoute('POST', '/certificates/revoke', 'CertificateController@revoke', true); +$router->addRoute('GET', '/certificates/download', 'CertificateController@download', true); $router->addRoute('GET', '/perimeters', 'PerimeterController@index', true); $router->addRoute('GET', '/perimeters/create', 'PerimeterController@showCreateForm', true); $router->addRoute('POST', '/perimeters/create', 'PerimeterController@create', true); diff --git a/app/src/Controllers/CertificateController.php b/app/src/Controllers/CertificateController.php index bd1d6d6..f1990e6 100644 --- a/app/src/Controllers/CertificateController.php +++ b/app/src/Controllers/CertificateController.php @@ -248,4 +248,118 @@ class CertificateController header('Location: /certificates'); exit(); } + + /** + * Gère le téléchargement des fichiers de certificats et clés. + */ + public function download() + { + if (!$this->authService->isLoggedIn()) { + header('Location: /login'); + exit(); + } + + // Récupérer les paramètres de la requête + $type = $_GET['type'] ?? null; + $fileName = $_GET['file'] ?? null; + $perimeterName = $_GET['perimeter'] ?? null; // Utilisé pour intermédiaires et simples + + // Log de la tentative de téléchargement + $userId = $this->authService->getUserId(); + $ipAddress = $_SERVER['REMOTE_ADDR']; + $this->logService->log('info', "Download attempt: type='{$type}', file='{$fileName}', perimeter='{$perimeterName}'", $userId, $ipAddress); + + // Validation basique des paramètres + if (empty($type) || empty($fileName)) { + $_SESSION['error'] = 'Missing download parameters.'; + $this->logService->log('warn', "Missing download parameters.", $userId, $ipAddress); + header('Location: /dashboard'); + exit(); + } + + // Sécurisation des noms de fichier et de périmètre + if (basename($fileName) !== $fileName || ($perimeterName && basename($perimeterName) !== $perimeterName)) { + $_SESSION['error'] = 'Invalid characters in file or perimeter name.'; + $this->logService->log('error', "Invalid characters in file or perimeter name for download.", $userId, $ipAddress); + header('Location: /dashboard'); + exit(); + } + + $filePath = ''; + + switch ($type) { + case 'root': + if ($fileName === 'ca.cert.pem') { + $filePath = ROOT_CA_PATH . '/certs/' . $fileName; + } elseif ($fileName === 'ca.key.pem') { + if ($this->authService->getUserRole() === 'admin') { + $filePath = ROOT_CA_PATH . '/private/' . $fileName; + } else { + $_SESSION['error'] = 'Unauthorized to download root key.'; + $this->logService->log('error', "Unauthorized attempt to download root CA key by user ID: {$userId}", $userId, $ipAddress); + header('Location: /dashboard'); + exit(); + } + } else { + $_SESSION['error'] = 'Invalid file specified for root certificate type.'; + $this->logService->log('warn', "Invalid file '{$fileName}' specified for root certificate type.", $userId, $ipAddress); + header('Location: /dashboard'); + exit(); + } + break; + case 'intermediate': + if (empty($perimeterName)) { + $_SESSION['error'] = 'Perimeter name missing for intermediate certificate.'; + $this->logService->log('warn', "Perimeter name missing for intermediate certificate download.", $userId, $ipAddress); + header('Location: /dashboard'); + exit(); + } + $filePath = INTERMEDIATE_CA_PATH_BASE . '/' . $perimeterName . '/certs/' . $fileName; + break; + case 'simple': + if (empty($perimeterName)) { + $_SESSION['error'] = 'Perimeter name missing for simple certificate.'; + $this->logService->log('warn', "Perimeter name missing for simple certificate download.", $userId, $ipAddress); + header('Location: /dashboard'); + exit(); + } + $filePath = INTERMEDIATE_CA_PATH_BASE . '/' . $perimeterName . '/certs/' . $fileName; + break; + default: + $_SESSION['error'] = 'Invalid certificate type for download.'; + $this->logService->log('warn', "Invalid certificate type for download: {$type}", $userId, $ipAddress); + header('Location: /dashboard'); + exit(); + } + + if (!empty($filePath) && file_exists($filePath) && is_readable($filePath)) { + $mimeType = 'application/octet-stream'; + $fileExtension = strtolower(pathinfo($filePath, PATHINFO_EXTENSION)); + + if ($fileExtension === 'pem' || $fileExtension === 'key' || $fileExtension === 'crt' || $fileExtension === 'cer') { + $mimeType = 'application/x-pem-file'; + } + + header('Content-Description: File Transfer'); + header('Content-Type: ' . $mimeType); + header('Content-Disposition: attachment; filename="' . basename($filePath) . '"'); + header('Expires: 0'); + header('Cache-Control: must-revalidate'); + header('Pragma: public'); + header('Content-Length: ' . filesize($filePath)); + + if (ob_get_level()) { + ob_end_clean(); + } + + readfile($filePath); + $this->logService->log('info', "File '{$filePath}' downloaded successfully.", $userId, $ipAddress); + exit; + } else { + $_SESSION['error'] = 'File not found or not readable: ' . htmlspecialchars($fileName); + $this->logService->log('error', "File not found or not readable for download: {$filePath} (Type: {$type}, File: {$fileName}, Perimeter: {$perimeterName})", $userId, $ipAddress); + header('Location: /dashboard'); + exit(); + } + } } diff --git a/app/src/Controllers/DashboardController.php b/app/src/Controllers/DashboardController.php index 8938e9a..b53288f 100644 --- a/app/src/Controllers/DashboardController.php +++ b/app/src/Controllers/DashboardController.php @@ -2,10 +2,11 @@ namespace App\Controllers; -use App\Core\Database; use App\Services\AuthService; use App\Services\LanguageService; use App\Utils\DarkMode; +// Ensure App\Core\Database is imported +use App\Core\Database; /** * Contrôleur pour la page du tableau de bord. @@ -14,14 +15,19 @@ class DashboardController { private $authService; private $langService; + private $db; // Property to hold the database instance /** * Constructeur du DashboardController. */ public function __construct() { + // Initialize database connection here if it's meant to be a class property + // For now, authService initializes its own, and index() gets a new instance. + // If $this->db was intended, it should be $this->db = Database::getInstance(); $this->authService = new AuthService(Database::getInstance()); $this->langService = new LanguageService(APP_ROOT_DIR . '/src/Lang/'); + // $this->db = Database::getInstance(); // Uncomment if $db should be a class property accessible in index() via $this->db } /** @@ -42,6 +48,78 @@ class DashboardController $darkModeClass = DarkMode::getBodyClass(); $userRole = $this->authService->getUserRole(); // Pour afficher/masquer certains éléments + // Initialize database connection + // If $this->db was initialized in constructor, use $db = $this->db; + $db = Database::getInstance(); // Using a local instance as per original structure + + // Initialize structured certificates array + $structuredCertificates = [ + 'root' => null, + 'intermediates' => [], + ]; + + // Fetch Root Certificate + $stmt = $db->prepare("SELECT name FROM certificates WHERE type = 'root' LIMIT 1"); + $stmt->execute(); + $rootCert = $stmt->fetch(); + + if ($rootCert) { + $structuredCertificates['root'] = [ + 'name' => $rootCert['name'], + 'cert_path' => ROOT_CA_PATH . '/certs/ca.cert.pem', + 'key_path' => ROOT_CA_PATH . '/private/ca.key.pem', // Corrected path as per instructions + ]; + } else { + // Handle case where root certificate is not found, though unlikely + // You might want to log this or set a default structure + $structuredCertificates['root'] = [ + 'name' => 'N/A', + 'cert_path' => null, + 'key_path' => null, + ]; + } + + // Fetch Intermediate Certificates + $stmt = $db->prepare(" + SELECT c.id, c.name, c.functional_perimeter_id, fp.name as perimeter_name + FROM certificates c + JOIN functional_perimeters fp ON c.functional_perimeter_id = fp.id + WHERE c.type = 'intermediate' + ORDER BY fp.name ASC, c.name ASC + "); + $stmt->execute(); + $intermediateCerts = $stmt->fetchAll(); + + foreach ($intermediateCerts as $interCert) { + $intermediateData = [ + 'id' => $interCert['id'], + 'name' => $interCert['name'], + 'perimeter_name' => $interCert['perimeter_name'], + 'functional_perimeter_id' => $interCert['functional_perimeter_id'], // Pass perimeter_id for linking + 'final_certificates' => [], + ]; + + // Fetch Final Certificates for this Intermediate + // Ensure functional_perimeter_id is used in the query for 'simple' certificates + $stmtFinal = $db->prepare(" + SELECT name, type, expiration_date, is_revoked + FROM certificates + WHERE type = 'simple' AND functional_perimeter_id = ? + ORDER BY name ASC + "); + // Use $interCert['functional_perimeter_id'] which is the ID of the perimeter for this intermediate cert + $stmtFinal->execute([$interCert['functional_perimeter_id']]); + $finalCerts = $stmtFinal->fetchAll(); + + if ($finalCerts) { + foreach ($finalCerts as $finalCert) { + $intermediateData['final_certificates'][] = $finalCert; + } + } + $structuredCertificates['intermediates'][] = $intermediateData; + } + + // Pass data to the view require_once APP_ROOT_DIR . '/src/Views/dashboard/index.php'; } } diff --git a/app/src/Views/dashboard/index.php b/app/src/Views/dashboard/index.php index 0917bd9..83fc9f5 100644 --- a/app/src/Views/dashboard/index.php +++ b/app/src/Views/dashboard/index.php @@ -27,6 +27,68 @@ require_once APP_ROOT_DIR . '/src/Views/shared/header.php'; + +
+

+ + +

+ +
+

+

+ + + +

+

+ + + +

+
+ +

+ + + +

+ + +
+

( )

+

+ + + +

+ +
+ + + +

+ +
+ + +

+ +