develop #24
@ -6,6 +6,8 @@ declare(strict_types=1);
|
||||
namespace App\Controllers;
|
||||
|
||||
use App\Services\Ldap\LdapAuthService;
|
||||
use App\Services\Logging\LoggingService;
|
||||
use Throwable;
|
||||
|
||||
/**
|
||||
* Zuständig für alles rund um den Login:
|
||||
@ -26,8 +28,11 @@ class AuthController
|
||||
/** @var LdapAuthService Service, der die eigentliche LDAP/AD-Authentifizierung übernimmt */
|
||||
private LdapAuthService $ldapAuthService;
|
||||
|
||||
/** @var LoggingService Logger für technische Fehler */
|
||||
private LoggingService $logger;
|
||||
|
||||
/**
|
||||
* Übergibt die Konfiguration an den Controller und initialisiert den LDAP-Authentifizierungsservice.
|
||||
* Übergibt die Konfiguration an den Controller und initialisiert Services.
|
||||
*
|
||||
* @param array<string, mixed> $config Vollständige Konfiguration aus config.php
|
||||
*/
|
||||
@ -37,8 +42,10 @@ class AuthController
|
||||
$this->config = $config;
|
||||
|
||||
// LdapAuthService mit dem Teilbereich "ldap" aus der Konfiguration initialisieren.
|
||||
// Wenn 'ldap' nicht gesetzt ist, wird ein leeres Array übergeben (Fail fast erfolgt dann im Service).
|
||||
$this->ldapAuthService = new LdapAuthService($config['ldap'] ?? []);
|
||||
|
||||
// LoggingService mit dem Teilbereich "logging" aus der Konfiguration initialisieren.
|
||||
$this->logger = new LoggingService($config['logging'] ?? []);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -54,14 +61,14 @@ class AuthController
|
||||
|
||||
// Wichtig: Die View erwartet aktuell die Variable $error.
|
||||
return [
|
||||
'view' => $viewPath,
|
||||
'data' => [
|
||||
'error' => $errorMessage,
|
||||
'loginPage' => true,
|
||||
],
|
||||
'pageTitle' => 'Login',
|
||||
'view' => $viewPath,
|
||||
'data' => [
|
||||
'error' => $errorMessage,
|
||||
'loginPage' => true,
|
||||
],
|
||||
'pageTitle' => 'Login',
|
||||
// Beim Login ist typischerweise kein Menüpunkt aktiv.
|
||||
'activeMenu' => null,
|
||||
'activeMenu' => null,
|
||||
];
|
||||
}
|
||||
|
||||
@ -85,11 +92,26 @@ class AuthController
|
||||
// true = Authentifizierung erfolgreich
|
||||
// false = Anmeldedaten fachlich ungültig (Benutzer/Passwort falsch)
|
||||
$authenticated = $this->ldapAuthService->authenticate($username, $password);
|
||||
} catch (\Throwable $exception) {
|
||||
// Technischer Fehler (z. B. LDAP-Server nicht erreichbar, falsche Konfiguration).
|
||||
// In diesem Fall wird eine technische Fehlermeldung im Login-Formular angezeigt.
|
||||
} catch (Throwable $exception) {
|
||||
// HIER ist vorher dein Fehler entstanden:
|
||||
// - showLoginForm() wurde nur aufgerufen, das Ergebnis aber ignoriert
|
||||
// - danach kam ein "return;" ohne Rückgabewert → Rückgabetyp array wurde verletzt
|
||||
|
||||
// Technischen Fehler ausführlich ins Log schreiben
|
||||
$this->logger->logException(
|
||||
'Technischer Fehler bei der Anmeldung.',
|
||||
$exception,
|
||||
[
|
||||
'route' => 'login.submit',
|
||||
'username' => $username,
|
||||
'remote_addr'=> $_SERVER['REMOTE_ADDR'] ?? null,
|
||||
]
|
||||
);
|
||||
|
||||
// Für den Benutzer nur eine allgemeine, aber verständliche Meldung anzeigen
|
||||
return $this->showLoginForm(
|
||||
'Technischer Fehler bei der Anmeldung: ' . $exception->getMessage()
|
||||
'Technischer Fehler bei der Anmeldung. Bitte versuchen Sie es später erneut '
|
||||
. 'oder wenden Sie sich an den Administrator.'
|
||||
);
|
||||
}
|
||||
|
||||
@ -107,15 +129,15 @@ class AuthController
|
||||
// Benutzerinformationen in der Session hinterlegen.
|
||||
// Diese Daten werden später von requireLogin() bzw. im Layout ausgewertet.
|
||||
$_SESSION[$sessionKey] = [
|
||||
'username' => $username,
|
||||
'login_at' => date('c'), // ISO-8601 Datum/Zeit der Anmeldung
|
||||
'username' => $username,
|
||||
'login_at' => date('c'), // ISO-8601 Datum/Zeit der Anmeldung
|
||||
];
|
||||
|
||||
// Nach erfolgreicher Anmeldung zum Dashboard weiterleiten.
|
||||
// Kein direkter header()-Aufruf, sondern ein Redirect-Result
|
||||
// für die zentrale Steuerung in index.php.
|
||||
return [
|
||||
'redirect' => 'index.php?route=dashboard',
|
||||
'redirect' => 'index.php?route=dashboard',
|
||||
];
|
||||
}
|
||||
|
||||
@ -135,7 +157,7 @@ class AuthController
|
||||
|
||||
// Redirect-Result zur Login-Seite.
|
||||
return [
|
||||
'redirect' => 'index.php?route=login',
|
||||
'redirect' => 'index.php?route=login',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@ -6,6 +6,8 @@ declare(strict_types=1);
|
||||
namespace App\Controllers;
|
||||
|
||||
use App\Services\Ldap\LdapDirectoryService;
|
||||
use App\Services\Logging\LoggingService;
|
||||
use Throwable;
|
||||
|
||||
/**
|
||||
* Controller für die Benutzer- und Gruppenanzeige.
|
||||
@ -30,6 +32,9 @@ class UserManagementController
|
||||
/** @var LdapDirectoryService Service für das Lesen von Benutzern und Gruppen aus dem LDAP/AD */
|
||||
private LdapDirectoryService $directoryService;
|
||||
|
||||
/** @var LoggingService Logger für technische Fehler */
|
||||
private LoggingService $logger;
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $config Vollständige Konfiguration aus config.php
|
||||
*/
|
||||
@ -43,6 +48,9 @@ class UserManagementController
|
||||
|
||||
// Directory-Service initialisieren, der die eigentliche LDAP-Arbeit übernimmt.
|
||||
$this->directoryService = new LdapDirectoryService($ldapConfig);
|
||||
|
||||
// Logging-Service initialisieren.
|
||||
$this->logger = new LoggingService($config['logging'] ?? []);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -62,26 +70,35 @@ class UserManagementController
|
||||
// Benutzer- und Gruppenlisten aus dem AD laden.
|
||||
$users = $this->directoryService->getUsers();
|
||||
$groups = $this->directoryService->getGroups();
|
||||
} catch (\Throwable $exception) {
|
||||
// Sämtliche technischen Fehler (z. B. Verbindungs- oder Konfigurationsprobleme)
|
||||
// werden hier in eine für den Benutzer lesbare Fehlermeldung übersetzt.
|
||||
$error = 'Fehler beim Laden von Benutzern/Gruppen: ' . $exception->getMessage();
|
||||
} catch (Throwable $exception) {
|
||||
// Technische Details ins Log, für den Benutzer eine allgemeine Meldung.
|
||||
$this->logger->logException(
|
||||
'Fehler beim Laden von Benutzern/Gruppen.',
|
||||
$exception,
|
||||
[
|
||||
'route' => 'users',
|
||||
'remote_addr' => $_SERVER['REMOTE_ADDR'] ?? null,
|
||||
]
|
||||
);
|
||||
|
||||
$error = 'Fehler beim Laden von Benutzern/Gruppen. '
|
||||
. 'Bitte versuchen Sie es später erneut oder wenden Sie sich an den Administrator.';
|
||||
}
|
||||
|
||||
// Pfad zur eigentlichen View-Datei bestimmen.
|
||||
$viewPath = __DIR__ . '/../../public/views/users.php';
|
||||
|
||||
return [
|
||||
'view' => $viewPath,
|
||||
'data' => [
|
||||
// Die View erwartet aktuell $users, $groups, $error.
|
||||
'users' => $users,
|
||||
'groups' => $groups,
|
||||
'error' => $error,
|
||||
'loginPage' => false,
|
||||
],
|
||||
'pageTitle' => 'Benutzer & Gruppen',
|
||||
'activeMenu' => 'users',
|
||||
'view' => $viewPath,
|
||||
'data' => [
|
||||
// Die View erwartet aktuell $users, $groups, $error.
|
||||
'users' => $users,
|
||||
'groups' => $groups,
|
||||
'error' => $error,
|
||||
'loginPage' => false,
|
||||
],
|
||||
'pageTitle' => 'Benutzer & Gruppen',
|
||||
'activeMenu' => 'users',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
134
app/Services/Logging/LoggingService.php
Normal file
134
app/Services/Logging/LoggingService.php
Normal file
@ -0,0 +1,134 @@
|
||||
<?php
|
||||
|
||||
// Strenge Typprüfung für Parameter- und Rückgabetypen aktivieren.
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Services\Logging;
|
||||
|
||||
use DateTimeImmutable;
|
||||
use Throwable;
|
||||
|
||||
/**
|
||||
* Einfacher File-Logger für die AdminTool-Anwendung.
|
||||
*
|
||||
* Ziele:
|
||||
* - Technische Details werden in eine Log-Datei unter public/logs/ geschrieben.
|
||||
* - In der Weboberfläche erscheinen nur verständliche, fachliche Fehlermeldungen.
|
||||
*/
|
||||
class LoggingService
|
||||
{
|
||||
/** @var string Vollständiger Pfad zum Log-Verzeichnis */
|
||||
private string $logDir;
|
||||
|
||||
/** @var string Dateiname der Log-Datei */
|
||||
private string $logFile;
|
||||
|
||||
/** @var int Minimale Log-Stufe, ab der geschrieben wird. */
|
||||
private int $minLevel;
|
||||
|
||||
/**
|
||||
* Zuordnung der Log-Level zu numerischen Werten zur Filterung.
|
||||
*
|
||||
* @var array<string, int>
|
||||
*/
|
||||
private const array LEVEL_MAP = [
|
||||
'debug' => 100,
|
||||
'info' => 200,
|
||||
'warning' => 300,
|
||||
'error' => 400,
|
||||
];
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $config Teilkonfiguration "logging" aus config.php
|
||||
*/
|
||||
public function __construct(array $config)
|
||||
{
|
||||
// Standard: public/logs relativ zum Projektroot
|
||||
$baseDir = $config['log_dir'] ?? (__DIR__ . '/../../../public/logs');
|
||||
$fileName = $config['log_file'] ?? 'app.log';
|
||||
$level = strtolower((string)($config['min_level'] ?? 'info'));
|
||||
|
||||
$this->logDir = rtrim($baseDir, DIRECTORY_SEPARATOR);
|
||||
$this->logFile = $fileName;
|
||||
$this->minLevel = self::LEVEL_MAP[$level] ?? self::LEVEL_MAP['info'];
|
||||
|
||||
$this->ensureLogDirectoryExists();
|
||||
}
|
||||
|
||||
/**
|
||||
* Stellt sicher, dass das Log-Verzeichnis existiert.
|
||||
*/
|
||||
private function ensureLogDirectoryExists(): void
|
||||
{
|
||||
if (is_dir($this->logDir) === true) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (@mkdir($this->logDir, 0775, true) === false && is_dir($this->logDir) === false) {
|
||||
// Wenn das Anlegen fehlschlägt, wenigstens einen Eintrag im PHP-Error-Log hinterlassen.
|
||||
error_log(sprintf('LoggingService: Konnte Log-Verzeichnis "%s" nicht anlegen.', $this->logDir));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Allgemeiner Log-Eintrag.
|
||||
*
|
||||
* @param string $level Log-Level (debug|info|warning|error)
|
||||
* @param string $message Nachrichtentext
|
||||
* @param array<string, mixed> $context Zusätzliche Kontextinformationen
|
||||
*/
|
||||
public function log(string $level, string $message, array $context = []): void
|
||||
{
|
||||
$level = strtolower($level);
|
||||
$numericLevel = self::LEVEL_MAP[$level] ?? self::LEVEL_MAP['error'];
|
||||
|
||||
// Alles unterhalb der minimalen Stufe ignorieren.
|
||||
if ($numericLevel < $this->minLevel) {
|
||||
return;
|
||||
}
|
||||
|
||||
$timestamp = new DateTimeImmutable()->format('Y-m-d H:i:s');
|
||||
|
||||
$contextJson = $context === []
|
||||
? '{}'
|
||||
: (string)json_encode($context, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
|
||||
|
||||
$line = sprintf(
|
||||
"[%s] %-7s %s %s%s",
|
||||
$timestamp,
|
||||
strtoupper($level),
|
||||
$message,
|
||||
$contextJson,
|
||||
PHP_EOL
|
||||
);
|
||||
|
||||
$filePath = $this->logDir . DIRECTORY_SEPARATOR . $this->logFile;
|
||||
|
||||
if (@file_put_contents($filePath, $line, FILE_APPEND | LOCK_EX) === false) {
|
||||
// Fallback, damit Fehler beim Logging selbst nicht die App zerschießen.
|
||||
error_log(sprintf('LoggingService: Konnte in Log-Datei "%s" nicht schreiben.', $filePath));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Komfortmethode, um Exceptions strukturiert zu loggen.
|
||||
*
|
||||
* @param string $message Kurzer Kontexttext zur Exception
|
||||
* @param Throwable $exception Die geworfene Exception
|
||||
* @param array<string, mixed> $context Zusätzlicher Kontext (Route, Benutzername, Remote-IP, ...)
|
||||
*/
|
||||
public function logException(string $message, Throwable $exception, array $context = []): void
|
||||
{
|
||||
$exceptionContext = [
|
||||
'exception_class' => get_class($exception),
|
||||
'exception_message' => $exception->getMessage(),
|
||||
'file' => $exception->getFile(),
|
||||
'line' => $exception->getLine(),
|
||||
'trace' => $exception->getTraceAsString(),
|
||||
];
|
||||
|
||||
$mergedContext = array_merge($context, $exceptionContext);
|
||||
|
||||
$this->log('error', $message, $mergedContext);
|
||||
}
|
||||
}
|
||||
@ -49,4 +49,14 @@ return [
|
||||
'storage_used' => '1.3.6.1.2.1.25.2.3.1.6',
|
||||
],
|
||||
],
|
||||
|
||||
// Logging-Konfiguration
|
||||
'logging' => [
|
||||
// Standard: public/logs relativ zum Projekt-Root
|
||||
'log_dir' => __DIR__ . '/../public/logs',
|
||||
// Name der Logdatei
|
||||
'log_file' => 'app.log',
|
||||
// Minimale Stufe: debug, info, warning, error
|
||||
'min_level' => 'info',
|
||||
],
|
||||
];
|
||||
|
||||
@ -23,6 +23,10 @@ declare(strict_types=1);
|
||||
// Eine neue Session wird gestartet und die entsprechende Variable ($_SESSION) angelegt oder eine bestehende wird fortgesetzt.
|
||||
session_start();
|
||||
|
||||
// PHP-Fehler nur ins Log schreiben, nicht direkt im Browser anzeigen
|
||||
error_reporting(E_ALL);
|
||||
ini_set('display_errors', '0');
|
||||
|
||||
/*
|
||||
* Registriert eine Autoload-Funktion für Klassen mit dem Namespace-Präfix "App\".
|
||||
* Statt jede Klasse manuell über "require pfad_zur_klasse.php" einzubinden,
|
||||
@ -68,6 +72,61 @@ $config = require $configPath;
|
||||
use App\Controllers\AuthController;
|
||||
use App\Controllers\DashboardController;
|
||||
use App\Controllers\UserManagementController;
|
||||
use App\Services\Logging\LoggingService;
|
||||
|
||||
// Globalen Logger initialisieren, damit auch Fehler außerhalb der Controller
|
||||
// (z. B. in index.php selbst) sauber protokolliert werden.
|
||||
$globalLogger = new LoggingService($config['logging'] ?? []);
|
||||
|
||||
/**
|
||||
* Globale Fehlerbehandlung:
|
||||
* - PHP-Fehler (Warnings, Notices, ...) werden in den Logger geschrieben.
|
||||
* - Unbehandelte Exceptions werden ebenfalls geloggt und führen zu einer generischen 500er-Meldung.
|
||||
*/
|
||||
set_error_handler(
|
||||
static function (
|
||||
int $severity,
|
||||
string $message,
|
||||
string $file = '',
|
||||
int $line = 0
|
||||
) use ($globalLogger): bool {
|
||||
// Fehler nur loggen, wenn sie durch error_reporting() nicht unterdrückt sind.
|
||||
if ((error_reporting() & $severity) === 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$globalLogger->log(
|
||||
'error',
|
||||
'PHP-Fehler: ' . $message,
|
||||
[
|
||||
'severity' => $severity,
|
||||
'file' => $file,
|
||||
'line' => $line,
|
||||
]
|
||||
);
|
||||
|
||||
// false zurückgeben = PHP darf seinen Standard-Handler zusätzlich verwenden
|
||||
// (der Browser sieht wegen display_errors=0 trotzdem nichts).
|
||||
return false;
|
||||
}
|
||||
);
|
||||
|
||||
set_exception_handler(
|
||||
static function (Throwable $exception) use ($globalLogger): void {
|
||||
$globalLogger->logException(
|
||||
'Unbehandelte Exception in der Anwendung.',
|
||||
$exception,
|
||||
[
|
||||
'request_uri' => $_SERVER['REQUEST_URI'] ?? null,
|
||||
'route' => $_GET['route'] ?? null,
|
||||
]
|
||||
);
|
||||
|
||||
http_response_code(500);
|
||||
echo 'Es ist ein unerwarteter Fehler aufgetreten. '
|
||||
. 'Bitte versuchen Sie es später erneut oder wenden Sie sich an den Administrator.';
|
||||
}
|
||||
);
|
||||
|
||||
/**
|
||||
* Hilfsfunktion: Prüft, ob ein Benutzer eingeloggt ist.
|
||||
@ -100,7 +159,7 @@ function handleResult(?array $result): void
|
||||
}
|
||||
|
||||
if (isset($result['redirect']) === true) {
|
||||
header('Location: ' . (string)$result['redirect']);
|
||||
header('Location: ' . $result['redirect']);
|
||||
exit;
|
||||
}
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user