feat(verzeichnis): SCIM-Endpunkt als dritte Quellenart (#029)
Prüfung / pruefung (push) Canceled after 0s
Prüfung / e2e (push) Canceled after 0s

Bisher holt Laufzettel Personen aus dem Verzeichnis. Das setzt Lesezugriff und
einen Netzweg dorthin voraus, und beides ist nicht ueberall gegeben. Mit der Art
SCIM ruft stattdessen der Anbieter an — sofort, nicht beim naechsten Intervall.

Umgesetzt ist, was authentik, Entra ID und Okta wirklich schicken: Users mit dem
Filter userName eq, POST/GET/PUT/PATCH/DELETE, dazu ServiceProviderConfig,
Schemas und ResourceTypes. Jeder Handler prueft sein Token selbst.

Ein Token je Quelle, argon2-gehasht, einmal angezeigt — es ist der einzige
Mandantenbeweis, den eine Zustellung mitbringt. DELETE deaktiviert und loescht
nichts. Eine SCIM-Quelle wird nicht abgeglichen, an zwei Stellen gehalten: ein
Abgleich, der dort nichts liest und ERFOLG meldet, haette ueber die
Abschaltregel die ganze Belegschaft deaktiviert.

Zwei Befunde kamen aus Gegenproben, nicht aus Tests:

- PATCH remove wirkte gar nicht. Der Schalter nurGesetzte las bei PATCH jedes
  null als "nicht mitgeschickt"; eine beim Anbieter entfernte Abteilung stand
  hier weiter. Noetig war er nie — patchAnwenden liefert immer den
  vollstaendigen Zielzustand.
- Entra ID schickt active als "True"/"False". Beim Deaktivieren kommt mit einem
  reinen value === true zufaellig das Richtige heraus, beim Aktivieren nicht:
  ein wieder freigeschaltetes Konto waere fuer immer inaktiv geblieben.

Dazu ein Fehler aus #028: in verbindungTestenAktion stand zweimal dieselbe
Bedingung, der zweite Zweig war seit dem ersten Tag unerreichbar.

1432 Tests (vorher 1373). Klickprobe am gebauten Stand mit einer echten
Zustellung. Gegen echte Mandanten ist nichts gelaufen — die Anfrageformen
stammen aus der Dokumentation, und das steht so in der Testdatei.

Closes #49

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Florian Wilde
2026-10-10 19:50:54 +02:00
co-authored by Claude Opus 5
parent 76a61ec771
commit fcd67018b8
32 changed files with 3672 additions and 141 deletions
+28
View File
@@ -378,6 +378,34 @@ was darauf steht, ist aus der Hand gegeben.
## 5g. Der Verzeichnis-Abgleich: vier Schichten und eine Notbremse
Eine Verzeichnisquelle hat seit #028 eine **Art** — `LDAP`, `GRAPH` oder `SCIM` —,
und die erste Frage ist nicht, wie gelesen wird, sondern **in welche Richtung**:
| Art | Richtung | Wo der Code liegt |
|---|---|---|
| `LDAP` | Laufzettel holt | `src/server/ldap/*` |
| `GRAPH` | Laufzettel holt | `src/server/graph/*` |
| `SCIM` | der Anbieter schickt | `src/app/api/scim/*`, `src/server/scim/*`, `src/server/services/scim.ts` |
Die ersten zwei laufen durch **denselben** Abgleich; die eine Stelle, die die Art
kennt, ist `eintraegeDerQuelleStroemen` in
`src/server/verzeichnis/quellenleser.ts`. Eine zweite Verzweigung waere eine
zweite Stelle, die sich an eine neue Quellenart erinnern muss, und die vergisst
man.
**Bei `SCIM` gibt es keinen Abgleich**, und das ist an zwei Stellen gehalten:
`faelligeQuellen` nimmt solche Quellen nicht auf, und die Weiche wirft. Der Grund
ist nicht Ordnungsliebe — ein Abgleich, der dort nichts liest und `ERFOLG` meldet,
haette ueber die Abschaltregel die ganze Belegschaft deaktiviert. Dasselbe Muster
wie der Reviewbefund aus #016, nur mit einer anderen Ursache.
Was beim Schreiben danach gilt, gilt fuer alle drei gleich: anlegen, uebernehmen,
aktualisieren, deaktivieren, **nie loeschen** — auch ein `DELETE` eines
SCIM-Anbieters setzt nur `aktiv = false`. Das **Token** einer SCIM-Quelle ist der
einzige Mandantenbeweis, den eine Zustellung mitbringt; es steht als argon2-Hash
in der Datenbank, wird einmal angezeigt und kommt in keiner Sicht und keinem
Protokolleintrag vor.
Das LDAP-Modul liegt unter `src/server/ldap/` und ist absichtlich in vier Dateien
geteilt, von denen nur eine das Netz anfasst: