Files
boss/tests/boss-permissions.test.ts

190 lines
5.8 KiB
TypeScript

import test from "node:test";
import assert from "node:assert/strict";
import os from "node:os";
import path from "node:path";
import { mkdtemp, rm } from "node:fs/promises";
let runtimeRoot = "";
let data: typeof import("../src/lib/boss-data");
let permissions: typeof import("../src/lib/boss-permissions");
let baseState: Awaited<ReturnType<typeof import("../src/lib/boss-data")["readState"]>>;
async function setup() {
if (!runtimeRoot) {
runtimeRoot = await mkdtemp(path.join(os.tmpdir(), "boss-rbac-permissions-"));
process.env.BOSS_RUNTIME_ROOT = runtimeRoot;
process.env.BOSS_STATE_FILE = path.join(runtimeRoot, "boss-state.json");
}
if (!data) {
data = await import("../src/lib/boss-data.ts");
baseState = structuredClone(await data.readState());
}
if (!permissions) {
permissions = await import("../src/lib/boss-permissions.ts");
}
}
test.after(async () => {
if (runtimeRoot) {
await rm(runtimeRoot, { recursive: true, force: true });
}
});
test.beforeEach(async () => {
await setup();
await data.writeState({
...structuredClone(baseState),
accountDeviceGrants: [],
accountProjectGrants: [],
accountSkillGrants: [],
skillCatalog: [],
permissionAuditLogs: [],
});
});
test("highest admin can access every device and project without explicit grants", async () => {
const state = await data.readState();
const session = {
account: "krisolo",
role: "highest_admin" as const,
displayName: "Boss 超级管理员",
};
assert.equal(permissions.canAccessDevice(state, session, "mac-studio", "device.view"), true);
assert.equal(permissions.canAccessProject(state, session, "audit-collab", "project.view"), true);
});
test("device.view grant gives project read visibility but not thread chat", async () => {
const state = await data.readState();
state.accountDeviceGrants = [
{
grantId: "grant-device-view",
account: "worker@example.com",
deviceId: "mac-studio",
permissions: ["device.view"],
grantedBy: "krisolo",
grantedAt: "2026-04-26T12:00:00+08:00",
},
];
await data.writeState(state);
const next = await data.readState();
const session = {
account: "worker@example.com",
role: "member" as const,
displayName: "Worker",
};
assert.equal(permissions.canAccessDevice(next, session, "mac-studio", "device.view"), true);
assert.equal(permissions.canAccessProject(next, session, "master-agent", "project.view"), true);
assert.equal(permissions.canAccessProject(next, session, "master-agent", "thread.chat"), false);
});
test("explicit project thread.chat grant allows posting to that project", async () => {
const state = await data.readState();
state.accountProjectGrants = [
{
grantId: "grant-thread-chat",
account: "worker@example.com",
projectId: "master-agent",
permissions: ["project.view", "thread.chat", "master_agent.ask"],
grantedBy: "krisolo",
grantedAt: "2026-04-26T12:00:00+08:00",
},
];
await data.writeState(state);
const next = await data.readState();
const session = {
account: "worker@example.com",
role: "member" as const,
displayName: "Worker",
};
assert.equal(permissions.canAccessProject(next, session, "master-agent", "project.view"), true);
assert.equal(permissions.canAccessProject(next, session, "master-agent", "thread.chat"), true);
assert.equal(permissions.canAccessProject(next, session, "master-agent", "computer.control"), false);
});
test("expired grants are ignored", async () => {
const state = await data.readState();
state.accountDeviceGrants = [
{
grantId: "expired-device-grant",
account: "worker@example.com",
deviceId: "mac-studio",
permissions: ["device.view"],
grantedBy: "krisolo",
grantedAt: "2026-04-25T12:00:00+08:00",
expiresAt: "2000-01-01T00:00:00.000Z",
},
];
await data.writeState(state);
const next = await data.readState();
const session = {
account: "worker@example.com",
role: "member" as const,
displayName: "Worker",
};
assert.equal(permissions.canAccessDevice(next, session, "mac-studio", "device.view"), false);
});
test("legacy device account ownership remains a compatibility fallback", async () => {
const state = await data.readState();
state.devices.push({
id: "worker-mac",
name: "Worker Mac",
avatar: "W",
account: "worker@example.com",
source: "production",
status: "online",
projects: ["worker-project"],
quota5h: 0,
quota7d: 0,
lastSeenAt: "2026-04-26T12:00:00+08:00",
preferredExecutionMode: "cli",
});
state.projects.push({
id: "worker-project",
name: "Worker Project",
pinned: false,
systemPinned: false,
deviceIds: ["worker-mac"],
preview: "Owned by worker.",
updatedAt: "2026-04-26T12:00:00+08:00",
lastMessageAt: "2026-04-26T12:00:00+08:00",
isGroup: false,
threadMeta: {
projectId: "worker-project",
threadId: "thread-worker-project",
threadDisplayName: "Worker Project",
folderName: "Worker",
activityIconCount: 0,
updatedAt: "2026-04-26T12:00:00+08:00",
codexThreadRef: "thread-worker-project",
codexFolderRef: "worker",
},
groupMembers: [],
createdByAgent: true,
collaborationMode: "development",
approvalState: "not_required",
unreadCount: 0,
riskLevel: "low",
messages: [],
goals: [],
versions: [],
});
await data.writeState(state);
const next = await data.readState();
const session = {
account: "worker@example.com",
role: "member" as const,
displayName: "Worker",
};
assert.equal(permissions.canAccessDevice(next, session, "worker-mac", "device.view"), true);
assert.equal(permissions.canAccessProject(next, session, "worker-project", "project.view"), true);
});