# #028 M15 – Quellenarten und Microsoft-Graph-Quelle **Status:** offen **Im Tracker:** [#48](https://git.wilde-server.de/florian.wilde/Laufzettel/issues/48) **Meilenstein:** v0.2.0 **Labels:** phase-2, backend, frontend **Geschätzt:** 10 h **Tatsächlich:** – ## Ziel Ein Haus ohne lokales AD hat kein LDAP, auf das sich zeigen ließe. Für reine Entra-ID-Umgebungen braucht es eine zweite Art von Verzeichnisquelle, die über Microsoft Graph liest — mit denselben Regeln wie der LDAP-Abgleich: anlegen, aktualisieren, deaktivieren, **nie löschen**. Dieser Meilenstein legt dazu die Weiche, die auch [#029](029-scim-endpunkt.md) braucht: `Verzeichnisquelle` bekommt eine **Art**. Heute ist jede Quelle implizit LDAP. ## Zu entscheiden - [ ] **Ein Enum `QuellenArt {LDAP GRAPH SCIM}` auf `Verzeichnisquelle`** — Empfehlung. Die Felder `url`, `bindDn` und `basisDn` sind dann je Art unterschiedlich belegt; was eine Art braucht, prüft der Service, nicht die Datenbank. Die Alternative wäre eine Tabelle je Art mit gemeinsamer Oberklasse — mehr Struktur, als drei Arten rechtfertigen. - [ ] **Wie `{{ou}}` in Entra ID aussieht.** Es gibt keinen DN und keine OU. Ehrlich ist, den Platzhalter bei einer Graph-Quelle **leer** zu lassen, statt etwas hineinzudeuten — und das in der Anleitung zu sagen. Wer OU-abhängige Befehle fährt, braucht ein lokales AD. ## Aufgaben - [ ] `QuellenArt` als Enum, Migration mit `LDAP` für alle bestehenden Zeilen - [ ] Graph-Zugang: `tenantId`, `clientId`, `clientSecret` (verschlüsselt wie das Bind-Passwort, nie in einer Antwort), Token über Client-Credentials-Flow, Zwischenspeicher bis kurz vor Ablauf - [ ] Lesen über `GET /v1.0/users` mit `$select`, Seitenabruf über `@odata.nextLink`; der Vorgesetzte über `$expand=manager($select=id)` - [ ] Attributzuordnung mit Graph-Vorgaben (`givenName`, `surname`, `displayName`, `mail`, `userPrincipalName`, `onPremisesSamAccountName`, `department`, `jobTitle`, `accountEnabled`) - [ ] `externeId` ist die Entra-`id` (GUID). Sie ist stabil und übersteht eine Umbenennung - [ ] „Verbindung testen“ wie bei LDAP: die ersten zehn zugeordneten Treffer zeigen - [ ] Der Abgleich läuft durch **denselben** Dienst wie LDAP — nur das Holen der Einträge unterscheidet sich. Sonst driften die Regeln auseinander - [ ] Admin → Verzeichnis zeigt die Art je Quelle und bietet beim Anlegen die Wahl - [ ] Anleitung Kapitel 06 und `docs/betrieb/`: was in Entra ID einzurichten ist (App-Registrierung, `User.Read.All`, `Directory.Read.All`, Administratoreinwilligung) ## Akzeptanzkriterien - [ ] Test gegen einen **abspielenden HTTP-Doppelgänger** mit aufgezeichneten Graph-Antworten: erster Abgleich legt an, zweiter aktualisiert ohne Dubletten, ein `accountEnabled: false` setzt `aktiv = false` - [ ] Test: der Seitenabruf folgt `@odata.nextLink` über mindestens drei Seiten - [ ] Test: `clientSecret` erscheint in keiner Antwort und in keinem Protokolleintrag - [ ] Test: eine Graph-Quelle lässt `{{ou}}` leer, statt einen Wert zu erfinden - [ ] Der LDAP-Weg verhält sich unverändert — alle Tests aus #016 bleiben grün - [ ] **Offen und benannt:** gegen einen echten Entra-Mandanten ist das nicht gelaufen; die Gegenprobe gehört in eine Prüfliste wie bei [#024](024-proxmox-echtlauf.md)