Files
tessera-ctl/.planning/quick/260811-f9i-fix-objectguid-existence-sweep-to-use-eq/PLAN.md
T
schalli f0c763d3e2
Tessera CI/CD / Lint & Type Check (push) Successful in 44s
Tessera CI/CD / Tests (push) Successful in 47s
Tessera CI/CD / Build & Publish Images (push) Successful in 27s
docs(16): record the objectGUID sweep defect found by UAT test 2
Quick task 260811-f9i: plan and summary of the fix, STATE.md row, and the UAT
test 2 result. Test 2 was the read-only A2 check against the real directory --
it turned assumption A2 from "unverified" into "false as implemented" and
surfaced a defect that would have deleted every AD-bound group on the first
real sync.

Also records why the defect survived review: the spec mocks built their
expected filter with the same escape helper the production code used, so the
test asserted self-consistency rather than directory behaviour.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-11 11:06:27 +02:00

79 lines
3.3 KiB
Markdown

---
quick_id: 260811-f9i
slug: fix-objectguid-existence-sweep-to-use-eq
date: 2026-08-11
status: planned
relates_to: 16-ad-gruppen-synchronisation
severity: critical
---
# Quick Task: objectGUID-Existenzprüfung auf EqualityFilter mit Rohbytes umstellen
## Problem
`syncBoundGroupsForTenant()` (`apps/api/src/ldap/ldap.service.ts:1318`) baut den
Filter der Existenzprüfung als **String**:
```ts
const filter = `(objectGUID=${LdapService.escapeLdapFilterBuffer(guidBuffer)})`;
```
`escapeLdapFilterBuffer()` erzeugt `\1e\4b\4d…`. ldapts wandelt diese Sequenzen
beim Parsen des Filter-Strings nicht in Rohbytes zurück, also steht auf dem Draht
ein anderer Vergleichswert als gemeint. Die Suche trifft nie.
Am 2026-08-11 read-only gegen das echte AD (`balios.ctl.local`) gemessen, Sonde
`CN=Domain Admins,CN=Users,DC=ctl,DC=local`, GUID `1e4b4df0b3caeb4c9b49fddf05227e10`:
| Variante | Treffer |
|---|---|
| `(objectGUID=\1e\4b…)` — heutiger Code | 0 |
| `(objectGUID=\1E\4B…)` — Großbuchstaben | 0 |
| `new EqualityFilter({ attribute: 'objectGUID', value: <Buffer> })` | 1, korrekte DN |
| `EqualityFilter` mit escaptem String als Wert | 0 |
| Kontrolle `(cn=Domain Admins)` | 1 |
Der Domain Controller ist also in Ordnung; die Annahme A2 aus `16-RESEARCH.md`
ist für die gewählte Umsetzung falsch.
## Auswirkung
Der Ablauf in `syncBoundGroupsForTenant()` ist: schmale Suche über die
konfigurierten Base-DNs → kein Treffer → weite Suche über `domainRoots`
(WR-03-Absicherung) → kein Treffer → Gruppe gilt als im AD gelöscht
(`ldap.service.ts:1432`). **Beide** Suchen verwenden denselben kaputten Filter.
Folge: der erste echte Sync-Lauf hätte jede AD-gebundene Gruppe gelöscht, samt
`GroupMembership` und `ModuleGrant` per Kaskade, und vorher die
Standardgruppen-Markierung weitergereicht (D-06). Der Bericht hätte das als
reguläre Löschung ausgewiesen. Auf alpha ist nichts passiert — dort lief nie ein
Sync.
## Tasks
1. `EqualityFilter` aus `ldapts` importieren; in `syncBoundGroupsForTenant()` den
String-Filter durch ein `EqualityFilter`-Objekt mit dem rohen 16-Byte-Buffer
ersetzen. Beide Schleifen (Base-DNs und `domainRoots`) teilen sich weiterhin
denselben Filterwert.
2. `escapeLdapFilterBuffer()` entfernen — nach Task 1 ohne Aufrufer, und die
Methode ist genau die Falle, in die der Code gelaufen ist. `escapeLdapFilterValue()`
(String-Werte, RFC 4515) bleibt unberührt, die ist korrekt und wird weiter genutzt.
3. Die Mocks in `ldap.service.spec.ts`, die auf dem escapten String matchen
(Zeilen 579/586, 1060, 1587), auf den Buffer-Wert des Filterobjekts umstellen.
4. Regressionstests ergänzen: die Existenzprüfung übergibt ein Filterobjekt mit
`attribute: 'objectGUID'` und einem `Buffer`-Wert, der byteweise dem
gespeicherten `ldapObjectGuid` entspricht — und **kein** String. Ein Test hält
ausdrücklich fest, dass ein String-Filter der Form `(objectGUID=…)` nicht mehr
vorkommt.
## Erfolgskriterium
`pnpm --filter @tessera/api test` läuft grün, und ein Test schlägt fehl, sobald
jemand wieder einen escapten String als GUID-Filter übergibt.
## Offen nach diesem Fix
Der Fix belegt die Filtermechanik, nicht das Verhalten des Sync-Laufs. UAT 1, 3
und 4 (Umbenennen und Löschen im AD) bleiben offen und sollen gegen einen
Wegwerf-Domänencontroller nachgezogen werden.