diff --git a/.gitignore b/.gitignore index 5c1516e..09376cf 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,4 @@ /.idea/ /.vscode/ /storage/ -/config.php +/config.php \ No newline at end of file diff --git a/app/Controllers/UserManagementController.php b/app/Controllers/UserManagementController.php new file mode 100644 index 0000000..8b20a81 --- /dev/null +++ b/app/Controllers/UserManagementController.php @@ -0,0 +1,153 @@ + Vollständige Anwendungskonfiguration (aus config.php) */ + private array $config; + + /** @var LdapDirectoryService Service für das Lesen von Benutzern und Gruppen aus dem LDAP/AD */ + private LdapDirectoryService $directoryService; + + /** + * @param array $config Vollständige Konfiguration aus config.php + */ + public function __construct(array $config) + { + // Komplette Konfiguration speichern (falls später weitere Werte benötigt werden). + $this->config = $config; + + // LDAP-Konfiguration aus der Gesamt-Konfiguration herausziehen. + $ldapConfig = $config['ldap'] ?? []; + + // Directory-Service initialisieren, der die eigentliche LDAP-Arbeit übernimmt. + $this->directoryService = new LdapDirectoryService($ldapConfig); + } + + /** + * Zeigt Benutzer- und Gruppenliste an. + * Wird typischerweise über die Route "users" (index.php?route=users) aufgerufen. + */ + public function show(): void + { + // Standardwerte für die View-Variablen vorbereiten. + $error = null; + $users = []; + $groups = []; + + try { + // 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(); + } + + // Pfad zur eigentlichen View-Datei bestimmen. + $viewPath = __DIR__ . '/../../public/views/users.php'; + + // Falls die View-Datei (noch) nicht existiert, Fallback-Ausgabe verwenden. + if (file_exists($viewPath) === false) { + $this->renderInline($users, $groups, $error); + return; + } + + // Variablen $users, $groups, $error stehen in der View zur Verfügung, + // weil sie im aktuellen Scope definiert sind. + require $viewPath; + } + + /** + * Fallback-Ausgabe, falls noch keine View-Datei existiert. + * + * @param array> $users Liste der Benutzer-Datensätze + * @param array> $groups Liste der Gruppen-Datensätze + * @param string|null $error Fehlermeldung (falls vorhanden) + */ + private function renderInline(array $users, array $groups, ?string $error): void + { + ?> + + + + + AD Admin Tool – Benutzer & Gruppen + + +

Benutzer & Gruppen

+ + + +

+ + +

Benutzer

+ + + + + + + + + + + + + + + + + +
Benutzername (sAMAccountName)AnzeigenameE-Mail
+ +

Gruppen

+ + + + + + + + + + + + + + + + + +
Gruppenname (sAMAccountName)CNBeschreibung
+ +

+ Zurück zum Dashboard | + Logout +

+ + + + */ + /** @var array LDAP-spezifische Konfiguration (server, port, timeout, etc.) */ private array $config; /** - * @param array $ldapConfig + * @param array $ldapConfig Teilbereich "ldap" aus der config.php */ public function __construct(array $ldapConfig) { + // LDAP-Konfiguration in der Instanz speichern. + // Dadurch steht sie in allen Methoden dieser Klasse zur Verfügung. $this->config = $ldapConfig; } @@ -24,26 +40,43 @@ class LdapConnectionHelper * aber ohne Bind. Den Bind führen die aufrufenden Services durch. * * @return resource LDAP-Verbindungs-Handle + * + * @throws RuntimeException wenn der Server nicht konfiguriert ist oder die Verbindung scheitert */ public function createConnection() { + // Server, Port und Timeout aus der Konfiguration lesen. + // Die Null-Koaleszenz-Operatoren (??) setzen Standardwerte, falls Keys fehlen. $server = (string)($this->config['server'] ?? ''); $port = (int)($this->config['port'] ?? 636); $timeout = (int)($this->config['timeout'] ?? 5); + // Ohne Server-Adresse kann keine Verbindung aufgebaut werden. if ($server === '') { throw new RuntimeException('LDAP-Konfiguration ist unvollständig (server fehlt).'); } + // Verbindung zum LDAP/AD-Server herstellen. + // ldap_connect liefert entweder ein Verbindungs-Handle (Resource) oder false. $connection = ldap_connect($server, $port); + // Wenn keine Verbindung aufgebaut werden konnte, Exception werfen. if ($connection === false) { throw new RuntimeException('LDAP-Verbindung konnte nicht aufgebaut werden.'); } + // LDAP-Version auf 3 setzen, dies ist die gängige Standardversion. ldap_set_option($connection, LDAP_OPT_PROTOCOL_VERSION, 3); + + // Netzwerk-Timeout setzen, damit Anfragen nicht ewig hängen bleiben. ldap_set_option($connection, LDAP_OPT_NETWORK_TIMEOUT, $timeout); + // Referral-Following deaktivieren (0 = aus). + // Das verhindert, dass der Client automatisch zu anderen LDAP-Servern weitergeleitet wird. + ldap_set_option($connection, LDAP_OPT_REFERRALS, 0); + + // Verbindungs-Handle an aufrufende Services zurückgeben. + // Dort wird dann der eigentliche ldap_bind durchgeführt. return $connection; } } diff --git a/app/Services/Ldap/LdapDirectoryService.php b/app/Services/Ldap/LdapDirectoryService.php new file mode 100644 index 0000000..b49079d --- /dev/null +++ b/app/Services/Ldap/LdapDirectoryService.php @@ -0,0 +1,203 @@ + LDAP-Konfiguration (inkl. base_dn, bind_dn, bind_password) */ + private array $config; + + /** @var LdapConnectionHelper Zentrale Hilfsklasse für den Aufbau von LDAP-Verbindungen */ + private LdapConnectionHelper $connectionHelper; + + /** + * @param array $ldapConfig Teilbereich "ldap" aus der config.php + */ + public function __construct(array $ldapConfig) + { + // Vollständige LDAP-Konfiguration speichern. + $this->config = $ldapConfig; + + // Gemeinsamen Verbindungs-Helper initialisieren. + // Dieser kümmert sich um ldap_connect und die grundlegenden Optionen. + $this->connectionHelper = new LdapConnectionHelper($ldapConfig); + } + + /** + * Stellt eine LDAP-Verbindung her und bindet sich mit dem technischen Konto. + * + * @return resource LDAP-Verbindungs-Handle + * + * @throws RuntimeException wenn Bind-Daten fehlen oder der Bind fehlschlägt + */ + private function connect() + { + // Technischen Bind-DN (Service-Account) aus der Konfiguration lesen. + $bindDn = (string)($this->config['bind_dn'] ?? ''); + $bindPassword = (string)($this->config['bind_password'] ?? ''); + + // Ohne Bind-DN oder Passwort kann kein technischer Bind durchgeführt werden. + if ($bindDn === '' || $bindPassword === '') { + throw new RuntimeException('LDAP-Binddaten für technisches Konto sind nicht konfiguriert.'); + } + + // Verbindung über den zentralen Helper erstellen (Server, Port, Optionen). + $connection = $this->connectionHelper->createConnection(); + + // Mit dem technischen Konto binden, um Lesezugriffe durchführen zu können. + // Das @-Zeichen unterdrückt PHP-Warnings bei Fehlversuchen. + if (@ldap_bind($connection, $bindDn, $bindPassword) !== true) { + // Bei fehlgeschlagenem Bind Verbindung wieder schließen. + ldap_unbind($connection); + throw new RuntimeException('LDAP-Bind mit technischem Konto ist fehlgeschlagen.'); + } + + // Erfolgreich gebundene Verbindung zurückgeben. + return $connection; + } + + /** + * Liefert eine Liste von Benutzern aus dem AD. + * + * @return array> Liste von Benutzer-Datensätzen + * [ + * [ + * 'samaccountname' => 'user1', + * 'displayname' => 'User Eins', + * 'mail' => 'user1@example.local', + * ], + * ... + * ] + * + * @throws RuntimeException bei Konfigurations- oder Verbindungsproblemen + */ + public function getUsers(): array + { + // Base-DN ist der Startpunkt der Suche im Verzeichnisbaum (z. B. DC=...,DC=...). + $baseDn = (string)($this->config['base_dn'] ?? ''); + if ($baseDn === '') { + throw new RuntimeException('LDAP base_dn für Benutzersuche ist nicht konfiguriert.'); + } + + // Verbindung mit technischem Bind herstellen. + $connection = $this->connect(); + + // Standard-Filter für Benutzer in AD: + // - objectClass=user + // - objectCategory=person + $filter = '(&(objectClass=user)(objectCategory=person))'; + + // Nur relevante Attribute auslesen, um Datenmenge klein zu halten. + $attributes = ['sAMAccountName', 'displayName', 'mail']; + + // LDAP-Suche unterhalb des Base-DN durchführen. + $search = @ldap_search($connection, $baseDn, $filter, $attributes); + if ($search === false) { + // Bei einem Fehler die Verbindung schließen und Exception werfen. + ldap_unbind($connection); + throw new RuntimeException('LDAP-Suche nach Benutzern ist fehlgeschlagen.'); + } + + // Suchergebnisse in ein PHP-Array umwandeln. + $entries = ldap_get_entries($connection, $search); + + // Verbindung wieder schließen, da sie nicht mehr benötigt wird. + ldap_unbind($connection); + + // Ergebnisliste für die View vorbereiten. + $users = []; + + // Wenn die Struktur unerwartet ist, leere Liste zurückgeben. + if (!is_array($entries) || !isset($entries['count'])) { + return $users; + } + + // Jeden einzelnen Eintrag aus dem Ergebnis verarbeiten. + for ($i = 0; $i < $entries['count']; $i++) { + $entry = $entries[$i]; + + // Attribute sind im Ergebnis verschachtelt, daher Zugriff über [...][0]. + $users[] = [ + 'samaccountname' => isset($entry['samaccountname'][0]) ? (string)$entry['samaccountname'][0] : '', + 'displayname' => isset($entry['displayname'][0]) ? (string)$entry['displayname'][0] : '', + 'mail' => isset($entry['mail'][0]) ? (string)$entry['mail'][0] : '', + ]; + } + + return $users; + } + + /** + * Liefert eine Liste von Gruppen aus dem AD. + * + * @return array> Liste von Gruppen-Datensätzen + * + * @throws RuntimeException bei Konfigurations- oder Verbindungsproblemen + */ + public function getGroups(): array + { + // Base-DN für die Gruppensuche aus der Konfiguration lesen. + $baseDn = (string)($this->config['base_dn'] ?? ''); + if ($baseDn === '') { + throw new RuntimeException('LDAP base_dn für Gruppensuche ist nicht konfiguriert.'); + } + + // Verbindung mit technischem Bind herstellen. + $connection = $this->connect(); + + // Filter für Gruppenobjekte im AD. + $filter = '(objectClass=group)'; + + // Attribute, die für die Darstellung relevant sind. + $attributes = ['sAMAccountName', 'cn', 'description']; + + // LDAP-Suche ausführen. + $search = @ldap_search($connection, $baseDn, $filter, $attributes); + if ($search === false) { + ldap_unbind($connection); + throw new RuntimeException('LDAP-Suche nach Gruppen ist fehlgeschlagen.'); + } + + // Ergebnisse holen. + $entries = ldap_get_entries($connection, $search); + + // Verbindung schließen. + ldap_unbind($connection); + + $groups = []; + + // Struktur prüfen, leere Liste zurückgeben, falls unerwartet. + if (!is_array($entries) || !isset($entries['count'])) { + return $groups; + } + + // Alle Einträge in ein flacheres Array-Format transformieren. + for ($i = 0; $i < $entries['count']; $i++) { + $entry = $entries[$i]; + + $groups[] = [ + 'samaccountname' => isset($entry['samaccountname'][0]) ? (string)$entry['samaccountname'][0] : '', + 'cn' => isset($entry['cn'][0]) ? (string)$entry['cn'][0] : '', + 'description' => isset($entry['description'][0]) ? (string)$entry['description'][0] : '', + ]; + } + + return $groups; + } +} diff --git a/config/config.php b/config/config.php index 3578ba1..a17c805 100644 --- a/config/config.php +++ b/config/config.php @@ -18,6 +18,9 @@ return [ // Optional: Timeout in Sekunden 'timeout' => 5, + + 'bind_dn' => 'CN=Service IIS,OU=WebAppUsers,DC=ITFA-PROJ-DOM,DC=local', + 'bind_password' => '$7aE!R$l$D!p1Q9l458K8@O6&', ], 'security' => [ diff --git a/public/index.php b/public/index.php index f9b853e..f84856c 100644 --- a/public/index.php +++ b/public/index.php @@ -47,6 +47,7 @@ $config = require $configPath; use App\Controllers\AuthController; use App\Controllers\DashboardController; +use App\Controllers\UserManagementController; // Hilfsfunktion für geschützte Routen function requireLogin(array $config): void @@ -71,6 +72,9 @@ $authController = new AuthController($config); // Neue Instanz der Klasse DashboardController erstellen (wird bei Bedarf über den Autoloader geladen). $dashboardController = new DashboardController($config); +// Neue Instanz der Klasse UserManagmentController erstellen (wird bei Bedarf über den Autoloader geladen). +$userManagementController = new UserManagementController($config); + // Anhand des Routing-Ziels (route) entscheiden, welcher Code ausgeführt wird. switch ($route) { case 'login': @@ -94,6 +98,11 @@ switch ($route) { $dashboardController->show(); break; + case 'users': + requireLogin($config); + $userManagementController->show(); + break; + default: http_response_code(404); echo 'Route nicht gefunden.'; diff --git a/public/js/demo/datatables-demo.js b/public/js/demo/datatables-demo.js index f2eecbf..1cc7c51 100644 --- a/public/js/demo/datatables-demo.js +++ b/public/js/demo/datatables-demo.js @@ -1,4 +1,6 @@ // Call the dataTables jQuery plugin $(document).ready(function() { - $('#dataTable').DataTable(); + $('#dataTable').DataTable(); + $('#usersTable').DataTable(); + $('#groupsTable').DataTable(); }); diff --git a/public/views/users.php b/public/views/users.php new file mode 100644 index 0000000..ebd600a --- /dev/null +++ b/public/views/users.php @@ -0,0 +1,520 @@ +> $users */ +/** @var array> $groups */ +/** @var string|null $error */ +?> + + + + + + + + + + + + SB Admin 2 - Tables + + + + + + + + + + + + + + + + +
+ + + + + + +
+ + +
+ + + + + + +
+ + +

Benutzer & Gruppen

+ +

+ +

+ +

DataTables is a third party plugin that is used to generate the demo table below. + For more information about DataTables, please visit the official DataTables documentation.

+ + +
+
+
Benutzer
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + +
AnmeldenameAnzeigenameE-Mail
AnmeldenameAnzeigenameE-Mail
+
+
+
+
+
+
Gruppen
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + +
GruppennameCNBeschreibung
GruppennameCNBeschreibung
+
+
+
+ +
+ + +
+ + + +
+
+ +
+
+ + +
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file