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

验证:授权矩阵 23 段通过,Node 24 下 Web 63/63,类型检查、lint 与文档严格构建通过。
2026-10-31 09:40:23 +08:00

77 lines
8.1 KiB
Markdown
Raw Permalink 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.
---
title: 角色与权限
description: 能力目录、岗位模板、分配范围与业务关系的授权契约。
---
# 角色与权限
授权依次检查能力、该能力的数据范围、数据行上的业务关系。服务端统一入口为 `lib/authz/`,路由预检不能替代服务断言。共享目录 `PERMISSION_META` 提供分类、风险、关系提示、合法范围、标签和默认角色;完整键目录读取源码,不在文档维护固定数量。
## 身份与模板
| 主体 | 生效能力 |
| -------------------- | -------------------------------------------------- |
| ADMIN | 固定拥有全部目录键及全校范围,忽略权限组 |
| TEACHER | 显式分配模板的可分配能力并集;管理员专用键永久无效 |
| 未显式分配的 TEACHER | 数据库教师默认模板与固定 TEACHING 范围 |
| CLASS_DEVICE | 空集;接口明确允许班级端并校验本班关系 |
学生是 Student 名单记录,没有账号。HEAD/SUBJECT 是班主任和科任关系,级长/部长是岗位模板,不增加 User.role。权限管理只允许 ADMIN;旧 ADMIN 默认模板保留存储但隐藏、忽略。
`PermissionGroup` 保存能力和新分配的建议范围,`UserPermissionGroup` 保存实际 `scope/scopeValue`。模板改建议范围不会改变已有分配;模板改能力会影响其成员。同一模板可分配不同范围,多条分配按键取并集。空分配恢复默认模板,界面不会将未分组成员自动转换成显式默认组。
## 能力目录与范围
能力分为管理员专用、班级级、组织只读。管理员专用能力包括组织与账号维护、权限/系统/数据库、全局周次、全校时间表、升级归档、安装包管理等;具体清单以 `PERMISSION_META.kind` 为准。`teacher:read` 和 `update:check` 为组织只读,忽略班级范围。其余班级级能力按分配范围求值。
| 范围 | 语义 |
| -------- | -------------------------- |
| TEACHING | 本人担任班主任或任课的班级 |
| HEAD | 仅本人担任班主任的班级 |
| GRADE | 指定一个年级 |
| DIVISION | 指定多个年级 |
| ALL | 全校 |
`parsePermissionScope` 统一校验范围。GRADE/DIVISION 必须选择已有年级或升级计划中的年级,范围值按逗号分隔、去重排序;其余范围值必须为空串。旧 SELF_CLASS 仅在迁移和旧快照恢复中映射为 TEACHING/HEAD,不是运行时枚举。
`loadAuthPrincipal` 在请求主体上缓存只读能力和分配快照,没有可变的“当前权限键”。`classIdsFor(user, key)` 和 `classWhere(user, key)` 强制传键,无键或无授权返回空集合;不会合并其他能力的范围。`classWhere` 过滤 Class 表主键,下游子表使用明确的 classId 条件。
## 关系与写入
| 操作 | 功能与关系 |
| --------------------------------------------- | -------------------------------------------------------------------------------- |
| 建班/删班/届别班号、班主任/任课分配、教师账号 | 管理员专用,记录审计;最后一个管理员不可降级或删除 |
| 班级账号 | class:account + 本班班主任,管理员例外;班级码只给班主任或管理员 |
| 班级学期设置/班级周次 | class:settings + 本班班主任,管理员例外 |
| 名单、日课表与课程写入 | 对应写键;TEACHING/HEAD 要求班主任,显式年级/部/全校范围允许范围内维护 |
| 学生/课表导入 | 导入键与相应写键同时满足;先校验全部班级再写,学生及任课导入使用事务 |
| 班级时间表写入 | schedule:write + 本班班主任;按存储 classId 校验,所有引用班级也要校验 |
| 全校时间表、全局周次 | 管理员;班级设置使用独立接口 |
| 作业/成绩写入 | assertContentWrite;有科目要求该科任教,无科目要求班主任;原科目与目标科目都检查 |
| 作业提交/未交名单 | homework:write + 同样的科目关系;班级端明确允许本班作业 |
| 通知/叫人 | 发布能力和范围,再检查班主任/任课关系;班级端限本班 |
| 通知删除 | notification:delete + 发布者/班主任/管理员 |
| 课件 | 本人文件、对应功能范围与本人授课班级共同约束,不赋予班主任同事文件访问权 |
ALL 也不能替代班主任、任课、发布者关系,只有 ADMIN 无条件绕过关系。班级端可代录作业,不能写成绩。跨班调班检查源班与目标班。事务中的断言使用同一事务连接,不能因事务路径省略授权。
成绩列表、考试、排名和集体分析都要求 grade:read 与对应班级范围。学生明细另要求 student:grade-query。关闭班级自助查询后,本班设备仍可获得集体聚合,个人明细、原始成绩、考试列表和排名拒绝。实时载荷同样受隐私开关保护,服务端执行稀疏样本和补集抑制。
## 实时与设备
班级房间为 `class:{id}:{channel}`,事件映射见 `SOCKET_EVENT_CHANNELS`。班级端加入本班全部通道;教师按各域读键及范围加入,core 由 class:read/schedule:read 决定。显式订阅再次校验通道,握手与 HTTP 共用数据库身份加载;权限/账号变更断开旧订阅。
心跳、进度和命令回执只接受 CLASS_DEVICE,deviceKey 必须属于会话班级。远控只排队允许的系统指令,自动化始终假执行。安装包登记/删除使用管理员 client:update;班级推送使用 client:update-push。下载要求本班班级端或有范围的 client:read。版本与文件名在请求校验和路径构造两处拒绝不安全路径段。
## 权限页与审计
权限页默认展示成员与范围,每条分配独立选择范围,保存前预览新增/移除能力、每键合并范围、班级列表、混合范围提示、班主任专属提示和风险标记。模板页仅提供可分配能力,旧管理员键显示为停用。组织只读能力标注“不限班级”。路由用 `meta.permission` 检查读取能力;维护页面额外用 `meta.menuActions` 声明可执行动作,动作须在至少一个可读班级生效才显示侧栏、搜索及页签,有部分功能可操作就保留入口。`permissions/me.actionScopes` 结合范围和班主任、任课、发布者关系提供动作班级;班级管理动作另用 `manageScopes`。纯查看页面按读取能力展示,服务端始终做最终判定。
学期周次维护入口要求全校周次写能力或至少一个可维护的本班设置范围。非全校维护者进入后默认选择可编辑班级,仍可切换查看授权范围内的其他班级。
目录、模板、成员、分配预览和审计的公开契约见[API](api.md)。模板/分配和账号敏感操作写入 PermissionAudit;审计不保存密码明文或哈希。迁移与快照兼容见[数据模型](data-model.md)。
## 验证
`pnpm check:authz` 扫描旧授权符号和缺少显式键的查询,并接入 lint。`pnpm verify:authz` 使用临时 SQLite 验证接管班级、管理员提权、跨键借范围、科目改挂、跨班时间表/周次/导入、成绩隐私、通道订阅、设备冒用和旧快照等攻击场景。其他专项回归保留生命周期、课表、课件、设备与升级覆盖。验证不触及真实班级、不访问外网、不执行系统指令。