ldap/benutzer-und-gruppen-anzeigen #4

Merged
blaerf merged 9 commits from ldap/benutzer-und-gruppen-anzeigen into main 2025-11-27 13:29:28 +00:00
2 changed files with 356 additions and 0 deletions
Showing only changes of commit 010366f7f5 - Show all commits

View File

@ -0,0 +1,153 @@
<?php
// Strenge Typprüfung für Parameter- und Rückgabetypen aktivieren.
declare(strict_types=1);
namespace App\Controllers;
use App\Services\Ldap\LdapDirectoryService;
/**
* Controller für die Benutzer- und Gruppenanzeige.
*
* Aufgaben:
* - holt über den LdapDirectoryService die Listen von Benutzern und Gruppen
* - behandelt technische Fehler und bereitet eine Fehlermeldung für die View auf
* - gibt die Daten an eine View-Datei (public/views/users.php) weiter
*
* WICHTIG:
* - Es werden aktuell nur Daten angezeigt (Read-only).
* - Es findet keine Änderung im Active Directory statt.
*/
class UserManagementController
{
/** @var array<string, mixed> 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<string, mixed> $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<int, array<string, string>> $users Liste der Benutzer-Datensätze
* @param array<int, array<string, string>> $groups Liste der Gruppen-Datensätze
* @param string|null $error Fehlermeldung (falls vorhanden)
*/
private function renderInline(array $users, array $groups, ?string $error): void
{
?>
<!doctype html>
<html lang="de">
<head>
<meta charset="utf-8">
<title>AD Admin Tool Benutzer &amp; Gruppen</title>
</head>
<body>
<h1>Benutzer &amp; Gruppen</h1>
<?php if ($error !== null): ?>
<!-- Fehlermeldung ausgeben, HTML-sicher maskiert -->
<p style="color: red;"><?php echo htmlspecialchars($error, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8'); ?></p>
<?php endif; ?>
<h2>Benutzer</h2>
<table border="1" cellpadding="4" cellspacing="0">
<thead>
<tr>
<th>Benutzername (sAMAccountName)</th>
<th>Anzeigename</th>
<th>E-Mail</th>
</tr>
</thead>
<tbody>
<?php foreach ($users as $user): ?>
<tr>
<td><?php echo htmlspecialchars($user['samaccountname'], ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8'); ?></td>
<td><?php echo htmlspecialchars($user['displayname'], ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8'); ?></td>
<td><?php echo htmlspecialchars($user['mail'], ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8'); ?></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<h2>Gruppen</h2>
<table border="1" cellpadding="4" cellspacing="0">
<thead>
<tr>
<th>Gruppenname (sAMAccountName)</th>
<th>CN</th>
<th>Beschreibung</th>
</tr>
</thead>
<tbody>
<?php foreach ($groups as $group): ?>
<tr>
<td><?php echo htmlspecialchars($group['samaccountname'], ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8'); ?></td>
<td><?php echo htmlspecialchars($group['cn'], ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8'); ?></td>
<td><?php echo htmlspecialchars($group['description'], ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8'); ?></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<p>
<a href="index.php?route=dashboard">Zurück zum Dashboard</a> |
<a href="index.php?route=logout">Logout</a>
</p>
</body>
</html>
<?php
}
}

View File

@ -0,0 +1,203 @@
<?php
// Strenge Typprüfung für Parameter- und Rückgabetypen aktivieren.
declare(strict_types=1);
namespace App\Services\Ldap;
use RuntimeException;
/**
* Service zum Lesen von Objekten aus dem Active Directory.
*
* Aktueller Umfang:
* - Liste von Benutzern (sAMAccountName, displayName, mail)
* - Liste von Gruppen (sAMAccountName, cn, description)
*
* Technische Details:
* - Verwendet ein technisches Konto (bind_dn + bind_password) für Lesezugriffe
* - Nutzt LdapConnectionHelper zum Aufbau der Verbindung
*/
class LdapDirectoryService
{
/** @var array<string, mixed> 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<string, mixed> $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<int, array<string, string>> 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<int, array<string, string>> 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;
}
}