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
+31
-7
@@ -314,18 +314,42 @@ model BenutzerKunde {
|
||||
/// genau eine (das Firmen-AD); mehrere sind erlaubt, etwa eine zweite Domaene.
|
||||
/// Nur fuer die eigene Organisation – im Modus DIENSTLEISTUNG gibt es keinen
|
||||
/// Abgleich zu Kunden-ADs.
|
||||
/// Woher eine Verzeichnisquelle liest (#028).
|
||||
///
|
||||
/// Bis #028 war jede Quelle implizit LDAP. `SCIM` steht hier bereits, weil #029
|
||||
/// dieselbe Weiche braucht — behandelt wird es noch nicht, und der Dienst weist
|
||||
/// es mit einer Meldung ab, statt stillschweigend nichts zu tun.
|
||||
enum QuellenArt {
|
||||
LDAP
|
||||
GRAPH
|
||||
SCIM
|
||||
}
|
||||
|
||||
model Verzeichnisquelle {
|
||||
id String @id @default(cuid())
|
||||
id String @id @default(cuid())
|
||||
kundeId String
|
||||
name String
|
||||
aktiv Boolean @default(false)
|
||||
url String
|
||||
bindDn String
|
||||
aktiv Boolean @default(false)
|
||||
/// Bestehende Zeilen sind LDAP – das war vor #028 die einzige Art.
|
||||
art QuellenArt @default(LDAP)
|
||||
/// Ab hier: **nur bei `art = LDAP`** gesetzt. Welche Felder eine Art braucht,
|
||||
/// haelt der CHECK `Verzeichnisquelle_felder_je_art` fest — eine Graph-Quelle
|
||||
/// mit einem `basisDn` waere eine Zeile, die zwei Dinge gleichzeitig behauptet.
|
||||
url String?
|
||||
bindDn String?
|
||||
/// AES-256-GCM aus APP_SECRET. Darf keine API-Antwort erreichen; dafuer sorgt
|
||||
/// der Serializer in der Service-Schicht, abgedeckt durch einen eigenen Test.
|
||||
bindPasswortVerschluesselt String
|
||||
basisDn String
|
||||
filter String
|
||||
bindPasswortVerschluesselt String?
|
||||
basisDn String?
|
||||
filter String?
|
||||
|
||||
/// Ab hier: **nur bei `art = GRAPH`**. Der Mandant in Entra ID und die
|
||||
/// App-Registrierung, mit der gelesen wird (Client-Credentials-Flow).
|
||||
tenantId String?
|
||||
clientId String?
|
||||
/// Verschluesselt wie das Bind-Passwort und mit demselben Vorsatz: es darf
|
||||
/// keine Antwort und keinen Protokolleintrag erreichen.
|
||||
clientSecretVerschluesselt String?
|
||||
/// Zuordnung der LDAP-Attribute auf unsere Felder, mit AD-Vorgaben.
|
||||
attributZuordnung Json
|
||||
/// Eigenes CA-Zertifikat, falls ldaps gegen eine interne CA laeuft.
|
||||
|
||||
Reference in New Issue
Block a user