147 lines
5.0 KiB
TypeScript
147 lines
5.0 KiB
TypeScript
import { describe, expect, it } from "@suaveplan/testing/runner";
|
|
import { MemoryAdapter } from "../src/adapters/memory/memory.js";
|
|
import { PermissionCache } from "../src/cache/permission-cache/permission-cache.js";
|
|
import { type Ability, buildAbility } from "../src/core/ability/ability.js";
|
|
import { PermissionManager } from "../src/core/permission-manager/permission-manager.js";
|
|
import { PolicyEngine } from "../src/core/policy-engine/policy-engine.js";
|
|
import { RoleManager } from "../src/core/role-manager/role-manager.js";
|
|
import { CircularInheritanceError } from "../src/errors/permission-error.js";
|
|
import {
|
|
createAdminRole,
|
|
createViewerRole,
|
|
} from "../src/presets/role-presets/role-presets.js";
|
|
import type { Permission } from "../src/types/permissions.types.js";
|
|
|
|
function makePerm(id: string, resource = "posts", action = "read"): Permission {
|
|
return { id, resource, action };
|
|
}
|
|
|
|
describe("Full permissions flow", () => {
|
|
it("role inheritance transitivity", { timeout: 5000 }, async () => {
|
|
const adapter = new MemoryAdapter();
|
|
const roleManager = new RoleManager(adapter);
|
|
const _manager = new PermissionManager(adapter);
|
|
|
|
const cPerm = makePerm("p-c", "comments", "read");
|
|
const bPerm = makePerm("p-b", "posts", "write");
|
|
const aPerm = makePerm("p-a", "posts", "read");
|
|
|
|
await adapter.createRole({ id: "c", name: "C", permissions: [cPerm] });
|
|
await adapter.createRole({
|
|
id: "b",
|
|
name: "B",
|
|
permissions: [bPerm],
|
|
inherits: ["c"],
|
|
});
|
|
await adapter.createRole({
|
|
id: "a",
|
|
name: "A",
|
|
permissions: [aPerm],
|
|
inherits: ["b"],
|
|
});
|
|
|
|
const perms = await roleManager.getEffectivePermissions("a");
|
|
const permIds = perms.map((p) => p.id);
|
|
expect(permIds).toContain("p-a");
|
|
expect(permIds).toContain("p-b");
|
|
expect(permIds).toContain("p-c");
|
|
});
|
|
|
|
it("policy engine with cache", { timeout: 5000 }, async () => {
|
|
const adapter = new MemoryAdapter();
|
|
await adapter.createRole({
|
|
id: "r1",
|
|
name: "R",
|
|
permissions: [makePerm("p1")],
|
|
});
|
|
await adapter.assignRole({
|
|
id: "ur1",
|
|
userId: "u1",
|
|
roleId: "r1",
|
|
assignedAt: new Date(),
|
|
});
|
|
|
|
const manager = new PermissionManager(adapter);
|
|
const cache = new PermissionCache({ ttl: 60_000 });
|
|
const engine = new PolicyEngine(manager, { cache });
|
|
|
|
const r1 = await engine.evaluate("u1", "posts", "read");
|
|
const r2 = await engine.evaluate("u1", "posts", "read");
|
|
expect(r1.allowed).toBe(true);
|
|
expect(r2.matchedRule).toBe("cached-allow");
|
|
|
|
// Invalidate cache and re-check
|
|
cache.invalidate("u1");
|
|
const r3 = await engine.evaluate("u1", "posts", "read");
|
|
expect(r3.allowed).toBe(true);
|
|
expect(r3.matchedRule).toBe("posts:read");
|
|
});
|
|
|
|
it("circular inheritance detection", { timeout: 5000 }, async () => {
|
|
const adapter = new MemoryAdapter();
|
|
const roleManager = new RoleManager(adapter);
|
|
|
|
await adapter.createRole({
|
|
id: "x",
|
|
name: "X",
|
|
permissions: [],
|
|
inherits: ["y"],
|
|
});
|
|
await adapter.createRole({
|
|
id: "y",
|
|
name: "Y",
|
|
permissions: [],
|
|
inherits: ["x"],
|
|
});
|
|
|
|
await expect(
|
|
roleManager.getRoleInheritanceChain("x")
|
|
).rejects.toBeInstanceOf(CircularInheritanceError);
|
|
});
|
|
|
|
it("preset roles work with PermissionManager", {
|
|
timeout: 5000,
|
|
}, async () => {
|
|
const adapter = new MemoryAdapter();
|
|
await adapter.createRole(createAdminRole());
|
|
await adapter.assignRole({
|
|
id: "ur-admin",
|
|
userId: "admin-user",
|
|
roleId: "admin",
|
|
assignedAt: new Date(),
|
|
});
|
|
|
|
await adapter.createRole(createViewerRole());
|
|
await adapter.assignRole({
|
|
id: "ur-viewer",
|
|
userId: "viewer-user",
|
|
roleId: "viewer",
|
|
assignedAt: new Date(),
|
|
});
|
|
|
|
const manager = new PermissionManager(adapter);
|
|
expect(await manager.hasPermission("admin-user", "any", "any")).toBe(
|
|
true
|
|
);
|
|
expect(
|
|
await manager.hasPermission("viewer-user", "posts", "read")
|
|
).toBe(true);
|
|
expect(
|
|
await manager.hasPermission("viewer-user", "posts", "delete")
|
|
).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe("Ability system end-to-end", () => {
|
|
it("can/cannot consistency", { timeout: 5000 }, () => {
|
|
const ability: Ability = buildAbility((b) => {
|
|
b.can("read", "posts");
|
|
b.cannot("delete", "posts");
|
|
});
|
|
expect(ability.can("read", "posts")).toBe(true);
|
|
expect(ability.cannot("read", "posts")).toBe(false);
|
|
expect(ability.can("delete", "posts")).toBe(false);
|
|
expect(ability.cannot("delete", "posts")).toBe(true);
|
|
});
|
|
});
|