From 125d685ba5809e73a09d60ea848b76951cfb5ebb Mon Sep 17 00:00:00 2001 From: blaerf Date: Thu, 27 Nov 2025 05:20:07 +0000 Subject: [PATCH 1/5] README.md aktualisiert --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 368be31..cc6f67b 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ Ich habe mir auch noch Gedanken zum Git-Workflow gemacht und es nieder geschrieb Eine Ordner Struktur habe ich mir auch überlegt und schon mal angelegt. Alle Infos habe ich soweit im Wiki zusammen getragen. Meine Tests habe ich, wie im Git-workflow beschrieben, in ein eigenes branch (structure/first-structure) gepackt. -Bitte auch das Wiki beachten und durchlesen, falls nötig! +Bitte auch das Wiki beachten und durchlesen! --- From 8b7359f31982c760800436ceb070aed87e666330 Mon Sep 17 00:00:00 2001 From: blaerf Date: Thu, 27 Nov 2025 13:29:28 +0000 Subject: [PATCH 2/5] ldap/benutzer-und-gruppen-anzeigen (#4) Co-authored-by: blaerf Reviewed-on: https://git.eckertplayground.de/taarly/PHP_AdminTool_Projekt/pulls/4 --- .gitignore | 2 +- app/Controllers/UserManagementController.php | 153 ++++++ app/Services/Ldap/LdapConnectionHelper.php | 37 +- app/Services/Ldap/LdapDirectoryService.php | 203 ++++++++ config/config.php | 3 + public/index.php | 9 + public/js/demo/datatables-demo.js | 4 +- public/views/users.php | 520 +++++++++++++++++++ 8 files changed, 927 insertions(+), 4 deletions(-) create mode 100644 app/Controllers/UserManagementController.php create mode 100644 app/Services/Ldap/LdapDirectoryService.php create mode 100644 public/views/users.php 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 From f6cdaa6108eb6d719385f513f046e8e25465dd2d Mon Sep 17 00:00:00 2001 From: blaerf Date: Thu, 27 Nov 2025 14:34:17 +0100 Subject: [PATCH 3/5] Links angepasst --- public/views/dashboard.php | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/public/views/dashboard.php b/public/views/dashboard.php index d55019a..710dca3 100644 --- a/public/views/dashboard.php +++ b/public/views/dashboard.php @@ -36,7 +36,7 @@ declare(strict_types=1);