Code refactoring und Kommentare hinzugefügt

This commit is contained in:
blaerf 2025-11-16 18:43:48 +01:00
parent 7bde20e968
commit 61a517ada7
6 changed files with 508 additions and 31 deletions

View File

@ -0,0 +1,167 @@
<?php
// Strenge Typprüfung für Parameter- und Rückgabetypen aktivieren.
declare(strict_types=1);
namespace App\Controllers;
use App\Services\Ldap\LdapAuthService;
/**
* Zuständig für alles rund um den Login:
* - Login-Formular anzeigen
* - Login-Daten verarbeiten (Authentifizierung gegen LDAP/AD)
* - Logout durchführen
*/
class AuthController
{
/** @var array<string, mixed> Konfigurationswerte der Anwendung (aus config.php) */
private array $config;
/** @var LdapAuthService Service, der die eigentliche LDAP/AD-Authentifizierung übernimmt */
private LdapAuthService $ldapAuthService;
/**
* Übergibt die Konfiguration an den Controller und initialisiert den LDAP-Authentifizierungsservice.
*
* @param array<string, mixed> $config Vollständige Konfiguration aus config.php
*/
public function __construct(array $config)
{
// Komplette Config in der Instanz speichern (z. B. für Zugriff auf security- oder ldap-Settings)
$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'] ?? []);
}
/**
* Zeigt das Login-Formular an.
* Optional kann eine Fehlermeldung übergeben werden, die in der View dargestellt wird.
*/
public function showLoginForm(?string $errorMessage = null): void
{
// Pfad zur Login-View (Template-Datei) ermitteln.
$viewPath = __DIR__ . '/../../public/views/login.php';
// Variable für die View vorbereiten (wird in der eingebundenen Datei verwendet).
$error = $errorMessage;
// Falls die View-Datei (noch) nicht existiert, einen Fallback-HTML-Output verwenden.
if (file_exists($viewPath) === false) {
$this->renderInlineLogin($error);
return;
}
// View-Datei einbinden. Variablen aus dieser Methode (z. B. $error) sind dort verfügbar.
require $viewPath;
}
/**
* Verarbeitet das Login-Formular:
* - Liest Benutzername und Passwort aus $_POST
* - Ruft den LdapAuthService zur Authentifizierung auf
* - Setzt bei Erfolg die Session und leitet zum Dashboard weiter
* - Zeigt bei Fehlern erneut das Login-Formular mit Fehlermeldung an
*/
public function processLogin(): void
{
// Formulardaten aus dem POST-Request lesen.
$username = trim($_POST['username'] ?? '');
$password = (string)($_POST['password'] ?? '');
try {
// Versuch, den Benutzer per LDAP/AD zu authentifizieren.
// 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.
$this->showLoginForm('Technischer Fehler bei der Anmeldung: ' . $exception->getMessage());
return;
}
// Fachlich fehlgeschlagene Anmeldung (z. B. falsches Passwort).
if ($authenticated === false) {
$this->showLoginForm('Benutzername oder Passwort ist ungültig.');
return;
}
// Ab hier ist die Anmeldung erfolgreich.
// Session-Key für den eingeloggten Benutzer aus der Konfiguration lesen.
// Wenn nicht gesetzt, wird "admin_user" als Standard verwendet.
$sessionKey = $this->config['security']['session_key_user'] ?? 'admin_user';
// Benutzerinformationen in der Session hinterlegen.
// Diese Daten werden später von requireLogin() ausgewertet.
$_SESSION[$sessionKey] = [
'username' => $username,
'login_at' => date('c'), // ISO-8601 Datum/Zeit der Anmeldung
];
// Nach erfolgreicher Anmeldung zum Dashboard weiterleiten.
header('Location: index.php?route=dashboard');
exit;
}
/**
* Meldet den aktuell eingeloggten Benutzer ab, indem der entsprechende Session-Eintrag entfernt wird,
* und leitet anschließend zurück auf die Login-Seite.
*/
public function logout(): void
{
// Session-Key für den eingeloggten Benutzer aus der Konfiguration lesen.
$sessionKey = $this->config['security']['session_key_user'] ?? 'admin_user';
// Eintrag aus der Session entfernen → Benutzer gilt als ausgeloggt.
unset($_SESSION[$sessionKey]);
// Zur Login-Seite zurückleiten.
header('Location: index.php?route=login');
exit;
}
/**
* Fallback-Ausgabe für das Login-Formular, falls noch keine separate View-Datei existiert.
* Gibt direkt HTML aus (inline-Template).
*/
private function renderInlineLogin(?string $errorMessage): void
{
?>
<!doctype html>
<html lang="de">
<head>
<meta charset="utf-8">
<title>AD Admin Tool Login</title>
</head>
<body>
<h1>AD Admin Tool Login</h1>
<?php if ($errorMessage !== null): ?>
<!-- Fehlermeldung ausgeben, HTML-Ausgabe wird dabei sicher maskiert -->
<p style="color: red;"><?php echo htmlspecialchars($errorMessage, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8'); ?></p>
<?php endif; ?>
<!-- Einfaches Login-Formular, das per POST an die Route "login.submit" gesendet wird -->
<form action="index.php?route=login.submit" method="post">
<div>
<label for="username">Benutzername:</label>
<input type="text" id="username" name="username" required>
</div>
<div>
<label for="password">Passwort:</label>
<input type="password" id="password" name="password" required>
</div>
<button type="submit">Anmelden</button>
</form>
</body>
</html>
<?php
}
}

View File

@ -0,0 +1,98 @@
<?php
// Strenge Typprüfung für Parameter- und Rückgabetypen aktivieren.
declare(strict_types=1);
namespace App\Services\Ldap;
use RuntimeException;
/**
* Service zur Authentifizierung von Benutzern gegen ein Active Directory per LDAP/LDAPS.
* Bekommt Benutzername und Passwort und liefert:
* - true -> Anmeldedaten sind gültig
* - false -> Anmeldedaten sind fachlich ungültig (z. B. falsches Passwort)
* Technische Fehler (z. B. falsche Konfiguration, keine Verbindung) werden als Exception geworfen.
*/
class LdapAuthService
{
/** @var array<string, mixed> LDAP-spezifische Konfiguration (Server, Port, Domain-Suffix, Timeout, etc.) */
private array $config;
/**
* Erwartet den Teilbereich "ldap" aus der allgemeinen Konfiguration (config.php).
*
* @param array<string, mixed> $ldapConfig Konfiguration für die LDAP-Verbindung
*/
public function __construct(array $ldapConfig)
{
// LDAP-Konfiguration in der Instanz speichern.
$this->config = $ldapConfig;
}
/**
* Führt die eigentliche LDAP/AD-Authentifizierung durch.
*
* @param string $username Benutzername (ohne Domain-Suffix, z. B. "administrator")
* @param string $password Passwort im Klartext (wird direkt an ldap_bind übergeben)
*
* @return bool true bei erfolgreicher Anmeldung, false bei fachlich ungültigen Anmeldedaten
*
* @throws RuntimeException bei technischen Problemen (z. B. fehlende Konfiguration, Verbindungsfehler)
*/
public function authenticate(string $username, string $password): bool
{
// Wenn Benutzername oder Passwort leer sind, gar nicht erst versuchen zu binden.
// Das ist ein fachlich ungültiger Login und wird mit false behandelt.
if ($username === '' || $password === '') {
return false;
}
// Benötigte Werte aus der LDAP-Konfiguration auslesen.
// Falls einzelne Werte fehlen, werden sinnvolle Standardwerte gesetzt.
$server = (string)($this->config['server'] ?? '');
$port = (int)($this->config['port'] ?? 636);
$domainSuffix = (string)($this->config['domain_suffix'] ?? '');
$timeout = (int)($this->config['timeout'] ?? 5);
// Ohne Server-Adresse oder Domain-Suffix ist eine sinnvolle Anmeldung nicht möglich.
// Das ist ein Konfigurationsfehler und wird als technischer Fehler behandelt.
if ($server === '' || $domainSuffix === '') {
throw new RuntimeException('LDAP-Konfiguration ist unvollständig.');
}
// Verbindung zum LDAP/AD-Server herstellen.
// Rückgabewert ist eine Ressource (Verbindungshandle) oder false bei Fehlern.
$connection = ldap_connect($server, $port);
if ($connection === false) {
throw new RuntimeException('LDAP-Verbindung konnte nicht aufgebaut werden.');
}
// LDAP-Optionen setzen:
// - Protokollversion 3 (Standard in aktuellen Umgebungen)
// - Netzwerk-Timeout, damit die Anfrage nicht unendlich hängt
ldap_set_option($connection, LDAP_OPT_PROTOCOL_VERSION, 3);
ldap_set_option($connection, LDAP_OPT_NETWORK_TIMEOUT, $timeout);
// UPN (User Principal Name) aus Benutzername und Domain-Suffix bilden.
// Beispiel: "administrator" + "@ITFA-PROJ-DOM.local" -> "administrator@ITFA-PROJ-DOM.local"
$bindRdn = $username . $domainSuffix;
// Eigentliche Authentifizierung:
// ldap_bind versucht, sich mit den angegebenen Anmeldedaten am AD/LDAP anzumelden.
// Rückgabewerte:
// - true -> Anmeldedaten sind gültig
// - false -> Anmeldedaten sind ungültig (z. B. falsches Passwort)
//
// Das @-Zeichen unterdrückt PHP-Warnings, damit Fehlermeldungen nicht unformatiert im HTML landen.
$bindResult = @ldap_bind($connection, $bindRdn, $password);
// Verbindung zum LDAP-Server wieder schließen.
ldap_unbind($connection);
// Bei erfolgreichem Bind (true) -> Authentifizierung erfolgreich.
// Bei false -> fachlich fehlgeschlagene Anmeldung (z. B. falsche Credentials).
return $bindResult === true;
}
}

27
config/config.php Normal file
View File

@ -0,0 +1,27 @@
<?php
/** Typsicherheit aktivieren
* var = "1" != var = 1
*/
declare(strict_types=1);
return [
'ldap' => [
// LDAP-URL des Domain Controllers
'server' => 'ITFA-PROJ-SRV.ITFA-PROJ-DOM.local',
'port' => 389,
// wird an den Benutzernamen angehängt (z.B. "admin" + "@ITFA-PROJ-DOM.local")
'domain_suffix' => '@ITFA-PROJ-DOM.local',
// Base DN der Domäne
'base_dn' => 'DC=ITFA-PROJ-DOM,DC=local',
// Optional: Timeout in Sekunden
'timeout' => 5,
],
'security' => [
// Session-Key unter dem der eingeloggte Admin gespeichert wird
'session_key_user' => 'admin_user',
],
];

100
public/index.php Normal file
View File

@ -0,0 +1,100 @@
<?php
// Strenge Typprüfung für Parameter- und Rückgabetypen aktivieren.
declare(strict_types=1);
// Eine neue Session wird gestartet und die entsprechende Variable ($_SESSION) angelegt oder eine bestehende wird fortgesetzt.
session_start();
/*
* Registriert eine Autoload-Funktion für Klassen mit dem Namespace-Präfix "App\".
* Statt jede Klasse manuell über "require pfad_zur_klasse.php" einzubinden,
* versucht PHP nun automatisch, die passende Datei zu laden, sobald eine Klasse
* im Namespace "App\..." verwendet wird.
*/
spl_autoload_register(
// Anonyme Funktion, die den Klassennamen prüft und den Dateipfad zur Klasse ermittelt.
static function (string $class): void {
$prefix = 'App\\';
$baseDir = __DIR__ . '/../app/';
$len = strlen($prefix);
if (strncmp($prefix, $class, $len) !== 0) {
return;
}
$relativeClass = substr($class, $len);
$file = $baseDir . str_replace('\\', DIRECTORY_SEPARATOR, $relativeClass) . '.php';
if (file_exists($file) === true) {
require $file;
}
}
);
// Pfad zur Konfigurationsdatei prüfen und Konfiguration laden
$configPath = __DIR__ . '/../config/config.php';
if (file_exists($configPath) === false) {
// Fail fast: ohne Konfiguration macht die App keinen Sinn
http_response_code(500);
echo 'Konfigurationsdatei config/config.php wurde nicht gefunden. '
. 'Bitte config.example.php kopieren und anpassen.';
exit;
}
/** @var array<string, mixed> $config */
$config = require $configPath;
use App\Controllers\AuthController;
// Hilfsfunktion für geschützte Routen
function requireLogin(array $config): void
{
// session_key_user aus dem Config-Array lesen. Wenn nicht gesetzt oder null, Standard "admin_user" verwenden.
$sessionKey = $config['security']['session_key_user'] ?? 'admin_user';
// Prüfen, ob in $_SESSION unter diesem Key ein eingeloggter Benutzer hinterlegt ist.
// Falls nicht, zurück zur Login-Seite umleiten.
if (isset($_SESSION[$sessionKey]) === false) {
header('Location: index.php?route=login');
exit;
}
}
// Route aus dem GET-Parameter lesen. Wenn nicht gesetzt, Standardroute "login" verwenden.
$route = $_GET['route'] ?? 'login';
// Neue Instanz der Klasse AuthController erstellen (wird bei Bedarf über den Autoloader geladen).
$authController = new AuthController($config);
// Anhand des Routing-Ziels (route) entscheiden, welcher Code ausgeführt wird.
switch ($route) {
case 'login':
$authController->showLoginForm();
break;
case 'login.submit':
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
header('Location: index.php?route=login');
exit;
}
$authController->processLogin();
break;
case 'logout':
$authController->logout();
break;
case 'dashboard':
requireLogin($config);
// Später: DashboardController aufrufen
echo '<h1>Dashboard (Platzhalter)</h1>';
echo '<p>Hier kommt später dein SNMP/Server-Status hin.</p>';
echo '<p><a href="index.php?route=logout">Logout</a></p>';
break;
default:
http_response_code(404);
echo 'Route nicht gefunden.';
break;
}

View File

@ -1,33 +1,9 @@
<?php
session_start();
require_once __DIR__ . '/../config/ldap.php';
declare(strict_types=1);
$error = "";
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
var_dump($_POST);
$username = isset($_POST['username']) ? trim($_POST['username']) : "";
$password = isset($_POST['password']) ? $_POST['password'] : "";
$ldapConn = ldap_authenticate($username, $password);
if ($ldapConn === false) {
$error = "Anmeldung fehlgeschlagen. Benutzername oder Passwort falsch.";
} else {
// Optional: User-Infos aus AD lesen, z. B. displayName
// $result = ldap_search($ldapConn, $LDAP_BASE_DN, "(sAMAccountName=$username)", ["displayName"]);
// $entries = ldap_get_entries($ldapConn, $result);
$_SESSION['logged_in'] = true;
$_SESSION['username'] = $username;
// $_SESSION['displayName'] = $entries[0]['displayname'][0] ?? $username;
ldap_unbind($ldapConn);
header("Location: dashboard.php");
exit();
}
}
/**
* @var string|null $error
*/
?>
<!DOCTYPE html>
@ -73,12 +49,12 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
<div class="text-center">
<h1 class="h4 text-gray-900 mb-4">Welcome Back!</h1>
</div>
<?php if ($error !== ""): ?>
<?php if ($error !== null): ?>
<div class="alert alert-danger" role="alert">
<?php echo htmlspecialchars($error); ?>
<?php echo htmlspecialchars($error, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8'); ?>
</div>
<?php endif; ?>
<form class="user" method="post" action="<?php echo $_SERVER['PHP_SELF']; ?>">
<form class="user" method="post" action="index.php?route=login.submit">
<div class="form-group">
<input type="text" name="username" class="form-control form-control-user"
id="username" aria-describedby="usernameHelp"

109
public/views/login.php Normal file
View File

@ -0,0 +1,109 @@
<?php
declare(strict_types=1);
/**
* @var string|null $error
*/
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="description" content="">
<meta name="author" content="">
<title>SB Admin 2 - Login</title>
<!-- Custom fonts for this template-->
<link href="vendor/fontawesome-free/css/all.min.css" rel="stylesheet" type="text/css">
<link
href="https://fonts.googleapis.com/css?family=Nunito:200,200i,300,300i,400,400i,600,600i,700,700i,800,800i,900,900i"
rel="stylesheet">
<!-- Custom styles for this template-->
<link href="css/sb-admin-2.min.css" rel="stylesheet">
</head>
<body class="bg-gradient-primary">
<div class="container">
<!-- Outer Row -->
<div class="row justify-content-center">
<div class="col-xl-10 col-lg-12 col-md-9">
<div class="card o-hidden border-0 shadow-lg my-5">
<div class="card-body p-0">
<!-- Nested Row within Card Body -->
<div class="row">
<div class="col-lg-6 d-none d-lg-block bg-login-image"></div>
<div class="col-lg-6">
<div class="p-5">
<div class="text-center">
<h1 class="h4 text-gray-900 mb-4">Welcome Back!</h1>
</div>
<?php if ($error !== null): ?>
<div class="alert alert-danger" role="alert">
<?php echo htmlspecialchars($error, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8'); ?>
</div>
<?php endif; ?>
<form class="user" method="post" action="index.php?route=login.submit">
<div class="form-group">
<input type="text" name="username" class="form-control form-control-user"
id="username" aria-describedby="usernameHelp"
placeholder="Enter Username...">
</div>
<div class="form-group">
<input type="password" name="password" class="form-control form-control-user"
id="password" placeholder="Password">
</div>
<div class="form-group">
<div class="custom-control custom-checkbox small">
<input type="checkbox" class="custom-control-input" id="customCheck">
<label class="custom-control-label" for="customCheck">Remember
Me</label>
</div>
</div>
<button type="submit" class="btn btn-primary btn-user btn-block">
Anmelden
</button>
</form>
<hr>
<div class="text-center">
<a class="small" href="forgot-password.php">Forgot Password?</a>
</div>
<div class="text-center">
<a class="small" href="register.php">Create an Account!</a>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Bootstrap core JavaScript-->
<script src="vendor/jquery/jquery.min.js"></script>
<script src="vendor/bootstrap/js/bootstrap.bundle.min.js"></script>
<!-- Core plugin JavaScript-->
<script src="vendor/jquery-easing/jquery.easing.min.js"></script>
<!-- Custom scripts for all pages-->
<script src="js/sb-admin-2.min.js"></script>
</body>
</html>