feat(ldap): per-user exclude/denylist filter for sync
Add a per-username denylist so individual accounts (service accounts like administrator, krbtgt, guest, ldap$) can be excluded from LDAP sync, independent of the group/OU include-filter which only scopes the search. - schema: LdapConfig.userExcludeList String[] (+ migration) - sync: skip excluded usernames (case-insensitive) before recording the DN, so an already-imported user added to the list gets deactivated next sync - DTO / config service / controller / scheduler: thread userExcludeList through - web: exclude-list admin UI section (add/remove/save) + de/en translations - tests: 3 specs covering empty list, case-insensitive skip, deactivation Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -26,6 +26,7 @@ interface LdapConfigData {
|
||||
bindPassword?: string | null;
|
||||
searchFilter: string;
|
||||
groupFilterDns: string[];
|
||||
userExcludeList: string[];
|
||||
fieldMappings: Array<{
|
||||
ldapField: string;
|
||||
tesseraField: string;
|
||||
@@ -205,11 +206,21 @@ export class LdapService {
|
||||
// Track all DNs found in this sync for deactivation logic
|
||||
const syncedDns: string[] = [];
|
||||
|
||||
// Per-user exclude/denylist: individual usernames (sAMAccountName) the
|
||||
// admin never wants imported, e.g. service accounts like administrator,
|
||||
// krbtgt, guest, ldap$. Distinct from groupFilterDns, which only limits
|
||||
// which OUs/groups are searched. Normalized to lowercase to match the
|
||||
// case-insensitive username handling below.
|
||||
const excludeSet = new Set(
|
||||
(config.userExcludeList ?? [])
|
||||
.map((u) => u.trim().toLowerCase())
|
||||
.filter(Boolean),
|
||||
);
|
||||
|
||||
// 4. Process each LDAP entry
|
||||
for (const entry of searchEntries) {
|
||||
try {
|
||||
const dn = entry.dn;
|
||||
syncedDns.push(dn);
|
||||
|
||||
// Map LDAP fields to Tessera fields.
|
||||
// ldapts represents a missing/absent attribute as an empty array
|
||||
@@ -231,6 +242,17 @@ export class LdapService {
|
||||
// Require at minimum a username. Normalize to lowercase so
|
||||
// logins stay case-insensitive regardless of AD casing.
|
||||
const username = mappedData['username']?.toLowerCase();
|
||||
|
||||
// Skip excluded users before recording the DN as synced. Leaving an
|
||||
// excluded entry out of syncedDns means that if the admin adds an
|
||||
// already-imported user to the exclude list, the deactivation pass
|
||||
// below will deactivate them on the next sync.
|
||||
if (username && excludeSet.has(username)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
syncedDns.push(dn);
|
||||
|
||||
if (!username) {
|
||||
result.errors.push(
|
||||
`Entry ${dn}: no username mapped (check sAMAccountName mapping)`,
|
||||
|
||||
Reference in New Issue
Block a user