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
@@ -1,8 +1,9 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
// 12 S4 against a real lldap: sign in by uid and by email, connect by email, no user made for a
|
||||
// stranger, admin by memberOf, Test, LDAPS. Needs LDAP_LIVE_URL (ldap://127.0.0.1:<port>) and
|
||||
// LDAP_LIVE_HTTP (its web port, to make the users); LDAP_LIVE_LDAPS_URL to try LDAPS too, with
|
||||
// NODE_EXTRA_CA_CERTS naming the CA of its certificate. Admin password: LDAP_LIVE_ADMIN_PASS.
|
||||
// 12 S4-S5 against a real lldap: sign in by uid and by email, connect by email, no user made for
|
||||
// a stranger, admin by memberOf, Test, LDAPS, directoryCheck disabling a removed user. Needs
|
||||
// LDAP_LIVE_URL (ldap://127.0.0.1:<port>) and LDAP_LIVE_HTTP (its web port, to make the users);
|
||||
// LDAP_LIVE_LDAPS_URL to try LDAPS too, with NODE_EXTRA_CA_CERTS naming the CA of its
|
||||
// certificate. Admin password: LDAP_LIVE_ADMIN_PASS.
|
||||
// Passed against lldap 0.6.3:
|
||||
// docker run -d -p 127.0.0.1:13890:3890 -p 127.0.0.1:16360:6360 -p 127.0.0.1:17170:17170 \
|
||||
// -e LLDAP_JWT_SECRET=… -e LLDAP_KEY_SEED=… -e LLDAP_LDAP_USER_PASS=admin-pass-123 \
|
||||
@@ -16,6 +17,7 @@ import { encrypt } from '../src/crypto/secrets.ts';
|
||||
import { userIdentities, users } from '../src/db/schema.ts';
|
||||
import { resetLoginLimiter } from '../src/auth/login-limiter.ts';
|
||||
import { setSetting } from '../src/services/settings.ts';
|
||||
import { checkDirectory } from '../src/sso/directory.ts';
|
||||
import { hasTestDb } from './db.ts';
|
||||
import { ADMIN, H, createTestApp } from './app.ts';
|
||||
|
||||
@@ -35,6 +37,7 @@ describe.skipIf(!hasTestDb || !URL_ || !HTTP)('LDAP with a real lldap (12 S4)',
|
||||
const tag = randomBytes(3).toString('hex');
|
||||
const ann = `ann${tag}`;
|
||||
const bob = `bob${tag}`;
|
||||
const cat = `cat${tag}`;
|
||||
const group = `pbx-admins-${tag}`;
|
||||
let t: Awaited<ReturnType<typeof createTestApp>>;
|
||||
let jwt: string;
|
||||
@@ -82,6 +85,7 @@ describe.skipIf(!hasTestDb || !URL_ || !HTTP)('LDAP with a real lldap (12 S4)',
|
||||
for (const [id, name] of [
|
||||
[ann, 'Ann Lee'],
|
||||
[bob, 'Bob Roe'],
|
||||
[cat, 'Cat Poe'],
|
||||
])
|
||||
await gql(
|
||||
`mutation { createUser(user: { id: "${id}", email: "${id}@example.com", displayName: "${name}" }) { id } }`,
|
||||
@@ -92,7 +96,7 @@ describe.skipIf(!hasTestDb || !URL_ || !HTTP)('LDAP with a real lldap (12 S4)',
|
||||
await gql(`mutation { addUserToGroup(userId: "${ann}", groupId: ${g.createGroup.id}) { ok } }`);
|
||||
const c = new Client({ url: URL_! });
|
||||
await c.bind(`uid=admin,ou=people,${BASE}`, ADMIN_PASS);
|
||||
for (const id of [ann, bob])
|
||||
for (const id of [ann, bob, cat])
|
||||
await c.exop(
|
||||
'1.3.6.1.4.1.4203.1.11.1',
|
||||
passwordModify(`uid=${id},ou=people,${BASE}`, `${id}-directory-pw`),
|
||||
@@ -103,12 +107,13 @@ describe.skipIf(!hasTestDb || !URL_ || !HTTP)('LDAP with a real lldap (12 S4)',
|
||||
await t.clearMustChange(ADMIN.email);
|
||||
admin = await t.login(ADMIN.email, ADMIN.password);
|
||||
await t.addUser(`${ann}@example.com`, 'ann-local-pw-12');
|
||||
await t.addUser(`${cat}@example.com`, 'cat-local-pw-12');
|
||||
await setSetting(t.db, 'auth.ldap', settings());
|
||||
}, 60_000);
|
||||
|
||||
afterAll(async () => {
|
||||
await t?.close();
|
||||
for (const id of [ann, bob])
|
||||
for (const id of [ann, bob, cat])
|
||||
await gql(`mutation { deleteUser(userId: "${id}") { ok } }`).catch(() => {});
|
||||
});
|
||||
|
||||
@@ -193,6 +198,22 @@ describe.skipIf(!hasTestDb || !URL_ || !HTTP)('LDAP with a real lldap (12 S4)',
|
||||
expect(bad.json()).toMatchObject({ ok: false, error: expect.stringContaining('refused') });
|
||||
});
|
||||
|
||||
it('directoryCheck disables someone removed from the directory, and only them (§4.5)', async () => {
|
||||
resetLoginLimiter();
|
||||
expect((await login(cat, `${cat}-directory-pw`)).statusCode).toBe(200); // connects cat
|
||||
await gql(`mutation { deleteUser(userId: "${cat}") { ok } }`);
|
||||
await checkDirectory({ db: t.db, log: { info: () => {}, warn: () => {} } });
|
||||
const active = async (who: string) =>
|
||||
(
|
||||
await t.db
|
||||
.select()
|
||||
.from(users)
|
||||
.where(eq(users.email, `${who}@example.com`))
|
||||
)[0]!.isActive;
|
||||
expect(await active(cat)).toBe(false);
|
||||
expect(await active(ann)).toBe(true);
|
||||
});
|
||||
|
||||
it.skipIf(!LDAPS)('LDAPS with a private CA (NODE_EXTRA_CA_CERTS)', async () => {
|
||||
resetLoginLimiter();
|
||||
await setSetting(t.db, 'auth.ldap', settings(LDAPS));
|
||||
|
||||
Reference in new issue
Block a user