auth: directory leavers check, dashboard warning, Samba AD check (S5)
Spec 12 S5: job directoryCheck (hourly) looks up every active user with a directory account; one deleted, disabled there or no longer matched by the user filter is disabled here, sessions ended. Nothing changes when the directory is unreachable or a run would disable most people; the last admin with a password is kept; nobody is re-enabled. Dashboard warning directory_check (system.directoryWarning). The by-id search is a filter object: ldapts string filters send bytes >= 0x80 as UTF-8, so AD's objectGUID never matched. Live test against a Samba 4 AD DC: nested-group admin, the disabled bit, objectGUID, a deleted user disabled.
This commit is contained in:
1 parent
b06335586b
commit
19610777a4
14 files changed
+540
-17
No files matched your search
@@ -2,7 +2,9 @@
|
||||
// LDAP / LDAPS password check (12 §4) on ldapts: search the person with the service account,
|
||||
// bind as them with their password, read whether the admin filter matches them.
|
||||
import {
|
||||
AndFilter,
|
||||
Client,
|
||||
EqualityFilter,
|
||||
FilterParser,
|
||||
InvalidCredentialsError,
|
||||
NoSuchObjectError,
|
||||
@@ -182,6 +184,49 @@ export async function ldapAuthenticate(
|
||||
}
|
||||
}
|
||||
|
||||
/** `(&(<id attribute>=<id>)<userFilter with {login} → *>)`: still there, and still let in (§4.5). */
|
||||
export function byIdFilter(s: Pick<LdapSettings, 'idAttribute' | 'userFilter'>, id: string) {
|
||||
// An object, not a string: ldapts parses "\ff" in a string filter to U+00FF and sends it as
|
||||
// UTF-8 (two bytes), so a binary objectGUID would never match. A Buffer goes out as is.
|
||||
const value = s.idAttribute === 'objectGUID' ? Buffer.from(id, 'hex') : id;
|
||||
return new AndFilter({
|
||||
filters: [
|
||||
new EqualityFilter({ attribute: s.idAttribute, value }),
|
||||
FilterParser.parseString(anyLogin(s.userFilter)),
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* The leaver check (12 §4.5): each id → its entry, when the directory still has it and the user
|
||||
* filter still lets it in. One connection for all. Throws DirectoryUnavailable on any failure,
|
||||
* so a half-done run never reads as "gone".
|
||||
*/
|
||||
export async function lookupIds(
|
||||
s: LdapSettings,
|
||||
ids: string[],
|
||||
): Promise<Map<string, DirectoryEntry>> {
|
||||
const c = await connect(s);
|
||||
try {
|
||||
const found = new Map<string, DirectoryEntry>();
|
||||
for (const id of ids) {
|
||||
const r = await c.search(s.baseDn, {
|
||||
scope: 'sub',
|
||||
filter: byIdFilter(s, id),
|
||||
attributes: attributes(s),
|
||||
explicitBufferAttributes: buffers(s),
|
||||
sizeLimit: 2,
|
||||
});
|
||||
if (r.searchEntries.length === 1) found.set(id, await toEntry(c, s, r.searchEntries[0]!));
|
||||
}
|
||||
return found;
|
||||
} catch (err) {
|
||||
throw unavailable(err);
|
||||
} finally {
|
||||
await c.unbind().catch(() => {});
|
||||
}
|
||||
}
|
||||
|
||||
/** Settings → Sign-in → Directory → Test (12 §4.1), step by step so it can say which failed. */
|
||||
export async function testLdap(
|
||||
s: LdapSettings,
|
||||
|
||||
Reference in new issue
Block a user