7.1 KiB
7.1 KiB
phase, plan, subsystem, tags, status, dependency-graph, tech-stack, key-files, decisions, metrics
| phase | plan | subsystem | tags | status | dependency-graph | tech-stack | key-files | decisions | metrics | ||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| quick-260728-lih | 01 | ldap |
|
complete |
|
|
|
|
|
Quick Task 260728-lih: LDAP Sync Selektiv + Auto-Sync Default Summary
LDAP directory sync made strictly selective (empty group/OU filter now syncs nothing instead of importing the whole directory) and new LDAP configs default to auto-sync OFF (syncIntervalMin 60 -> 0).
What Was Built
Three coordinated, atomically-committed changes:
Task 1 — Schema default + migration (commit c54e424)
LdapConfig.syncIntervalMindefault changed from60to0inschema.prisma.isActive @default(true)left completely untouched — it gates LDAP login (auth.service.ts:55), not auto-sync.- Migration
20260728134010_ldap_sync_interval_default_offgenerated and applied against the localtesseraPostgres DB (containertessera-ctl-db-1, started fresh for this task — it wasExited). The emitted SQL is exactly:No data rewrite of existing rows — verified live:ALTER TABLE "LdapConfig" ALTER COLUMN "syncIntervalMin" SET DEFAULT 0;information_schema.columnsshowscolumn_default = 0forLdapConfig.syncIntervalMinafter the migration.
Task 2 — Selective-only sync semantics + tests (commit 57bc7f9)
collectSearchEntries(): the empty/undefinedgroupFilterDnsbranch no longer runsclient.search(config.baseDn, ...)— it returns[]immediately. Doc comment updated to state this is a deliberate no-op, not "identical to pre-filter behavior."syncUsersForTenant(): added an early-return guard as the very first statement after constructing the emptyresultobject — beforenew Client(...), before any bind/search, and before the deactivation loop (~line 631 in the pre-change file). Whenconfig.groupFilterDnsis missing or empty, the method returns the empty successful result (created=0/updated=0/deactivated=0/errors=[]) immediately. This is the critical safety fix: an empty selection can no longer mass-deactivate every existing LDAP user, because the deactivation loop is never reached.ldap.service.spec.ts:- The three existing exclude-list tests (
describe('...per-user exclude list')) now use a non-emptybaseConfig.groupFilterDns(['ou=people,dc=example,dc=com']) so they still exercise the search/import/exclude/deactivation logic — all three still pass unchanged in behavior. - New
describe('LdapService.syncUsersForTenant — empty selection no-op')block proves the safety property: with an existing LDAP user inprisma.user.findMany's mock return andgroupFilterDns: [], the sync result is exactly{created:0,updated:0,deactivated:0,errors:[]}, andmockSearch,mockBind,userService.create,prisma.user.update, andprisma.ldapConfig.updatewere all NEVER called.
- The three existing exclude-list tests (
Task 3 — Frontend default + i18n rewording (commit 63a07ab)
page.tsx: the create-formformDatainitializer'ssyncIntervalMinchanged from60to0, matching the new backend default for brand-new configs. Thedata.syncIntervalMin ?? 60fallback infetchConfig(only relevant when editing an already-saved, non-null DB value) was deliberately left as-is — out of scope, never fires for real data.de.json+en.jsonunderadmin.ldap.groupFilter:descriptionandemptyMeansAllreworded in both locales from "no selection imports everyone under the base DN" to "no selection syncs nothing," matching the new backend semantics exactly as specified in the plan (verbatim strings).
Verification Results
- API test suite:
cd apps/api && pnpm vitest run— 394/394 tests passed across 31 test files, including the updatedldap.service.spec.ts(16/16 passed: 3 updated exclude-list tests + 1 new no-op test + 12 pre-existing unrelated LDAP tests). - API typecheck:
cd apps/api && pnpm exec tsc --noEmit— clean, no errors. - Schema/migration verification: both automated checks from the plan passed —
grep -Eq 'syncIntervalMin[[:space:]]+Int[[:space:]]+@default\(0\)' apps/api/prisma/schema.prismaand the livecolumn_default = 0psql check. - Frontend verification: all four checks from the plan's Task 3
<verify>passed —syncIntervalMin: 0,present inpage.tsx; bothde.json/en.jsonparse as valid JSON;"nichts synchronisiert"appears exactly 2x inde.json;"nothing is synced"appears exactly 2x inen.json. - Untouched files confirmed:
git diff --statacross all 3 commits shows zero changes toldap-sync.scheduler.tsorauth.service.ts, matching the plan's explicit "do not touch" constraints. - No frontend test suite exists for
admin/ldap/page.tsx(confirmed via search) — not a regression, no coverage was removed.
Deviations from Plan
None — plan executed exactly as written. All file paths, migration steps, exact string values, and test assertions matched the plan's action blocks.
Threat Model Verification
All three registered threats from the plan's <threat_model> are mitigated/accepted as specified:
- T-lih-01 (DoS via mass-deactivation): Mitigated — the early-return guard runs before the deactivation loop; proven by the new no-op unit test (
prisma.user.updatenever called). - T-lih-02 (Tampering via migration data rewrite): Mitigated — migration SQL contains only
SET DEFAULT 0, verified live; no drift was proposed by Prisma, so no halt-and-report was needed. - T-lih-03 (Elevation via isActive change): Accepted, correctly —
isActive @default(true)was not touched; verified viagit diff.
Self-Check: PASSED
Files:
- FOUND: apps/api/prisma/schema.prisma
- FOUND: apps/api/prisma/migrations/20260728134010_ldap_sync_interval_default_off/migration.sql
- FOUND: apps/api/src/ldap/ldap.service.ts
- FOUND: apps/api/src/ldap/ldap.service.spec.ts
- FOUND: apps/web/src/app/(portal)/admin/ldap/page.tsx
- FOUND: apps/web/src/messages/de.json
- FOUND: apps/web/src/messages/en.json
Commits: