PHP_AdminTool_Projekt/app/Services/Ldap/LdapDirectoryService.php
2025-11-27 13:29:28 +00:00

204 lines
7.4 KiB
PHP

<?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;
}
}