基线:
origin/test@ bc983dba(分支 feature/pc-rbac-plan,只读,未改任何代码)
取证:主会话亲跑 + 两个执行层子代理 tee 落盘原始输出(/tmp/rbac-verify-A.log 1598 行 · /tmp/rbac-verify-B.log),关键项主会话复核
标签:云机环境 · 代码与 test 库只读核查 · 非生产验收证据
底子已经有一半,但不能直接拿来给客户用。
role_menu_permissions(role, menu_key) 表 + 菜单注册表 + 前端菜单显隐 + admin 专属配置页。platform_staff 一个;不能新建角色组、不能给账号挂角色组。app/models/user.py:35 role = Column(String(20)) —— 一个账号只有一个 role 字符串app/models/user.py:186-191 chk_role:role IN ('admin','org_admin','h5_user','org_seat','channel','platform_staff')migrations/versions/phase56_002_user_roles_channel.py:23-26(4 值 → 6 值)app/models/role_menu_permission.py:13 role_menu_permissions,主键 (role, menu_key)app/core/menu_registry.py:14 CONSOLE_ROLES = ["admin", "platform_staff"]app/services/menu_permission_service.py:73-106 set_role_menus —— role 不在 CONSOLE_ROLES 直接 400menu_registry.py:16-52 共 20 项(docstring 写「16 项」已过时),分 平台管理 7 / 结算管理 9 / 设置管理 3 / 导演台 1menu_registry.py:65 system.role_permissions主会话亲跑:
require_menu( 在 app/api 使用处:2(全在 monitoring.py)
require_role("admin") 使用处:89 处 / 17 个文件(对照组)
require_admin / _require_admin 都只是 require_role("admin") 的变量别名(api_v1.py:24、admin_flywheel.py:33、admin_devices.py:20),全仓没有独立定义settlement/backend/app/auth/menu_guard.py:25 自带一份 require_menu,读核心库的 role_menu_permissions(只读镜像 app/models/core_readonly.py)grep -rn "require_menu(" settlement/backend/app/api | wc -l = 44);settlement 的 /api/admin 接口 100% 走 require_menurequire_zhaoyang_permission 35 处实调,17 个权限常量(zhaoyang_permissions.py:79-112)ROLE_PERMISSION_MAP admin=全部 17 项;platform_staff = MOBILE_STAFF 13 项(含收款确认、门店激活、渠道审核等写操作)require_zhaoyang_permission 实调 0 处(只有定义本身)app/auth/jwt_local.py:33-64 issue_access_token:payload role 是单值字符串,带 linked_org_id / device_id / device_epoch,无 permissions claimjwt_local.py:115-165 verify_token 把单值包成 roles: [role](长度恒 1)app/api/auth.py:62-82 POST /api/auth/login 无角色白名单web/app/login/page.tsx:134/137 登录后统一去 /change-password 或 /dashboardweb/app/(dashboard)/layout.tsx:133-139 只查是否登录app/api/admin_users.py 8 个路由全部 Depends(require_role("admin")),无一用 require_menuapp/services/user_mgmt_service.py:132-133 只允许 admin / org_admin / platform_staffweb/app/(dashboard)/admin/users/new/page.tsx:29,105-133 三张固定角色卡片AdminConsoleShell.tsx:36-37 注释「bug2(长官 2026-08-08)· 用户管理菜单屏蔽…直接输 URL /admin/users 仍可用」web/app/(dashboard)/admin/AdminConsoleShell.tsx:29-91 navGroups 20 项,HREF_TO_MENU_KEY:104-131,isItemVisible:134-140,调 /api/me/menus:173-176web/middleware.ts 不存在(find 0 命中;对照组 find 同目录 .ts/.tsx 共 480 个)allowed_menus|me/menus 全前端只在 AdminConsoleShell 一个文件消费web/app/(dashboard)/admin/role-permissions/page.tsx(234 行)只有 admin(恒全勾禁改)和 platform_staff 两列SELECT role,count(*) FROM users GROUP BY role → 0 行;对照组 pg_class WHERE relname='users' → 1pg_stat_user_tables:users n_live_tup=423 · role_menu_permissions n_live_tup=33 → 0 行是 RLS 造成的假 0,不是空表role_menu_permissions、wecom_service_groups(企微客服分组,无关)→ 库里没有任何角色组表linked_org_id 0 处(对照组 org_id 命中 10 个文件)app/api/admin_seats.py 有按 org 过滤,但前提是先过 require_role("admin")——是 admin 的筛选条件,不是数据隔离app/models/audit.py:24-59 AuthAuditLog 只记 5 类登录事件;user_id 是被操作人,操作人只在 detail JSON 里user_mgmt_service.py:205-238 update_user_profile(能改 role / is_active / linked_org_id)不写任何审计migrations/versions/phase57_001_role_menu_permissions.py upgrade 只种 admin(全开)+ platform_staff(结算 7 项 + 个人设置)docs/superpowers/plans/specs/phase58-dynamic-role-management-spec-v0.1.md(7-11 立项):长官原话「角色要能自定义,不是固化 3 个」roles + users.role 改成外键 + 角色=名称+菜单集合 + 合并权限配置页进角色管理*phase58* 只命中这 1 个 spec,无 tech/impl → 未开工app/api/admin_rumor_questions.py:84/109/131/161 题库审核 4 个接口(列表/通过/修改/驳回)只要求登录,Depends(get_current_user),无任何角色校验;router 级别也无 dependencies(main.py:269 直接 include)monitoring.py 同类管理接口用了 require_menu(3 处命中)| Phase 58 做法 | 问题(按上面 verify) |
|---|---|
users.role 从 CHECK 改为外键指向 roles 表 |
role 串被 require_role 89 处 + JWT + 结算 menu_guard + ROLE_PERMISSION_MAP 深度依赖,改语义影响面最大 |
| 自定义角色 = 泛化的 platform_staff | platform_staff 在结算端自带 13 项操作权限(含收款确认/门店激活),客户账号挂上去等于把这些写操作一起放出去 |
| 可授权池只有结算 7 项 | 跟「把功能开放给客户」冲突,需长官重新定开放清单 |
数据层(新增,不改存量)
- console_role_groups:id · name · description · data_scope(all / own_org,见 3.4)· is_active · created_by · created_at/updated_at
- console_role_group_menus:(group_id, menu_key) 主键
- users.console_role_group_id:外键,可空
- users.role 新增一个值 console_user(CHECK 6→7,照 phase56_002 先例)。客户账号一律用这个 role:
- 它不在 ROLE_PERMISSION_MAP 里 → 结算端 17 项操作权限默认为空,不会像 platform_staff 那样自带写权限
- 它不在任何 require_role("admin") 里 → 默认什么都进不去,只靠角色组授权放行(默认拒绝)
- 存量 admin / platform_staff / role_menu_permissions 原样保留,零迁移、零行为变化
判定层(两个后端各改一处)
- 核心 app/auth/middleware.py:121 require_menu 与结算 settlement/backend/app/auth/menu_guard.py:25 require_menu 同步改:
admin 直接放行 → 若用户挂了角色组,查 console_role_group_menus → 否则走老逻辑查 role_menu_permissions(platform_staff 兼容)
- 每次请求实时查库(现状就是这样),改了角色组权限立即生效,不用重新登录;停用角色组或账号也立即生效
- 结算服务读核心库要新增两张只读镜像表(core_readonly.py)+ 数据库给结算账号授 SELECT 权限 —— 授权要 root 做,需提前约
- /api/me/menus 返回角色组菜单,前端菜单显隐自动跟上(D8 已是 allowed set 驱动)
接口层(工作量主体,按开放清单逐个做)
- 每个要开放给客户的菜单,把对应后端接口从 require_role("admin") 改成「admin 或 有该菜单权限」
- 结算 9 项已经是 require_menu,但同时挂着 zhaoyang 权限;给 console_user 要按菜单映射出对应的 zhaoyang 权限(Phase 58 §5 已设想「按 allowed_menus 映射细粒度权限」)
- 平台管理组的菜单(机构管理、席位活动等)目前全是 require_role("admin"),开哪个改哪个,不开的不动
- 永不进可授权池:角色组管理本身、用户管理、权限配置、审计日志、系统监控(防客户给自己提权)
前端
- 恢复「用户管理」菜单(8-08 被摘),新建账号时可选「客户账号」+ 选角色组;列表加角色组列、启用/停用
- 新增「角色组管理」页:列表 + 新建/编辑(名称 + 按 平台/结算/设置 分组勾选菜单)+ 停用;Phase 57 的权限配置页合并进来(Phase 58 已拍过这条)
- 加页面级拦截:AdminConsoleShell 按当前路由查 allowed set,无权限显示「无权限」,不再能手输 URL 看页面壳
审计
- 角色组的增删改、账号角色组变更、启停用 → 写审计,记操作人 id(补上 D11 的空洞)
| 批 | 内容 | 依赖 |
|---|---|---|
| A 底座 | 两张表 + users 加列 + CHECK 加 console_user 迁移;两端 require_menu 改造;角色组 CRUD 接口;审计;contract 测试 | 结算只读授权需 root |
| B 前端 | 角色组管理页 + 用户管理页改造 + 页面级无权限拦截 | A |
| C 开放功能 | 按长官定的开放清单逐个改接口鉴权(+ 数据范围过滤) | A、长官清单 |
| D 验收 | test 环境端到端:admin 建角色组「XX 客户」勾 N 个菜单 → 建客户账号挂上 → 登录只见 N 个菜单 → 手输其他 URL 显示无权限 → 调未授权接口 403 → 停用后立即 401/403 → 存量 admin/platform_staff 零回归 | A-C |
A、B 工作量相对固定;C 的大小完全取决于开放哪些菜单、要不要只看自己的数据。
角色组只能控制「能进哪个菜单」。D10 已证实后台所有列表都是全平台数据。
例如把「结算列表」开给某个机构客户,他会看到所有机构的结算单。
如果客户只能看自己的数据,每个开放的菜单都要额外做按机构过滤(角色组 data_scope=own_org + 账号 linked_org_id + 接口过滤),这部分是按菜单逐个做的活,不能靠角色组一次解决。
另:D14 题库审核接口没有角色校验,建议马上单独修(改动约 4 行 + 测试),和本需求分开发。