feat(rbac): implement permission evaluation engine and unit test suite

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
WeiCHEN
2026-09-13 02:50:20 +00:00
committed by weichen
co-authored by Cursor
parent aa9636c10f
commit bd52108c8a
2 changed files with 468 additions and 0 deletions
+253
View File
@@ -0,0 +1,253 @@
import test from 'node:test';
import assert from 'node:assert/strict';
import {
DEFAULT_ROLES,
hasPermission,
isDeliverableVisible,
filterVisibleDeliverablesWithRBAC,
canPerformDocAction,
} from './rbac';
import { UserProfile } from '../types/docgov';
import { DeliverableItem } from '../types/workbench';
import { RoleDefinition } from '../types/rbac';
const sampleUsers: Record<string, UserProfile> = {
admin: {
id: 100,
name: '系统管理员',
employeeId: 'ADMIN-001',
department: '系统运维部',
position: '系统管理员',
roleId: 'role-admin',
},
director: {
id: 3,
name: '王总监',
employeeId: 'SMDF-0018',
department: '医疗器械研发事业部',
position: '微创器械研发总监',
roleId: 'role-director',
},
lead: {
id: 2,
name: '李主管',
employeeId: 'SMDF-0205',
department: '机器人与微创内窥镜研发中心',
position: '研发项目技术主管',
roleId: 'role-tech-lead',
},
engineer: {
id: 1,
name: '张工',
employeeId: 'SMDF-0821',
department: '机器人与微创内窥镜研发中心',
position: '资深医疗器械方案工程师',
roleId: 'role-engineer',
},
guest: {
id: 99,
name: '外部审计专员',
employeeId: 'GUEST-001',
department: '第三方认证机构',
position: '外部合规审查员',
roleId: 'role-guest',
},
};
const createMockDoc = (overrides: Partial<DeliverableItem> = {}): DeliverableItem => ({
docId: 'doc-mock-001',
projectId: 1,
taskKey: 'feasibility',
title: '测试受控交付物',
stage: 'V0.1',
category: 'workflow',
creatorId: 1,
creatorName: '张工',
isFixedWorkflow: true,
isTemplateOnly: false,
lifecycleState: 'drafting',
isClaimed: true,
author: '张工',
reviewer: '李主管',
approver: '王总监',
updatedAt: '2026-09-08 12:00',
...overrides,
});
test('用例 1: DEFAULT_ROLES 包含 5 种默认角色且具备预期权限', () => {
assert.equal(DEFAULT_ROLES.length, 5);
const admin = DEFAULT_ROLES.find((r) => r.code === 'ADMIN');
const engineer = DEFAULT_ROLES.find((r) => r.code === 'ENGINEER');
const guest = DEFAULT_ROLES.find((r) => r.code === 'GUEST');
assert.ok(admin);
assert.ok(admin.isSystem);
assert.ok(admin.permissions.includes('perm:manage'));
assert.ok(engineer);
assert.ok(engineer.permissions.includes('workflow:view_all'));
assert.equal(engineer.permissions.includes('project:create'), false);
assert.ok(guest);
assert.equal(guest.permissions.includes('workflow:view_all'), false);
});
test('用例 2: 系统管理员 (ADMIN) 与具备 perm:manage 用户的通配豁免特权', () => {
assert.equal(hasPermission(sampleUsers.admin, DEFAULT_ROLES, 'project:create'), true);
assert.equal(hasPermission(sampleUsers.admin, DEFAULT_ROLES, 'perm:manage'), true);
assert.equal(hasPermission(sampleUsers.admin, DEFAULT_ROLES, 'doc:override_visibility'), true);
// 普通工程师无管理权限
assert.equal(hasPermission(sampleUsers.engineer, DEFAULT_ROLES, 'perm:manage'), false);
assert.equal(hasPermission(sampleUsers.engineer, DEFAULT_ROLES, 'project:create'), false);
});
test('用例 3: 常规工作流基线可见性 (workflow:view_all) 判定', () => {
const normalDoc = createMockDoc();
// 工程师有 workflow:view_all -> 可见
assert.equal(isDeliverableVisible(sampleUsers.engineer, DEFAULT_ROLES, normalDoc), true);
// 外部审计员无 workflow:view_all 且不是责任人 -> 不可见
assert.equal(isDeliverableVisible(sampleUsers.guest, DEFAULT_ROLES, normalDoc), false);
});
test('用例 4: 单文档覆盖 - designated_only (仅编制/审核/批准人可见,其他用户被精确拦截)', () => {
const restrictedDoc = createMockDoc({
author: '张工',
reviewer: '李主管',
approver: '赵院长', // 王总监不是责任人
visibilityRule: {
mode: 'designated_only',
overrideReason: '敏感临床事故报告,收紧仅责任人可见',
configuredBy: '李主管',
configuredAt: '2026-09-08 14:00',
},
});
// 张工是编制人 -> 可见
assert.equal(isDeliverableVisible(sampleUsers.engineer, DEFAULT_ROLES, restrictedDoc), true);
// 李主管是审核人 -> 可见
assert.equal(isDeliverableVisible(sampleUsers.lead, DEFAULT_ROLES, restrictedDoc), true);
// 王总监虽为研发总监,但非三权责任人 -> 被单文档覆盖拦截 (不可见)
assert.equal(isDeliverableVisible(sampleUsers.director, DEFAULT_ROLES, restrictedDoc), false);
// 系统管理员超级权限仍可见
assert.equal(isDeliverableVisible(sampleUsers.admin, DEFAULT_ROLES, restrictedDoc), true);
});
test('用例 5: 单文档覆盖 - role_whitelist (仅白名单内角色可见,其他角色被精确拦截)', () => {
const secretPatentDoc = createMockDoc({
author: '外聘专家',
reviewer: '外聘审查',
approver: '外聘顾问',
visibilityRule: {
mode: 'role_whitelist',
allowedRoleIds: ['role-director'], // 仅总监可见
overrideReason: '核心专利保密算法拆解',
},
});
// 王总监拥有 role-director -> 可见
assert.equal(isDeliverableVisible(sampleUsers.director, DEFAULT_ROLES, secretPatentDoc), true);
// 张工 (role-engineer) 不在白名单中 -> 不可见
assert.equal(isDeliverableVisible(sampleUsers.engineer, DEFAULT_ROLES, secretPatentDoc), false);
// 李主管 (role-tech-lead) 不在白名单中 -> 不可见
assert.equal(isDeliverableVisible(sampleUsers.lead, DEFAULT_ROLES, secretPatentDoc), false);
});
test('用例 6: 单文档覆盖 - public (放宽可见性,无 workflow:view_all 权限的访客也能查阅)', () => {
const publicSpecDoc = createMockDoc({
author: '张工',
reviewer: '李主管',
approver: '王总监',
visibilityRule: {
mode: 'public',
overrideReason: '产品公开白皮书,供外部审计或用户公开查阅',
},
});
// 访客原本无 workflow:view_all,但单文档设为 public -> 可见
assert.equal(isDeliverableVisible(sampleUsers.guest, DEFAULT_ROLES, publicSpecDoc), true);
});
test('用例 7: 私密文档隔离强红线 (category === private 无论任何管理员或单文档覆盖,非创建者绝对不可见)', () => {
const privateDraft = createMockDoc({
category: 'private',
creatorId: 1, // 张工创建
creatorName: '张工',
visibilityRule: {
mode: 'public', // 即使误设 public 也绝不能穿透 private 隔离
},
});
// 创建者本人可见
assert.equal(isDeliverableVisible(sampleUsers.engineer, DEFAULT_ROLES, privateDraft), true);
// 其他人不可见
assert.equal(isDeliverableVisible(sampleUsers.lead, DEFAULT_ROLES, privateDraft), false);
assert.equal(isDeliverableVisible(sampleUsers.director, DEFAULT_ROLES, privateDraft), false);
assert.equal(isDeliverableVisible(sampleUsers.admin, DEFAULT_ROLES, privateDraft), false);
assert.equal(isDeliverableVisible(sampleUsers.guest, DEFAULT_ROLES, privateDraft), false);
});
test('用例 8: 核心业务动作权限拦截判定 (canPerformDocAction & hasPermission)', () => {
// 1. 创建项目权限
assert.equal(canPerformDocAction('create_project', sampleUsers.director, DEFAULT_ROLES), true);
assert.equal(canPerformDocAction('create_project', sampleUsers.lead, DEFAULT_ROLES), true);
assert.equal(canPerformDocAction('create_project', sampleUsers.engineer, DEFAULT_ROLES), false);
assert.equal(canPerformDocAction('create_project', sampleUsers.guest, DEFAULT_ROLES), false);
// 2. 指派文档法定三权责任人
assert.equal(canPerformDocAction('assign_lead', sampleUsers.director, DEFAULT_ROLES), true);
assert.equal(canPerformDocAction('assign_lead', sampleUsers.lead, DEFAULT_ROLES), true);
assert.equal(canPerformDocAction('assign_lead', sampleUsers.engineer, DEFAULT_ROLES), false);
// 3. 配置单文档覆盖
assert.equal(canPerformDocAction('override_visibility', sampleUsers.director, DEFAULT_ROLES), true);
assert.equal(canPerformDocAction('override_visibility', sampleUsers.lead, DEFAULT_ROLES), true);
assert.equal(canPerformDocAction('override_visibility', sampleUsers.engineer, DEFAULT_ROLES), false);
// 4. 用户个人级特例微调 (customPermissions) 赋予方案工程师特例权限
const customEngineer: UserProfile = {
...sampleUsers.engineer,
customPermissions: ['project:create'],
};
assert.equal(canPerformDocAction('create_project', customEngineer, DEFAULT_ROLES), true);
});
test('用例 9: filterVisibleDeliverablesWithRBAC 能够批处理过滤大盘交付物', () => {
const docs: DeliverableItem[] = [
createMockDoc({ docId: 'doc-1', title: '常规工作流' }),
createMockDoc({
docId: 'doc-2',
title: '绝密算法',
author: '外聘专家',
visibilityRule: { mode: 'role_whitelist', allowedRoleIds: ['role-director'] },
}),
createMockDoc({
docId: 'doc-3',
title: '张工个人草案',
category: 'private',
creatorId: 1,
}),
];
// 张工视角: 能看常规工作流(doc-1)和个人草案(doc-3),不能看绝密算法(doc-2)
const zhangVisible = filterVisibleDeliverablesWithRBAC(docs, sampleUsers.engineer, DEFAULT_ROLES);
assert.deepEqual(
zhangVisible.map((d) => d.docId),
['doc-1', 'doc-3']
);
// 王总监视角: 能看常规工作流(doc-1)和绝密算法(doc-2),不能看张工私密草案(doc-3)
const wangVisible = filterVisibleDeliverablesWithRBAC(docs, sampleUsers.director, DEFAULT_ROLES);
assert.deepEqual(
wangVisible.map((d) => d.docId),
['doc-1', 'doc-2']
);
});
+215
View File
@@ -0,0 +1,215 @@
import { RoleDefinition, PermissionKey } from '../types/rbac';
import { UserProfile } from '../types/docgov';
import { DeliverableItem } from '../types/workbench';
/**
* 系统内置与预置全局角色库
*/
export const DEFAULT_ROLES: RoleDefinition[] = [
{
id: 'role-admin',
name: '系统管理员',
code: 'ADMIN',
description: '拥有系统所有功能与数据的全量管理权限(防死锁兜底)。',
isSystem: true,
permissions: [
'project:create',
'project:assign_lead',
'perm:manage',
'workflow:config_visibility',
'doc:override_visibility',
'doc:export',
'workflow:view_all',
],
createdAt: '2026-09-08 00:00',
updatedAt: '2026-09-08 00:00',
},
{
id: 'role-director',
name: '研发总监',
code: 'DIRECTOR',
description: '负责研发中心整体项目决策、立项审查、权限监督与终审批准。',
isSystem: false,
permissions: [
'project:create',
'project:assign_lead',
'workflow:config_visibility',
'doc:override_visibility',
'doc:export',
'workflow:view_all',
],
createdAt: '2026-09-08 00:00',
updatedAt: '2026-09-08 00:00',
},
{
id: 'role-tech-lead',
name: '研发技术主管',
code: 'TECH_LEAD',
description: '负责具体研发管线的进度把控、文档责任人分配与技术审核。',
isSystem: false,
permissions: [
'project:create',
'project:assign_lead',
'doc:override_visibility',
'doc:export',
'workflow:view_all',
],
createdAt: '2026-09-08 00:00',
updatedAt: '2026-09-08 00:00',
},
{
id: 'role-engineer',
name: '方案工程师',
code: 'ENGINEER',
description: '负责医疗器械规格设计、受控正文编制、技术测算与推演。',
isSystem: false,
permissions: ['doc:export', 'workflow:view_all'],
createdAt: '2026-09-08 00:00',
updatedAt: '2026-09-08 00:00',
},
{
id: 'role-guest',
name: '外部审计/访客',
code: 'GUEST',
description: '仅允许通过单文档覆盖或公开授权查阅特定报告,默认不可见内部工作流。',
isSystem: false,
permissions: ['doc:export'],
createdAt: '2026-09-08 00:00',
updatedAt: '2026-09-08 00:00',
},
];
/**
* 判定用户是否具备指定全局功能权限
*/
export function hasPermission(
user: UserProfile,
roles: RoleDefinition[],
permKey: PermissionKey
): boolean {
// 1. 查找用户角色定义
const role = roles.find((r) => r.id === user.roleId);
// 2. 超级管理员拥有全量特权通配
if (role?.code === 'ADMIN' || role?.permissions.includes('perm:manage')) {
return true;
}
// 3. 用户个人级特例微调优先 (追加权限)
if (user.customPermissions?.includes(permKey)) {
return true;
}
// 4. 角色默认权限集合
if (role && role.permissions.includes(permKey)) {
return true;
}
return false;
}
/**
* 交付物可见性 5 步决策流水线
* 1. private 个人私密文档:严格隔离,仅创建者本人可见(绝对红线)
* 2. shared 项目协作资料:项目全员可见
* 3. 管理员特权:ADMIN / perm:manage 全员可见
* 4. 三权法定责任人保底:编制人、审核人、批准人必定可见
* 5. 单文档覆盖规则 (Override Priority):
* - designated_only: 仅责任人可见 (非责任人已在步骤4排除,直接拒绝)
* - role_whitelist: 仅白名单内角色可见
* - public: 全员可见
* 6. 回退继承工作流基线:用户角色需具备 workflow:view_all 权限
*/
export function isDeliverableVisible(
user: UserProfile,
roles: RoleDefinition[],
doc: DeliverableItem
): boolean {
// 步骤 1:个人私密文档 (private) 绝对隔离
if (doc.category === 'private') {
return doc.creatorId === user.id;
}
// 步骤 2:项目协作参考库 (shared) 对项目成员全员可见
if (doc.category === 'shared') {
return true;
}
// 步骤 3:管理员超级特权
const role = roles.find((r) => r.id === user.roleId);
if (role?.code === 'ADMIN' || role?.permissions.includes('perm:manage')) {
return true;
}
// 步骤 4:法定三权责任人保底 (防止指派后本人看不见无法履责)
const isAssignee =
doc.author === user.name ||
doc.reviewer === user.name ||
doc.approver === user.name;
if (isAssignee) {
return true;
}
// 步骤 5:单文档覆盖策略判定
const rule = doc.visibilityRule;
if (rule && rule.mode !== 'inherit') {
// 5.1 特例人员白名单 (特聘临床专家等)
if (rule.allowedUserIds?.includes(user.id)) {
return true;
}
switch (rule.mode) {
case 'designated_only':
// 步骤 4 已经排除了非责任人,因此此处直接拒绝
return false;
case 'role_whitelist':
// 判定当前用户全局角色是否在白名单中
return Boolean(user.roleId && rule.allowedRoleIds?.includes(user.roleId));
case 'public':
// 放宽为全员公开可见
return true;
default:
break;
}
}
// 步骤 6:回退继承工作流默认基线规则
return hasPermission(user, roles, 'workflow:view_all');
}
/**
* 批量过滤大盘交付物
*/
export function filterVisibleDeliverablesWithRBAC(
deliverables: DeliverableItem[],
user: UserProfile,
roles: RoleDefinition[]
): DeliverableItem[] {
return deliverables.filter((doc) => isDeliverableVisible(user, roles, doc));
}
/**
* 核心业务动作权限拦截判定
*/
export function canPerformDocAction(
action: 'assign_lead' | 'override_visibility' | 'create_project',
user: UserProfile,
roles: RoleDefinition[]
): boolean {
switch (action) {
case 'create_project':
return hasPermission(user, roles, 'project:create');
case 'assign_lead':
return hasPermission(user, roles, 'project:assign_lead');
case 'override_visibility':
return (
hasPermission(user, roles, 'doc:override_visibility') ||
hasPermission(user, roles, 'perm:manage')
);
default:
return false;
}
}