--- status: complete --- # Quick Task 260707-csw: LDAP AD Anbindung — Zugangsdaten aus XWiki vorbefuellen und Import-Filter fuer Benutzer/Gruppen ## What shipped 1. **AD connection prefill** — `admin/ldap` page now defaults a brand-new config form to the CTL Active Directory values extracted from `user-files/xwiki.cfg` (server `balios.ctl.local:3268`, base DN `dc=ctl,dc=local`, AD person search filter, down-level bind-DN hint). Password stays blank. Editing an existing config still shows its real saved values (unchanged behavior). 2. **Selective group/OU import filter** — new `groupFilterDns String[]` column on `LdapConfig` (additive migration, empty array = current "import everyone" behavior). `GET /ldap/groups` discovers AD groups/OUs under the base DN; admin can select from the discovered list or add DNs manually; selection persists via `PATCH /ldap/config` and is editable any time post-setup. `syncUsersForTenant` now restricts its directory search to members of selected groups (via escaped `memberOf` clauses) or users under selected OUs (extra search bases), for both manual sync and the scheduled cron sync. 3. **Bugfix caught during manual testing (not in original plan):** `CreateLdapConfigDto`'s optional fields (`searchFilter`, `syncIntervalMin`, `isActive`, `groupFilterDns`) had TS class-field default initializers. NestJS's `ValidationPipe` applies those defaults via `plainToInstance` even when the field is absent from the request body, so any partial PATCH silently reset the other fields to hardcoded defaults. Caught live: saving the group filter alone reset `searchFilter` back to `(objectClass=person)`, clobbering the AD-specific filter. Fixed by removing the initializers — the service layer's own `?? default` fallback already covers create-time defaults. 4. **Pre-existing i18n gap fixed (user-approved scope addition):** the `admin.ldap` message block referenced by the page (`t('title')`, `t('connectionTitle')`, `t('serverUrl')`, etc.) did not exist in `de.json`/`en.json` at all, so the whole page rendered raw translation keys instead of text. Backfilled the full set of keys the page actually uses, in addition to the new group-filter keys. ## Verification performed - `prisma validate`, `prisma generate`, API `type-check`, API `build` — all green. - Web `type-check` green; both message JSON files parse. - Migration applied live via container restart (`Applying migration 20260707090000_add_ldap_group_filter`), confirmed idempotent entrypoint (`prisma migrate deploy`). - End-to-end manual test via Playwright against the running dev stack: - Fresh `/admin/ldap` page load shows AD-prefilled connection form, password blank. - Created a config (test bind DN/password — dev environment, no live AD reachable from this sandbox), confirmed default field mappings (`displayName`, `mail`, `sAMAccountName`) are AD-correct. - Added a group DN manually, saved the filter, reloaded the page — filter persisted. - Verified via direct DB query (`SELECT ... FROM "LdapConfig"`) that `groupFilterDns` persisted correctly and, after the DTO fix, that `searchFilter`/`syncIntervalMin`/`isActive` are no longer clobbered by a groupFilterDns-only PATCH. ## Known limitations / follow-ups for the user - **Not tested against a live AD server** — `balios.ctl.local` is not reachable from this environment. `GET /ldap/groups` discovery and the `memberOf`-filtered sync are implemented per the plan's design and pass all static checks, but have not been exercised against real Active Directory data. Recommend testing `testConnection`, `Gruppen/OUs suchen`, and a real sync once this is deployed somewhere with network access to the CTL AD. - The dev database now has one `LdapConfig` test row (server/base DN are the real CTL AD values; bind DN/password are placeholders `ctl\testservice` / `testpassword`) with one filter DN saved. Replace the bind credentials with the real service-account before relying on this in a real environment. - The i18n backfill only covers keys this page actually calls — it does not audit the rest of the app for similar gaps.