Files
Agent-System/web/src/engine/rbac.ts
T
weichenandCursor a56d033d3c feat(web): fail-closed capabilities and admin roles from server API
Stop persisting roles to localStorage as authority; AdminSettingsModal reads
and writes role matrices via /api/v1/admin/roles; hasPermission denies when
capabilities are not loaded from /auth/me.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-13 02:50:22 +00:00

213 lines
6.3 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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',
},
];
/**
* 判定用户是否具备指定全局功能权限。
* capabilities 来自服务端 /auth/me 时为唯一权威来源(fail closed)。
*/
export function hasPermission(
_user: UserProfile,
_roles: RoleDefinition[],
permKey: PermissionKey,
capabilities?: string[]
): boolean {
if (capabilities === undefined) {
return false;
}
if (capabilities.includes('perm:manage')) {
return true;
}
return capabilities.includes(permKey);
}
/**
* 交付物可见性 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:回退继承工作流默认基线规则(文档可见性仍用角色矩阵作 UI 展示兜底)
return hasPermission(user, roles, 'workflow:view_all', capabilitiesFromRole(roles, user.roleId));
}
/**
* 批量过滤大盘交付物
*/
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[],
capabilities?: string[]
): boolean {
switch (action) {
case 'create_project':
return hasPermission(user, roles, 'project:create', capabilities);
case 'assign_lead':
return hasPermission(user, roles, 'project:assign_lead', capabilities);
case 'override_visibility':
return (
hasPermission(user, roles, 'doc:override_visibility', capabilities) ||
hasPermission(user, roles, 'perm:manage', capabilities)
);
default:
return false;
}
}
/** 从角色定义提取 capabilities(测试与文档可见性兜底用)。 */
export function capabilitiesFromRole(roles: RoleDefinition[], roleId?: string): string[] {
if (!roleId) return [];
return roles.find((r) => r.id === roleId)?.permissions ?? [];
}