mirror of
https://github.com/tips-of-mine/gestion-certificats2.git
synced 2025-06-28 13:58:42 +02:00
Merge pull request #9 from tips-of-mine/feat/certificate-download
Implémenter la fonctionnalité de téléchargement des certificats depui…
This commit is contained in:
@ -186,6 +186,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