mono/packages/acl/tests/vfs-acl.e2e.test.ts

190 lines
7.5 KiB
TypeScript

/**
* VFS ACL — End-to-end test (permission-boundary checks)
*
* Verifies raw ACL permission checks against path-scoped resources.
*
* Uses the real vfs-settings.json fixture under tests/vfs/root/<ownerId>/:
* - Owner → wildcard on "/" (entire tree)
* - User aaa → read+list on "/" (global)
* - User ccc → read+write+list+mkdir+delete on "/shared"
* - User ccc → read+list on "/docs"
* - Stranger → nothing
*/
import { describe, it, expect, beforeAll } from 'vitest';
import { resolve } from 'node:path';
import { Acl } from '../src/Acl.js';
import { MemoryBackend } from '../src/data/MemoryBackend.js';
import { loadVfsSettings, vfsResource } from '../src/vfs/vfs-acl.js';
import type { AclResult } from '../src/interfaces.js';
/** Unwrap AclResult — asserts ok and returns data. */
function d<T>(result: AclResult<T>): T {
if (!result.ok) throw new Error(`Expected ok, got ${result.code}: ${result.message}`);
return result.data;
}
// ---------------------------------------------------------------------------
// Constants
// ---------------------------------------------------------------------------
const OWNER_ID = '3bb4cfbf-318b-44d3-a9d3-35680e738421';
const READ_ONLY_USER = 'aaaaaaaa-1111-2222-3333-bbbbbbbbbbbb';
const FULL_GRANT_USER = 'cccccccc-4444-5555-6666-dddddddddddd';
const STRANGER_USER = '99999999-0000-0000-0000-ffffffffffff';
// Path-scoped resources
const ROOT = vfsResource(OWNER_ID, '/');
const SHARED = vfsResource(OWNER_ID, '/shared');
const DOCS = vfsResource(OWNER_ID, '/docs');
const PRIVATE = vfsResource(OWNER_ID, '/private');
const USER_DIR = resolve(import.meta.dirname!, 'vfs/root', OWNER_ID);
// ---------------------------------------------------------------------------
// Tests
// ---------------------------------------------------------------------------
describe('VFS ACL — e2e', () => {
let acl: Acl;
beforeAll(async () => {
acl = new Acl(new MemoryBackend());
const settings = await loadVfsSettings(acl, USER_DIR);
expect(settings).not.toBeNull();
expect(settings!.owner).toBe(OWNER_ID);
});
// =====================================================================
// Owner — wildcard on "/" → full access on every resource
// =====================================================================
describe(`owner (${OWNER_ID})`, () => {
it('can do anything on /', async () => {
expect(d(await acl.isAllowed(OWNER_ID, ROOT, 'read'))).toBe(true);
expect(d(await acl.isAllowed(OWNER_ID, ROOT, 'write'))).toBe(true);
expect(d(await acl.isAllowed(OWNER_ID, ROOT, 'list'))).toBe(true);
expect(d(await acl.isAllowed(OWNER_ID, ROOT, 'mkdir'))).toBe(true);
expect(d(await acl.isAllowed(OWNER_ID, ROOT, 'delete'))).toBe(true);
expect(d(await acl.isAllowed(OWNER_ID, ROOT, 'rename'))).toBe(true);
expect(d(await acl.isAllowed(OWNER_ID, ROOT, 'copy'))).toBe(true);
});
});
// =====================================================================
// Read-only user — read + list on "/" (global)
// =====================================================================
describe(`read-only user (${READ_ONLY_USER})`, () => {
it('can read on /', async () => {
expect(d(await acl.isAllowed(READ_ONLY_USER, ROOT, 'read'))).toBe(true);
});
it('can list on /', async () => {
expect(d(await acl.isAllowed(READ_ONLY_USER, ROOT, 'list'))).toBe(true);
});
it('CANNOT write on /', async () => {
expect(d(await acl.isAllowed(READ_ONLY_USER, ROOT, 'write'))).toBe(false);
});
it('CANNOT mkdir on /', async () => {
expect(d(await acl.isAllowed(READ_ONLY_USER, ROOT, 'mkdir'))).toBe(false);
});
it('CANNOT delete on /', async () => {
expect(d(await acl.isAllowed(READ_ONLY_USER, ROOT, 'delete'))).toBe(false);
});
it('CANNOT rename on /', async () => {
expect(d(await acl.isAllowed(READ_ONLY_USER, ROOT, 'rename'))).toBe(false);
});
});
// =====================================================================
// Full-grant user — per-path grants
// /shared → read, write, list, mkdir, delete
// /docs → read, list
// / → nothing
// =====================================================================
describe(`full-grant user (${FULL_GRANT_USER})`, () => {
it('can read on /shared', async () => {
expect(d(await acl.isAllowed(FULL_GRANT_USER, SHARED, 'read'))).toBe(true);
});
it('can write on /shared', async () => {
expect(d(await acl.isAllowed(FULL_GRANT_USER, SHARED, 'write'))).toBe(true);
});
it('can list on /shared', async () => {
expect(d(await acl.isAllowed(FULL_GRANT_USER, SHARED, 'list'))).toBe(true);
});
it('can mkdir on /shared', async () => {
expect(d(await acl.isAllowed(FULL_GRANT_USER, SHARED, 'mkdir'))).toBe(true);
});
it('can delete on /shared', async () => {
expect(d(await acl.isAllowed(FULL_GRANT_USER, SHARED, 'delete'))).toBe(true);
});
it('CANNOT rename on /shared (not granted)', async () => {
expect(d(await acl.isAllowed(FULL_GRANT_USER, SHARED, 'rename'))).toBe(false);
});
it('CANNOT copy on /shared (not granted)', async () => {
expect(d(await acl.isAllowed(FULL_GRANT_USER, SHARED, 'copy'))).toBe(false);
});
// /docs — read + list only
it('can read on /docs', async () => {
expect(d(await acl.isAllowed(FULL_GRANT_USER, DOCS, 'read'))).toBe(true);
});
it('can list on /docs', async () => {
expect(d(await acl.isAllowed(FULL_GRANT_USER, DOCS, 'list'))).toBe(true);
});
it('CANNOT write on /docs', async () => {
expect(d(await acl.isAllowed(FULL_GRANT_USER, DOCS, 'write'))).toBe(false);
});
// /private — no grant at all
it('CANNOT read on /private', async () => {
expect(d(await acl.isAllowed(FULL_GRANT_USER, PRIVATE, 'read'))).toBe(false);
});
it('CANNOT list on /private', async () => {
expect(d(await acl.isAllowed(FULL_GRANT_USER, PRIVATE, 'list'))).toBe(false);
});
// root "/" — no grant
it('CANNOT read on / (no root grant)', async () => {
expect(d(await acl.isAllowed(FULL_GRANT_USER, ROOT, 'read'))).toBe(false);
});
});
// =====================================================================
// Stranger — no access at all
// =====================================================================
describe(`stranger (${STRANGER_USER})`, () => {
for (const res of [ROOT, SHARED, DOCS, PRIVATE]) {
it(`CANNOT read on ${res}`, async () => {
expect(d(await acl.isAllowed(STRANGER_USER, res, 'read'))).toBe(false);
});
}
it('CANNOT write on /shared', async () => {
expect(d(await acl.isAllowed(STRANGER_USER, SHARED, 'write'))).toBe(false);
});
it('CANNOT list on /docs', async () => {
expect(d(await acl.isAllowed(STRANGER_USER, DOCS, 'list'))).toBe(false);
});
});
});