feat(verzeichnis): Entra ID als Quelle ueber Microsoft Graph (#48)
Ein Haus ohne lokales Active Directory hat kein LDAP, auf das sich zeigen liesse. Eine Verzeichnisquelle hat jetzt eine **Art** — LDAP oder GRAPH —, und beide laufen durch **denselben** Abgleich: anlegen, aktualisieren, deaktivieren, nie loeschen. Nur das Holen der Eintraege unterscheidet sich; zwei Abgleiche nebeneinander wuerden auseinanderdriften, und die Zusage „es wird nichts geloescht“ gaelte dann fuer eine Art und fuer die andere nicht mehr ganz. Gelesen wird mit dem Client-Credentials-Flow, seitenweise ueber @odata.nextLink, der Vorgesetzte ueber $expand=manager($select=id). Das Client-Secret wird verschluesselt gespeichert wie das Bind-Passwort und erreicht keine Antwort und keinen Protokolleintrag. Die Art ist **unveraenderlich**, und das steht nicht im Issue: an ihr haengt, was `externeId` bedeutet. Im AD eine objectGUID, in Entra ID eine andere GUID — nach einem Wechsel wuerde kein einziger Mitarbeiter wiedererkannt, der naechste Abgleich legte die ganze Belegschaft neu an und setzte die alten Zeilen auf aktiv = false, mit allen Laeufen und Geraeten daran. Der teuerste Fund beim Bauen: „in Entra ID deaktiviert“ als *uebersprungen* zu melden haette die Leute **aktiv** gelassen. Uebersprungen heisst im Abgleich „nicht zuordenbar“, und solche Eintraege werden seit #016 absichtlich nicht abgeschaltet. Gefiltert wird deshalb serverseitig mit `$filter=accountEnabled eq true`, als Gegenstueck zum LDAP-Standardfilter. Gefunden hat es der Test — aber nur, weil der Doppelgaenger den Filter wirklich auswertet statt ihn bloss zu empfangen. {{ou}} bleibt bei einer Graph-Quelle leer: Entra ID hat keine OU, und etwas hineinzudeuten waere ein Wert, den kein PowerShell-Befehl brauchen kann — er saehe nur richtig aus. Steht an der Abteilung eine Ziel-OU gepflegt, greift die. Gegen einen echten Entra-Mandanten ist das **nicht** gelaufen. Geprueft ist es gegen einen HTTP-Doppelgaenger mit aufgezeichneten Antworten; die Pruefliste fuer den ersten echten Lauf steht am Ende von docs/betrieb/entra-id.md. Closes #48 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
0158d0292f
commit
76a61ec771
+67
-2
@@ -34,12 +34,13 @@ als Unterpunkte mit Aufgabe und Dauer.
|
||||
| #033 | M20 – Live-Aktualisierung per SSE statt Polling | 1 h 40 min |
|
||||
| #034 | M21 – Benachrichtigungen per Mail und Webhook | 3 h 30 min |
|
||||
| #035 | M22 – Mitarbeiter-Selbstauskunft | 2 h 50 min |
|
||||
| | **Summe** | **56 h 25 min** |
|
||||
| #028 | M15 – Quellenarten und Microsoft-Graph-Quelle | 4 h 10 min |
|
||||
| | **Summe** | **60 h 35 min** |
|
||||
|
||||
| Meilenstein | Tatsächlich | Status |
|
||||
|---|---|---|
|
||||
| v0.1.0 | 35 h 20 min | veröffentlicht |
|
||||
| v0.2.0 | 21 h 05 min | in Arbeit |
|
||||
| v0.2.0 | 25 h 15 min | in Arbeit |
|
||||
|
||||
---
|
||||
|
||||
@@ -1370,3 +1371,67 @@ und nicht nach jeder einzelnen — das ist der Grund, warum er alles auf einmal
|
||||
- Vor beiden wäre ein unabhängiger Review über die sechs Meilensteine dieser Phase angebracht —
|
||||
M17 bis M22 liegen ohne fremden Blick auf `main`.
|
||||
- Weiter offen und nicht von hier aus machbar: **#024** Proxmox-Einzeiler auf einem echten PVE-Host.
|
||||
|
||||
---
|
||||
|
||||
## 2026-10-06 · Sitzung 21 · 09:00–13:10
|
||||
|
||||
**Issue:** #028 (M15)
|
||||
**Version:** 0.2.0 (unveröffentlicht)
|
||||
|
||||
### Erledigt
|
||||
|
||||
- **Eine Verzeichnisquelle hat eine Art**: `LDAP`, `GRAPH` oder `SCIM`. Bestehende Zeilen sind
|
||||
`LDAP` — das war vor #028 die einzige.
|
||||
- **Entra ID über Microsoft Graph**: Client-Credentials-Flow mit Token-Zwischenspeicher, Seitenabruf
|
||||
über `@odata.nextLink`, Vorgesetzter über `$expand`.
|
||||
- **Derselbe Abgleich für beide Arten.** Die Weiche sitzt in
|
||||
`src/server/verzeichnis/quellenleser.ts` und ist die einzige Stelle, die die Art kennt.
|
||||
- 1373 Vitest-Tests (vorher 1342), davon 31 neue gegen einen HTTP-Doppelgänger mit aufgezeichneten
|
||||
Graph-Antworten.
|
||||
- Im gebauten Stand eine Graph-Quelle wirklich angelegt: Art gespeichert, Geheimnis verschlüsselt,
|
||||
nicht im Quelltext der Seite.
|
||||
|
||||
### Entscheidungen
|
||||
|
||||
- **Ein Enum auf der Quelle, keine Tabelle je Art.** Drei Arten rechtfertigen keine Vererbung; was
|
||||
eine Art braucht, hält ein CHECK fest — in **beide** Richtungen, sonst wäre eine Graph-Quelle mit
|
||||
`basisDn` möglich.
|
||||
- **Die Art ist unveränderlich.** An ihr hängt, was `externeId` bedeutet; nach einem Wechsel würde
|
||||
kein Mitarbeiter wiedererkannt. Steht so nicht im Issue und ist die wichtigste Zusage dieses
|
||||
Meilensteins.
|
||||
- **`{{ou}}` bleibt bei Graph leer.** Etwas hineinzudeuten wäre ein Wert, den kein PowerShell-Befehl
|
||||
brauchen kann — und er sähe richtig aus.
|
||||
- **`SCIM` steht im Enum, wird aber abgewiesen.** Ein Wert, der stillschweigend nichts tut, hätte
|
||||
hier die ganze Belegschaft deaktiviert.
|
||||
|
||||
### Probleme
|
||||
|
||||
- **Der teuerste Fund: „deaktiviert“ als „übersprungen“ zu melden hätte Leute aktiv gelassen.**
|
||||
„Übersprungen“ heißt im Abgleich „nicht zuordenbar“, und solche Einträge werden seit #016
|
||||
absichtlich nicht abgeschaltet. Ein gesperrtes Entra-Konto wäre damit genau umgekehrt behandelt
|
||||
worden als ein gesperrtes AD-Konto. Jetzt filtert die Abfrage serverseitig, als Gegenstück zum
|
||||
LDAP-Standardfilter.
|
||||
**Gefunden hat es der Test — aber nur, weil der Doppelgänger den Filter wirklich auswertet.** Hätte
|
||||
er den Filter bloß empfangen und ignoriert, wäre der Test grün gewesen und der Fehler im Betrieb
|
||||
aufgefallen. Ein Doppelgänger, der nur antwortet, prüft die Anfrage nicht.
|
||||
- **`URLSearchParams` kodiert `$` zu `%24`** und die Klammern in `manager($select=id)`. Graph nimmt
|
||||
das teils an und antwortet teils mit „Invalid expand clause“. Jetzt von Hand zusammengesetzt.
|
||||
- **Ein Import-Zyklus drohte**: die Weiche braucht `zugangAus`, und der Dienst braucht die Weiche.
|
||||
Beides aufgelöst durch ein eigenes Modul. In ESM läuft so ein Zyklus oft — aber nicht zuverlässig.
|
||||
- **Zwei eigene Testfehler**: die Token-Grenze liegt bei +3540 s und nicht „ungefähr eine Stunde“,
|
||||
und die Klickprobe zählte die Felder der Bearbeiten-Formulare mit.
|
||||
|
||||
### Was offen bleibt, und das ist benannt
|
||||
|
||||
Gegen einen **echten Entra-Mandanten** ist nichts davon gelaufen. Die Form der Antworten im
|
||||
Doppelgänger stammt aus der Dokumentation, nicht aus einem Mitschnitt — stimmt sie nicht, stimmen
|
||||
Test und Wirklichkeit nicht. Die Prüfliste steht am Ende von `docs/betrieb/entra-id.md`, acht
|
||||
Punkte, darunter ein Mandant mit mehr als 999 Benutzern. Dieselbe Art offener Punkt wie #024.
|
||||
|
||||
### Nächster Schritt
|
||||
|
||||
- **M16 (#029)** SCIM-Endpunkt ist der letzte Meilenstein von Phase 2. Die Weiche dafür steht.
|
||||
- Vor #029 wäre ein unabhängiger Review angebracht: **sieben Meilensteine** dieser Phase liegen ohne
|
||||
fremden Blick auf `main`, und diese Sitzung hat wieder einen Fehler gezeigt, den die Suite nur
|
||||
deshalb gefunden hat, weil der Doppelgänger streng genug war.
|
||||
|
||||
Reference in New Issue
Block a user