feat(authz): 重写权限模板分配与班级授权边界

拆分能力、分配范围和业务关系,固定管理员能力并收紧组织级写入口。
补齐内容、课表、设备会话和实时通道授权,兼容旧库分配及历史快照。
重写权限分配预览,按实际可操作范围展示维护入口,合并学生入口并独立学年升级页。
增加授权攻击矩阵及静态门禁,更新隔离回归与开发契约。

验证:授权矩阵 23 段通过,Node 24 下 Web 63/63,类型检查、lint 与文档严格构建通过。
This commit is contained in:
LoveChengke committed 2026-10-31 09:40:23 +08:00
1 parent 682818dd4a
commit 66be3a63b1
115 files changed
+4463 -2747

No files matched your search

+5 -15
View File
@@ -1563,28 +1563,18 @@ async function main() {
`含 homework:write=${teacher2ResetPermissions.includes('homework:write')}`,
);
// 管理员可以分配权限组,但始终保留两个管理入口
const assignAdmin = await api(`/permissions/users/${adminMember?.id}/groups`, {
method: 'PUT',
token: adminToken,
body: { groupIds: permissionGroupId ? [permissionGroupId] : [] },
body: { assignments: [] },
});
const fixedAdmin = await api('/permissions/me', { token: adminToken });
record(
'管理员可以分配权限组(PUT /users/:id/groups → 200)',
Boolean(adminMember) &&
assignAdmin.status === 200 &&
Array.isArray(assignAdmin.payload?.data?.permissions) &&
assignAdmin.payload.data.permissions.includes('permission:manage') &&
assignAdmin.payload.data.permissions.includes('system:manage'),
`status=${assignAdmin.status} 响应=${assignAdmin.payload?.message ?? ''}`,
'管理员固定全集并拒绝分配模板',
assignAdmin.status === 400 && fixedAdmin.payload?.data?.permissions.includes('teacher:manage'),
`status=${assignAdmin.status}`,
);
await api(`/permissions/users/${adminMember?.id}/groups`, {
method: 'PUT',
token: adminToken,
body: { groupIds: [] },
});
// 内置权限组不可删除:先找一个 builtin 的(**演示库当前没有内置组**,此时只报事实、不算失败)
const groupsList = await api('/permissions/groups', { token: adminToken });
const builtinGroup = (groupsList.payload?.data ?? []).find((item) => item.builtin === true) ?? null;