mirror of
https://github.com/tips-of-mine/gestion-certificats2.git
synced 2025-06-28 09:18:42 +02:00
Add files via upload
This commit is contained in:
57
app/public/css/dark-mode.css
Normal file
57
app/public/css/dark-mode.css
Normal file
@ -0,0 +1,57 @@
|
||||
/* Styles pour le mode sombre */
|
||||
body.dark-mode {
|
||||
--bg-color: #2c2c2c;
|
||||
--text-color: #e0e0e0;
|
||||
--container-bg: #3a3a3a;
|
||||
--container-shadow: 0 2px 4px rgba(0, 0, 0, 0.4);
|
||||
--header-bg: #1a4d7c;
|
||||
--header-text: #f0f0f0;
|
||||
--nav-bg: #444;
|
||||
--nav-link-color: #9cb3cc;
|
||||
--nav-link-hover-bg: #555;
|
||||
--table-border-color: #555;
|
||||
--table-header-bg: #1a4d7c;
|
||||
--table-header-text: #fff;
|
||||
--table-row-even-bg: #333;
|
||||
--button-primary-bg: #0056b3;
|
||||
--button-primary-hover-bg: #004085;
|
||||
--button-secondary-bg: #5a6268;
|
||||
--button-secondary-hover-bg: #43484f;
|
||||
--button-danger-bg: #a71d2a;
|
||||
--button-danger-hover-bg: #7a151f;
|
||||
--status-revoked-color: #ff6666;
|
||||
--status-active-color: #66ff66;
|
||||
--message-success-color: #a3e6a3;
|
||||
--message-error-color: #ff9999;
|
||||
--input-border: #666;
|
||||
--input-focus-border: #007bff;
|
||||
}
|
||||
|
||||
body.dark-mode .app-footer {
|
||||
background-color: #1a1a1a;
|
||||
color: #ccc;
|
||||
}
|
||||
|
||||
body.dark-mode tr.revoked-cert {
|
||||
background-color: #5c2c2c; /* Fond plus foncé pour le mode sombre */
|
||||
color: #aaa;
|
||||
}
|
||||
body.dark-mode tr.revoked-cert:hover {
|
||||
background-color: #703c3c;
|
||||
}
|
||||
|
||||
/* Spécifiques pour les inputs en mode sombre */
|
||||
body.dark-mode form input[type="text"],
|
||||
body.dark-mode form input[type="password"],
|
||||
body.dark-mode form select {
|
||||
background-color: #4a4a4a;
|
||||
color: var(--text-color);
|
||||
border: 1px solid var(--input-border);
|
||||
}
|
||||
|
||||
body.dark-mode form input[type="text"]:focus,
|
||||
body.dark-mode form input[type="password"]:focus,
|
||||
body.dark-mode form select:focus {
|
||||
border-color: var(--input-focus-border);
|
||||
box-shadow: 0 0 0 3px rgba(0, 123, 255, 0.4);
|
||||
}
|
366
app/public/css/style.css
Normal file
366
app/public/css/style.css
Normal file
@ -0,0 +1,366 @@
|
||||
/* Variables CSS pour faciliter le basculement entre les thèmes */
|
||||
:root {
|
||||
--bg-color: #f4f4f4;
|
||||
--text-color: #333;
|
||||
--container-bg: #fff;
|
||||
--container-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
--header-bg: #0056b3;
|
||||
--header-text: #fff;
|
||||
--nav-bg: #e2e2e2;
|
||||
--nav-link-color: #0056b3;
|
||||
--nav-link-hover-bg: #d1d1d1;
|
||||
--table-border-color: #ddd;
|
||||
--table-header-bg: #0056b3;
|
||||
--table-header-text: #fff;
|
||||
--table-row-even-bg: #f2f2f2;
|
||||
--button-primary-bg: #007bff;
|
||||
--button-primary-hover-bg: #0056b3;
|
||||
--button-secondary-bg: #6c757d;
|
||||
--button-secondary-hover-bg: #5a6268;
|
||||
--button-danger-bg: #dc3545;
|
||||
--button-danger-hover-bg: #bd2130;
|
||||
--status-revoked-color: red;
|
||||
--status-active-color: green;
|
||||
--message-success-color: green;
|
||||
--message-error-color: red;
|
||||
--input-border: #ccc;
|
||||
--input-focus-border: #007bff;
|
||||
}
|
||||
|
||||
/* Styles généraux */
|
||||
body {
|
||||
font-family: 'Inter', sans-serif; /* Utilisation de Inter comme demandé */
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
background-color: var(--bg-color);
|
||||
color: var(--text-color);
|
||||
line-height: 1.6;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 960px;
|
||||
margin: 20px auto;
|
||||
background-color: var(--container-bg);
|
||||
padding: 20px 30px;
|
||||
border-radius: 12px; /* Coins arrondis */
|
||||
box-shadow: var(--container-shadow);
|
||||
flex-grow: 1; /* Permet au container de prendre de l'espace */
|
||||
}
|
||||
|
||||
h1, h2 {
|
||||
color: var(--header-bg);
|
||||
margin-top: 0;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
/* En-tête de l'application */
|
||||
.app-header {
|
||||
background-color: var(--header-bg);
|
||||
color: var(--header-text);
|
||||
padding: 15px 0;
|
||||
box-shadow: 0 2px 5px rgba(0,0,0,0.2);
|
||||
}
|
||||
|
||||
.header-content {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
max-width: 960px;
|
||||
margin: 0 auto;
|
||||
padding: 0 20px;
|
||||
}
|
||||
|
||||
.app-title h1 {
|
||||
color: var(--header-text);
|
||||
margin: 0;
|
||||
font-size: 1.8em;
|
||||
}
|
||||
|
||||
.header-controls {
|
||||
display: flex;
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
.language-switcher, .dark-mode-switcher {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.lang-button, .dark-mode-button {
|
||||
padding: 8px 12px;
|
||||
text-decoration: none;
|
||||
color: var(--header-text);
|
||||
border: 1px solid rgba(255, 255, 255, 0.4);
|
||||
border-radius: 8px; /* Coins arrondis */
|
||||
transition: background-color 0.3s ease;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
}
|
||||
|
||||
.lang-button:hover, .dark-mode-button:hover {
|
||||
background-color: rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
|
||||
.lang-button.active {
|
||||
background-color: rgba(255, 255, 255, 0.3);
|
||||
border-color: var(--header-text);
|
||||
}
|
||||
|
||||
/* Styles de navigation */
|
||||
nav ul {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 20px 0;
|
||||
background-color: var(--nav-bg);
|
||||
border-radius: 10px; /* Coins arrondis */
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
overflow: hidden; /* Pour que les coins arrondis fonctionnent bien avec le hover */
|
||||
box-shadow: inset 0 1px 3px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
nav ul li {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
nav ul li a {
|
||||
display: block;
|
||||
padding: 12px 20px;
|
||||
text-decoration: none;
|
||||
color: var(--nav-link-color);
|
||||
font-weight: bold;
|
||||
transition: background-color 0.3s ease, color 0.3s ease;
|
||||
border-radius: 8px; /* Appliquer aux liens pour le hover */
|
||||
}
|
||||
|
||||
nav ul li a:hover {
|
||||
background-color: var(--nav-link-hover-bg);
|
||||
color: var(--button-primary-hover-bg); /* Ou une autre couleur contrastante */
|
||||
}
|
||||
|
||||
/* Styles des messages */
|
||||
.success-message {
|
||||
background-color: #d4edda;
|
||||
color: var(--message-success-color);
|
||||
border: 1px solid #c3e6cb;
|
||||
padding: 10px;
|
||||
border-radius: 8px;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.error-message {
|
||||
background-color: #f8d7da;
|
||||
color: var(--message-error-color);
|
||||
border: 1px solid #f5c6cb;
|
||||
padding: 10px;
|
||||
border-radius: 8px;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
/* Styles de tableau */
|
||||
.table-responsive {
|
||||
overflow-x: auto; /* Pour les petits écrans */
|
||||
}
|
||||
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin-top: 20px;
|
||||
border-radius: 12px; /* Coins arrondis pour le tableau */
|
||||
overflow: hidden; /* Important pour que les coins arrondis soient visibles */
|
||||
}
|
||||
|
||||
table, th, td {
|
||||
border: 1px solid var(--table-border-color);
|
||||
}
|
||||
|
||||
th, td {
|
||||
padding: 12px 15px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
th {
|
||||
background-color: var(--table-header-bg);
|
||||
color: var(--table-header-text);
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
tr:nth-child(even) {
|
||||
background-color: var(--table-row-even-bg);
|
||||
}
|
||||
|
||||
tr:hover {
|
||||
background-color: rgba(0, 0, 0, 0.05); /* Léger survol */
|
||||
}
|
||||
|
||||
/* Styles des boutons */
|
||||
.button {
|
||||
display: inline-block;
|
||||
padding: 10px 20px;
|
||||
font-size: 1em;
|
||||
text-decoration: none;
|
||||
color: white;
|
||||
border-radius: 8px; /* Coins arrondis */
|
||||
transition: background-color 0.3s ease, transform 0.2s ease;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.button:active {
|
||||
transform: translateY(1px);
|
||||
}
|
||||
|
||||
.primary-button {
|
||||
background-color: var(--button-primary-bg);
|
||||
}
|
||||
|
||||
.primary-button:hover {
|
||||
background-color: var(--button-primary-hover-bg);
|
||||
}
|
||||
|
||||
.secondary-button {
|
||||
background-color: var(--button-secondary-bg);
|
||||
}
|
||||
|
||||
.secondary-button:hover {
|
||||
background-color: var(--button-secondary-hover-bg);
|
||||
}
|
||||
|
||||
.danger-button {
|
||||
background-color: var(--button-danger-bg);
|
||||
}
|
||||
|
||||
.danger-button:hover {
|
||||
background-color: var(--button-danger-hover-bg);
|
||||
}
|
||||
|
||||
.logout-button {
|
||||
background-color: #f44336;
|
||||
}
|
||||
.logout-button:hover {
|
||||
background-color: #d32f2f;
|
||||
}
|
||||
|
||||
.actions-bar {
|
||||
display: flex;
|
||||
justify-content: flex-start; /* Alignement à gauche */
|
||||
gap: 10px; /* Espace entre les boutons */
|
||||
margin-bottom: 20px;
|
||||
flex-wrap: wrap; /* Pour la réactivité sur mobile */
|
||||
}
|
||||
|
||||
/* Formulaires */
|
||||
form label {
|
||||
display: block;
|
||||
margin-bottom: 8px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
form input[type="text"],
|
||||
form input[type="password"],
|
||||
form select {
|
||||
width: calc(100% - 22px); /* 100% moins padding et border */
|
||||
padding: 10px;
|
||||
margin-bottom: 15px;
|
||||
border: 1px solid var(--input-border);
|
||||
border-radius: 8px; /* Coins arrondis */
|
||||
font-size: 1em;
|
||||
box-sizing: border-box; /* Inclut padding et border dans la largeur */
|
||||
}
|
||||
|
||||
form input[type="text"]:focus,
|
||||
form input[type="password"]:focus,
|
||||
form select:focus {
|
||||
border-color: var(--input-focus-border);
|
||||
outline: none;
|
||||
box-shadow: 0 0 0 3px rgba(0, 123, 255, 0.25);
|
||||
}
|
||||
|
||||
form button[type="submit"] {
|
||||
width: auto;
|
||||
padding: 12px 25px;
|
||||
font-size: 1.1em;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
/* Messages de statut de certificat */
|
||||
.status-revoked {
|
||||
color: var(--status-revoked-color);
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.status-active {
|
||||
color: var(--status-active-color);
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
/* Ligne de certificat révoqué */
|
||||
tr.revoked-cert {
|
||||
text-decoration: line-through;
|
||||
color: #888;
|
||||
background-color: #fce4e4; /* Léger fond rouge pour le mode clair */
|
||||
}
|
||||
tr.revoked-cert:hover {
|
||||
background-color: #fcc;
|
||||
}
|
||||
|
||||
/* Pied de page de l'application */
|
||||
.app-footer {
|
||||
background-color: #333;
|
||||
color: #f4f4f4;
|
||||
text-align: center;
|
||||
padding: 15px 0;
|
||||
margin-top: auto; /* Pousse le footer en bas */
|
||||
}
|
||||
|
||||
.app-footer .container {
|
||||
max-width: 960px;
|
||||
margin: 0 auto;
|
||||
padding: 0 20px;
|
||||
background-color: transparent; /* Pas de fond blanc pour le footer */
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
/* Réactivité mobile */
|
||||
@media (max-width: 768px) {
|
||||
.container {
|
||||
margin: 10px;
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
.app-header .header-content {
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
nav ul {
|
||||
flex-direction: column;
|
||||
gap: 5px;
|
||||
}
|
||||
|
||||
nav ul li a {
|
||||
padding: 10px 15px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.actions-bar {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.button {
|
||||
width: 100%;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
form input[type="text"],
|
||||
form input[type="password"],
|
||||
form select {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
57
app/public/dark-mode.css
Normal file
57
app/public/dark-mode.css
Normal file
@ -0,0 +1,57 @@
|
||||
/* Styles pour le mode sombre */
|
||||
body.dark-mode {
|
||||
--bg-color: #2c2c2c;
|
||||
--text-color: #e0e0e0;
|
||||
--container-bg: #3a3a3a;
|
||||
--container-shadow: 0 2px 4px rgba(0, 0, 0, 0.4);
|
||||
--header-bg: #1a4d7c;
|
||||
--header-text: #f0f0f0;
|
||||
--nav-bg: #444;
|
||||
--nav-link-color: #9cb3cc;
|
||||
--nav-link-hover-bg: #555;
|
||||
--table-border-color: #555;
|
||||
--table-header-bg: #1a4d7c;
|
||||
--table-header-text: #fff;
|
||||
--table-row-even-bg: #333;
|
||||
--button-primary-bg: #0056b3;
|
||||
--button-primary-hover-bg: #004085;
|
||||
--button-secondary-bg: #5a6268;
|
||||
--button-secondary-hover-bg: #43484f;
|
||||
--button-danger-bg: #a71d2a;
|
||||
--button-danger-hover-bg: #7a151f;
|
||||
--status-revoked-color: #ff6666;
|
||||
--status-active-color: #66ff66;
|
||||
--message-success-color: #a3e6a3;
|
||||
--message-error-color: #ff9999;
|
||||
--input-border: #666;
|
||||
--input-focus-border: #007bff;
|
||||
}
|
||||
|
||||
body.dark-mode .app-footer {
|
||||
background-color: #1a1a1a;
|
||||
color: #ccc;
|
||||
}
|
||||
|
||||
body.dark-mode tr.revoked-cert {
|
||||
background-color: #5c2c2c; /* Fond plus foncé pour le mode sombre */
|
||||
color: #aaa;
|
||||
}
|
||||
body.dark-mode tr.revoked-cert:hover {
|
||||
background-color: #703c3c;
|
||||
}
|
||||
|
||||
/* Spécifiques pour les inputs en mode sombre */
|
||||
body.dark-mode form input[type="text"],
|
||||
body.dark-mode form input[type="password"],
|
||||
body.dark-mode form select {
|
||||
background-color: #4a4a4a;
|
||||
color: var(--text-color);
|
||||
border: 1px solid var(--input-border);
|
||||
}
|
||||
|
||||
body.dark-mode form input[type="text"]:focus,
|
||||
body.dark-mode form input[type="password"]:focus,
|
||||
body.dark-mode form select:focus {
|
||||
border-color: var(--input-focus-border);
|
||||
box-shadow: 0 0 0 3px rgba(0, 123, 255, 0.4);
|
||||
}
|
162
app/public/index.php
Normal file
162
app/public/index.php
Normal file
@ -0,0 +1,162 @@
|
||||
<?php
|
||||
|
||||
// Démarrer la session PHP
|
||||
session_start();
|
||||
|
||||
// Inclusion des fichiers fondamentaux
|
||||
require_once __DIR__ . '/../src/Core/Autoloader.php';
|
||||
require_once __DIR__ . '/../src/Core/Router.php';
|
||||
require_once __DIR__ . '/../src/Core/Database.php';
|
||||
require_once __DIR__ . '/../src/config/app.php'; // Charge les constantes d'application
|
||||
|
||||
// Enregistrement de l'autoloader pour charger les classes automatiquement
|
||||
\App\Core\Autoloader::register();
|
||||
|
||||
// Importation des classes à utiliser
|
||||
use App\Core\Database;
|
||||
use App\Core\Router;
|
||||
use App\Services\AuthService;
|
||||
use App\Services\LanguageService;
|
||||
use App\Services\LogService;
|
||||
use App\Utils\DarkMode;
|
||||
|
||||
// Initialisation de la connexion à la base de données
|
||||
try {
|
||||
Database::connect(DB_HOST, DB_NAME, DB_USER, DB_PASSWORD);
|
||||
} catch (PDOException $e) {
|
||||
// En cas d'erreur de connexion, logguer et afficher un message générique
|
||||
error_log("Database connection error: " . $e->getMessage());
|
||||
die("Une erreur est survenue lors de la connexion à la base de données. Veuillez réessayer plus tard.");
|
||||
}
|
||||
|
||||
// Initialisation des services principaux
|
||||
$dbInstance = Database::getInstance(); // Récupère l'instance PDO
|
||||
$authService = new AuthService($dbInstance);
|
||||
$logService = new LogService(APP_LOG_PATH);
|
||||
$langService = new LanguageService(APP_ROOT_DIR . '/src/Lang/');
|
||||
|
||||
// ----------------------------------------------------
|
||||
// Gestion de la Langue et du Mode Sombre via URL ou Session
|
||||
// ----------------------------------------------------
|
||||
|
||||
// Traitement du changement de langue
|
||||
if (isset($_GET['lang'])) {
|
||||
$langService->setLanguage($_GET['lang']);
|
||||
// Redirige pour nettoyer le paramètre GET de l'URL
|
||||
header('Location: ' . strtok($_SERVER['REQUEST_URI'], '?'));
|
||||
exit();
|
||||
}
|
||||
$currentLang = $langService->getLanguage();
|
||||
$translations = $langService->getTranslations(); // Charge les traductions pour la langue actuelle
|
||||
|
||||
// Traitement du mode sombre
|
||||
DarkMode::init(); // Initialise le mode sombre si ce n'est pas déjà fait
|
||||
if (isset($_GET['dark_mode'])) {
|
||||
DarkMode::toggle($_GET['dark_mode']);
|
||||
// Redirige pour nettoyer le paramètre GET de l'URL
|
||||
header('Location: ' . strtok($_SERVER['REQUEST_URI'], '?'));
|
||||
exit();
|
||||
}
|
||||
|
||||
// ----------------------------------------------------
|
||||
// Log de chaque requête entrante (pour le débogage/audit)
|
||||
// ----------------------------------------------------
|
||||
$logService->log('info', 'Requête reçue: ' . $_SERVER['REQUEST_METHOD'] . ' ' . $_SERVER['REQUEST_URI'], $authService->getUserId(), $_SERVER['REMOTE_ADDR']);
|
||||
|
||||
|
||||
// ----------------------------------------------------
|
||||
// Processus d'initialisation de l'application au premier lancement
|
||||
// Crée le Root CA et le premier compte administrateur si non existants.
|
||||
// ----------------------------------------------------
|
||||
|
||||
$stmt = $dbInstance->query("SELECT COUNT(*) FROM users");
|
||||
$userCount = $stmt->fetchColumn();
|
||||
|
||||
// Vérifier l'existence du certificat root
|
||||
$rootCertExists = file_exists(ROOT_CA_PATH . '/certs/ca.cert.pem');
|
||||
|
||||
if ($userCount === 0 || !$rootCertExists) {
|
||||
// Afficher une page d'initialisation ou un message d'attente
|
||||
echo "<!DOCTYPE html><html lang=\"fr\"><head><meta charset=\"UTF-8\"><title>Initialisation</title><link rel=\"stylesheet\" href=\"/css/style.css\"></head><body>";
|
||||
echo "<div class=\"container\"><h1>Initialisation de l'Application</h1>";
|
||||
echo "<p>Ceci est le premier lancement. Nous allons configurer la base de données, créer le certificat Root CA et le premier compte administrateur.</p>";
|
||||
|
||||
// Création du certificat Root CA si non existant
|
||||
if (!$rootCertExists) {
|
||||
echo "<p>Création du certificat Root CA en cours...</p>";
|
||||
$logService->log('info', 'Lancement de la création du certificat Root CA.', null, $_SERVER['REMOTE_ADDR']);
|
||||
// Exécution du script shell de création de certificat root
|
||||
$command = escapeshellcmd(SCRIPTS_PATH . '/create_root_cert.sh'); // Utilise la constante
|
||||
$output = shell_exec($command . ' 2>&1');
|
||||
$logService->log('info', "Résultat création Root CA: " . $output, null, $_SERVER['REMOTE_ADDR']);
|
||||
|
||||
if (file_exists(ROOT_CA_PATH . '/certs/ca.cert.pem')) {
|
||||
echo "<p>Certificat Root CA créé avec succès.</p>";
|
||||
// Extraire la date d'expiration du certificat créé pour l'enregistrer en BDD
|
||||
$certInfo = shell_exec("openssl x509 -in " . escapeshellarg(ROOT_CA_PATH . '/certs/ca.cert.pem') . " -noout -enddate 2>/dev/null | cut -d= -f2");
|
||||
$expirationTimestamp = strtotime($certInfo);
|
||||
$expirationDate = $expirationTimestamp ? date('Y-m-d H:i:s', $expirationTimestamp) : (new DateTime('+10 years'))->format('Y-m-d H:i:s');
|
||||
|
||||
// Enregistrer le certificat root dans la base de données
|
||||
$stmt = $dbInstance->prepare("INSERT INTO certificates (name, type, expiration_date) VALUES (?, ?, ?)");
|
||||
$stmt->execute(['ca.cert.pem', 'root', $expirationDate]);
|
||||
} else {
|
||||
echo "<p style=\"color: red;\">Erreur lors de la création du certificat Root CA. Veuillez vérifier les logs PHP et Docker.</p>";
|
||||
echo "<pre>" . htmlspecialchars($output) . "</pre>";
|
||||
// Arrête l'exécution pour que l'utilisateur puisse voir l'erreur
|
||||
exit();
|
||||
}
|
||||
}
|
||||
|
||||
// Création du premier compte administrateur si non existant
|
||||
if ($userCount === 0) {
|
||||
echo "<p>Création du premier compte administrateur...</p>";
|
||||
$adminUsername = 'admin';
|
||||
$adminPasswordPlain = 'adminpass'; // Mot de passe par défaut très faible, À CHANGER IMMÉDIATEMENT EN PRODUCTION !
|
||||
$adminPasswordHashed = password_hash($adminPasswordPlain, PASSWORD_DEFAULT);
|
||||
|
||||
$stmt = $dbInstance->prepare("INSERT INTO users (username, password, role) VALUES (?, ?, ?)");
|
||||
$stmt->execute([$adminUsername, $adminPasswordHashed, 'admin']);
|
||||
$logService->log('info', "Compte administrateur '$adminUsername' créé.", $stmt->lastInsertId(), $_SERVER['REMOTE_ADDR']);
|
||||
echo "<p>Compte administrateur 'admin' créé avec succès. Mot de passe initial: <b>{$adminPasswordPlain}</b> (veuillez le changer après la première connexion !)</p>";
|
||||
}
|
||||
|
||||
echo "<p>Initialisation terminée. Redirection vers la page de connexion dans 5 secondes...</p>";
|
||||
echo "</div></body></html>";
|
||||
// Redirection automatique après l'initialisation
|
||||
header('Refresh: 5; URL=/login');
|
||||
exit();
|
||||
}
|
||||
// ----------------------------------------------------
|
||||
// Fin du processus d'initialisation
|
||||
// ----------------------------------------------------
|
||||
|
||||
|
||||
// ----------------------------------------------------
|
||||
// Configuration du routeur de l'application
|
||||
// ----------------------------------------------------
|
||||
$router = new Router();
|
||||
|
||||
// Routes publiques (accessibles sans authentification)
|
||||
$router->addRoute('GET', '/', 'HomeController@index');
|
||||
$router->addRoute('GET', '/login', 'AuthController@showLoginForm');
|
||||
$router->addRoute('POST', '/login', 'AuthController@login');
|
||||
|
||||
// Routes protégées (nécessitent une authentification)
|
||||
// Le dernier paramètre 'true' indique que la route nécessite une authentification
|
||||
$router->addRoute('GET', '/dashboard', 'DashboardController@index', true);
|
||||
$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', '/perimeters', 'PerimeterController@index', true);
|
||||
$router->addRoute('GET', '/perimeters/create', 'PerimeterController@showCreateForm', true);
|
||||
$router->addRoute('POST', '/perimeters/create', 'PerimeterController@create', true);
|
||||
$router->addRoute('GET', '/users', 'UserController@index', true);
|
||||
$router->addRoute('GET', '/users/create', 'UserController@showCreateForm', true);
|
||||
$router->addRoute('POST', '/users/create', 'UserController@create', true);
|
||||
$router->addRoute('POST', '/users/delete', 'UserController@delete', true);
|
||||
$router->addRoute('GET', '/logout', 'AuthController@logout', true);
|
||||
|
||||
// Exécuter le routage
|
||||
$router->dispatch();
|
162
app/public/index.php.bak
Normal file
162
app/public/index.php.bak
Normal file
@ -0,0 +1,162 @@
|
||||
<?php
|
||||
|
||||
// Démarrer la session PHP
|
||||
session_start();
|
||||
|
||||
// Inclusion des fichiers fondamentaux
|
||||
require_once __DIR__ . '/../src/Core/Autoloader.php';
|
||||
require_once __DIR__ . '/../src/Core/Router.php';
|
||||
require_once __DIR__ . '/../src/Core/Database.php';
|
||||
require_once __DIR__ . '/../src/config/app.php'; // Charge les constantes d'application
|
||||
|
||||
// Enregistrement de l'autoloader pour charger les classes automatiquement
|
||||
\App\Core\Autoloader::register();
|
||||
|
||||
// Importation des classes à utiliser
|
||||
use App\Core\Database;
|
||||
use App\Core\Router;
|
||||
use App\Services\AuthService;
|
||||
use App\Services\LanguageService;
|
||||
use App\Services\LogService;
|
||||
use App\Utils\DarkMode;
|
||||
|
||||
// Initialisation de la connexion à la base de données
|
||||
try {
|
||||
Database::connect(DB_HOST, DB_NAME, DB_USER, DB_PASSWORD);
|
||||
} catch (PDOException $e) {
|
||||
// En cas d'erreur de connexion, logguer et afficher un message générique
|
||||
error_log("Database connection error: " . $e->getMessage());
|
||||
die("Une erreur est survenue lors de la connexion à la base de données. Veuillez réessayer plus tard.");
|
||||
}
|
||||
|
||||
// Initialisation des services principaux
|
||||
$dbInstance = Database::getInstance(); // Récupère l'instance PDO
|
||||
$authService = new AuthService($dbInstance);
|
||||
$logService = new LogService(APP_LOG_PATH);
|
||||
$langService = new LanguageService(APP_ROOT_DIR . '/src/Lang/');
|
||||
|
||||
// ----------------------------------------------------
|
||||
// Gestion de la Langue et du Mode Sombre via URL ou Session
|
||||
// ----------------------------------------------------
|
||||
|
||||
// Traitement du changement de langue
|
||||
if (isset($_GET['lang'])) {
|
||||
$langService->setLanguage($_GET['lang']);
|
||||
// Redirige pour nettoyer le paramètre GET de l'URL
|
||||
header('Location: ' . strtok($_SERVER['REQUEST_URI'], '?'));
|
||||
exit();
|
||||
}
|
||||
$currentLang = $langService->getLanguage();
|
||||
$translations = $langService->getTranslations(); // Charge les traductions pour la langue actuelle
|
||||
|
||||
// Traitement du mode sombre
|
||||
DarkMode::init(); // Initialise le mode sombre si ce n'est pas déjà fait
|
||||
if (isset($_GET['dark_mode'])) {
|
||||
DarkMode::toggle($_GET['dark_mode']);
|
||||
// Redirige pour nettoyer le paramètre GET de l'URL
|
||||
header('Location: ' . strtok($_SERVER['REQUEST_URI'], '?'));
|
||||
exit();
|
||||
}
|
||||
|
||||
// ----------------------------------------------------
|
||||
// Log de chaque requête entrante (pour le débogage/audit)
|
||||
// ----------------------------------------------------
|
||||
$logService->log('info', 'Requête reçue: ' . $_SERVER['REQUEST_METHOD'] . ' ' . $_SERVER['REQUEST_URI'], $authService->getUserId(), $_SERVER['REMOTE_ADDR']);
|
||||
|
||||
|
||||
// ----------------------------------------------------
|
||||
// Processus d'initialisation de l'application au premier lancement
|
||||
// Crée le Root CA et le premier compte administrateur si non existants.
|
||||
// ----------------------------------------------------
|
||||
|
||||
$stmt = $dbInstance->query("SELECT COUNT(*) FROM users");
|
||||
$userCount = $stmt->fetchColumn();
|
||||
|
||||
// Vérifier l'existence du certificat root
|
||||
$rootCertExists = file_exists(ROOT_CA_PATH . '/certs/ca.cert.pem');
|
||||
|
||||
if ($userCount === 0 || !$rootCertExists) {
|
||||
// Afficher une page d'initialisation ou un message d'attente
|
||||
echo "<!DOCTYPE html><html lang=\"fr\"><head><meta charset=\"UTF-8\"><title>Initialisation</title><link rel=\"stylesheet\" href=\"/css/style.css\"></head><body>";
|
||||
echo "<div class=\"container\"><h1>Initialisation de l'Application</h1>";
|
||||
echo "<p>Ceci est le premier lancement. Nous allons configurer la base de données, créer le certificat Root CA et le premier compte administrateur.</p>";
|
||||
|
||||
// Création du certificat Root CA si non existant
|
||||
if (!$rootCertExists) {
|
||||
echo "<p>Création du certificat Root CA en cours...</p>";
|
||||
$logService->log('info', 'Lancement de la création du certificat Root CA.', null, $_SERVER['REMOTE_ADDR']);
|
||||
// Exécution du script shell de création de certificat root
|
||||
$command = escapeshellcmd(SCRIPTS_PATH . '/create_root_cert.sh'); // Utilise la constante
|
||||
$output = shell_exec($command . ' 2>&1');
|
||||
$logService->log('info', "Résultat création Root CA: " . $output, null, $_SERVER['REMOTE_ADDR']);
|
||||
|
||||
if (file_exists(ROOT_CA_PATH . '/certs/ca.cert.pem')) {
|
||||
echo "<p>Certificat Root CA créé avec succès.</p>";
|
||||
// Extraire la date d'expiration du certificat créé pour l'enregistrer en BDD
|
||||
$certInfo = shell_exec("openssl x509 -in " . escapeshellarg(ROOT_CA_PATH . '/certs/ca.cert.pem') . " -noout -enddate 2>/dev/null | cut -d= -f2");
|
||||
$expirationTimestamp = strtotime($certInfo);
|
||||
$expirationDate = $expirationTimestamp ? date('Y-m-d H:i:s', $expirationTimestamp) : (new DateTime('+10 years'))->format('Y-m-d H:i:s');
|
||||
|
||||
// Enregistrer le certificat root dans la base de données
|
||||
$stmt = $dbInstance->prepare("INSERT INTO certificates (name, type, expiration_date) VALUES (?, ?, ?)");
|
||||
$stmt->execute(['ca.cert.pem', 'root', $expirationDate]);
|
||||
} else {
|
||||
echo "<p style=\"color: red;\">Erreur lors de la création du certificat Root CA. Veuillez vérifier les logs PHP et Docker.</p>";
|
||||
echo "<pre>" . htmlspecialchars($output) . "</pre>";
|
||||
// Arrête l'exécution pour que l'utilisateur puisse voir l'erreur
|
||||
exit();
|
||||
}
|
||||
}
|
||||
|
||||
// Création du premier compte administrateur si non existant
|
||||
if ($userCount === 0) {
|
||||
echo "<p>Création du premier compte administrateur...</p>";
|
||||
$adminUsername = 'admin';
|
||||
$adminPasswordPlain = 'adminpass'; // Mot de passe par défaut très faible, À CHANGER IMMÉDIATEMENT EN PRODUCTION !
|
||||
$adminPasswordHashed = password_hash($adminPasswordPlain, PASSWORD_DEFAULT);
|
||||
|
||||
$stmt = $dbInstance->prepare("INSERT INTO users (username, password, role) VALUES (?, ?, ?)");
|
||||
$stmt->execute([$adminUsername, $adminPasswordHashed, 'admin']);
|
||||
$logService->log('info', "Compte administrateur '$adminUsername' créé.", $stmt->lastInsertId(), $_SERVER['REMOTE_ADDR']);
|
||||
echo "<p>Compte administrateur 'admin' créé avec succès. Mot de passe initial: <b>{$adminPasswordPlain}</b> (veuillez le changer après la première connexion !)</p>";
|
||||
}
|
||||
|
||||
echo "<p>Initialisation terminée. Redirection vers la page de connexion dans 5 secondes...</p>";
|
||||
echo "</div></body></html>";
|
||||
// Redirection automatique après l'initialisation
|
||||
header('Refresh: 5; URL=/login');
|
||||
exit();
|
||||
}
|
||||
// ----------------------------------------------------
|
||||
// Fin du processus d'initialisation
|
||||
// ----------------------------------------------------
|
||||
|
||||
|
||||
// ----------------------------------------------------
|
||||
// Configuration du routeur de l'application
|
||||
// ----------------------------------------------------
|
||||
$router = new Router();
|
||||
|
||||
// Routes publiques (accessibles sans authentification)
|
||||
$router->addRoute('GET', '/', 'HomeController@index');
|
||||
$router->addRoute('GET', '/login', 'AuthController@showLoginForm');
|
||||
$router->addRoute('POST', '/login', 'AuthController@login');
|
||||
|
||||
// Routes protégées (nécessitent une authentification)
|
||||
// Le dernier paramètre 'true' indique que la route nécessite une authentification
|
||||
$router->addRoute('GET', '/dashboard', 'DashboardController@index', true);
|
||||
$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', '/perimeters', 'PerimeterController@index', true);
|
||||
$router->addRoute('GET', '/perimeters/create', 'PerimeterController@showCreateForm', true);
|
||||
$router->addRoute('POST', '/perimeters/create', 'PerimeterController@create', true);
|
||||
$router->addRoute('GET', '/users', 'UserController@index', true);
|
||||
$router->addRoute('GET', '/users/create', 'UserController@showCreateForm', true);
|
||||
$router->addRoute('POST', '/users/create', 'UserController@create', true);
|
||||
$router->addRoute('POST', '/users/delete', 'UserController@delete', true);
|
||||
$router->addRoute('GET', '/logout', 'AuthController@logout', true); # CORRIGÉ: de AuthController@@logout à AuthController@logout
|
||||
|
||||
// Exécuter le routage
|
||||
$router->dispatch();
|
47
app/public/ocsp_responder.php
Normal file
47
app/public/ocsp_responder.php
Normal file
@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
// Démarrer la session PHP (si nécessaire pour la journalisation utilisateur, sinon peut être omis)
|
||||
session_start();
|
||||
|
||||
// Inclusion des fichiers fondamentaux
|
||||
require_once __DIR__ . '/../src/Core/Autoloader.php';
|
||||
require_once __DIR__ . '/../src/Core/Database.php';
|
||||
require_once __DIR__ . '/../src/config/app.php'; // Charge les constantes d'application
|
||||
|
||||
// Enregistrement de l'autoloader
|
||||
\App\Core\Autoloader::register();
|
||||
|
||||
// Importation des classes
|
||||
use App\Core\Database;
|
||||
use App\Controllers\OcspController; // Votre contrôleur spécifique pour l'OCSP
|
||||
use App\Services\AuthService;
|
||||
use App\Services\LogService;
|
||||
|
||||
// Initialisation de la connexion à la base de données (si le contrôleur OCSP en a besoin)
|
||||
try {
|
||||
Database::connect(DB_HOST, DB_NAME, DB_USER, DB_PASSWORD);
|
||||
} catch (PDOException $e) {
|
||||
error_log("OCSP: Database connection error: " . $e->getMessage());
|
||||
http_response_code(500);
|
||||
die("OCSP service temporarily unavailable.");
|
||||
}
|
||||
|
||||
$dbInstance = Database::getInstance();
|
||||
$authService = new AuthService($dbInstance); // Peut être utilisé pour logguer des requêtes OCSP anonymes
|
||||
$logService = new LogService(APP_LOG_PATH);
|
||||
|
||||
|
||||
// La logique OCSP réelle serait plus complexe.
|
||||
// Ce script attendrait une requête POST OCSP (application/ocsp-request)
|
||||
// et appellerait le contrôleur OCSP pour la traiter.
|
||||
|
||||
// Pour un POC, nous allons simplement appeler la méthode du contrôleur dédiée.
|
||||
// En production, Nginx redirigerait une requête HTTP POST spécifique vers ce script.
|
||||
// Le client OCSP enverrait une requête binaire.
|
||||
// Pour l'instant, ce script est juste un point d'entrée.
|
||||
|
||||
$controller = new OcspController();
|
||||
$controller->handleRequest();
|
||||
|
||||
// Log la requête OCSP
|
||||
$logService->log('info', 'Requête OCSP reçue.', null, $_SERVER['REMOTE_ADDR']);
|
47
app/public/ocsp_responder.php.bak
Normal file
47
app/public/ocsp_responder.php.bak
Normal file
@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
// Démarrer la session PHP (si nécessaire pour la journalisation utilisateur, sinon peut être omis)
|
||||
session_start();
|
||||
|
||||
// Inclusion des fichiers fondamentaux
|
||||
require_once __DIR__ . '/../src/Core/Autoloader.php';
|
||||
require_once __DIR__ . '/../src/Core/Database.php';
|
||||
require_once __DIR__ . '/../src/config/app.php'; // Charge les constantes d'application
|
||||
|
||||
// Enregistrement de l'autoloader
|
||||
\App\Core\Autoloader::register();
|
||||
|
||||
// Importation des classes
|
||||
use App\Core\Database;
|
||||
use App\Controllers\OcspController; // Votre contrôleur spécifique pour l'OCSP
|
||||
use App\Services\AuthService;
|
||||
use App\Services\LogService;
|
||||
|
||||
// Initialisation de la connexion à la base de données (si le contrôleur OCSP en a besoin)
|
||||
try {
|
||||
Database::connect(DB_HOST, DB_NAME, DB_USER, DB_PASSWORD);
|
||||
} catch (PDOException $e) {
|
||||
error_log("OCSP: Database connection error: " . $e->getMessage());
|
||||
http_response_code(500);
|
||||
die("OCSP service temporarily unavailable.");
|
||||
}
|
||||
|
||||
$dbInstance = Database::getInstance();
|
||||
$authService = new AuthService($dbInstance); // Peut être utilisé pour logguer des requêtes OCSP anonymes
|
||||
$logService = new LogService(APP_LOG_PATH);
|
||||
|
||||
|
||||
// La logique OCSP réelle serait plus complexe.
|
||||
// Ce script attendrait une requête POST OCSP (application/ocsp-request)
|
||||
// et appellerait le contrôleur OCSP pour la traiter.
|
||||
|
||||
// Pour un POC, nous allons simplement appeler la méthode du contrôleur dédiée.
|
||||
// En production, Nginx redirigerait une requête HTTP POST spécifique vers ce script.
|
||||
// Le client OCSP enverrait une requête binaire.
|
||||
// Pour l'instant, ce script est juste un point d'entrée.
|
||||
|
||||
$controller = new OcspController();
|
||||
$controller->handleRequest();
|
||||
|
||||
// Log la requête OCSP
|
||||
$logService->log('info', 'Requête OCSP reçue.', null, $_SERVER['REMOTE_ADDR']);
|
366
app/public/style.css
Normal file
366
app/public/style.css
Normal file
@ -0,0 +1,366 @@
|
||||
/* Variables CSS pour faciliter le basculement entre les thèmes */
|
||||
:root {
|
||||
--bg-color: #f4f4f4;
|
||||
--text-color: #333;
|
||||
--container-bg: #fff;
|
||||
--container-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
--header-bg: #0056b3;
|
||||
--header-text: #fff;
|
||||
--nav-bg: #e2e2e2;
|
||||
--nav-link-color: #0056b3;
|
||||
--nav-link-hover-bg: #d1d1d1;
|
||||
--table-border-color: #ddd;
|
||||
--table-header-bg: #0056b3;
|
||||
--table-header-text: #fff;
|
||||
--table-row-even-bg: #f2f2f2;
|
||||
--button-primary-bg: #007bff;
|
||||
--button-primary-hover-bg: #0056b3;
|
||||
--button-secondary-bg: #6c757d;
|
||||
--button-secondary-hover-bg: #5a6268;
|
||||
--button-danger-bg: #dc3545;
|
||||
--button-danger-hover-bg: #bd2130;
|
||||
--status-revoked-color: red;
|
||||
--status-active-color: green;
|
||||
--message-success-color: green;
|
||||
--message-error-color: red;
|
||||
--input-border: #ccc;
|
||||
--input-focus-border: #007bff;
|
||||
}
|
||||
|
||||
/* Styles généraux */
|
||||
body {
|
||||
font-family: 'Inter', sans-serif; /* Utilisation de Inter comme demandé */
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
background-color: var(--bg-color);
|
||||
color: var(--text-color);
|
||||
line-height: 1.6;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 960px;
|
||||
margin: 20px auto;
|
||||
background-color: var(--container-bg);
|
||||
padding: 20px 30px;
|
||||
border-radius: 12px; /* Coins arrondis */
|
||||
box-shadow: var(--container-shadow);
|
||||
flex-grow: 1; /* Permet au container de prendre de l'espace */
|
||||
}
|
||||
|
||||
h1, h2 {
|
||||
color: var(--header-bg);
|
||||
margin-top: 0;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
/* En-tête de l'application */
|
||||
.app-header {
|
||||
background-color: var(--header-bg);
|
||||
color: var(--header-text);
|
||||
padding: 15px 0;
|
||||
box-shadow: 0 2px 5px rgba(0,0,0,0.2);
|
||||
}
|
||||
|
||||
.header-content {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
max-width: 960px;
|
||||
margin: 0 auto;
|
||||
padding: 0 20px;
|
||||
}
|
||||
|
||||
.app-title h1 {
|
||||
color: var(--header-text);
|
||||
margin: 0;
|
||||
font-size: 1.8em;
|
||||
}
|
||||
|
||||
.header-controls {
|
||||
display: flex;
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
.language-switcher, .dark-mode-switcher {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.lang-button, .dark-mode-button {
|
||||
padding: 8px 12px;
|
||||
text-decoration: none;
|
||||
color: var(--header-text);
|
||||
border: 1px solid rgba(255, 255, 255, 0.4);
|
||||
border-radius: 8px; /* Coins arrondis */
|
||||
transition: background-color 0.3s ease;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
}
|
||||
|
||||
.lang-button:hover, .dark-mode-button:hover {
|
||||
background-color: rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
|
||||
.lang-button.active {
|
||||
background-color: rgba(255, 255, 255, 0.3);
|
||||
border-color: var(--header-text);
|
||||
}
|
||||
|
||||
/* Styles de navigation */
|
||||
nav ul {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 20px 0;
|
||||
background-color: var(--nav-bg);
|
||||
border-radius: 10px; /* Coins arrondis */
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
overflow: hidden; /* Pour que les coins arrondis fonctionnent bien avec le hover */
|
||||
box-shadow: inset 0 1px 3px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
nav ul li {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
nav ul li a {
|
||||
display: block;
|
||||
padding: 12px 20px;
|
||||
text-decoration: none;
|
||||
color: var(--nav-link-color);
|
||||
font-weight: bold;
|
||||
transition: background-color 0.3s ease, color 0.3s ease;
|
||||
border-radius: 8px; /* Appliquer aux liens pour le hover */
|
||||
}
|
||||
|
||||
nav ul li a:hover {
|
||||
background-color: var(--nav-link-hover-bg);
|
||||
color: var(--button-primary-hover-bg); /* Ou une autre couleur contrastante */
|
||||
}
|
||||
|
||||
/* Styles des messages */
|
||||
.success-message {
|
||||
background-color: #d4edda;
|
||||
color: var(--message-success-color);
|
||||
border: 1px solid #c3e6cb;
|
||||
padding: 10px;
|
||||
border-radius: 8px;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.error-message {
|
||||
background-color: #f8d7da;
|
||||
color: var(--message-error-color);
|
||||
border: 1px solid #f5c6cb;
|
||||
padding: 10px;
|
||||
border-radius: 8px;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
/* Styles de tableau */
|
||||
.table-responsive {
|
||||
overflow-x: auto; /* Pour les petits écrans */
|
||||
}
|
||||
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin-top: 20px;
|
||||
border-radius: 12px; /* Coins arrondis pour le tableau */
|
||||
overflow: hidden; /* Important pour que les coins arrondis soient visibles */
|
||||
}
|
||||
|
||||
table, th, td {
|
||||
border: 1px solid var(--table-border-color);
|
||||
}
|
||||
|
||||
th, td {
|
||||
padding: 12px 15px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
th {
|
||||
background-color: var(--table-header-bg);
|
||||
color: var(--table-header-text);
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
tr:nth-child(even) {
|
||||
background-color: var(--table-row-even-bg);
|
||||
}
|
||||
|
||||
tr:hover {
|
||||
background-color: rgba(0, 0, 0, 0.05); /* Léger survol */
|
||||
}
|
||||
|
||||
/* Styles des boutons */
|
||||
.button {
|
||||
display: inline-block;
|
||||
padding: 10px 20px;
|
||||
font-size: 1em;
|
||||
text-decoration: none;
|
||||
color: white;
|
||||
border-radius: 8px; /* Coins arrondis */
|
||||
transition: background-color 0.3s ease, transform 0.2s ease;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.button:active {
|
||||
transform: translateY(1px);
|
||||
}
|
||||
|
||||
.primary-button {
|
||||
background-color: var(--button-primary-bg);
|
||||
}
|
||||
|
||||
.primary-button:hover {
|
||||
background-color: var(--button-primary-hover-bg);
|
||||
}
|
||||
|
||||
.secondary-button {
|
||||
background-color: var(--button-secondary-bg);
|
||||
}
|
||||
|
||||
.secondary-button:hover {
|
||||
background-color: var(--button-secondary-hover-bg);
|
||||
}
|
||||
|
||||
.danger-button {
|
||||
background-color: var(--button-danger-bg);
|
||||
}
|
||||
|
||||
.danger-button:hover {
|
||||
background-color: var(--button-danger-hover-bg);
|
||||
}
|
||||
|
||||
.logout-button {
|
||||
background-color: #f44336;
|
||||
}
|
||||
.logout-button:hover {
|
||||
background-color: #d32f2f;
|
||||
}
|
||||
|
||||
.actions-bar {
|
||||
display: flex;
|
||||
justify-content: flex-start; /* Alignement à gauche */
|
||||
gap: 10px; /* Espace entre les boutons */
|
||||
margin-bottom: 20px;
|
||||
flex-wrap: wrap; /* Pour la réactivité sur mobile */
|
||||
}
|
||||
|
||||
/* Formulaires */
|
||||
form label {
|
||||
display: block;
|
||||
margin-bottom: 8px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
form input[type="text"],
|
||||
form input[type="password"],
|
||||
form select {
|
||||
width: calc(100% - 22px); /* 100% moins padding et border */
|
||||
padding: 10px;
|
||||
margin-bottom: 15px;
|
||||
border: 1px solid var(--input-border);
|
||||
border-radius: 8px; /* Coins arrondis */
|
||||
font-size: 1em;
|
||||
box-sizing: border-box; /* Inclut padding et border dans la largeur */
|
||||
}
|
||||
|
||||
form input[type="text"]:focus,
|
||||
form input[type="password"]:focus,
|
||||
form select:focus {
|
||||
border-color: var(--input-focus-border);
|
||||
outline: none;
|
||||
box-shadow: 0 0 0 3px rgba(0, 123, 255, 0.25);
|
||||
}
|
||||
|
||||
form button[type="submit"] {
|
||||
width: auto;
|
||||
padding: 12px 25px;
|
||||
font-size: 1.1em;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
/* Messages de statut de certificat */
|
||||
.status-revoked {
|
||||
color: var(--status-revoked-color);
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.status-active {
|
||||
color: var(--status-active-color);
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
/* Ligne de certificat révoqué */
|
||||
tr.revoked-cert {
|
||||
text-decoration: line-through;
|
||||
color: #888;
|
||||
background-color: #fce4e4; /* Léger fond rouge pour le mode clair */
|
||||
}
|
||||
tr.revoked-cert:hover {
|
||||
background-color: #fcc;
|
||||
}
|
||||
|
||||
/* Pied de page de l'application */
|
||||
.app-footer {
|
||||
background-color: #333;
|
||||
color: #f4f4f4;
|
||||
text-align: center;
|
||||
padding: 15px 0;
|
||||
margin-top: auto; /* Pousse le footer en bas */
|
||||
}
|
||||
|
||||
.app-footer .container {
|
||||
max-width: 960px;
|
||||
margin: 0 auto;
|
||||
padding: 0 20px;
|
||||
background-color: transparent; /* Pas de fond blanc pour le footer */
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
/* Réactivité mobile */
|
||||
@media (max-width: 768px) {
|
||||
.container {
|
||||
margin: 10px;
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
.app-header .header-content {
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
nav ul {
|
||||
flex-direction: column;
|
||||
gap: 5px;
|
||||
}
|
||||
|
||||
nav ul li a {
|
||||
padding: 10px 15px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.actions-bar {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.button {
|
||||
width: 100%;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
form input[type="text"],
|
||||
form input[type="password"],
|
||||
form select {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
98
app/src/Controllers/AuthController.php
Normal file
98
app/src/Controllers/AuthController.php
Normal file
@ -0,0 +1,98 @@
|
||||
<?php
|
||||
|
||||
namespace App\Controllers;
|
||||
|
||||
use App\Core\Database;
|
||||
use App\Services\AuthService;
|
||||
use App\Services\LanguageService;
|
||||
use App\Utils\DarkMode;
|
||||
use App\Services\LogService; // Pour les logs de connexion
|
||||
|
||||
/**
|
||||
* Contrôleur pour la gestion de l'authentification (connexion, déconnexion).
|
||||
*/
|
||||
class AuthController
|
||||
{
|
||||
private $authService;
|
||||
private $langService;
|
||||
private $logService;
|
||||
|
||||
/**
|
||||
* Constructeur du AuthController.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$db = Database::getInstance();
|
||||
$this->authService = new AuthService($db);
|
||||
$this->langService = new LanguageService(APP_ROOT_DIR . '/src/Lang/');
|
||||
$this->logService = new LogService(APP_LOG_PATH);
|
||||
}
|
||||
|
||||
/**
|
||||
* Affiche le formulaire de connexion.
|
||||
* Redirige vers le tableau de bord si l'utilisateur est déjà connecté.
|
||||
*/
|
||||
public function showLoginForm()
|
||||
{
|
||||
if ($this->authService->isLoggedIn()) {
|
||||
header('Location: /dashboard');
|
||||
exit();
|
||||
}
|
||||
|
||||
// Charge les traductions et la classe pour le mode sombre pour la vue
|
||||
global $translations; // Utilise la variable globale chargée dans index.php
|
||||
$currentLang = $this->langService->getLanguage();
|
||||
$darkModeClass = DarkMode::getBodyClass();
|
||||
|
||||
// Récupère les messages d'erreur/succès de la session
|
||||
$error = $_SESSION['error'] ?? null;
|
||||
unset($_SESSION['error']); // Supprime le message après l'avoir affiché
|
||||
|
||||
require_once APP_ROOT_DIR . '/src/Views/auth/login.php';
|
||||
}
|
||||
|
||||
/**
|
||||
* Traite la soumission du formulaire de connexion.
|
||||
*/
|
||||
public function login()
|
||||
{
|
||||
// Vérifie si la requête est bien un POST
|
||||
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
||||
header('Location: /login');
|
||||
exit();
|
||||
}
|
||||
|
||||
$username = trim($_POST['username'] ?? '');
|
||||
$password = $_POST['password'] ?? '';
|
||||
$ipAddress = $_SERVER['REMOTE_ADDR'];
|
||||
|
||||
// Validation simple des entrées
|
||||
if (empty($username) || empty($password)) {
|
||||
$_SESSION['error'] = $this->langService->__('login_error_empty_fields');
|
||||
header('Location: /login');
|
||||
exit();
|
||||
}
|
||||
|
||||
if ($this->authService->login($username, $password, $ipAddress)) {
|
||||
// Connexion réussie
|
||||
header('Location: /dashboard');
|
||||
exit();
|
||||
} else {
|
||||
// Connexion échouée
|
||||
$_SESSION['error'] = $this->langService->__('login_error_credentials');
|
||||
header('Location: /login');
|
||||
exit();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Déconnecte l'utilisateur.
|
||||
*/
|
||||
public function logout()
|
||||
{
|
||||
$ipAddress = $_SERVER['REMOTE_ADDR'];
|
||||
$this->authService->logout($ipAddress);
|
||||
header('Location: /login');
|
||||
exit();
|
||||
}
|
||||
}
|
251
app/src/Controllers/CertificateController.php
Normal file
251
app/src/Controllers/CertificateController.php
Normal file
@ -0,0 +1,251 @@
|
||||
<?php
|
||||
|
||||
namespace App\Controllers;
|
||||
|
||||
use App\Core\Database;
|
||||
use App\Services\AuthService;
|
||||
use App\Services\LogService;
|
||||
use App\Services\LanguageService;
|
||||
use App\Utils\DarkMode;
|
||||
|
||||
/**
|
||||
* Contrôleur pour la gestion des certificats.
|
||||
* (Création, révocation, affichage).
|
||||
*/
|
||||
class CertificateController
|
||||
{
|
||||
private $db;
|
||||
private $authService;
|
||||
private $logService;
|
||||
private $langService;
|
||||
|
||||
/**
|
||||
* Constructeur du CertificateController.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->db = Database::getInstance();
|
||||
$this->authService = new AuthService($this->db);
|
||||
$this->logService = new LogService(APP_LOG_PATH);
|
||||
$this->langService = new LanguageService(APP_ROOT_DIR . '/src/Lang/');
|
||||
}
|
||||
|
||||
/**
|
||||
* Affiche la liste des certificats, regroupés par périmètre fonctionnel.
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
if (!$this->authService->isLoggedIn()) {
|
||||
header('Location: /login');
|
||||
exit();
|
||||
}
|
||||
|
||||
global $translations;
|
||||
$currentLang = $this->langService->getLanguage();
|
||||
$darkModeClass = DarkMode::getBodyClass();
|
||||
$userRole = $this->authService->getUserRole();
|
||||
|
||||
// Récupérer les périmètres et les certificats
|
||||
// Joindre pour obtenir le nom du périmètre
|
||||
$certificates = $this->db->query("
|
||||
SELECT
|
||||
c.id, c.name, c.type, c.expiration_date, c.is_revoked, c.revoked_at,
|
||||
fp.name as perimeter_name
|
||||
FROM
|
||||
certificates c
|
||||
LEFT JOIN
|
||||
functional_perimeters fp ON c.functional_perimeter_id = fp.id
|
||||
ORDER BY
|
||||
fp.name IS NULL DESC, fp.name ASC, c.type DESC, c.expiration_date DESC
|
||||
")->fetchAll();
|
||||
|
||||
// Regrouper les certificats par périmètre fonctionnel
|
||||
$groupedCertificates = [];
|
||||
foreach ($certificates as $cert) {
|
||||
$perimeterName = $cert['perimeter_name'] ?? 'Certificats Root'; // Nom pour le groupe Root
|
||||
if (!isset($groupedCertificates[$perimeterName])) {
|
||||
$groupedCertificates[$perimeterName] = [];
|
||||
}
|
||||
$groupedCertificates[$perimeterName][] = $cert;
|
||||
}
|
||||
|
||||
$successMessage = $_SESSION['success'] ?? null;
|
||||
unset($_SESSION['success']);
|
||||
$errorMessage = $_SESSION['error'] ?? null;
|
||||
unset($_SESSION['error']);
|
||||
|
||||
require_once APP_ROOT_DIR . '/src/Views/certificates/index.php';
|
||||
}
|
||||
|
||||
/**
|
||||
* Affiche le formulaire de création d'un nouveau certificat.
|
||||
*/
|
||||
public function showCreateForm()
|
||||
{
|
||||
if (!$this->authService->isLoggedIn()) {
|
||||
header('Location: /login');
|
||||
exit();
|
||||
}
|
||||
|
||||
global $translations;
|
||||
$currentLang = $this->langService->getLanguage();
|
||||
$darkModeClass = DarkMode::getBodyClass();
|
||||
|
||||
// Récupérer la liste des périmètres fonctionnels pour le sélecteur
|
||||
$perimeters = $this->db->query("SELECT id, name FROM functional_perimeters ORDER BY name ASC")->fetchAll();
|
||||
|
||||
$errorMessage = $_SESSION['error'] ?? null;
|
||||
unset($_SESSION['error']);
|
||||
|
||||
require_once APP_ROOT_DIR . '/src/Views/certificates/create.php';
|
||||
}
|
||||
|
||||
/**
|
||||
* Traite la soumission du formulaire de création de certificat.
|
||||
*/
|
||||
public function create()
|
||||
{
|
||||
if (!$this->authService->isLoggedIn() || $_SERVER['REQUEST_METHOD'] !== 'POST') {
|
||||
header('Location: /login');
|
||||
exit();
|
||||
}
|
||||
|
||||
$subdomainName = trim($_POST['subdomain_name'] ?? '');
|
||||
$functionalPerimeterId = $_POST['functional_perimeter_id'] ?? null;
|
||||
$ipAddress = $_SERVER['REMOTE_ADDR'];
|
||||
$userId = $this->authService->getUserId();
|
||||
|
||||
if (empty($subdomainName) || empty($functionalPerimeterId)) {
|
||||
$_SESSION['error'] = $this->langService->__('cert_create_error_empty_fields');
|
||||
header('Location: /certificates/create');
|
||||
exit();
|
||||
}
|
||||
|
||||
// Récupérer le nom du périmètre fonctionnel pour le script shell
|
||||
$stmt = $this->db->prepare("SELECT name FROM functional_perimeters WHERE id = ?");
|
||||
$stmt->execute([$functionalPerimeterId]);
|
||||
$perimeter = $stmt->fetch();
|
||||
|
||||
if (!$perimeter) {
|
||||
$_SESSION['error'] = $this->langService->__('cert_create_error_perimeter_not_found');
|
||||
header('Location: /certificates/create');
|
||||
exit();
|
||||
}
|
||||
$functionalPerimeterName = $perimeter['name'];
|
||||
|
||||
// Préparer la commande du script shell
|
||||
// Important: utiliser escapeshellarg pour protéger les arguments
|
||||
$command = escapeshellcmd(SCRIPTS_PATH . '/create_cert.sh') . ' ' .
|
||||
escapeshellarg($subdomainName) . ' ' .
|
||||
escapeshellarg($functionalPerimeterName);
|
||||
|
||||
$this->logService->log('info', "Tentative de création du certificat '$subdomainName' pour le périmètre '$functionalPerimeterName'. Commande: '$command'", $userId, $ipAddress);
|
||||
|
||||
// Exécuter le script shell
|
||||
$output = shell_exec($command . ' 2>&1'); // Redirige stderr vers stdout
|
||||
|
||||
// Vérifier le résultat du script (simple vérification de chaîne, une meilleure parsage serait utile)
|
||||
if (strpos($output, "Certificat '${subdomainName}.${functionalPerimeterName}.cert' créé avec succès") !== false) {
|
||||
// Extraire la date d'expiration du certificat créé (en lisant le fichier cert ou en estimant 1 an)
|
||||
$certFileName = "{$subdomainName}.{$functionalPerimeterName}.cert.pem";
|
||||
$fullCertPath = INTERMEDIATE_CA_PATH_BASE . "/{$functionalPerimeterName}/certs/{$certFileName}";
|
||||
|
||||
$expirationDate = (new \DateTime('+1 year'))->format('Y-m-d H:i:s'); // Valeur par défaut
|
||||
if (file_exists($fullCertPath)) {
|
||||
$certInfo = shell_exec("openssl x509 -in " . escapeshellarg($fullCertPath) . " -noout -enddate 2>/dev/null | cut -d= -f2");
|
||||
$expirationTimestamp = strtotime($certInfo);
|
||||
if ($expirationTimestamp) {
|
||||
$expirationDate = date('Y-m-d H:i:s', $expirationTimestamp);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Enregistrer le certificat dans la base de données
|
||||
$stmt = $this->db->prepare("INSERT INTO certificates (name, type, functional_perimeter_id, expiration_date) VALUES (?, ?, ?, ?)");
|
||||
$stmt->execute([$certFileName, 'simple', $functionalPerimeterId, $expirationDate]);
|
||||
|
||||
$this->logService->log('info', "Certificat '{$certFileName}' créé et enregistré pour le périmètre '{$functionalPerimeterName}'.", $userId, $ipAddress);
|
||||
$_SESSION['success'] = $this->langService->__('cert_create_success');
|
||||
} else {
|
||||
$_SESSION['error'] = $this->langService->__('cert_create_error', ['output' => htmlspecialchars($output)]);
|
||||
$this->logService->log('error', "Échec création certificat '$subdomainName' pour périmètre '$functionalPerimeterName'. Output: $output", $userId, $ipAddress);
|
||||
}
|
||||
|
||||
header('Location: /certificates');
|
||||
exit();
|
||||
}
|
||||
|
||||
/**
|
||||
* Traite la révocation d'un certificat.
|
||||
*/
|
||||
public function revoke()
|
||||
{
|
||||
if (!$this->authService->isLoggedIn() || $_SERVER['REQUEST_METHOD'] !== 'POST') {
|
||||
header('Location: /login');
|
||||
exit();
|
||||
}
|
||||
|
||||
$certificateId = $_POST['certificate_id'] ?? null;
|
||||
$ipAddress = $_SERVER['REMOTE_ADDR'];
|
||||
$userId = $this->authService->getUserId();
|
||||
|
||||
if (empty($certificateId)) {
|
||||
$_SESSION['error'] = $this->langService->__('cert_revoke_error_id_missing');
|
||||
header('Location: /certificates');
|
||||
exit();
|
||||
}
|
||||
|
||||
// Récupérer les informations du certificat depuis la DB
|
||||
$stmt = $this->db->prepare("SELECT c.name, c.type, fp.name as perimeter_name FROM certificates c LEFT JOIN functional_perimeters fp ON c.functional_perimeter_id = fp.id WHERE c.id = ?");
|
||||
$stmt->execute([$certificateId]);
|
||||
$cert = $stmt->fetch();
|
||||
|
||||
if (!$cert) {
|
||||
$_SESSION['error'] = $this->langService->__('cert_revoke_error_not_found');
|
||||
header('Location: /certificates');
|
||||
exit();
|
||||
}
|
||||
|
||||
// Empêcher la révocation des certificats Root ou Intermédiaires via l'interface
|
||||
if ($cert['type'] === 'root' || $cert['type'] === 'intermediate') {
|
||||
$_SESSION['error'] = $this->langService->__('cert_revoke_error_ca_revocation');
|
||||
header('Location: /certificates');
|
||||
exit();
|
||||
}
|
||||
|
||||
// Préparer le nom de base du certificat pour le script (sans l'extension .pem)
|
||||
$certBaseName = str_replace('.cert.pem', '.cert', $cert['name']);
|
||||
$functionalPerimeterName = $cert['perimeter_name'];
|
||||
|
||||
// Vérifier si le certificat n'est pas déjà révoqué dans la DB
|
||||
if ($cert['is_revoked']) {
|
||||
$_SESSION['error'] = $this->langService->__('cert_revoke_error_already_revoked');
|
||||
header('Location: /certificates');
|
||||
exit();
|
||||
}
|
||||
|
||||
// Appeler le script shell de révocation
|
||||
$command = escapeshellcmd(SCRIPTS_PATH . '/revoke_cert.sh') . ' ' .
|
||||
escapeshellarg($certBaseName) . ' ' .
|
||||
escapeshellarg($functionalPerimeterName);
|
||||
|
||||
$this->logService->log('info', "Tentative de révocation du certificat '{$cert['name']}' pour le périmètre '$functionalPerimeterName'. Commande: '$command'", $userId, $ipAddress);
|
||||
|
||||
$output = shell_exec($command . ' 2>&1');
|
||||
|
||||
if (strpos($output, "Certificat '$certBaseName' révoqué avec succès.") !== false) {
|
||||
// Mettre à jour le statut du certificat dans la base de données
|
||||
$stmt = $this->db->prepare("UPDATE certificates SET is_revoked = TRUE, revoked_at = NOW() WHERE id = ?");
|
||||
$stmt->execute([$certificateId]);
|
||||
|
||||
$this->logService->log('info', "Certificat '{$cert['name']}' révoqué et enregistré en DB.", $userId, $ipAddress);
|
||||
$_SESSION['success'] = $this->langService->__('cert_revoke_success');
|
||||
} else {
|
||||
$_SESSION['error'] = $this->langService->__('cert_revoke_error', ['output' => htmlspecialchars($output)]);
|
||||
$this->logService->log('error', "Échec révocation certificat '{$cert['name']}': $output", $userId, $ipAddress);
|
||||
}
|
||||
|
||||
header('Location: /certificates');
|
||||
exit();
|
||||
}
|
||||
}
|
47
app/src/Controllers/DashboardController.php
Normal file
47
app/src/Controllers/DashboardController.php
Normal file
@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
namespace App\Controllers;
|
||||
|
||||
use App\Core\Database;
|
||||
use App\Services\AuthService;
|
||||
use App\Services\LanguageService;
|
||||
use App\Utils\DarkMode;
|
||||
|
||||
/**
|
||||
* Contrôleur pour la page du tableau de bord.
|
||||
*/
|
||||
class DashboardController
|
||||
{
|
||||
private $authService;
|
||||
private $langService;
|
||||
|
||||
/**
|
||||
* Constructeur du DashboardController.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->authService = new AuthService(Database::getInstance());
|
||||
$this->langService = new LanguageService(APP_ROOT_DIR . '/src/Lang/');
|
||||
}
|
||||
|
||||
/**
|
||||
* Affiche le tableau de bord.
|
||||
* Redirige vers la page de connexion si l'utilisateur n'est pas connecté.
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
if (!$this->authService->isLoggedIn()) {
|
||||
header('Location: /login');
|
||||
exit();
|
||||
}
|
||||
|
||||
// Récupère les traductions et les informations pour la vue
|
||||
global $translations;
|
||||
$currentLang = $this->langService->getLanguage();
|
||||
$username = $this->authService->getUsername();
|
||||
$darkModeClass = DarkMode::getBodyClass();
|
||||
$userRole = $this->authService->getUserRole(); // Pour afficher/masquer certains éléments
|
||||
|
||||
require_once APP_ROOT_DIR . '/src/Views/dashboard/index.php';
|
||||
}
|
||||
}
|
45
app/src/Controllers/HomeController.php
Normal file
45
app/src/Controllers/HomeController.php
Normal file
@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
namespace App\Controllers;
|
||||
|
||||
use App\Core\Database;
|
||||
use App\Services\AuthService;
|
||||
use App\Services\LanguageService;
|
||||
use App\Utils\DarkMode;
|
||||
|
||||
/**
|
||||
* Contrôleur pour la page d'accueil.
|
||||
* Redirige vers le tableau de bord si l'utilisateur est déjà connecté.
|
||||
*/
|
||||
class HomeController
|
||||
{
|
||||
private $authService;
|
||||
private $langService;
|
||||
|
||||
/**
|
||||
* Constructeur du HomeController.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->authService = new AuthService(Database::getInstance());
|
||||
$this->langService = new LanguageService(APP_ROOT_DIR . '/src/Lang/');
|
||||
}
|
||||
|
||||
/**
|
||||
* Affiche la page d'accueil ou redirige.
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
// Si l'utilisateur est déjà connecté, le rediriger vers le tableau de bord
|
||||
if ($this->authService->isLoggedIn()) {
|
||||
header('Location: /dashboard');
|
||||
exit();
|
||||
}
|
||||
|
||||
// Sinon, afficher la page de connexion
|
||||
// On réutilise la logique de showLoginForm de AuthController pour éviter la duplication.
|
||||
// On pourrait aussi faire un require de la vue directement.
|
||||
$authController = new AuthController();
|
||||
$authController->showLoginForm();
|
||||
}
|
||||
}
|
76
app/src/Controllers/OcspController.php
Normal file
76
app/src/Controllers/OcspController.php
Normal file
@ -0,0 +1,76 @@
|
||||
<?php
|
||||
|
||||
namespace App\Controllers;
|
||||
|
||||
use App\Core\Database;
|
||||
use App\Services\LogService;
|
||||
|
||||
/**
|
||||
* Contrôleur simple pour simuler un répondeur OCSP.
|
||||
* ATTENTION: Ce n'est pas une implémentation robuste d'un répondeur OCSP de production.
|
||||
* Un répondeur OCSP réel écouterait les requêtes binaires et répondrait en conséquence.
|
||||
* Ce contrôleur est juste pour illustrer le point d'entrée.
|
||||
*/
|
||||
class OcspController
|
||||
{
|
||||
private $db;
|
||||
private $logService;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->db = Database::getInstance();
|
||||
$this->logService = new LogService(APP_LOG_PATH);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gère les requêtes OCSP.
|
||||
* En production, cette méthode devrait lire la requête OCSP binaire du corps de la requête HTTP,
|
||||
* puis utiliser une bibliothèque ou un outil OpenSSL pour générer une réponse OCSP valide.
|
||||
* Pour ce POC, nous allons juste logguer et retourner un message simple.
|
||||
*/
|
||||
public function handleRequest()
|
||||
{
|
||||
$ipAddress = $_SERVER['REMOTE_ADDR'];
|
||||
$requestMethod = $_SERVER['REQUEST_METHOD'];
|
||||
$requestBody = file_get_contents('php://input'); // Récupère le corps de la requête (pour les requêtes POST OCSP)
|
||||
|
||||
$this->logService->log('info', "Requête OCSP reçue. Méthode: {$requestMethod}, Taille du corps: " . strlen($requestBody) . " octets.", null, $ipAddress);
|
||||
|
||||
// En-tête pour une réponse OCSP (Content-Type standard)
|
||||
header('Content-Type: application/ocsp-response');
|
||||
http_response_code(200); // OK
|
||||
|
||||
// --- Logique OCSP simplifiée pour POC ---
|
||||
// En réalité, vous devriez:
|
||||
// 1. Parser la requête OCSP ($requestBody) pour identifier le certificat à vérifier et son émetteur.
|
||||
// 2. Chercher le statut de ce certificat dans votre base de données (table `certificates`).
|
||||
// 3. Utiliser OpenSSL ou une bibliothèque PKI pour générer une réponse OCSP signée.
|
||||
// Ceci impliquerait d'avoir le certificat et la clé du répondeur OCSP (souvent le CA intermédiaire lui-même)
|
||||
// et l'index.txt et crl.pem du CA émetteur.
|
||||
// 4. Envoyer la réponse binaire.
|
||||
|
||||
// Pour l'exemple, nous allons retourner une réponse factice ou une erreur.
|
||||
// Une vraie réponse OCSP est un format binaire ASN.1.
|
||||
// Retourner du texte est INCORRECT pour un client OCSP.
|
||||
// Ceci est une SIMULATION pour le POC.
|
||||
|
||||
// Si la requête est un GET (pour les petites requêtes), le "cert" et l'"issuer" pourraient être dans les paramètres
|
||||
// if ($requestMethod === 'GET' && isset($_GET['cert']) && isset($_GET['issuer'])) {
|
||||
// $certHash = $_GET['cert'];
|
||||
// $issuerHash = $_GET['issuer'];
|
||||
// $this->logService->log('info', "Requête OCSP GET pour Cert: $certHash, Issuer: $issuerHash", null, $ipAddress);
|
||||
// // Simuler une réponse "bon" ou "révoqué"
|
||||
// echo "OCSP Response: Good (Simulated)";
|
||||
// } else {
|
||||
// echo "OCSP Responder (POC): Expects binary POST request or specific GET parameters.";
|
||||
// }
|
||||
|
||||
// Retourner une réponse OCSP vide ou d'erreur (pour les clients qui s'attendent à du binaire)
|
||||
// Un client OCSP s'attend à une réponse binaire, pas du texte.
|
||||
// Pour éviter les erreurs chez le client OCSP, il vaut mieux renvoyer une réponse binaire valide
|
||||
// ou au moins une réponse HTTP 500 pour indiquer un problème.
|
||||
// Pour un POC minimaliste sans générer de binaire:
|
||||
// C'est juste un marqueur de place. La vraie réponse serait générée par OpenSSL.
|
||||
echo ""; // Réponse vide ou générer une vraie réponse binaire
|
||||
}
|
||||
}
|
147
app/src/Controllers/PerimeterController.php
Normal file
147
app/src/Controllers/PerimeterController.php
Normal file
@ -0,0 +1,147 @@
|
||||
<?php
|
||||
|
||||
namespace App\Controllers;
|
||||
|
||||
use App\Core\Database;
|
||||
use App\Services\AuthService;
|
||||
use App\Services\LogService;
|
||||
use App\Services\LanguageService;
|
||||
use App\Utils\DarkMode;
|
||||
|
||||
/**
|
||||
* Contrôleur pour la gestion des périmètres fonctionnels.
|
||||
* (Création, affichage).
|
||||
*/
|
||||
class PerimeterController
|
||||
{
|
||||
private $db;
|
||||
private $authService;
|
||||
private $logService;
|
||||
private $langService;
|
||||
|
||||
/**
|
||||
* Constructeur du PerimeterController.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->db = Database::getInstance();
|
||||
$this->authService = new AuthService($this->db);
|
||||
$this->logService = new LogService(APP_LOG_PATH);
|
||||
$this->langService = new LanguageService(APP_ROOT_DIR . '/src/Lang/');
|
||||
}
|
||||
|
||||
/**
|
||||
* Affiche la liste des périmètres fonctionnels.
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
if (!$this->authService->isLoggedIn()) {
|
||||
header('Location: /login');
|
||||
exit();
|
||||
}
|
||||
|
||||
global $translations;
|
||||
$currentLang = $this->langService->getLanguage();
|
||||
$darkModeClass = DarkMode::getBodyClass();
|
||||
|
||||
$perimeters = $this->db->query("SELECT * FROM functional_perimeters ORDER BY name ASC")->fetchAll();
|
||||
|
||||
$successMessage = $_SESSION['success'] ?? null;
|
||||
unset($_SESSION['success']);
|
||||
$errorMessage = $_SESSION['error'] ?? null;
|
||||
unset($_SESSION['error']);
|
||||
|
||||
require_once APP_ROOT_DIR . '/src/Views/perimeters/index.php';
|
||||
}
|
||||
|
||||
/**
|
||||
* Affiche le formulaire de création d'un nouveau périmètre fonctionnel.
|
||||
*/
|
||||
public function showCreateForm()
|
||||
{
|
||||
if (!$this->authService->isLoggedIn() || $this->authService->getUserRole() !== 'admin') {
|
||||
$_SESSION['error'] = $this->langService->__('permission_denied');
|
||||
header('Location: /dashboard');
|
||||
exit();
|
||||
}
|
||||
|
||||
global $translations;
|
||||
$currentLang = $this->langService->getLanguage();
|
||||
$darkModeClass = DarkMode::getBodyClass();
|
||||
|
||||
$errorMessage = $_SESSION['error'] ?? null;
|
||||
unset($_SESSION['error']);
|
||||
|
||||
require_once APP_ROOT_DIR . '/src/Views/perimeters/create.php';
|
||||
}
|
||||
|
||||
/**
|
||||
* Traite la soumission du formulaire de création de périmètre fonctionnel.
|
||||
*/
|
||||
public function create()
|
||||
{
|
||||
if (!$this->authService->isLoggedIn() || $this->authService->getUserRole() !== 'admin' || $_SERVER['REQUEST_METHOD'] !== 'POST') {
|
||||
$_SESSION['error'] = $this->langService->__('permission_denied');
|
||||
header('Location: /dashboard');
|
||||
exit();
|
||||
}
|
||||
|
||||
$perimeterName = trim($_POST['name'] ?? '');
|
||||
$ipAddress = $_SERVER['REMOTE_ADDR'];
|
||||
$userId = $this->authService->getUserId();
|
||||
|
||||
if (empty($perimeterName)) {
|
||||
$_SESSION['error'] = $this->langService->__('perimeter_create_error_empty_name');
|
||||
header('Location: /perimeters/create');
|
||||
exit();
|
||||
}
|
||||
|
||||
// Vérifier si le périmètre existe déjà
|
||||
$stmt = $this->db->prepare("SELECT COUNT(*) FROM functional_perimeters WHERE name = ?");
|
||||
$stmt->execute([$perimeterName]);
|
||||
if ($stmt->fetchColumn() > 0) {
|
||||
$_SESSION['error'] = $this->langService->__('perimeter_create_error_exists');
|
||||
header('Location: /perimeters/create');
|
||||
exit();
|
||||
}
|
||||
|
||||
// Appeler le script shell pour créer le certificat intermédiaire
|
||||
$command = escapeshellcmd(SCRIPTS_PATH . '/create_intermediate_cert.sh') . ' ' . escapeshellarg($perimeterName);
|
||||
|
||||
$this->logService->log('info', "Tentative de création du périmètre '$perimeterName' et de son certificat intermédiaire. Commande: '$command'", $userId, $ipAddress);
|
||||
|
||||
$output = shell_exec($command . ' 2>&1');
|
||||
|
||||
if (strpos($output, "Certificat Intermédiaire CA pour '$perimeterName' créé avec succès") !== false) {
|
||||
// Enregistrer le périmètre dans la base de données
|
||||
$stmt = $this->db->prepare("INSERT INTO functional_perimeters (name, intermediate_cert_name) VALUES (?, ?)");
|
||||
$intermediateCertFileName = "intermediate.cert.pem"; // Nom générique du fichier pour l'intermédiaire
|
||||
$stmt->execute([$perimeterName, $intermediateCertFileName]);
|
||||
$perimeterId = $this->db->lastInsertId();
|
||||
|
||||
// Enregistrer le certificat intermédiaire dans la table des certificats
|
||||
$fullCertPath = INTERMEDIATE_CA_PATH_BASE . "/{$perimeterName}/certs/intermediate.cert.pem";
|
||||
$expirationDate = (new \DateTime('+5 years'))->format('Y-m-d H:i:s'); // Valeur par défaut
|
||||
if (file_exists($fullCertPath)) {
|
||||
$certInfo = shell_exec("openssl x509 -in " . escapeshellarg($fullCertPath) . " -noout -enddate 2>/dev/null | cut -d= -f2");
|
||||
$expirationTimestamp = strtotime($certInfo);
|
||||
if ($expirationTimestamp) {
|
||||
$expirationDate = date('Y-m-d H:i:s', $expirationTimestamp);
|
||||
}
|
||||
}
|
||||
|
||||
$stmt = $this->db->prepare("INSERT INTO certificates (name, type, functional_perimeter_id, expiration_date) VALUES (?, ?, ?, ?)");
|
||||
$stmt->execute([$intermediateCertFileName, 'intermediate', $perimeterId, $expirationDate]);
|
||||
|
||||
|
||||
$this->logService->log('info', "Périmètre fonctionnel '$perimeterName' créé avec succès et certificat intermédiaire généré.", $userId, $ipAddress);
|
||||
$_SESSION['success'] = $this->langService->__('perimeter_create_success');
|
||||
} else {
|
||||
$_SESSION['error'] = $this->langService->__('perimeter_create_error', ['output' => htmlspecialchars($output)]);
|
||||
$this->logService->log('error', "Échec création périmètre '$perimeterName': $output", $userId, $ipAddress);
|
||||
}
|
||||
|
||||
header('Location: /perimeters');
|
||||
exit();
|
||||
}
|
||||
}
|
212
app/src/Controllers/UserController.php
Normal file
212
app/src/Controllers/UserController.php
Normal file
@ -0,0 +1,212 @@
|
||||
<?php
|
||||
|
||||
namespace App\Controllers;
|
||||
|
||||
use App\Core\Database;
|
||||
use App\Services\AuthService;
|
||||
use App\Services\LogService;
|
||||
use App\Services\LanguageService;
|
||||
use App\Utils\DarkMode;
|
||||
|
||||
/**
|
||||
* Contrôleur pour la gestion des utilisateurs.
|
||||
* (Création, suppression, affichage).
|
||||
* Nécessite un rôle 'admin'.
|
||||
*/
|
||||
class UserController
|
||||
{
|
||||
private $db;
|
||||
private $authService;
|
||||
private $logService;
|
||||
private $langService;
|
||||
|
||||
/**
|
||||
* Constructeur du UserController.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->db = Database::getInstance();
|
||||
$this->authService = new AuthService($this->db);
|
||||
$this->logService = new LogService(APP_LOG_PATH);
|
||||
$this->langService = new LanguageService(APP_ROOT_DIR . '/src/Lang/');
|
||||
}
|
||||
|
||||
/**
|
||||
* Vérifie si l'utilisateur est un administrateur.
|
||||
* Si non, redirige et affiche un message d'erreur.
|
||||
*/
|
||||
private function requireAdmin()
|
||||
{
|
||||
if (!$this->authService->isLoggedIn() || $this->authService->getUserRole() !== 'admin') {
|
||||
$_SESSION['error'] = $this->langService->__('permission_denied');
|
||||
header('Location: /dashboard');
|
||||
exit();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Affiche la liste des utilisateurs.
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
$this->requireAdmin();
|
||||
|
||||
global $translations;
|
||||
$currentLang = $this->langService->getLanguage();
|
||||
$darkModeClass = DarkMode::getBodyClass();
|
||||
|
||||
$users = $this->db->query("SELECT id, username, role, created_at FROM users ORDER BY username ASC")->fetchAll();
|
||||
|
||||
$successMessage = $_SESSION['success'] ?? null;
|
||||
unset($_SESSION['success']);
|
||||
$errorMessage = $_SESSION['error'] ?? null;
|
||||
unset($_SESSION['error']);
|
||||
|
||||
require_once APP_ROOT_DIR . '/src/Views/users/index.php';
|
||||
}
|
||||
|
||||
/**
|
||||
* Affiche le formulaire de création d'un nouvel utilisateur.
|
||||
*/
|
||||
public function showCreateForm()
|
||||
{
|
||||
$this->requireAdmin();
|
||||
|
||||
global $translations;
|
||||
$currentLang = $this->langService->getLanguage();
|
||||
$darkModeClass = DarkMode::getBodyClass();
|
||||
|
||||
$errorMessage = $_SESSION['error'] ?? null;
|
||||
unset($_SESSION['error']);
|
||||
|
||||
require_once APP_ROOT_DIR . '/src/Views/users/create.php';
|
||||
}
|
||||
|
||||
/**
|
||||
* Traite la soumission du formulaire de création d'utilisateur.
|
||||
*/
|
||||
public function create()
|
||||
{
|
||||
$this->requireAdmin();
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
||||
header('Location: /users/create');
|
||||
exit();
|
||||
}
|
||||
|
||||
$username = trim($_POST['username'] ?? '');
|
||||
$password = $_POST['password'] ?? '';
|
||||
$role = $_POST['role'] ?? 'user';
|
||||
$ipAddress = $_SERVER['REMOTE_ADDR'];
|
||||
$adminUserId = $this->authService->getUserId();
|
||||
|
||||
if (empty($username) || empty($password)) {
|
||||
$_SESSION['error'] = $this->langService->__('user_create_error_empty_fields');
|
||||
header('Location: /users/create');
|
||||
exit();
|
||||
}
|
||||
if (!in_array($role, ['admin', 'user'])) {
|
||||
$_SESSION['error'] = $this->langService->__('user_create_error_invalid_role');
|
||||
header('Location: /users/create');
|
||||
exit();
|
||||
}
|
||||
|
||||
// Vérifier si l'utilisateur existe déjà
|
||||
$stmt = $this->db->prepare("SELECT COUNT(*) FROM users WHERE username = ?");
|
||||
$stmt->execute([$username]);
|
||||
if ($stmt->fetchColumn() > 0) {
|
||||
$_SESSION['error'] = $this->langService->__('user_create_error_exists', ['username' => htmlspecialchars($username)]);
|
||||
header('Location: /users/create');
|
||||
exit();
|
||||
}
|
||||
|
||||
// Hacher le mot de passe
|
||||
$hashedPassword = password_hash($password, PASSWORD_DEFAULT);
|
||||
|
||||
try {
|
||||
$stmt = $this->db->prepare("INSERT INTO users (username, password, role) VALUES (?, ?, ?)");
|
||||
$stmt->execute([$username, $hashedPassword, $role]);
|
||||
$newUserId = $this->db->lastInsertId();
|
||||
|
||||
$this->logService->log('info', "Nouvel utilisateur '{$username}' ({$role}) créé par l'administrateur.", $adminUserId, $ipAddress);
|
||||
$_SESSION['success'] = $this->langService->__('user_create_success', ['username' => htmlspecialchars($username)]);
|
||||
} catch (\PDOException $e) {
|
||||
error_log("Erreur lors de la création de l'utilisateur: " . $e->getMessage());
|
||||
$_SESSION['error'] = $this->langService->__('user_create_error_db');
|
||||
$this->logService->log('error', "Échec création utilisateur '{$username}': " . $e->getMessage(), $adminUserId, $ipAddress);
|
||||
}
|
||||
|
||||
header('Location: /users');
|
||||
exit();
|
||||
}
|
||||
|
||||
/**
|
||||
* Supprime un utilisateur.
|
||||
*/
|
||||
public function delete()
|
||||
{
|
||||
$this->requireAdmin();
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
||||
header('Location: /users');
|
||||
exit();
|
||||
}
|
||||
|
||||
$userIdToDelete = $_POST['user_id'] ?? null;
|
||||
$ipAddress = $_SERVER['REMOTE_ADDR'];
|
||||
$adminUserId = $this->authService->getUserId();
|
||||
|
||||
if (empty($userIdToDelete)) {
|
||||
$_SESSION['error'] = $this->langService->__('user_delete_error_id_missing');
|
||||
header('Location: /users');
|
||||
exit();
|
||||
}
|
||||
|
||||
// Empêcher un admin de se supprimer lui-même (ou le dernier admin)
|
||||
if ($userIdToDelete == $adminUserId) {
|
||||
$_SESSION['error'] = $this->langService->__('user_delete_error_self_delete');
|
||||
header('Location: /users');
|
||||
exit();
|
||||
}
|
||||
|
||||
// Vérifier s'il reste au moins un administrateur après la suppression
|
||||
$stmt = $this->db->prepare("SELECT role FROM users WHERE id = ?");
|
||||
$stmt->execute([$userIdToDelete]);
|
||||
$userRoleToDelete = $stmt->fetchColumn();
|
||||
|
||||
if ($userRoleToDelete === 'admin') {
|
||||
$stmt = $this->db->query("SELECT COUNT(*) FROM users WHERE role = 'admin'");
|
||||
$adminCount = $stmt->fetchColumn();
|
||||
if ($adminCount <= 1) {
|
||||
$_SESSION['error'] = $this->langService->__('user_delete_error_last_admin');
|
||||
header('Location: /users');
|
||||
exit();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
try {
|
||||
// Récupérer le nom d'utilisateur avant suppression pour le log
|
||||
$stmt = $this->db->prepare("SELECT username FROM users WHERE id = ?");
|
||||
$stmt->execute([$userIdToDelete]);
|
||||
$usernameToDelete = $stmt->fetchColumn();
|
||||
|
||||
$stmt = $this->db->prepare("DELETE FROM users WHERE id = ?");
|
||||
$stmt->execute([$userIdToDelete]);
|
||||
|
||||
if ($stmt->rowCount() > 0) {
|
||||
$this->logService->log('info', "Utilisateur '{$usernameToDelete}' (ID: {$userIdToDelete}) supprimé par l'administrateur.", $adminUserId, $ipAddress);
|
||||
$_SESSION['success'] = $this->langService->__('user_delete_success', ['username' => htmlspecialchars($usernameToDelete)]);
|
||||
} else {
|
||||
$_SESSION['error'] = $this->langService->__('user_delete_error_not_found');
|
||||
}
|
||||
} catch (\PDOException $e) {
|
||||
error_log("Erreur lors de la suppression de l'utilisateur: " . $e->getMessage());
|
||||
$_SESSION['error'] = $this->langService->__('user_delete_error_db');
|
||||
$this->logService->log('error', "Échec suppression utilisateur ID: {$userIdToDelete}: " . $e->getMessage(), $adminUserId, $ipAddress);
|
||||
}
|
||||
|
||||
header('Location: /users');
|
||||
exit();
|
||||
}
|
||||
}
|
42
app/src/Core/Autoloader.php
Normal file
42
app/src/Core/Autoloader.php
Normal file
@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
namespace App\Core;
|
||||
|
||||
/**
|
||||
* Autoloader PSR-4 pour les classes de l'application.
|
||||
*/
|
||||
class Autoloader
|
||||
{
|
||||
/**
|
||||
* Enregistre l'autoloader dans la pile de chargement de PHP.
|
||||
*/
|
||||
public static function register()
|
||||
{
|
||||
spl_autoload_register(function ($class) {
|
||||
// Préfixe du namespace de l'application
|
||||
$prefix = 'App\\';
|
||||
|
||||
// Répertoire de base où se trouvent les fichiers de l'application (src/)
|
||||
$baseDir = __DIR__ . '/../'; // Cela pointe vers /app/src/
|
||||
|
||||
// Vérifie si la classe utilise le préfixe du namespace
|
||||
$len = strlen($prefix);
|
||||
if (strncmp($prefix, $class, $len) !== 0) {
|
||||
// Si la classe n'utilise pas notre préfixe, passe au prochain autoloader enregistré
|
||||
return;
|
||||
}
|
||||
|
||||
// Récupère le nom de la classe relatif au namespace de base
|
||||
$relativeClass = substr($class, $len);
|
||||
|
||||
// Convertit le nom de la classe relatif en chemin de fichier
|
||||
// Remplace les séparateurs de namespace par des séparateurs de répertoire et ajoute l'extension .php
|
||||
$file = $baseDir . str_replace('\\', '/', $relativeClass) . '.php';
|
||||
|
||||
// Si le fichier existe, l'inclut
|
||||
if (file_exists($file)) {
|
||||
require $file;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
71
app/src/Core/Database.php
Normal file
71
app/src/Core/Database.php
Normal file
@ -0,0 +1,71 @@
|
||||
<?php
|
||||
|
||||
namespace App\Core;
|
||||
|
||||
use PDO;
|
||||
use PDOException;
|
||||
|
||||
/**
|
||||
* Classe singleton pour gérer la connexion à la base de données MySQL.
|
||||
*/
|
||||
class Database
|
||||
{
|
||||
private static $instance = null; // Instance unique de la connexion PDO
|
||||
private $conn; // L'objet de connexion PDO
|
||||
|
||||
/**
|
||||
* Constructeur privé pour empêcher l'instanciation directe (Singleton).
|
||||
*
|
||||
* @param string $host Nom d'hôte de la base de données
|
||||
* @param string $dbName Nom de la base de données
|
||||
* @param string $user Nom d'utilisateur de la base de données
|
||||
* @param string $password Mot de passe de la base de données
|
||||
* @throws PDOException Si la connexion échoue
|
||||
*/
|
||||
private function __construct($host, $dbName, $user, $password)
|
||||
{
|
||||
$dsn = "mysql:host=$host;dbname=$dbName;charset=utf8mb4";
|
||||
$options = [
|
||||
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, // Rapporte les erreurs SQL sous forme d'exceptions
|
||||
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, // Récupère les résultats sous forme de tableaux associatifs
|
||||
PDO::ATTR_EMULATE_PREPARES => false, // Désactive l'émulation des requêtes préparées pour une meilleure sécurité
|
||||
];
|
||||
try {
|
||||
$this->conn = new PDO($dsn, $user, $password, $options);
|
||||
} catch (PDOException $e) {
|
||||
// Log l'erreur plutôt que de l'afficher directement en production
|
||||
error_log("Erreur de connexion à la base de données: " . $e->getMessage());
|
||||
// Relance l'exception après l'avoir logguée
|
||||
throw new PDOException("Impossible de se connecter à la base de données.", (int)$e->getCode());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Connecte à la base de données ou retourne l'instance existante.
|
||||
*
|
||||
* @param string $host Nom d'hôte de la base de données
|
||||
* @param string $dbName Nom de la base de données
|
||||
* @param string $user Nom d'utilisateur de la base de données
|
||||
* @param string $password Mot de passe de la base de données
|
||||
*/
|
||||
public static function connect($host, $dbName, $user, $password)
|
||||
{
|
||||
if (self::$instance === null) {
|
||||
self::$instance = new Database($host, $dbName, $user, $password);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retourne l'instance PDO de la connexion à la base de données.
|
||||
*
|
||||
* @return PDO L'objet de connexion PDO
|
||||
* @throws \Exception Si la connexion n'a pas été établie au préalable
|
||||
*/
|
||||
public static function getInstance()
|
||||
{
|
||||
if (self::$instance === null) {
|
||||
throw new \Exception("La base de données n'est pas connectée. Appelez Database::connect() d'abord.");
|
||||
}
|
||||
return self::$instance->conn;
|
||||
}
|
||||
}
|
91
app/src/Core/Router.php
Normal file
91
app/src/Core/Router.php
Normal file
@ -0,0 +1,91 @@
|
||||
<?php
|
||||
|
||||
namespace App\Core;
|
||||
|
||||
use App\Services\AuthService;
|
||||
use App\Utils\DarkMode; // Assurez-vous d'importer la classe DarkMode
|
||||
|
||||
/**
|
||||
* Simple routeur pour diriger les requêtes HTTP vers les contrôleurs appropriés.
|
||||
*/
|
||||
class Router
|
||||
{
|
||||
private $routes = []; // Tableau pour stocker toutes les routes définies
|
||||
private $authService; // Service d'authentification pour vérifier l'accès aux routes protégées
|
||||
|
||||
/**
|
||||
* Constructeur du routeur.
|
||||
* Initialise le service d'authentification.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->authService = new AuthService(Database::getInstance());
|
||||
}
|
||||
|
||||
/**
|
||||
* Ajoute une nouvelle route au routeur.
|
||||
*
|
||||
* @param string $method Méthode HTTP (GET, POST, etc.)
|
||||
* @param string $path Chemin de l'URL (ex: '/', '/dashboard')
|
||||
* @param string $controllerAction Action du contrôleur (ex: 'HomeController@index')
|
||||
* @param bool $requiresAuth Indique si la route nécessite une authentification (true par défaut)
|
||||
*/
|
||||
public function addRoute($method, $path, $controllerAction, $requiresAuth = false)
|
||||
{
|
||||
$this->routes[] = [
|
||||
'method' => $method,
|
||||
'path' => $path,
|
||||
'controllerAction' => $controllerAction,
|
||||
'requiresAuth' => $requiresAuth
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispatche la requête entrante vers le contrôleur et l'action correspondants.
|
||||
* Gère également les redirections pour l'authentification et les erreurs 404.
|
||||
*/
|
||||
public function dispatch()
|
||||
{
|
||||
// Récupère le chemin de l'URL demandé (sans les paramètres GET)
|
||||
$requestUri = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
|
||||
// Récupère la méthode HTTP de la requête (GET, POST, etc.)
|
||||
$requestMethod = $_SERVER['REQUEST_METHOD'];
|
||||
|
||||
foreach ($this->routes as $route) {
|
||||
// Vérifie si la méthode et le chemin correspondent à une route définie
|
||||
if ($route['method'] === $requestMethod && $route['path'] === $requestUri) {
|
||||
// Si la route nécessite une authentification et que l'utilisateur n'est pas connecté
|
||||
if ($route['requiresAuth'] && !$this->authService->isLoggedIn()) {
|
||||
// Redirige vers la page de connexion
|
||||
header('Location: /login');
|
||||
exit();
|
||||
}
|
||||
|
||||
// Sépare le nom du contrôleur et de l'action
|
||||
list($controllerName, $actionName) = explode('@', $route['controllerAction']);
|
||||
// Construit le nom complet de la classe du contrôleur avec son namespace
|
||||
$controllerClass = "App\\Controllers\\" . $controllerName;
|
||||
|
||||
// Vérifie si la classe du contrôleur existe
|
||||
if (class_exists($controllerClass)) {
|
||||
// Instancie le contrôleur
|
||||
$controller = new $controllerClass();
|
||||
// Vérifie si la méthode de l'action existe dans le contrôleur
|
||||
if (method_exists($controller, $actionName)) {
|
||||
// Appelle la méthode de l'action
|
||||
$controller->$actionName();
|
||||
return; // Termine l'exécution après avoir traité la route
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Si aucune route correspondante n'est trouvée, retourne une erreur 404
|
||||
http_response_code(404);
|
||||
// Utilisation de DarkMode::getBodyClass() pour éviter l'erreur de syntaxe
|
||||
echo "<!DOCTYPE html><html lang=\"fr\"><head><meta charset=\"UTF-8\"><title>404 Non Trouvé</title><link rel=\"stylesheet\" href=\"/css/style.css\"></head><body class=\"" . DarkMode::getBodyClass() . "\">";
|
||||
echo "<div class=\"container\"><h1>404 Non Trouvé</h1>";
|
||||
echo "<p>La page que vous avez demandée n'a pas pu être trouvée.</p>";
|
||||
echo "<p><a href=\"/\">Retour à l'accueil</a></p></div></body></html>";
|
||||
}
|
||||
}
|
84
app/src/Lang/de.json
Normal file
84
app/src/Lang/de.json
Normal file
@ -0,0 +1,84 @@
|
||||
{
|
||||
"app_name": "Zertifikatsverwaltung",
|
||||
"login_title": "Login - Zertifikatsverwaltung",
|
||||
"login_heading": "Anmeldung zur Anwendung",
|
||||
"username": "Benutzername",
|
||||
"password": "Passwort",
|
||||
"login_button": "Anmelden",
|
||||
"dark_mode": "Dunkler Modus",
|
||||
"light_mode": "Heller Modus",
|
||||
"dashboard_title": "Dashboard",
|
||||
"welcome": "Willkommen, {username}!",
|
||||
"logout": "Abmelden",
|
||||
"certificates": "Zertifikate",
|
||||
"functional_perimeters": "Funktionale Perimeter",
|
||||
"users": "Benutzer",
|
||||
"quick_actions": "Schnellaktionen",
|
||||
"create_new_certificate": "Neues Zertifikat erstellen",
|
||||
"create_new_perimeter": "Neuen Perimeter erstellen",
|
||||
"new_user": "Neuer Benutzer",
|
||||
"certificate_name": "Zertifikatsname",
|
||||
"type": "Typ",
|
||||
"expiration_date": "Ablaufdatum",
|
||||
"status": "Status",
|
||||
"revoked": "Widerrufen",
|
||||
"active": "Aktiv",
|
||||
"actions": "Aktionen",
|
||||
"revoke_certificate": "Widerrufen",
|
||||
"confirm_revoke": "Sind Sie sicher, dass Sie dieses Zertifikat widerrufen möchten? Diese Aktion ist irreversibel und macht das Zertifikat ungültig.",
|
||||
"perimeter_name": "Perimetername",
|
||||
"intermediate_cert_file": "Zwischenzertifikat-Datei",
|
||||
"created_at": "Erstellt am",
|
||||
"create_perimeter_button": "Perimeter erstellen",
|
||||
"create_new_user": "Neuen Benutzer erstellen",
|
||||
"user_role": "Rolle",
|
||||
"admin": "Administrator",
|
||||
"user": "Benutzer",
|
||||
"create_user_button": "Benutzer erstellen",
|
||||
"delete_user": "Löschen",
|
||||
"confirm_delete_user": "Sind Sie sicher, dass Sie diesen Benutzer löschen möchten? Diese Aktion ist irreversibel.",
|
||||
"new_certificate_heading": "Neues Zertifikat erstellen",
|
||||
"subdomain_name": "Subdomain / CN-Name",
|
||||
"select_perimeter": "Funktionalen Perimeter auswählen",
|
||||
"select_perimeter_placeholder": "Wählen Sie einen Perimeter",
|
||||
"create_certificate": "Zertifikat erstellen",
|
||||
"root": "Root",
|
||||
"intermediate": "Zwischen",
|
||||
"simple": "Einfach",
|
||||
"back_to_dashboard": "Zurück zum Dashboard",
|
||||
"back_to_cert_list": "Zurück zur Zertifikatsliste",
|
||||
"back_to_perimeter_list": "Zurück zur Perimeterliste",
|
||||
"back_to_user_list": "Zurück zur Benutzerliste",
|
||||
"no_certificates_yet": "Es wurden noch keine Zertifikate erstellt.",
|
||||
"no_perimeters_yet": "Es wurden noch keine funktionalen Perimeter erstellt.",
|
||||
"no_users_yet": "Es wurden noch keine Benutzer erstellt.",
|
||||
"login_error_empty_fields": "Bitte geben Sie Ihren Benutzernamen und Ihr Passwort ein.",
|
||||
"login_error_credentials": "Falscher Benutzername oder Passwort.",
|
||||
"permission_denied": "Sie haben nicht die notwendigen Berechtigungen, um auf diese Seite zuzugreifen.",
|
||||
"cert_create_error_empty_fields": "Subdomain-Name und funktionaler Perimeter sind erforderlich.",
|
||||
"cert_create_error_perimeter_not_found": "Ausgewählter funktionaler Perimeter nicht gefunden.",
|
||||
"cert_create_success": "Zertifikat erfolgreich erstellt.",
|
||||
"cert_create_error": "Fehler beim Erstellen des Zertifikats: {output}",
|
||||
"cert_revoke_error_id_missing": "Zertifikats-ID für den Widerruf fehlt.",
|
||||
"cert_revoke_error_not_found": "Zertifikat für den Widerruf nicht gefunden.",
|
||||
"cert_revoke_error_ca_revocation": "ROOT- und INTERMEDIATE-Zertifikate können aus PKI-Sicherheitsgründen nicht über die Schnittstelle widerrufen werden.",
|
||||
"cert_revoke_error_already_revoked": "Dieses Zertifikat ist bereits widerrufen.",
|
||||
"cert_revoke_success": "Zertifikat erfolgreich widerrufen.",
|
||||
"cert_revoke_error": "Fehler beim Widerrufen des Zertifikats: {output}",
|
||||
"perimeter_create_error_empty_name": "Der Name des funktionalen Perimeters ist erforderlich.",
|
||||
"perimeter_create_error_exists": "Ein funktionaler Perimeter mit diesem Namen existiert bereits.",
|
||||
"perimeter_create_success": "Funktionaler Perimeter und sein Zwischenzertifikat erfolgreich erstellt.",
|
||||
"perimeter_create_error": "Fehler beim Erstellen des funktionalen Perimeters: {output}",
|
||||
"user_create_error_empty_fields": "Benutzername und Passwort sind erforderlich.",
|
||||
"user_create_error_invalid_role": "Ungültige Benutzerrolle.",
|
||||
"user_create_error_exists": "Ein Benutzer mit dem Benutzernamen '{username}' existiert bereits.",
|
||||
"user_create_success": "Benutzer '{username}' erfolgreich erstellt.",
|
||||
"user_create_error_db": "Fehler beim Erstellen des Benutzers in der Datenbank.",
|
||||
"user_delete_error_id_missing": "Benutzer-ID für das Löschen fehlt.",
|
||||
"user_delete_error_self_delete": "Sie können Ihr eigenes Konto nicht löschen.",
|
||||
"user_delete_error_last_admin": "Das letzte Administratorkonto kann nicht gelöscht werden.",
|
||||
"user_delete_success": "Benutzer '{username}' erfolgreich gelöscht.",
|
||||
"user_delete_error_not_found": "Benutzer zum Löschen nicht gefunden.",
|
||||
"user_delete_error_db": "Fehler beim Löschen des Benutzers aus der Datenbank.",
|
||||
"self_delete_not_allowed": "Sie können sich nicht selbst löschen."
|
||||
}
|
84
app/src/Lang/en.json
Normal file
84
app/src/Lang/en.json
Normal file
@ -0,0 +1,84 @@
|
||||
{
|
||||
"app_name": "Certificate Management",
|
||||
"login_title": "Login - Certificate Management",
|
||||
"login_heading": "Log in to the application",
|
||||
"username": "Username",
|
||||
"password": "Password",
|
||||
"login_button": "Log In",
|
||||
"dark_mode": "Dark Mode",
|
||||
"light_mode": "Light Mode",
|
||||
"dashboard_title": "Dashboard",
|
||||
"welcome": "Welcome, {username}!",
|
||||
"logout": "Log Out",
|
||||
"certificates": "Certificates",
|
||||
"functional_perimeters": "Functional Perimeters",
|
||||
"users": "Users",
|
||||
"quick_actions": "Quick Actions",
|
||||
"create_new_certificate": "Create a new certificate",
|
||||
"create_new_perimeter": "Create a new perimeter",
|
||||
"new_user": "New user",
|
||||
"certificate_name": "Certificate Name",
|
||||
"type": "Type",
|
||||
"expiration_date": "Expiration Date",
|
||||
"status": "Status",
|
||||
"revoked": "Revoked",
|
||||
"active": "Active",
|
||||
"actions": "Actions",
|
||||
"revoke_certificate": "Revoke",
|
||||
"confirm_revoke": "Are you sure you want to revoke this certificate? This action is irreversible and will invalidate the certificate.",
|
||||
"perimeter_name": "Perimeter Name",
|
||||
"intermediate_cert_file": "Intermediate Certificate File",
|
||||
"created_at": "Created On",
|
||||
"create_perimeter_button": "Create Perimeter",
|
||||
"create_new_user": "Create a new user",
|
||||
"user_role": "Role",
|
||||
"admin": "Administrator",
|
||||
"user": "User",
|
||||
"create_user_button": "Create User",
|
||||
"delete_user": "Delete",
|
||||
"confirm_delete_user": "Are you sure you want to delete this user? This action is irreversible.",
|
||||
"new_certificate_heading": "Create a New Certificate",
|
||||
"subdomain_name": "Subdomain / CN Name",
|
||||
"select_perimeter": "Select a Functional Perimeter",
|
||||
"select_perimeter_placeholder": "Choose a perimeter",
|
||||
"create_certificate": "Create Certificate",
|
||||
"root": "Root",
|
||||
"intermediate": "Intermediate",
|
||||
"simple": "Simple",
|
||||
"back_to_dashboard": "Back to Dashboard",
|
||||
"back_to_cert_list": "Back to Certificate List",
|
||||
"back_to_perimeter_list": "Back to Perimeter List",
|
||||
"back_to_user_list": "Back to User List",
|
||||
"no_certificates_yet": "No certificates have been created yet.",
|
||||
"no_perimeters_yet": "No functional perimeters have been created yet.",
|
||||
"no_users_yet": "No users have been created yet.",
|
||||
"login_error_empty_fields": "Please enter your username and password.",
|
||||
"login_error_credentials": "Incorrect username or password.",
|
||||
"permission_denied": "You do not have the necessary permissions to access this page.",
|
||||
"cert_create_error_empty_fields": "Subdomain name and functional perimeter are required.",
|
||||
"cert_create_error_perimeter_not_found": "Selected functional perimeter not found.",
|
||||
"cert_create_success": "Certificate created successfully.",
|
||||
"cert_create_error": "Error creating certificate: {output}",
|
||||
"cert_revoke_error_id_missing": "Certificate ID missing for revocation.",
|
||||
"cert_revoke_error_not_found": "Certificate not found for revocation.",
|
||||
"cert_revoke_error_ca_revocation": "ROOT and INTERMEDIATE certificates cannot be revoked via the interface for PKI security reasons.",
|
||||
"cert_revoke_error_already_revoked": "This certificate is already revoked.",
|
||||
"cert_revoke_success": "Certificate revoked successfully.",
|
||||
"cert_revoke_error": "Error revoking certificate: {output}",
|
||||
"perimeter_create_error_empty_name": "Functional perimeter name is required.",
|
||||
"perimeter_create_error_exists": "A functional perimeter with this name already exists.",
|
||||
"perimeter_create_success": "Functional perimeter and its intermediate certificate created successfully.",
|
||||
"perimeter_create_error": "Error creating functional perimeter: {output}",
|
||||
"user_create_error_empty_fields": "Username and password are required.",
|
||||
"user_create_error_invalid_role": "Invalid user role.",
|
||||
"user_create_error_exists": "A user with the username '{username}' already exists.",
|
||||
"user_create_success": "User '{username}' created successfully.",
|
||||
"user_create_error_db": "Error creating user in the database.",
|
||||
"user_delete_error_id_missing": "User ID missing for deletion.",
|
||||
"user_delete_error_self_delete": "You cannot delete your own account.",
|
||||
"user_delete_error_last_admin": "Cannot delete the last administrator account.",
|
||||
"user_delete_success": "User '{username}' deleted successfully.",
|
||||
"user_delete_error_not_found": "User not found for deletion.",
|
||||
"user_delete_error_db": "Error deleting user from the database.",
|
||||
"self_delete_not_allowed": "You cannot delete yourself."
|
||||
}
|
84
app/src/Lang/es.json
Normal file
84
app/src/Lang/es.json
Normal file
@ -0,0 +1,84 @@
|
||||
{
|
||||
"app_name": "Gestión de Certificados",
|
||||
"login_title": "Iniciar Sesión - Gestión de Certificados",
|
||||
"login_heading": "Iniciar sesión en la aplicación",
|
||||
"username": "Nombre de usuario",
|
||||
"password": "Contraseña",
|
||||
"login_button": "Iniciar Sesión",
|
||||
"dark_mode": "Modo Oscuro",
|
||||
"light_mode": "Modo Claro",
|
||||
"dashboard_title": "Panel de Control",
|
||||
"welcome": "¡Bienvenido, {username}!",
|
||||
"logout": "Cerrar Sesión",
|
||||
"certificates": "Certificados",
|
||||
"functional_perimeters": "Perímetros Funcionales",
|
||||
"users": "Usuarios",
|
||||
"quick_actions": "Acciones Rápidas",
|
||||
"create_new_certificate": "Crear nuevo certificado",
|
||||
"create_new_perimeter": "Crear nuevo perímetro",
|
||||
"new_user": "Nuevo usuario",
|
||||
"certificate_name": "Nombre del Certificado",
|
||||
"type": "Tipo",
|
||||
"expiration_date": "Fecha de Caducidad",
|
||||
"status": "Estado",
|
||||
"revoked": "Revocado",
|
||||
"active": "Activo",
|
||||
"actions": "Acciones",
|
||||
"revoke_certificate": "Revocar",
|
||||
"confirm_revoke": "¿Estás seguro de que deseas revocar este certificado? Esta acción es irreversible y lo invalidará.",
|
||||
"perimeter_name": "Nombre del Perímetro",
|
||||
"intermediate_cert_file": "Archivo de Certificado Intermedio",
|
||||
"created_at": "Creado el",
|
||||
"create_perimeter_button": "Crear Perímetro",
|
||||
"create_new_user": "Crear nuevo usuario",
|
||||
"user_role": "Rol",
|
||||
"admin": "Administrador",
|
||||
"user": "Usuario",
|
||||
"create_user_button": "Crear Usuario",
|
||||
"delete_user": "Eliminar",
|
||||
"confirm_delete_user": "¿Estás seguro de que deseas eliminar a este usuario? Esta acción es irreversible.",
|
||||
"new_certificate_heading": "Crear un Nuevo Certificado",
|
||||
"subdomain_name": "Nombre de Subdominio / CN",
|
||||
"select_perimeter": "Seleccionar un Perímetro Funcional",
|
||||
"select_perimeter_placeholder": "Elige un perímetro",
|
||||
"create_certificate": "Crear Certificado",
|
||||
"root": "Raíz",
|
||||
"intermediate": "Intermedio",
|
||||
"simple": "Simple",
|
||||
"back_to_dashboard": "Volver al Panel de Control",
|
||||
"back_to_cert_list": "Volver a la Lista de Certificados",
|
||||
"back_to_perimeter_list": "Volver a la Lista de Perímetros",
|
||||
"back_to_user_list": "Volver a la Lista de Usuarios",
|
||||
"no_certificates_yet": "Todavía no se han creado certificados.",
|
||||
"no_perimeters_yet": "Todavía no se han creado perímetros funcionales.",
|
||||
"no_users_yet": "Todavía no se han creado usuarios.",
|
||||
"login_error_empty_fields": "Por favor, introduce tu nombre de usuario y contraseña.",
|
||||
"login_error_credentials": "Nombre de usuario o contraseña incorrectos.",
|
||||
"permission_denied": "No tienes los permisos necesarios para acceder a esta página.",
|
||||
"cert_create_error_empty_fields": "El nombre de subdominio y el perímetro funcional son obligatorios.",
|
||||
"cert_create_error_perimeter_not_found": "Perímetro funcional seleccionado no encontrado.",
|
||||
"cert_create_success": "Certificado creado correctamente.",
|
||||
"cert_create_error": "Error al crear el certificado: {output}",
|
||||
"cert_revoke_error_id_missing": "ID de certificado faltante para la revocación.",
|
||||
"cert_revoke_error_not_found": "Certificado no encontrado para la revocación.",
|
||||
"cert_revoke_error_ca_revocation": "Los certificados ROOT e INTERMEDIOS no pueden ser revocados a través de la interfaz por razones de seguridad PKI.",
|
||||
"cert_revoke_error_already_revoked": "Este certificado ya ha sido revocado.",
|
||||
"cert_revoke_success": "Certificado revocado correctamente.",
|
||||
"cert_revoke_error": "Error al revocar el certificado: {output}",
|
||||
"perimeter_create_error_empty_name": "El nombre del perímetro funcional es obligatorio.",
|
||||
"perimeter_create_error_exists": "Ya existe un perímetro funcional con este nombre.",
|
||||
"perimeter_create_success": "Perímetro funcional y su certificado intermedio creados correctamente.",
|
||||
"perimeter_create_error": "Error al crear el perímetro funcional: {output}",
|
||||
"user_create_error_empty_fields": "El nombre de usuario y la contraseña son obligatorios.",
|
||||
"user_create_error_invalid_role": "Rol de usuario no válido.",
|
||||
"user_create_error_exists": "Ya existe un usuario con el nombre '{username}'.",
|
||||
"user_create_success": "Usuario '{username}' creado correctamente.",
|
||||
"user_create_error_db": "Error al crear el usuario en la base de datos.",
|
||||
"user_delete_error_id_missing": "ID de usuario faltante para la eliminación.",
|
||||
"user_delete_error_self_delete": "No puedes eliminar tu propia cuenta.",
|
||||
"user_delete_error_last_admin": "No se puede eliminar la última cuenta de administrador.",
|
||||
"user_delete_success": "Usuario '{username}' eliminado correctamente.",
|
||||
"user_delete_error_not_found": "Usuario no encontrado para la eliminación.",
|
||||
"user_delete_error_db": "Error al eliminar el usuario de la base de datos.",
|
||||
"self_delete_not_allowed": "No puedes eliminarte a ti mismo."
|
||||
}
|
84
app/src/Lang/fr.json
Normal file
84
app/src/Lang/fr.json
Normal file
@ -0,0 +1,84 @@
|
||||
{
|
||||
"app_name": "Gestion Certificat",
|
||||
"login_title": "Connexion - Gestion Certificat",
|
||||
"login_heading": "Connexion à l'application",
|
||||
"username": "Nom d'utilisateur",
|
||||
"password": "Mot de passe",
|
||||
"login_button": "Se connecter",
|
||||
"dark_mode": "Mode Sombre",
|
||||
"light_mode": "Mode Clair",
|
||||
"dashboard_title": "Tableau de Bord",
|
||||
"welcome": "Bienvenue, {username} !",
|
||||
"logout": "Déconnexion",
|
||||
"certificates": "Certificats",
|
||||
"functional_perimeters": "Périmètres Fonctionnels",
|
||||
"users": "Utilisateurs",
|
||||
"quick_actions": "Actions Rapides",
|
||||
"create_new_certificate": "Créer un nouveau certificat",
|
||||
"create_new_perimeter": "Créer un nouveau périmètre",
|
||||
"new_user": "Nouvel utilisateur",
|
||||
"certificate_name": "Nom du certificat",
|
||||
"type": "Type",
|
||||
"expiration_date": "Date d'expiration",
|
||||
"status": "Statut",
|
||||
"revoked": "Révoqué",
|
||||
"active": "Actif",
|
||||
"actions": "Actions",
|
||||
"revoke_certificate": "Révoquer",
|
||||
"confirm_revoke": "Êtes-vous sûr de vouloir révoquer ce certificat ? Cette action est irréversible et rendra le certificat invalide.",
|
||||
"perimeter_name": "Nom du périmètre",
|
||||
"intermediate_cert_file": "Fichier Certificat Intermédiaire",
|
||||
"created_at": "Créé le",
|
||||
"create_perimeter_button": "Créer le périmètre",
|
||||
"create_new_user": "Créer un nouvel utilisateur",
|
||||
"user_role": "Rôle",
|
||||
"admin": "Administrateur",
|
||||
"user": "Utilisateur",
|
||||
"create_user_button": "Créer l'utilisateur",
|
||||
"delete_user": "Supprimer",
|
||||
"confirm_delete_user": "Êtes-vous sûr de vouloir supprimer cet utilisateur ? Cette action est irréversible.",
|
||||
"new_certificate_heading": "Créer un nouveau certificat",
|
||||
"subdomain_name": "Nom du sous-domaine / CN",
|
||||
"select_perimeter": "Sélectionner un périmètre fonctionnel",
|
||||
"select_perimeter_placeholder": "Choisissez un périmètre",
|
||||
"create_certificate": "Créer le certificat",
|
||||
"root": "Root",
|
||||
"intermediate": "Intermédiaire",
|
||||
"simple": "Simple",
|
||||
"back_to_dashboard": "Retour au Tableau de Bord",
|
||||
"back_to_cert_list": "Retour à la liste des certificats",
|
||||
"back_to_perimeter_list": "Retour à la liste des périmètres",
|
||||
"back_to_user_list": "Retour à la liste des utilisateurs",
|
||||
"no_certificates_yet": "Aucun certificat n'a encore été créé.",
|
||||
"no_perimeters_yet": "Aucun périmètre fonctionnel n'a encore été créé.",
|
||||
"no_users_yet": "Aucun utilisateur n'a encore été créé.",
|
||||
"login_error_empty_fields": "Veuillez saisir votre nom d'utilisateur et votre mot de passe.",
|
||||
"login_error_credentials": "Nom d'utilisateur ou mot de passe incorrect.",
|
||||
"permission_denied": "Vous n'avez pas les permissions nécessaires pour accéder à cette page.",
|
||||
"cert_create_error_empty_fields": "Le nom du sous-domaine et le périmètre fonctionnel sont requis.",
|
||||
"cert_create_error_perimeter_not_found": "Le périmètre fonctionnel sélectionné est introuvable.",
|
||||
"cert_create_success": "Certificat créé avec succès.",
|
||||
"cert_create_error": "Erreur lors de la création du certificat: {output}",
|
||||
"cert_revoke_error_id_missing": "ID du certificat manquant pour la révocation.",
|
||||
"cert_revoke_error_not_found": "Certificat introuvable pour la révocation.",
|
||||
"cert_revoke_error_ca_revocation": "Les certificats ROOT et INTERMÉDIAIRES ne peuvent pas être révoqués via l'interface pour des raisons de sécurité PKI.",
|
||||
"cert_revoke_error_already_revoked": "Ce certificat est déjà révoqué.",
|
||||
"cert_revoke_success": "Certificat révoqué avec succès.",
|
||||
"cert_revoke_error": "Erreur lors de la révocation du certificat: {output}",
|
||||
"perimeter_create_error_empty_name": "Le nom du périmètre fonctionnel est requis.",
|
||||
"perimeter_create_error_exists": "Un périmètre fonctionnel avec ce nom existe déjà.",
|
||||
"perimeter_create_success": "Périmètre fonctionnel et son certificat intermédiaire créés avec succès.",
|
||||
"perimeter_create_error": "Erreur lors de la création du périmètre fonctionnel: {output}",
|
||||
"user_create_error_empty_fields": "Le nom d'utilisateur et le mot de passe sont requis.",
|
||||
"user_create_error_invalid_role": "Rôle d'utilisateur invalide.",
|
||||
"user_create_error_exists": "Un utilisateur avec le nom '{username}' existe déjà.",
|
||||
"user_create_success": "Utilisateur '{username}' créé avec succès.",
|
||||
"user_create_error_db": "Erreur lors de la création de l'utilisateur dans la base de données.",
|
||||
"user_delete_error_id_missing": "ID de l'utilisateur manquant pour la suppression.",
|
||||
"user_delete_error_self_delete": "Vous ne pouvez pas supprimer votre propre compte.",
|
||||
"user_delete_error_last_admin": "Impossible de supprimer le dernier compte administrateur.",
|
||||
"user_delete_success": "Utilisateur '{username}' supprimé avec succès.",
|
||||
"user_delete_error_not_found": "Utilisateur introuvable pour la suppression.",
|
||||
"user_delete_error_db": "Erreur lors de la suppression de l'utilisateur dans la base de données.",
|
||||
"self_delete_not_allowed": "Vous ne pouvez pas vous supprimer vous-même."
|
||||
}
|
84
app/src/Lang/it.json
Normal file
84
app/src/Lang/it.json
Normal file
@ -0,0 +1,84 @@
|
||||
{
|
||||
"app_name": "Gestione Certificati",
|
||||
"login_title": "Accesso - Gestione Certificati",
|
||||
"login_heading": "Accedi all'applicazione",
|
||||
"username": "Nome utente",
|
||||
"password": "Password",
|
||||
"login_button": "Accedi",
|
||||
"dark_mode": "Modalità Scura",
|
||||
"light_mode": "Modalità Chiara",
|
||||
"dashboard_title": "Dashboard",
|
||||
"welcome": "Benvenuto, {username}!",
|
||||
"logout": "Esci",
|
||||
"certificates": "Certificati",
|
||||
"functional_perimeters": "Perimetri Funzionali",
|
||||
"users": "Utenti",
|
||||
"quick_actions": "Azioni Rapide",
|
||||
"create_new_certificate": "Crea un nuovo certificato",
|
||||
"create_new_perimeter": "Crea un nuovo perimetro",
|
||||
"new_user": "Nuovo utente",
|
||||
"certificate_name": "Nome Certificato",
|
||||
"type": "Tipo",
|
||||
"expiration_date": "Data di Scadenza",
|
||||
"status": "Stato",
|
||||
"revoked": "Revocato",
|
||||
"active": "Attivo",
|
||||
"actions": "Azioni",
|
||||
"revoke_certificate": "Revoca",
|
||||
"confirm_revoke": "Sei sicuro di voler revocare questo certificato? Questa azione è irreversibile e renderà il certificato non valido.",
|
||||
"perimeter_name": "Nome Perimetro",
|
||||
"intermediate_cert_file": "File Certificato Intermedio",
|
||||
"created_at": "Creato il",
|
||||
"create_perimeter_button": "Crea Perimetro",
|
||||
"create_new_user": "Crea un nuovo utente",
|
||||
"user_role": "Ruolo",
|
||||
"admin": "Amministratore",
|
||||
"user": "Utente",
|
||||
"create_user_button": "Crea Utente",
|
||||
"delete_user": "Elimina",
|
||||
"confirm_delete_user": "Sei sicuro di voler eliminare questo utente? Questa azione è irreversibile.",
|
||||
"new_certificate_heading": "Crea un Nuovo Certificato",
|
||||
"subdomain_name": "Nome Sottodominio / CN",
|
||||
"select_perimeter": "Seleziona un Perimetro Funzionale",
|
||||
"select_perimeter_placeholder": "Scegli un perimetro",
|
||||
"create_certificate": "Crea Certificato",
|
||||
"root": "Root",
|
||||
"intermediate": "Intermedio",
|
||||
"simple": "Semplice",
|
||||
"back_to_dashboard": "Torna alla Dashboard",
|
||||
"back_to_cert_list": "Torna all'Elenco Certificati",
|
||||
"back_to_perimeter_list": "Torna all'Elenco Perimetri",
|
||||
"back_to_user_list": "Torna all'Elenco Utenti",
|
||||
"no_certificates_yet": "Nessun certificato è stato ancora creato.",
|
||||
"no_perimeters_yet": "Nessun perimetro funzionale è stato ancora creato.",
|
||||
"no_users_yet": "Nessun utente è stato ancora creato.",
|
||||
"login_error_empty_fields": "Si prega di inserire nome utente e password.",
|
||||
"login_error_credentials": "Nome utente o password errati.",
|
||||
"permission_denied": "Non si dispone delle autorizzazioni necessarie per accedere a questa pagina.",
|
||||
"cert_create_error_empty_fields": "Nome sottodominio e perimetro funzionale sono obbligatori.",
|
||||
"cert_create_error_perimeter_not_found": "Perimetro funzionale selezionato non trovato.",
|
||||
"cert_create_success": "Certificato creato con successo.",
|
||||
"cert_create_error": "Errore durante la creazione del certificato: {output}",
|
||||
"cert_revoke_error_id_missing": "ID certificato mancante per la revoca.",
|
||||
"cert_revoke_error_not_found": "Certificato non trovato per la revoca.",
|
||||
"cert_revoke_error_ca_revocation": "I certificati ROOT e INTERMEDIATE non possono essere revocati tramite l'interfaccia per motivi di sicurezza PKI.",
|
||||
"cert_revoke_error_already_revoked": "Questo certificato è già stato revocato.",
|
||||
"cert_revoke_success": "Certificato revocato con successo.",
|
||||
"cert_revoke_error": "Errore durante la revoca del certificato: {output}",
|
||||
"perimeter_create_error_empty_name": "Il nome del perimetro funzionale è obbligatorio.",
|
||||
"perimeter_create_error_exists": "Esiste già un perimetro funzionale con questo nome.",
|
||||
"perimeter_create_success": "Perimetro funzionale e il suo certificato intermedio creati con successo.",
|
||||
"perimeter_create_error": "Errore durante la creazione del perimetro funzionale: {output}",
|
||||
"user_create_error_empty_fields": "Nome utente e password sono obbligatori.",
|
||||
"user_create_error_invalid_role": "Ruolo utente non valido.",
|
||||
"user_create_error_exists": "Esiste già un utente con il nome '{username}'.",
|
||||
"user_create_success": "Utente '{username}' creato con successo.",
|
||||
"user_create_error_db": "Errore durante la creazione dell'utente nel database.",
|
||||
"user_delete_error_id_missing": "ID utente mancante per l'eliminazione.",
|
||||
"user_delete_error_self_delete": "Non puoi eliminare il tuo account.",
|
||||
"user_delete_error_last_admin": "Impossibile eliminare l'ultimo account amministratore.",
|
||||
"user_delete_success": "Utente '{username}' eliminato con successo.",
|
||||
"user_delete_error_not_found": "Utente non trovato per l'eliminazione.",
|
||||
"user_delete_error_db": "Errore durante l'eliminazione dell'utente dal database.",
|
||||
"self_delete_not_allowed": "Non puoi eliminare te stesso."
|
||||
}
|
84
app/src/Lang/pt.json
Normal file
84
app/src/Lang/pt.json
Normal file
@ -0,0 +1,84 @@
|
||||
{
|
||||
"app_name": "Gestão de Certificados",
|
||||
"login_title": "Login - Gestão de Certificados",
|
||||
"login_heading": "Fazer login na aplicação",
|
||||
"username": "Nome de Utilizador",
|
||||
"password": "Palavra-passe",
|
||||
"login_button": "Entrar",
|
||||
"dark_mode": "Modo Escuro",
|
||||
"light_mode": "Modo Claro",
|
||||
"dashboard_title": "Painel de Controlo",
|
||||
"welcome": "Bem-vindo, {username}!",
|
||||
"logout": "Sair",
|
||||
"certificates": "Certificados",
|
||||
"functional_perimeters": "Perímetros Funcionais",
|
||||
"users": "Utilizadores",
|
||||
"quick_actions": "Ações Rápidas",
|
||||
"create_new_certificate": "Criar novo certificado",
|
||||
"create_new_perimeter": "Criar novo perímetro",
|
||||
"new_user": "Novo utilizador",
|
||||
"certificate_name": "Nome do Certificado",
|
||||
"type": "Tipo",
|
||||
"expiration_date": "Data de Expiração",
|
||||
"status": "Estado",
|
||||
"revoked": "Revogado",
|
||||
"active": "Ativo",
|
||||
"actions": "Ações",
|
||||
"revoke_certificate": "Revogar",
|
||||
"confirm_revoke": "Tem certeza que deseja revogar este certificado? Esta ação é irreversível e invalidará o certificado.",
|
||||
"perimeter_name": "Nome do Perímetro",
|
||||
"intermediate_cert_file": "Ficheiro de Certificado Intermédio",
|
||||
"created_at": "Criado em",
|
||||
"create_perimeter_button": "Criar Perímetro",
|
||||
"create_new_user": "Criar novo utilizador",
|
||||
"user_role": "Função",
|
||||
"admin": "Administrador",
|
||||
"user": "Utilizador",
|
||||
"create_user_button": "Criar Utilizador",
|
||||
"delete_user": "Eliminar",
|
||||
"confirm_delete_user": "Tem certeza que deseja eliminar este utilizador? Esta ação é irreversível.",
|
||||
"new_certificate_heading": "Criar Novo Certificado",
|
||||
"subdomain_name": "Nome do Subdomínio / CN",
|
||||
"select_perimeter": "Selecionar um Perímetro Funcional",
|
||||
"select_perimeter_placeholder": "Escolha um perímetro",
|
||||
"create_certificate": "Criar Certificado",
|
||||
"root": "Raiz",
|
||||
"intermediate": "Intermédio",
|
||||
"simple": "Simples",
|
||||
"back_to_dashboard": "Voltar ao Painel de Controlo",
|
||||
"back_to_cert_list": "Voltar à Lista de Certificados",
|
||||
"back_to_perimeter_list": "Voltar à Lista de Perímetros",
|
||||
"back_to_user_list": "Voltar à Lista de Utilizadores",
|
||||
"no_certificates_yet": "Nenhum certificado foi criado ainda.",
|
||||
"no_perimeters_yet": "Nenhum perímetro funcional foi criado ainda.",
|
||||
"no_users_yet": "Nenhum utilizador foi criado ainda.",
|
||||
"login_error_empty_fields": "Por favor, insira seu nome de utilizador e palavra-passe.",
|
||||
"login_error_credentials": "Nome de utilizador ou palavra-passe incorretos.",
|
||||
"permission_denied": "Não tem as permissões necessárias para aceder a esta página.",
|
||||
"cert_create_error_empty_fields": "O nome do subdomínio e o perímetro funcional são obrigatórios.",
|
||||
"cert_create_error_perimeter_not_found": "Perímetro funcional selecionado não encontrado.",
|
||||
"cert_create_success": "Certificado criado com sucesso.",
|
||||
"cert_create_error": "Erro ao criar certificado: {output}",
|
||||
"cert_revoke_error_id_missing": "ID do certificado em falta para revogação.",
|
||||
"cert_revoke_error_not_found": "Certificado não encontrado para revogação.",
|
||||
"cert_revoke_error_ca_revocation": "Certificados ROOT e INTERMEDIÁRIOS não podem ser revogados através da interface por razões de segurança PKI.",
|
||||
"cert_revoke_error_already_revoked": "Este certificado já está revogado.",
|
||||
"cert_revoke_success": "Certificado revogado com sucesso.",
|
||||
"cert_revoke_error": "Erro ao revogar certificado: {output}",
|
||||
"perimeter_create_error_empty_name": "O nome do perímetro funcional é obrigatório.",
|
||||
"perimeter_create_error_exists": "Já existe um perímetro funcional com este nome.",
|
||||
"perimeter_create_success": "Perímetro funcional e o seu certificado intermédio criados com sucesso.",
|
||||
"perimeter_create_error": "Erro ao criar perímetro funcional: {output}",
|
||||
"user_create_error_empty_fields": "Nome de utilizador e palavra-passe são obrigatórios.",
|
||||
"user_create_error_invalid_role": "Função de utilizador inválida.",
|
||||
"user_create_error_exists": "Já existe um utilizador com o nome '{username}'.",
|
||||
"user_create_success": "Utilizador '{username}' criado com sucesso.",
|
||||
"user_create_error_db": "Erro ao criar utilizador na base de dados.",
|
||||
"user_delete_error_id_missing": "ID de utilizador em falta para eliminação.",
|
||||
"user_delete_error_self_delete": "Não pode eliminar a sua própria conta.",
|
||||
"user_delete_error_last_admin": "Não é possível eliminar a última conta de administrador.",
|
||||
"user_delete_success": "Utilizador '{username}' eliminado com sucesso.",
|
||||
"user_delete_error_not_found": "Utilizador não encontrado para eliminação.",
|
||||
"user_delete_error_db": "Erro ao eliminar utilizador da base de dados.",
|
||||
"self_delete_not_allowed": "Não pode eliminar-se a si mesmo."
|
||||
}
|
117
app/src/Services/AuthService.php
Normal file
117
app/src/Services/AuthService.php
Normal file
@ -0,0 +1,117 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use PDO;
|
||||
|
||||
/**
|
||||
* Service d'authentification utilisateur.
|
||||
* Gère les connexions, déconnexions et vérifie l'état de l'authentification.
|
||||
*/
|
||||
class AuthService
|
||||
{
|
||||
private $db; // Instance PDO pour l'accès à la base de données
|
||||
private $logService; // Service de journalisation
|
||||
|
||||
/**
|
||||
* Constructeur du service d'authentification.
|
||||
*
|
||||
* @param PDO $db L'instance PDO de la base de données.
|
||||
*/
|
||||
public function __construct(PDO $db)
|
||||
{
|
||||
$this->db = $db;
|
||||
// Le chemin du fichier de log doit être défini dans config/app.php
|
||||
$this->logService = new LogService(APP_LOG_PATH);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tente de connecter un utilisateur.
|
||||
*
|
||||
* @param string $username Le nom d'utilisateur.
|
||||
* @param string $password Le mot de passe en clair.
|
||||
* @param string $ipAddress L'adresse IP de la requête de connexion.
|
||||
* @return bool Vrai si la connexion est réussie, faux sinon.
|
||||
*/
|
||||
public function login($username, $password, $ipAddress)
|
||||
{
|
||||
$stmt = $this->db->prepare("SELECT id, username, password, role FROM users WHERE username = ?");
|
||||
$stmt->execute([$username]);
|
||||
$user = $stmt->fetch();
|
||||
|
||||
if ($user && password_verify($password, $user['password'])) {
|
||||
// Connexion réussie : enregistre les informations de l session
|
||||
$_SESSION['user_id'] = $user['id'];
|
||||
$_SESSION['username'] = $user['username'];
|
||||
$_SESSION['role'] = $user['role'];
|
||||
|
||||
// Log de l'action de connexion réussie
|
||||
$this->logService->log('info', "Connexion réussie pour l'utilisateur '{$username}'.", $user['id'], $ipAddress);
|
||||
return true;
|
||||
} else {
|
||||
// Connexion échouée : log de la tentative
|
||||
$this->logService->log('warning', "Tentative de connexion échouée pour l'utilisateur '{$username}'.", null, $ipAddress);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Déconnecte l'utilisateur actuellement connecté.
|
||||
*
|
||||
* @param string $ipAddress L'adresse IP de la requête de déconnexion.
|
||||
* @return bool Vrai si la déconnexion est effectuée.
|
||||
*/
|
||||
public function logout($ipAddress)
|
||||
{
|
||||
$userId = $_SESSION['user_id'] ?? 'inconnu';
|
||||
$username = $_SESSION['username'] ?? 'inconnu';
|
||||
|
||||
// Détruit toutes les données de session
|
||||
session_destroy();
|
||||
$_SESSION = array(); // Vider le tableau $_SESSION
|
||||
|
||||
// Log de l'action de déconnexion
|
||||
$this->logService->log('info', "Déconnexion de l'utilisateur '{$username}'.", $userId, $ipAddress);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Vérifie si un utilisateur est actuellement connecté.
|
||||
*
|
||||
* @return bool Vrai si l'utilisateur est connecté, faux sinon.
|
||||
*/
|
||||
public function isLoggedIn()
|
||||
{
|
||||
return isset($_SESSION['user_id']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Récupère le rôle de l'utilisateur connecté.
|
||||
*
|
||||
* @return string|null Le rôle de l'utilisateur ('admin' ou 'user'), ou null si non connecté.
|
||||
*/
|
||||
public function getUserRole()
|
||||
{
|
||||
return $_SESSION['role'] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Récupère l'ID de l'utilisateur connecté.
|
||||
*
|
||||
* @return int|null L'ID de l'utilisateur, ou null si non connecté.
|
||||
*/
|
||||
public function getUserId()
|
||||
{
|
||||
return $_SESSION['user_id'] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Récupère le nom d'utilisateur de l'utilisateur connecté.
|
||||
*
|
||||
* @return string|null Le nom d'utilisateur, ou null si non connecté.
|
||||
*/
|
||||
public function getUsername()
|
||||
{
|
||||
return $_SESSION['username'] ?? null;
|
||||
}
|
||||
}
|
118
app/src/Services/LanguageService.php
Normal file
118
app/src/Services/LanguageService.php
Normal file
@ -0,0 +1,118 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
/**
|
||||
* Service pour gérer la langue de l'application et charger les traductions.
|
||||
*/
|
||||
class LanguageService
|
||||
{
|
||||
private $langDir; // Répertoire où sont stockés les fichiers de traduction
|
||||
private $currentLang; // Langue actuellement sélectionnée
|
||||
|
||||
/**
|
||||
* Constructeur du service de langue.
|
||||
* Initialise la langue actuelle à partir de la session ou une valeur par défaut.
|
||||
*
|
||||
* @param string $langDir Chemin absolu du répertoire des fichiers de traduction.
|
||||
*/
|
||||
public function __construct($langDir)
|
||||
{
|
||||
$this->langDir = rtrim($langDir, '/') . '/'; // Assure qu'il y a un slash final
|
||||
// Récupère la langue de la session ou utilise 'en' par default
|
||||
$this->currentLang = $_SESSION['lang'] ?? 'en';
|
||||
// Vérifie si la langue est supportée, sinon revient à 'en'
|
||||
// CORRIGÉ: La condition était incorrecte
|
||||
if (!in_array($this->currentLang, SUPPORTED_LANGUAGES)) {
|
||||
$this->currentLang = 'en';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Définit la langue de l'application.
|
||||
*
|
||||
* @param string $lang Le code de la langue (ex: 'fr', 'en').
|
||||
* @return bool Vrai si la langue a été définie avec succès, faux sinon.
|
||||
*/
|
||||
public function setLanguage($lang)
|
||||
{
|
||||
if (in_array($lang, SUPPORTED_LANGUAGES)) {
|
||||
$_SESSION['lang'] = $lang;
|
||||
$this->currentLang = $lang;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retourne la langue actuellement sélectionnée.
|
||||
*
|
||||
* @return string Le code de la langue.
|
||||
*/
|
||||
public function getLanguage()
|
||||
{
|
||||
return $this->currentLang;
|
||||
}
|
||||
|
||||
/**
|
||||
* Charge et retourne toutes les traductions pour la langue actuelle.
|
||||
*
|
||||
* @return array Tableau associatif des traductions.
|
||||
*/
|
||||
public function getTranslations()
|
||||
{
|
||||
$filePath = $this->langDir . $this->currentLang . '.json';
|
||||
if (file_exists($filePath)) {
|
||||
$content = file_get_contents($filePath);
|
||||
if ($content === false) {
|
||||
error_log("LanguageService: Impossible de lire le fichier de traduction: " . $filePath);
|
||||
return [];
|
||||
}
|
||||
$translations = json_decode($content, true);
|
||||
if (json_last_error() !== JSON_ERROR_NONE) {
|
||||
error_log("LanguageService: Erreur de décodage JSON pour le fichier: " . $filePath . " - " . json_last_error_msg());
|
||||
return [];
|
||||
}
|
||||
return $translations;
|
||||
}
|
||||
// Fallback à l'anglais si le fichier de la langue actuelle est manquant
|
||||
$englishFilePath = $this->langDir . 'en.json';
|
||||
if (file_exists($englishFilePath)) {
|
||||
$content = file_get_contents($englishFilePath);
|
||||
if ($content === false) {
|
||||
error_log("LanguageService: Impossible de lire le fichier de traduction anglais de secours: " . $englishFilePath);
|
||||
return [];
|
||||
}
|
||||
$translations = json_decode($content, true);
|
||||
if (json_last_error() !== JSON_ERROR_NONE) {
|
||||
error_log("LanguageService: Erreur de décodage JSON pour le fichier anglais de secours: " . $englishFilePath . " - " . json_last_error_msg());
|
||||
return [];
|
||||
}
|
||||
return $translations;
|
||||
}
|
||||
error_log("LanguageService: Aucun fichier de traduction trouvé pour la langue '" . $this->currentLang . "' ou 'en'.");
|
||||
return []; // Retourne un tableau vide si aucune traduction n'est trouvée
|
||||
}
|
||||
|
||||
/**
|
||||
* Traduit une clé donnée.
|
||||
*
|
||||
* @param string $key La clé de traduction.
|
||||
* @param array $replacements Tableau associatif de [placeholder => valeur] pour les remplacements.
|
||||
* @return string La chaîne traduite ou la clé si non trouvée.
|
||||
*/
|
||||
public function __($key, $replacements = [])
|
||||
{
|
||||
// Utilise la variable globale $translations qui est chargée dans index.php
|
||||
global $translations;
|
||||
|
||||
$translatedString = $translations[$key] ?? $key;
|
||||
|
||||
// Effectuer les remplacements de placeholders
|
||||
foreach ($replacements as $placeholder => $value) {
|
||||
$translatedString = str_replace("{" . $placeholder . "}", $value, $translatedString);
|
||||
}
|
||||
|
||||
return $translatedString;
|
||||
}
|
||||
}
|
117
app/src/Services/LanguageService.php.bak
Normal file
117
app/src/Services/LanguageService.php.bak
Normal file
@ -0,0 +1,117 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
/**
|
||||
* Service pour gérer la langue de l'application et charger les traductions.
|
||||
*/
|
||||
class LanguageService
|
||||
{
|
||||
private $langDir; // Répertoire où sont stockés les fichiers de traduction
|
||||
private $currentLang; // Langue actuellement sélectionnée
|
||||
|
||||
/**
|
||||
* Constructeur du service de langue.
|
||||
* Initialise la langue actuelle à partir de la session ou une valeur par défaut.
|
||||
*
|
||||
* @param string $langDir Chemin absolu du répertoire des fichiers de traduction.
|
||||
*/
|
||||
public function __construct($langDir)
|
||||
{
|
||||
$this->langDir = rtrim($langDir, '/') . '/'; // Assure qu'il y a un slash final
|
||||
// Récupère la langue de la session ou utilise 'en' par défaut
|
||||
$this->currentLang = $_SESSION['lang'] ?? 'en';
|
||||
// Vérifie si la langue est supportée, sinon revient à 'en'
|
||||
if (!in_array($this->currentLang, SUPPORTED_LANGUAGES)) {
|
||||
$this->currentLang = 'en';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Définit la langue de l'application.
|
||||
*
|
||||
* @param string $lang Le code de la langue (ex: 'fr', 'en').
|
||||
* @return bool Vrai si la langue a été définie avec succès, faux sinon.
|
||||
*/
|
||||
public function setLanguage($lang)
|
||||
{
|
||||
if (in_array($lang, SUPPORTED_LANGUAGES)) {
|
||||
$_SESSION['lang'] = $lang;
|
||||
$this->currentLang = $lang;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retourne la langue actuellement sélectionnée.
|
||||
*
|
||||
* @return string Le code de la langue.
|
||||
*/
|
||||
public function getLanguage()
|
||||
{
|
||||
return $this->currentLang;
|
||||
}
|
||||
|
||||
/**
|
||||
* Charge et retourne toutes les traductions pour la langue actuelle.
|
||||
*
|
||||
* @return array Tableau associatif des traductions.
|
||||
*/
|
||||
public function getTranslations()
|
||||
{
|
||||
$filePath = $this->langDir . $this->currentLang . '.json';
|
||||
if (file_exists($filePath)) {
|
||||
$content = file_get_contents($filePath);
|
||||
if ($content === false) {
|
||||
error_log("LanguageService: Impossible de lire le fichier de traduction: " . $filePath);
|
||||
return [];
|
||||
}
|
||||
$translations = json_decode($content, true);
|
||||
if (json_last_error() !== JSON_ERROR_NONE) {
|
||||
error_log("LanguageService: Erreur de décodage JSON pour le fichier: " . $filePath . " - " . json_last_error_msg());
|
||||
return [];
|
||||
}
|
||||
return $translations;
|
||||
}
|
||||
// Fallback à l'anglais si le fichier de la langue actuelle est manquant
|
||||
$englishFilePath = $this->langDir . 'en.json';
|
||||
if (file_exists($englishFilePath)) {
|
||||
$content = file_get_contents($englishFilePath);
|
||||
if ($content === false) {
|
||||
error_log("LanguageService: Impossible de lire le fichier de traduction anglais de secours: " . $englishFilePath);
|
||||
return [];
|
||||
}
|
||||
$translations = json_decode($content, true);
|
||||
if (json_last_error() !== JSON_ERROR_NONE) {
|
||||
error_log("LanguageService: Erreur de décodage JSON pour le fichier anglais de secours: " . $englishFilePath . " - " . json_last_error_msg());
|
||||
return [];
|
||||
}
|
||||
return $translations;
|
||||
}
|
||||
error_log("LanguageService: Aucun fichier de traduction trouvé pour la langue '" . $this->currentLang . "' ou 'en'.");
|
||||
return []; // Retourne un tableau vide si aucune traduction n'est trouvée
|
||||
}
|
||||
|
||||
/**
|
||||
* Traduit une clé donnée.
|
||||
*
|
||||
* @param string $key La clé de traduction.
|
||||
* @param array $replacements Tableau associatif de [placeholder => valeur] pour les remplacements.
|
||||
* @return string La chaîne traduite ou la clé si non trouvée.
|
||||
*/
|
||||
public function __($key, $replacements = [])
|
||||
{
|
||||
// Utilise la variable globale $translations qui est chargée dans index.php
|
||||
global $translations;
|
||||
|
||||
$translatedString = $translations[$key] ?? $key;
|
||||
|
||||
// Effectuer les remplacements de placeholders
|
||||
foreach ($replacements as $placeholder => $value) {
|
||||
$translatedString = str_replace("{" . $placeholder . "}", $value, $translatedString);
|
||||
}
|
||||
|
||||
return $translatedString;
|
||||
}
|
||||
}
|
48
app/src/Services/LogService.php
Normal file
48
app/src/Services/LogService.php
Normal file
@ -0,0 +1,48 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
/**
|
||||
* Service pour journaliser les actions de l'application dans un fichier.
|
||||
*/
|
||||
class LogService
|
||||
{
|
||||
private $logFile; // Chemin complet du fichier de log
|
||||
|
||||
/**
|
||||
* Constructeur du service de journalisation.
|
||||
*
|
||||
* @param string $logFile Chemin complet du fichier de log.
|
||||
*/
|
||||
public function __construct($logFile)
|
||||
{
|
||||
$this->logFile = $logFile;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enregistre un message dans le fichier de log.
|
||||
*
|
||||
* @param string $level Niveau de gravité du log (ex: 'info', 'warning', 'error').
|
||||
* @param string $message Le message à enregistrer.
|
||||
* @param int|null $userId L'ID de l'utilisateur si l'action est liée à un utilisateur.
|
||||
* @param string|null $ipAddress L'adresse IP d'où provient l'action.
|
||||
*/
|
||||
public function log($level, $message, $userId = null, $ipAddress = null)
|
||||
{
|
||||
$timestamp = date('Y-m-d H:i:s'); // Date et heure actuelle
|
||||
$logEntry = sprintf("[%s] [%s] ", $timestamp, strtoupper($level)); // Format de base du log
|
||||
|
||||
// Ajoute l'ID utilisateur si fourni
|
||||
if ($userId !== null) {
|
||||
$logEntry .= "[User: $userId] ";
|
||||
}
|
||||
// Ajoute l'adresse IP si fournie
|
||||
if ($ipAddress !== null) {
|
||||
$logEntry .= "[IP: $ipAddress] ";
|
||||
}
|
||||
$logEntry .= "$message\n"; // Ajoute le message et un saut de ligne
|
||||
|
||||
// Écrit le log dans le fichier, en ajoutant au contenu existant
|
||||
file_put_contents($this->logFile, $logEntry, FILE_APPEND);
|
||||
}
|
||||
}
|
56
app/src/Utils/DarkMode.php
Normal file
56
app/src/Utils/DarkMode.php
Normal file
@ -0,0 +1,56 @@
|
||||
<?php
|
||||
|
||||
namespace App\Utils;
|
||||
|
||||
/**
|
||||
* Utilitaire pour gérer le mode sombre/clair de l'application via la session.
|
||||
*/
|
||||
class DarkMode
|
||||
{
|
||||
/**
|
||||
* Initialise l'état du mode sombre dans la session si ce n'est pas déjà fait.
|
||||
* Le mode clair est le mode par défaut.
|
||||
*/
|
||||
public static function init()
|
||||
{
|
||||
if (!isset($_SESSION['dark_mode'])) {
|
||||
$_SESSION['dark_mode'] = false; // false = mode clair, true = mode sombre
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Bascule l'état du mode sombre ou le définit explicitement.
|
||||
*
|
||||
* @param string|null $mode 'on' pour activer, 'off' pour désactiver, null pour basculer.
|
||||
*/
|
||||
public static function toggle($mode = null)
|
||||
{
|
||||
if ($mode === 'on') {
|
||||
$_SESSION['dark_mode'] = true;
|
||||
} elseif ($mode === 'off') {
|
||||
$_SESSION['dark_mode'] = false;
|
||||
} else {
|
||||
$_SESSION['dark_mode'] = !($_SESSION['dark_mode'] ?? false); // Bascule l'état actuel
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Vérifie si le mode sombre est activé.
|
||||
*
|
||||
* @return bool Vrai si le mode sombre est activé, faux sinon.
|
||||
*/
|
||||
public static function isEnabled()
|
||||
{
|
||||
return $_SESSION['dark_mode'] ?? false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retourne la classe CSS à appliquer au body HTML en fonction de l'état du mode sombre.
|
||||
*
|
||||
* @return string La classe CSS ('dark-mode') ou une chaîne vide.
|
||||
*/
|
||||
public static function getBodyClass()
|
||||
{
|
||||
return self::isEnabled() ? 'dark-mode' : '';
|
||||
}
|
||||
}
|
51
app/src/Views/auth/login.php
Normal file
51
app/src/Views/auth/login.php
Normal file
@ -0,0 +1,51 @@
|
||||
<?php
|
||||
// On assume que $translations, $currentLang, $darkModeClass et $error sont définis par le contrôleur
|
||||
// global $translations; // Déjà global dans index.php si utilisé ici
|
||||
// global $currentLang;
|
||||
// global $darkModeClass;
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html lang="<?= htmlspecialchars($currentLang) ?>">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title><?= htmlspecialchars($translations['login_title']) ?></title>
|
||||
<link rel="stylesheet" href="/style.css">
|
||||
<link rel="stylesheet" href="/dark-mode.css">
|
||||
</head>
|
||||
<body class="<?= htmlspecialchars($darkModeClass) ?>">
|
||||
<div class="container">
|
||||
<h1><?= htmlspecialchars($translations['login_heading']) ?></h1>
|
||||
|
||||
<?php if (isset($error)): ?>
|
||||
<p class="error-message"><?= htmlspecialchars($error); ?></p>
|
||||
<?php endif; ?>
|
||||
|
||||
<form action="/login" method="post">
|
||||
<label for="username"><?= htmlspecialchars($translations['username']) ?>:</label><br>
|
||||
<input type="text" id="username" name="username" required autocomplete="username"><br><br>
|
||||
|
||||
<label for="password"><?= htmlspecialchars($translations['password']) ?>:</label><br>
|
||||
<input type="password" id="password" name="password" required autocomplete="current-password"><br><br>
|
||||
|
||||
<button type="submit" class="button primary-button"><?= htmlspecialchars($translations['login_button']) ?></button>
|
||||
</form>
|
||||
|
||||
<div class="options">
|
||||
<!-- Boutons de changement de langue -->
|
||||
<div class="language-switcher">
|
||||
<?php foreach (SUPPORTED_LANGUAGES as $lang): ?>
|
||||
<a href="?lang=<?= htmlspecialchars($lang) ?>" class="<?= $currentLang === $lang ? 'active' : '' ?>"><?= htmlspecialchars(strtoupper($lang)) ?></a>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
|
||||
<!-- Bouton de bascule du mode sombre -->
|
||||
<div class="dark-mode-switcher">
|
||||
<a href="?dark_mode=<?= \App\Utils\DarkMode::isEnabled() ? 'off' : 'on' ?>" class="button secondary-button">
|
||||
<?= \App\Utils\DarkMode::isEnabled() ? htmlspecialchars($translations['light_mode']) : htmlspecialchars($translations['dark_mode']) ?>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
32
app/src/Views/certificates/create.php
Normal file
32
app/src/Views/certificates/create.php
Normal file
@ -0,0 +1,32 @@
|
||||
<?php
|
||||
// Variables attendues du contrôleur: $perimeters, $translations, $currentLang, $darkModeClass, $errorMessage
|
||||
require_once APP_ROOT_DIR . '/src/Views/shared/header.php';
|
||||
?>
|
||||
|
||||
<div class="container">
|
||||
<h1><?= htmlspecialchars($translations['new_certificate_heading']) ?></h1>
|
||||
<a href="/certificates" class="button secondary-button"><?= htmlspecialchars($translations['back_to_cert_list']) ?></a>
|
||||
|
||||
<?php if (isset($errorMessage)): ?>
|
||||
<p class="error-message"><?= htmlspecialchars($errorMessage); ?></p>
|
||||
<?php endif; ?>
|
||||
|
||||
<form action="/certificates/create" method="post">
|
||||
<label for="subdomain_name"><?= htmlspecialchars($translations['subdomain_name']) ?>:</label><br>
|
||||
<input type="text" id="subdomain_name" name="subdomain_name" required placeholder="ex: www, api, mail"><br><br>
|
||||
|
||||
<label for="functional_perimeter_id"><?= htmlspecialchars($translations['select_perimeter']) ?>:</label><br>
|
||||
<select id="functional_perimeter_id" name="functional_perimeter_id" required>
|
||||
<option value="">-- <?= htmlspecialchars($translations['select_perimeter_placeholder']) ?> --</option>
|
||||
<?php foreach ($perimeters as $perimeter): ?>
|
||||
<option value="<?= htmlspecialchars($perimeter['id']) ?>"><?= htmlspecialchars($perimeter['name']) ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select><br><br>
|
||||
|
||||
<button type="submit" class="button primary-button"><?= htmlspecialchars($translations['create_certificate']) ?></button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<?php
|
||||
require_once APP_ROOT_DIR . '/src/Views/shared/footer.php';
|
||||
?>
|
71
app/src/Views/certificates/index.php
Normal file
71
app/src/Views/certificates/index.php
Normal file
@ -0,0 +1,71 @@
|
||||
<?php
|
||||
// Variables attendues du contrôleur: $groupedCertificates, $translations, $currentLang, $darkModeClass, $successMessage, $errorMessage, $userRole
|
||||
require_once APP_ROOT_DIR . '/src/Views/shared/header.php';
|
||||
?>
|
||||
|
||||
<div class="container">
|
||||
<h1><?= htmlspecialchars($translations['certificates']) ?></h1>
|
||||
<div class="actions-bar">
|
||||
<a href="/dashboard" class="button secondary-button"><?= htmlspecialchars($translations['back_to_dashboard']) ?></a>
|
||||
<a href="/certificates/create" class="button primary-button"><?= htmlspecialchars($translations['create_new_certificate']) ?></a>
|
||||
</div>
|
||||
|
||||
<?php if (isset($successMessage)): ?>
|
||||
<p class="success-message"><?= htmlspecialchars($successMessage); ?></p>
|
||||
<?php endif; ?>
|
||||
<?php if (isset($errorMessage)): ?>
|
||||
<p class="error-message"><?= htmlspecialchars($errorMessage); ?></p>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (empty($groupedCertificates)): ?>
|
||||
<p><?= htmlspecialchars($translations['no_certificates_yet']) ?></p>
|
||||
<?php else: ?>
|
||||
<?php foreach ($groupedCertificates as $perimeterName => $certsInPerimeter): ?>
|
||||
<h2 class="perimeter-heading"><?= htmlspecialchars($perimeterName) ?></h2>
|
||||
<div class="table-responsive">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th><?= htmlspecialchars($translations['certificate_name']) ?></th>
|
||||
<th><?= htmlspecialchars($translations['type']) ?></th>
|
||||
<th><?= htmlspecialchars($translations['expiration_date']) ?></th>
|
||||
<th><?= htmlspecialchars($translations['status']) ?></th>
|
||||
<th><?= htmlspecialchars($translations['actions']) ?></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($certsInPerimeter as $cert): ?>
|
||||
<tr class="<?= $cert['is_revoked'] ? 'revoked-cert' : '' ?>">
|
||||
<td><?= htmlspecialchars($cert['name']) ?></td>
|
||||
<td><?= htmlspecialchars($translations[$cert['type']] ?? $cert['type']) ?></td>
|
||||
<td><?= htmlspecialchars((new DateTime($cert['expiration_date']))->format('Y-m-d')) ?></td>
|
||||
<td>
|
||||
<?php if ($cert['is_revoked']): ?>
|
||||
<span class="status-revoked"><?= htmlspecialchars($translations['revoked']) ?></span>
|
||||
(<?= htmlspecialchars((new DateTime($cert['revoked_at']))->format('Y-m-d')) ?>)
|
||||
<?php else: ?>
|
||||
<span class="status-active"><?= htmlspecialchars($translations['active']) ?></span>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
<td>
|
||||
<?php
|
||||
// Seuls les certificats 'simple' et non révoqués peuvent être révoqués via l'interface
|
||||
if (!$cert['is_revoked'] && $cert['type'] === 'simple'): ?>
|
||||
<form action="/certificates/revoke" method="post" class="inline-form" onsubmit="return confirm('<?= htmlspecialchars($translations['confirm_revoke']) ?>');">
|
||||
<input type="hidden" name="certificate_id" value="<?= htmlspecialchars($cert['id']) ?>">
|
||||
<button type="submit" class="button danger-button"><?= htmlspecialchars($translations['revoke_certificate']) ?></button>
|
||||
</form>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<?php
|
||||
require_once APP_ROOT_DIR . '/src/Views/shared/footer.php';
|
||||
?>
|
34
app/src/Views/dashboard/index.php
Normal file
34
app/src/Views/dashboard/index.php
Normal file
@ -0,0 +1,34 @@
|
||||
<?php
|
||||
// Variables attendues du contrôleur: $translations, $currentLang, $darkModeClass, $username, $userRole
|
||||
require_once APP_ROOT_DIR . '/src/Views/shared/header.php';
|
||||
?>
|
||||
|
||||
<div class="container">
|
||||
<h1><?= str_replace('{username}', htmlspecialchars($username), htmlspecialchars($translations['welcome'])) ?></h1>
|
||||
|
||||
<nav>
|
||||
<ul>
|
||||
<li><a href="/certificates"><?= htmlspecialchars($translations['certificates']) ?></a></li>
|
||||
<li><a href="/perimeters"><?= htmlspecialchars($translations['functional_perimeters']) ?></a></li>
|
||||
<?php if ($userRole === 'admin'): ?>
|
||||
<li><a href="/users"><?= htmlspecialchars($translations['users']) ?></a></li>
|
||||
<?php endif; ?>
|
||||
<li><a href="/logout" class="button logout-button"><?= htmlspecialchars($translations['logout']) ?></a></li>
|
||||
</ul>
|
||||
</nav>
|
||||
|
||||
<section class="quick-actions">
|
||||
<h2><?= htmlspecialchars($translations['quick_actions']) ?></h2>
|
||||
<ul>
|
||||
<li><a href="/certificates/create" class="button create-button"><?= htmlspecialchars($translations['create_new_certificate']) ?></a></li>
|
||||
<li><a href="/perimeters/create" class="button create-button"><?= htmlspecialchars($translations['create_new_perimeter']) ?></a></li>
|
||||
<?php if ($userRole === 'admin'): ?>
|
||||
<li><a href="/users/create" class="button create-button"><?= htmlspecialchars($translations['new_user']) ?></a></li>
|
||||
<?php endif; ?>
|
||||
</ul>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<?php
|
||||
require_once APP_ROOT_DIR . '/src/Views/shared/footer.php';
|
||||
?>
|
24
app/src/Views/perimeters/create.php
Normal file
24
app/src/Views/perimeters/create.php
Normal file
@ -0,0 +1,24 @@
|
||||
<?php
|
||||
// Variables attendues du contrôleur: $translations, $currentLang, $darkModeClass, $errorMessage
|
||||
require_once APP_ROOT_DIR . '/src/Views/shared/header.php';
|
||||
?>
|
||||
|
||||
<div class="container">
|
||||
<h1><?= htmlspecialchars($translations['create_new_perimeter']) ?></h1>
|
||||
<a href="/perimeters" class="button secondary-button"><?= htmlspecialchars($translations['back_to_perimeter_list']) ?></a>
|
||||
|
||||
<?php if (isset($errorMessage)): ?>
|
||||
<p class="error-message"><?= htmlspecialchars($errorMessage); ?></p>
|
||||
<?php endif; ?>
|
||||
|
||||
<form action="/perimeters/create" method="post">
|
||||
<label for="name"><?= htmlspecialchars($translations['perimeter_name']) ?>:</label><br>
|
||||
<input type="text" id="name" name="name" required placeholder="ex: Finance, RH, Marketing"><br><br>
|
||||
|
||||
<button type="submit" class="button primary-button"><?= htmlspecialchars($translations['create_perimeter_button']) ?></button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<?php
|
||||
require_once APP_ROOT_DIR . '/src/Views/shared/footer.php';
|
||||
?>
|
51
app/src/Views/perimeters/index.php
Normal file
51
app/src/Views/perimeters/index.php
Normal file
@ -0,0 +1,51 @@
|
||||
<?php
|
||||
// Variables attendues du contrôleur: $perimeters, $translations, $currentLang, $darkModeClass, $successMessage, $errorMessage
|
||||
require_once APP_ROOT_DIR . '/src/Views/shared/header.php';
|
||||
?>
|
||||
|
||||
<div class="container">
|
||||
<h1><?= htmlspecialchars($translations['functional_perimeters']) ?></h1>
|
||||
<div class="actions-bar">
|
||||
<a href="/dashboard" class="button secondary-button"><?= htmlspecialchars($translations['back_to_dashboard']) ?></a>
|
||||
<?php if ($userRole === 'admin'): ?>
|
||||
<a href="/perimeters/create" class="button primary-button"><?= htmlspecialchars($translations['create_new_perimeter']) ?></a>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<?php if (isset($successMessage)): ?>
|
||||
<p class="success-message"><?= htmlspecialchars($successMessage); ?></p>
|
||||
<?php endif; ?>
|
||||
<?php if (isset($errorMessage)): ?>
|
||||
<p class="error-message"><?= htmlspecialchars($errorMessage); ?></p>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (empty($perimeters)): ?>
|
||||
<p><?= htmlspecialchars($translations['no_perimeters_yet']) ?></p>
|
||||
<?php else: ?>
|
||||
<div class="table-responsive">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th><?= htmlspecialchars($translations['perimeter_name']) ?></th>
|
||||
<th><?= htmlspecialchars($translations['intermediate_cert_file']) ?></th>
|
||||
<th><?= htmlspecialchars($translations['created_at']) ?></th>
|
||||
<!-- Ajouter des actions ici si nécessaire, comme supprimer un périmètre (avec prudence!) -->
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($perimeters as $perimeter): ?>
|
||||
<tr>
|
||||
<td><?= htmlspecialchars($perimeter['name']) ?></td>
|
||||
<td><?= htmlspecialchars($perimeter['intermediate_cert_name']) ?></td>
|
||||
<td><?= htmlspecialchars((new DateTime($perimeter['created_at']))->format('Y-m-d H:i:s')) ?></td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<?php
|
||||
require_once APP_ROOT_DIR . '/src/Views/shared/footer.php';
|
||||
?>
|
8
app/src/Views/shared/footer.php
Normal file
8
app/src/Views/shared/footer.php
Normal file
@ -0,0 +1,8 @@
|
||||
</main>
|
||||
<footer class="app-footer">
|
||||
<div class="container">
|
||||
<p>© <?= date('Y') ?> <?= htmlspecialchars($translations['app_name'] ?? 'Gestion Certificat') ?>. Tous droits réservés.</p>
|
||||
</div>
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
39
app/src/Views/shared/header.php
Normal file
39
app/src/Views/shared/header.php
Normal file
@ -0,0 +1,39 @@
|
||||
<?php
|
||||
// Variables attendues: $translations, $currentLang, $darkModeClass
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html lang="<?= htmlspecialchars($currentLang) ?>">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title><?= htmlspecialchars($translations['app_name'] ?? 'Gestion Certificat') ?></title>
|
||||
<link rel="stylesheet" href="/css/style.css">
|
||||
<link rel="stylesheet" href="/css/dark-mode.css">
|
||||
<!-- Font Awesome pour les icônes si besoin -->
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta3/css/all.min.css">
|
||||
</head>
|
||||
<body class="<?= htmlspecialchars($darkModeClass) ?>">
|
||||
<header class="app-header">
|
||||
<div class="header-content container">
|
||||
<div class="app-title">
|
||||
<h1><?= htmlspecialchars($translations['app_name'] ?? 'Gestion Certificat') ?></h1>
|
||||
</div>
|
||||
<div class="header-controls">
|
||||
<div class="language-switcher">
|
||||
<?php foreach (SUPPORTED_LANGUAGES as $lang): ?>
|
||||
<a href="?lang=<?= htmlspecialchars($lang) ?>" class="lang-button <?= $currentLang === $lang ? 'active' : '' ?>"><?= htmlspecialchars(strtoupper($lang)) ?></a>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<div class="dark-mode-switcher">
|
||||
<a href="?dark_mode=<?= \App\Utils\DarkMode::isEnabled() ? 'off' : 'on' ?>" class="dark-mode-button">
|
||||
<?php if (\App\Utils\DarkMode::isEnabled()): ?>
|
||||
<i class="fas fa-sun"></i> <?= htmlspecialchars($translations['light_mode']) ?>
|
||||
<?php else: ?>
|
||||
<i class="fas fa-moon"></i> <?= htmlspecialchars($translations['dark_mode']) ?>
|
||||
<?php endif; ?>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
<main>
|
33
app/src/Views/users/create.php
Normal file
33
app/src/Views/users/create.php
Normal file
@ -0,0 +1,33 @@
|
||||
<?php
|
||||
// Variables attendues du contrôleur: $translations, $currentLang, $darkModeClass, $errorMessage
|
||||
require_once APP_ROOT_DIR . '/src/Views/shared/header.php';
|
||||
?>
|
||||
|
||||
<div class="container">
|
||||
<h1><?= htmlspecialchars($translations['create_new_user']) ?></h1>
|
||||
<a href="/users" class="button secondary-button"><?= htmlspecialchars($translations['back_to_user_list']) ?></a>
|
||||
|
||||
<?php if (isset($errorMessage)): ?>
|
||||
<p class="error-message"><?= htmlspecialchars($errorMessage); ?></p>
|
||||
<?php endif; ?>
|
||||
|
||||
<form action="/users/create" method="post">
|
||||
<label for="username"><?= htmlspecialchars($translations['username']) ?>:</label><br>
|
||||
<input type="text" id="username" name="username" required autocomplete="new-username"><br><br>
|
||||
|
||||
<label for="password"><?= htmlspecialchars($translations['password']) ?>:</label><br>
|
||||
<input type="password" id="password" name="password" required autocomplete="new-password"><br><br>
|
||||
|
||||
<label for="role"><?= htmlspecialchars($translations['user_role']) ?>:</label><br>
|
||||
<select id="role" name="role" required>
|
||||
<option value="user"><?= htmlspecialchars($translations['user']) ?></option>
|
||||
<option value="admin"><?= htmlspecialchars($translations['admin']) ?></option>
|
||||
</select><br><br>
|
||||
|
||||
<button type="submit" class="button primary-button"><?= htmlspecialchars($translations['create_user_button']) ?></button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<?php
|
||||
require_once APP_ROOT_DIR . '/src/Views/shared/footer.php';
|
||||
?>
|
59
app/src/Views/users/index.php
Normal file
59
app/src/Views/users/index.php
Normal file
@ -0,0 +1,59 @@
|
||||
<?php
|
||||
// Variables attendues du contrôleur: $users, $translations, $currentLang, $darkModeClass, $successMessage, $errorMessage, $authService (pour getUserId)
|
||||
require_once APP_ROOT_DIR . '/src/Views/shared/header.php';
|
||||
?>
|
||||
|
||||
<div class="container">
|
||||
<h1><?= htmlspecialchars($translations['users']) ?></h1>
|
||||
<div class="actions-bar">
|
||||
<a href="/dashboard" class="button secondary-button"><?= htmlspecialchars($translations['back_to_dashboard']) ?></a>
|
||||
<a href="/users/create" class="button primary-button"><?= htmlspecialchars($translations['create_new_user']) ?></a>
|
||||
</div>
|
||||
|
||||
<?php if (isset($successMessage)): ?>
|
||||
<p class="success-message"><?= htmlspecialchars($successMessage); ?></p>
|
||||
<?php endif; ?>
|
||||
<?php if (isset($errorMessage)): ?>
|
||||
<p class="error-message"><?= htmlspecialchars($errorMessage); ?></p>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (empty($users)): ?>
|
||||
<p><?= htmlspecialchars($translations['no_users_yet']) ?></p>
|
||||
<?php else: ?>
|
||||
<div class="table-responsive">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th><?= htmlspecialchars($translations['username']) ?></th>
|
||||
<th><?= htmlspecialchars($translations['user_role']) ?></th>
|
||||
<th><?= htmlspecialchars($translations['created_at']) ?></th>
|
||||
<th><?= htmlspecialchars($translations['actions']) ?></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($users as $user): ?>
|
||||
<tr>
|
||||
<td><?= htmlspecialchars($user['username']) ?></td>
|
||||
<td><?= htmlspecialchars($translations[$user['role']] ?? $user['role']) ?></td>
|
||||
<td><?= htmlspecialchars((new DateTime($user['created_at']))->format('Y-m-d H:i:s')) ?></td>
|
||||
<td>
|
||||
<?php if ($user['id'] !== $authService->getUserId()): // Impossible de supprimer son propre compte ?>
|
||||
<form action="/users/delete" method="post" class="inline-form" onsubmit="return confirm('<?= htmlspecialchars($translations['confirm_delete_user']) ?>');">
|
||||
<input type="hidden" name="user_id" value="<?= htmlspecialchars($user['id']) ?>">
|
||||
<button type="submit" class="button danger-button"><?= htmlspecialchars($translations['delete_user']) ?></button>
|
||||
</form>
|
||||
<?php else: ?>
|
||||
<em><?= htmlspecialchars($translations['self_delete_not_allowed']) ?></em>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<?php
|
||||
require_once APP_ROOT_DIR . '/src/Views/shared/footer.php';
|
||||
?>
|
33
app/src/config/app.php
Normal file
33
app/src/config/app.php
Normal file
@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
// Chemin racine de l'application pour les chemins relatifs
|
||||
define('APP_ROOT_DIR', __DIR__ . '/../..');
|
||||
|
||||
// Configuration de la base de données
|
||||
// Ces valeurs sont aussi définies dans docker-compose.yml pour le conteneur MySQL
|
||||
define('DB_HOST', getenv('DB_HOST') ?: 'mysql');
|
||||
define('DB_NAME', getenv('DB_NAME') ?: 'cert_gestion');
|
||||
define('DB_USER', getenv('DB_USER') ?: 'user');
|
||||
define('DB_PASSWORD', getenv('DB_PASSWORD') ?: 'password_secret'); // À CHANGER ABSOLUMENT EN PRODUCTION !
|
||||
|
||||
// Configuration générale de l'application
|
||||
define('APP_NAME', 'Gestion Certificat');
|
||||
define('APP_ENV', 'development'); // 'production' ou 'development'
|
||||
|
||||
// Chemins des dossiers des certificats et scripts OpenSSL dans le conteneur PHP-FPM
|
||||
define('ROOT_CA_PATH', '/opt/tls/root');
|
||||
define('INTERMEDIATE_CA_PATH_BASE', '/opt/tls/intermediate'); // Base pour les CA intermédiaires par périmètre
|
||||
define('SCRIPTS_PATH', '/opt/scripts');
|
||||
|
||||
// Chemin du fichier de log principal de l'application PHP
|
||||
define('APP_LOG_PATH', '/var/log/app/app.log');
|
||||
|
||||
// Liste des langues supportées par l'application
|
||||
define('SUPPORTED_LANGUAGES', ['fr', 'en', 'de', 'it', 'pt', 'es']);
|
||||
|
||||
// Clé secrète pour la sécurité des sessions (TRÈS IMPORTANT !)
|
||||
// Générez une chaîne longue et aléatoire pour la production
|
||||
define('SESSION_SECRET', 'SuperStrongRandomSessionKeyForProduction_ChangeMe_1234567890ABCDEF');
|
||||
|
||||
// URL de base pour le service OCSP (doit correspondre à la configuration Nginx et des certificats)
|
||||
define('OCSP_URL', 'http://ocsp.cert-gestion.local/'); // À adapter à votre domaine réel
|
Reference in New Issue
Block a user