feat(rbac): implement permission evaluation engine and unit test suite
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
committed by
weichen
co-authored by
Cursor
parent
aa9636c10f
commit
bd52108c8a
@@ -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']
|
||||
);
|
||||
});
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user