# SCIM-Endpunkt betreiben Laufzettel kann Personen aus einem Verzeichnis **holen** (LDAP, Microsoft Graph) oder sich **beliefern lassen**. Der zweite Weg ist SCIM 2.0, und er ist die einzige Stelle, an der ein fremder Dienst von außen schreibend auf Laufzettel zugreift. Diese Seite beschreibt, was der Betreiber dafür wissen muss. Wie eine Quelle eingerichtet wird, steht in [Kapitel 06 der Anleitung](../anleitung/06-verzeichnis.md#scim-der-anbieter-schickt). ## Was der Endpunkt ist | | | |---|---| | Adresse | `https:///api/scim/v2` | | Anmeldung | `Authorization: Bearer `, ein Token je Quelle | | Medientyp | `application/scim+json` | | Normen | RFC 7643 (Schema), RFC 7644 (Protokoll) | Vollständig: ``` GET /api/scim/v2/ServiceProviderConfig GET /api/scim/v2/Schemas GET /api/scim/v2/ResourceTypes GET /api/scim/v2/Users?filter=userName eq "…" POST /api/scim/v2/Users GET /api/scim/v2/Users/{id} PUT /api/scim/v2/Users/{id} PATCH /api/scim/v2/Users/{id} DELETE /api/scim/v2/Users/{id} ``` Die drei Beschreibungs-Endpunkte sind nicht Zierde: manche Anbieter, Entra ID darunter, fragen sie beim „Test Connection“ ab und weigern sich sonst anzufangen. Sie verlangen **ebenfalls** ein Token. ## Was nicht unterstützt wird — und warum das so gemeldet wird `ServiceProviderConfig` sagt ausdrücklich `supported: false` für `bulk`, `sort`, `filter` und `etag`. Die **Verneinungen** sind der wichtigere Teil der Antwort: ein Anbieter, der `bulk` für unterstützt hält, schickt Anfragen, die niemand beantwortet, und meldet das als Fehler der Gegenseite. Nicht umgesetzt sind außerdem: - **`/Groups`.** Laufzettel führt keine Gruppen. Rollen entstehen beim Anmelden aus den OIDC-Gruppen (siehe [`authentik.md`](authentik.md)), nicht aus dem Provisioning. Ein Anbieter, der Gruppen zuweisen will, bekommt nichts zugewiesen — `ResourceTypes` nennt nur `User`. - **Allgemeine Filter.** Unterstützt wird genau `userName eq "…"` — die Frage, die ein Anbieter vor jedem Anlegen stellt. Alles andere ergibt `400` mit `scimType: invalidFilter`. Das ist Absicht: einen unverstandenen Filter zu ignorieren und **alle** Personen zu liefern wäre die gefährlichere Antwort, denn der Anbieter schlösse daraus, die gesuchte Person gäbe es schon, und legte sie nie an. - **Paginierung über 200 Einträge.** Ein `GET /Users` ohne Filter liefert höchstens 200 Zeilen. Die Anbieter fragen gefiltert; ein voller Abzug ist keine Betriebsart dieses Endpunkts. ## Das Token Ein Token je Quelle, erzeugt unter **Admin → Verzeichnis**, Form `lzscim_` + 43 Zeichen aus `randomBytes(32)`. - Es steht in der Datenbank **nur als argon2-Hash**. Ein CHECK hält das fest (`Verzeichnisquelle_scimtoken_gehasht`): ein roh gespeichertes Token weist die Datenbank ab, auch wenn jemand am Dienst vorbei schreibt. - Es wird **einmal** angezeigt, direkt nach dem Erzeugen. Danach gibt es keinen Weg, es zu lesen — auch nicht für einen Administrator, auch nicht aus einem Backup der Anwendung. - Es erscheint in **keinem Protokolleintrag**. Vermerkt wird „SCIM-Token erzeugt“ mit dem Namen der Quelle, nicht das Token; das Protokoll ist in der Anwendung ansehbar. - Ein neues ersetzt das alte. Es gibt genau eines je Quelle — zwei gültige wären zwei Dinge, die man widerrufen muss, und eines davon vergisst man. **Das Token ist der einzige Mandantenbeweis, den eine Zustellung mitbringt.** Aus ihm folgt die Quelle, aus der Quelle der Kunde, und daran hängt, welche Personen die Anfrage sehen und ändern darf. Ein Token einer anderen Quelle bekommt auf deren Personen `404` und nicht `403`: ob es eine Person gibt, ist für eine fremde Quelle keine Auskunft. Gleiche Antwort für **kein Token** und **falsches Token**: `401` mit `WWW-Authenticate: Bearer realm="Laufzettel SCIM"`. Die Unterscheidung wäre eine Auskunft darüber, ob ein geratenes Token die richtige Form hat. Widerrufen geht jederzeit über **Token widerrufen**. Eine Quelle **stilllegen**, ohne das Token beim Anbieter zu entfernen, geht über das Häkchen **Zustellungen annehmen** — der Endpunkt antwortet dann ebenfalls mit `401`. ## Reverse Proxy Der Pfad `/api/scim/` braucht **keine Sonderbehandlung**, aber er muss durchgelassen werden. Wer vor Laufzettel eine Regel hat, die `/api/` einschränkt oder nur bekannte Pfade weiterleitet, trägt ihn ein. Drei Punkte: - **Kein Cache.** Der Endpunkt setzt `Cache-Control: no-store` auf jede Antwort; ein Proxy, der das überschreibt, liefert veraltete Personenlisten. - **Der `Authorization`-Kopf muss ankommen.** Manche Konfigurationen entfernen ihn, wenn davor eine eigene Authentifizierung sitzt. Dann kommt jede Zustellung als `401` zurück. - **Eine Zustellung ist klein**, einige Kilobyte. Die Grenzen für Anhänge (`client_max_body_size`, siehe [`reverse-proxy.md`](reverse-proxy.md)) reichen weit aus. Eine IP-Einschränkung auf die Adressen des Anbieters ist möglich und sinnvoll, wenn er feste hat. authentik im eigenen Haus hat welche; Entra ID nicht zuverlässig — Microsoft veröffentlicht die Bereiche, aber sie ändern sich, und eine veraltete Liste sperrt das Provisioning aus. ## Was im Betrieb auffällt **Es wird nicht abgeglichen.** Eine SCIM-Quelle taucht im Zeitplan nicht auf. Das ist wichtig und nicht nur Kosmetik: ein Abgleich, der an einer SCIM-Quelle nichts liest und „Erfolg“ meldet, würde über die Abschaltregel die ganze Belegschaft deaktivieren. Der Zeitplan lässt sie deshalb aus, und „Jetzt abgleichen“ bricht mit einer Erklärung ab. **`DELETE` deaktiviert.** Der Anbieter bekommt `204`, die Zeile bleibt mit `aktiv = false`. An einer Person hängen Läufe und Geräte; ein archivierter Lauf muss auch in zehn Jahren noch sagen können, für wen er lief. Das steht auch in der Anleitung, weil es sonst wie ein Fehler wirkt. **Protokolleinträge haben keinen Benutzer.** Jede Zustellung schreibt einen `Protokoll`-Eintrag mit der Quelle als Urheber („Person angelegt (SCIM: authentik)“) und ohne `benutzerId`. Das ist ein Systemlauf wie der Zeitplan; ein erfundener Administrator wäre eine Lüge im Protokoll. **„Letzte Zustellung“ ist die Lebenszeichenanzeige.** Sie steht an der Quelle und wird bei jeder Anfrage gesetzt, die etwas geschrieben hat. Steht dort nichts, ist die Einrichtung beim Anbieter nicht fertig — nicht die Verbindung kaputt, denn es gibt keine. ## Prüfen von Hand Mit dem Token aus der Oberfläche, von einem Rechner aus, der die öffentliche Adresse erreicht: ```bash TOKEN='lzscim_…' BASIS='https://laufzettel.firma.de/api/scim/v2' # 1. Antwortet der Endpunkt überhaupt, und gilt das Token? curl -s -H "Authorization: Bearer $TOKEN" "$BASIS/ServiceProviderConfig" | head -c 400 # 2. Ohne Token muss 401 kommen. curl -s -o /dev/null -w '%{http_code}\n' "$BASIS/Users" # 3. Die Frage, die jeder Anbieter vor dem Anlegen stellt. curl -s -H "Authorization: Bearer $TOKEN" \ --get --data-urlencode 'filter=userName eq "mhuber@firma.example"' "$BASIS/Users" ``` Die zweite Zeile muss `401` ausgeben. Kommt dort `200`, läuft etwas vor Laufzettel, das den `Authorization`-Kopf selbst setzt — dann ist der Endpunkt offen, und das gehört sofort behoben. Anlegen von Hand ist zum Prüfen **nicht** nötig und auch nicht empfehlenswert: die Person stünde danach in der Quelle, und der Anbieter kennt sie nicht. Löschen lässt sie sich nicht, nur deaktivieren. ## Fehlerbilder | Antwort | Ursache | |---|---| | `401` | Token falsch, widerrufen, oder **Zustellungen annehmen** nicht angehakt. | | `404` auf eine bekannte Person | Sie gehört zu einer anderen Quelle. Jedes Token sieht nur seine eigene. | | `409 uniqueness` | Die Person gibt es in dieser Quelle schon — richtige Antwort auf ein wiederholtes `POST`, kein Datenverlust. | | `400 invalidFilter` | Ein Filter außer `userName eq "…"`. | | `400 invalidSyntax` | Kein gültiges JSON, oder ein `PATCH` ohne `Operations`. | | `500` | Ein Fehler in Laufzettel. Die Einzelheiten stehen im Protokoll des Dienstes, **nicht** in der Antwort: eine Datenbankmeldung nennt Host, Port und Benutzernamen. | `docker compose logs -f web` beziehungsweise `journalctl -u laufzettel -f` zeigt die Zeilen, die hinter einem `500` stehen, und auch die Pfade, die ein `PATCH` mitgeschickt und Laufzettel übergangen hat (`SCIM-PATCH: übergangene Pfade: …`) — die sind kein Fehler, aber die Antwort auf die Frage „warum steht das Feld nicht drin“.