From 91d2821034411321e07eb33e2095f6688f63bc07 Mon Sep 17 00:00:00 2001 From: blaerf Date: Fri, 5 Dec 2025 08:34:37 +0100 Subject: [PATCH 1/4] =?UTF-8?q?Logging-Service=20hinzugef=C3=BCgt?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/Controllers/AuthController.php | 56 +++++--- app/Controllers/UserManagementController.php | 45 +++++-- app/Services/Logging/LoggingService.php | 134 +++++++++++++++++++ config/config.php | 10 ++ public/index.php | 61 ++++++++- 5 files changed, 274 insertions(+), 32 deletions(-) create mode 100644 app/Services/Logging/LoggingService.php diff --git a/app/Controllers/AuthController.php b/app/Controllers/AuthController.php index 02e9905..d181cb9 100644 --- a/app/Controllers/AuthController.php +++ b/app/Controllers/AuthController.php @@ -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 $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', ]; } } diff --git a/app/Controllers/UserManagementController.php b/app/Controllers/UserManagementController.php index 02d197a..12a4efa 100644 --- a/app/Controllers/UserManagementController.php +++ b/app/Controllers/UserManagementController.php @@ -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 $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', ]; } } diff --git a/app/Services/Logging/LoggingService.php b/app/Services/Logging/LoggingService.php new file mode 100644 index 0000000..9b9b556 --- /dev/null +++ b/app/Services/Logging/LoggingService.php @@ -0,0 +1,134 @@ + + */ + private const array LEVEL_MAP = [ + 'debug' => 100, + 'info' => 200, + 'warning' => 300, + 'error' => 400, + ]; + + /** + * @param array $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 $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 $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); + } +} diff --git a/config/config.php b/config/config.php index a84872e..aebf7fe 100644 --- a/config/config.php +++ b/config/config.php @@ -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', + ], ]; diff --git a/public/index.php b/public/index.php index d7a3fc1..db133fd 100644 --- a/public/index.php +++ b/public/index.php @@ -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; } From 21c63b9d6ce2d99d6e2e108434f9841255f75813 Mon Sep 17 00:00:00 2001 From: blaerf Date: Fri, 5 Dec 2025 08:41:31 +0100 Subject: [PATCH 2/4] Kommentare angepasst --- app/Controllers/AuthController.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/Controllers/AuthController.php b/app/Controllers/AuthController.php index d181cb9..36c5981 100644 --- a/app/Controllers/AuthController.php +++ b/app/Controllers/AuthController.php @@ -32,7 +32,7 @@ class AuthController private LoggingService $logger; /** - * Übergibt die Konfiguration an den Controller und initialisiert Services. + * Übergibt die Konfiguration an den Controller und initialisiert den LDAP-Authentifizierungsservice. * * @param array $config Vollständige Konfiguration aus config.php */ @@ -42,6 +42,7 @@ 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. From 300719244e8fe24a07946ac6f3840bbd29bef1ab Mon Sep 17 00:00:00 2001 From: blaerf Date: Fri, 5 Dec 2025 09:03:01 +0100 Subject: [PATCH 3/4] LDAP Connection URI angepasst --- app/Services/Ldap/LdapConnectionHelper.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/Services/Ldap/LdapConnectionHelper.php b/app/Services/Ldap/LdapConnectionHelper.php index abd5b99..ac72aa5 100644 --- a/app/Services/Ldap/LdapConnectionHelper.php +++ b/app/Services/Ldap/LdapConnectionHelper.php @@ -58,7 +58,8 @@ class LdapConnectionHelper // Verbindung zum LDAP/AD-Server herstellen. // ldap_connect liefert entweder ein Verbindungs-Handle (Resource) oder false. - $connection = ldap_connect($server, $port); + $uri = $server . ':' . $port; + $connection = ldap_connect($uri); // Wenn keine Verbindung aufgebaut werden konnte, Exception werfen. if ($connection === false) { From 50d53b418377c6fd1ea1415da99cd8f4223162ff Mon Sep 17 00:00:00 2001 From: blaerf Date: Fri, 5 Dec 2025 09:08:14 +0100 Subject: [PATCH 4/4] LDAP Connection URI angepasst --- app/Services/Ldap/LdapConnectionHelper.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Services/Ldap/LdapConnectionHelper.php b/app/Services/Ldap/LdapConnectionHelper.php index ac72aa5..87788f1 100644 --- a/app/Services/Ldap/LdapConnectionHelper.php +++ b/app/Services/Ldap/LdapConnectionHelper.php @@ -58,7 +58,7 @@ class LdapConnectionHelper // Verbindung zum LDAP/AD-Server herstellen. // ldap_connect liefert entweder ein Verbindungs-Handle (Resource) oder false. - $uri = $server . ':' . $port; + $uri = "ldap://".$server . ':' . $port; $connection = ldap_connect($uri); // Wenn keine Verbindung aufgebaut werden konnte, Exception werfen.