mirror of
https://github.com/tips-of-mine/gestion-certificats2.git
synced 2025-06-28 12:48:42 +02:00
Implémenter la fonctionnalité de téléchargement des certificats depuis le dashboard
Ce commit introduit la possibilité pour vous de télécharger les fichiers de certificats (racine, intermédiaire, final) et la clé privée du certificat racine (administrateurs uniquement) directement depuis la page du tableau de bord. Changements inclus : - Ajout d'une méthode `download()` dans `CertificateController` pour gérer la logique de téléchargement sécurisé des fichiers. - Ajout d'une nouvelle route `GET /certificates/download`. - Correction et standardisation des liens de téléchargement dans la vue du dashboard pour assurer la transmission correcte des paramètres (type de certificat, nom de fichier, périmètre). - La méthode de téléchargement inclut la journalisation des tentatives et des erreurs, ainsi que la gestion des permissions pour la clé privée racine.
This commit is contained in:
@ -149,6 +149,7 @@ $router->addRoute('GET', '/certificates', 'CertificateController@index', true);
|
|||||||
$router->addRoute('GET', '/certificates/create', 'CertificateController@showCreateForm', true);
|
$router->addRoute('GET', '/certificates/create', 'CertificateController@showCreateForm', true);
|
||||||
$router->addRoute('POST', '/certificates/create', 'CertificateController@create', true);
|
$router->addRoute('POST', '/certificates/create', 'CertificateController@create', true);
|
||||||
$router->addRoute('POST', '/certificates/revoke', 'CertificateController@revoke', 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', 'PerimeterController@index', true);
|
||||||
$router->addRoute('GET', '/perimeters/create', 'PerimeterController@showCreateForm', true);
|
$router->addRoute('GET', '/perimeters/create', 'PerimeterController@showCreateForm', true);
|
||||||
$router->addRoute('POST', '/perimeters/create', 'PerimeterController@create', true);
|
$router->addRoute('POST', '/perimeters/create', 'PerimeterController@create', true);
|
||||||
|
@ -248,4 +248,118 @@ class CertificateController
|
|||||||
header('Location: /certificates');
|
header('Location: /certificates');
|
||||||
exit();
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,10 +2,11 @@
|
|||||||
|
|
||||||
namespace App\Controllers;
|
namespace App\Controllers;
|
||||||
|
|
||||||
use App\Core\Database;
|
|
||||||
use App\Services\AuthService;
|
use App\Services\AuthService;
|
||||||
use App\Services\LanguageService;
|
use App\Services\LanguageService;
|
||||||
use App\Utils\DarkMode;
|
use App\Utils\DarkMode;
|
||||||
|
// Ensure App\Core\Database is imported
|
||||||
|
use App\Core\Database;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Contrôleur pour la page du tableau de bord.
|
* Contrôleur pour la page du tableau de bord.
|
||||||
@ -14,14 +15,19 @@ class DashboardController
|
|||||||
{
|
{
|
||||||
private $authService;
|
private $authService;
|
||||||
private $langService;
|
private $langService;
|
||||||
|
private $db; // Property to hold the database instance
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructeur du DashboardController.
|
* Constructeur du DashboardController.
|
||||||
*/
|
*/
|
||||||
public function __construct()
|
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->authService = new AuthService(Database::getInstance());
|
||||||
$this->langService = new LanguageService(APP_ROOT_DIR . '/src/Lang/');
|
$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();
|
$darkModeClass = DarkMode::getBodyClass();
|
||||||
$userRole = $this->authService->getUserRole(); // Pour afficher/masquer certains éléments
|
$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';
|
require_once APP_ROOT_DIR . '/src/Views/dashboard/index.php';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -27,6 +27,68 @@ require_once APP_ROOT_DIR . '/src/Views/shared/header.php';
|
|||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
</ul>
|
</ul>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
<section class="certificates-overview">
|
||||||
|
<h2><?= htmlspecialchars($translations['certificates_overview_title'] ?? 'Certificates Overview') ?></h2>
|
||||||
|
|
||||||
|
<!-- Root Certificate -->
|
||||||
|
<h3><?= htmlspecialchars($translations['root_certificate_title'] ?? 'Root Certificate') ?></h3>
|
||||||
|
<?php if (isset($structuredCertificates['root']) && $structuredCertificates['root']): ?>
|
||||||
|
<div>
|
||||||
|
<p><strong><?= htmlspecialchars($translations['name'] ?? 'Name:') ?></strong> <?= htmlspecialchars($structuredCertificates['root']['name']) ?></p>
|
||||||
|
<p>
|
||||||
|
<a href="/certificates/download?type=root&file=ca.cert.pem" class="button">
|
||||||
|
<?= htmlspecialchars($translations['download_certificate_pem'] ?? 'Download Certificate (.pem)') ?>
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<a href="/certificates/download?type=root&file=ca.key.pem" class="button">
|
||||||
|
<?= htmlspecialchars($translations['download_key_pem'] ?? 'Download Private Key (.key)') ?>
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<?php else: ?>
|
||||||
|
<p><?= htmlspecialchars($translations['root_certificate_not_configured'] ?? 'Root certificate is not configured.') ?></p>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<!-- Intermediate Certificates -->
|
||||||
|
<h3><?= htmlspecialchars($translations['intermediate_certificates_title'] ?? 'Intermediate Certificates') ?></h3>
|
||||||
|
<?php if (isset($structuredCertificates['intermediates']) && !empty($structuredCertificates['intermediates'])): ?>
|
||||||
|
<?php foreach ($structuredCertificates['intermediates'] as $intermediate): ?>
|
||||||
|
<div class="intermediate-certificate">
|
||||||
|
<h4><?= htmlspecialchars($intermediate['name']) ?> (<?= htmlspecialchars($translations['perimeter'] ?? 'Perimeter:') ?> <?= htmlspecialchars($intermediate['perimeter_name']) ?>)</h4>
|
||||||
|
<p>
|
||||||
|
<a href="/certificates/download?type=intermediate&perimeter=<?= urlencode($intermediate['perimeter_name']) ?>&file=intermediate.cert.pem" class="button">
|
||||||
|
<?= htmlspecialchars($translations['download_certificate_pem'] ?? 'Download Certificate (.pem)') ?>
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h5><?= htmlspecialchars($translations['associated_final_certificates_title'] ?? 'Associated Final Certificates') ?></h5>
|
||||||
|
<?php if (isset($intermediate['final_certificates']) && !empty($intermediate['final_certificates'])): ?>
|
||||||
|
<ul>
|
||||||
|
<?php foreach ($intermediate['final_certificates'] as $finalCert): ?>
|
||||||
|
<li>
|
||||||
|
<?= htmlspecialchars($finalCert['name']) ?>
|
||||||
|
(<?= htmlspecialchars($translations['type'] ?? 'Type:') ?> <?= htmlspecialchars($finalCert['type']) ?>,
|
||||||
|
<?= htmlspecialchars($translations['expires'] ?? 'Expires:') ?> <?= htmlspecialchars($finalCert['expiration_date']) ?>,
|
||||||
|
<?= htmlspecialchars($translations['revoked'] ?? 'Revoked:') ?> <?= $finalCert['is_revoked'] ? htmlspecialchars($translations['yes'] ?? 'Yes') : htmlspecialchars($translations['no'] ?? 'No') ?>)
|
||||||
|
<p>
|
||||||
|
<a href="/certificates/download?type=simple&perimeter=<?= urlencode($intermediate['perimeter_name']) ?>&file=<?= urlencode($finalCert['name']) ?>" class="button download-button-small">
|
||||||
|
<?= htmlspecialchars($translations['download_certificate_pem'] ?? 'Download Certificate (.pem)') ?>
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</ul>
|
||||||
|
<?php else: ?>
|
||||||
|
<p><?= htmlspecialchars($translations['no_associated_final_certificates'] ?? 'No final certificates associated with this intermediate.') ?></p>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
<?php else: ?>
|
||||||
|
<p><?= htmlspecialchars($translations['no_intermediate_certificates_found'] ?? 'No intermediate certificates found.') ?></p>
|
||||||
|
<?php endif; ?>
|
||||||
|
</section>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<?php
|
<?php
|
||||||
|
Reference in New Issue
Block a user