diff --git a/db_migrations/phase5_material_remark_rbac.sql b/db_migrations/phase5_material_remark_rbac.sql new file mode 100644 index 0000000..b0f61d9 --- /dev/null +++ b/db_migrations/phase5_material_remark_rbac.sql @@ -0,0 +1,136 @@ +-- ============================================================================= +-- 基础信息 · 附件备注的读写权限修复 +-- +-- 现象 +-- 主管(SUPERVISOR)在基础信息里填「产品图备注 / 说明书备注」,点确定后 +-- 刷新页面又是空的 —— 看起来像「保存不了」。 +-- +-- 根因:写通、读断,且读侧引用的是**两个从未注册过的权限码** +-- 写侧:PUT /inbound/base/ 的 field_to_perm 里**没有**这两个字段, +-- 落入「不在映射中 → 默认允许」的兜底分支 → 直接落库。**保存是成功的。** +-- 读侧:列表/搜索接口用 apply_strict_rbac 按 field_permissions.py 过滤, +-- 该映射要求 material_list:productImageRemark / material_list:manualLinkRemark。 +-- 而这两个码 **在 sys_element 里根本不存在**,也从未授予任何角色 —— +-- 除 SUPER_ADMIN(走 material_list:* 通配符绕过)外,**没有任何人能读到**。 +-- 于是:填了 → 存进去了 → 回显被抹成 null → 看起来没保存。 +-- +-- 本次修复(读写双向) +-- 一、读侧:注册这两个元素,并授予与 material_list:files **完全相同的 6 个角色** +-- —— 逻辑是「能看图的人,就应该能看备注」。 +-- 二、写侧:新建 material_list:remark_edit,只授予 3 个核心管理角色。 +-- 后端同时把这两个字段显式纳入 field_to_perm 映射,废止那个兜底分支。 +-- +-- --------------------------------------------------------------------------- +-- ★ 为什么写侧要单独建码,而不复用 material_list:operation +-- material_list:operation 授予了 INBOUND / OUTBOUND / SUPERVISOR / +-- WAREHOUSE_MGR / SUPER_ADMIN 共 5 个角色,比业务要求的 3 个更宽。 +-- 复用会让出库员、入库员也能改备注,达不到「只有核心管理角色可改」。 +-- +-- ★ 为什么码用冒号形式(material_list:remark_edit) +-- 本系统有两类权限码:动作类(无冒号,如 outbound_return)与字段类 +-- (冒号形式,如 material_list:files)。这是**字段级**权限,与 +-- material_list:* 家族保持一致,便于在权限管理界面归组。 +-- ⚠ 它只用于 base.py 里的**精确匹配**(perm_code in user_permissions), +-- **切勿用 @permission_required('material_list:remark_edit') 包裹路由** —— +-- _expand_operation_perms() 会做前缀桥接,凡持有 material_list:operation +-- 的角色都会被放行,等于把刚收紧的口子又捅开。 +-- +-- 幂等:全部带存在性判断,可重复执行。 +-- 执行:docker exec -i inventory_db psql -U test -d inventory_system < 本文件 +-- ============================================================================= + +BEGIN; + +-- --------------------------------------------------------------------------- +-- 一、读侧:注册两个字段权限元素(此前从未注册,是「幽灵权限」) +-- --------------------------------------------------------------------------- +INSERT INTO sys_element (menu_code, name, code, element_type) +SELECT 'material_list', v.name, v.code, 'element' +FROM (VALUES + ('产品图备注', 'material_list:productImageRemark'), + ('说明书备注', 'material_list:manualLinkRemark') +) AS v(name, code) +WHERE NOT EXISTS (SELECT 1 FROM sys_element e WHERE e.code = v.code); + +-- 授予与 material_list:files 完全相同的 6 个角色 +INSERT INTO sys_role_permission (role_code, target_code, type, company_name) +SELECT r.role_code, e.code, 'element', NULL +FROM (VALUES ('material_list:productImageRemark'), ('material_list:manualLinkRemark')) AS e(code) +CROSS JOIN (VALUES + ('INBOUND'), ('OUTBOUND'), ('SALES'), + ('SUPERVISOR'), ('WAREHOUSE_MGR'), ('SUPER_ADMIN') +) AS r(role_code) +WHERE NOT EXISTS ( + SELECT 1 FROM sys_role_permission x + WHERE x.role_code = r.role_code AND x.target_code = e.code AND x.type = 'element' +); + +-- --------------------------------------------------------------------------- +-- 二、写侧:新建「附件备注编辑」权限码,只授予 3 个核心管理角色 +-- --------------------------------------------------------------------------- +INSERT INTO sys_element (menu_code, name, code, element_type) +SELECT 'material_list', '附件备注编辑(产品图/说明书)', 'material_list:remark_edit', 'element' +WHERE NOT EXISTS (SELECT 1 FROM sys_element WHERE code = 'material_list:remark_edit'); + +INSERT INTO sys_role_permission (role_code, target_code, type, company_name) +SELECT r.role_code, 'material_list:remark_edit', 'element', NULL +FROM (VALUES ('SUPER_ADMIN'), ('SUPERVISOR'), ('WAREHOUSE_MGR')) AS r(role_code) +WHERE NOT EXISTS ( + SELECT 1 FROM sys_role_permission x + WHERE x.role_code = r.role_code + AND x.target_code = 'material_list:remark_edit' + AND x.type = 'element' +); + +COMMIT; + + +-- ============================================================================= +-- 执行后核对 +-- ============================================================================= +SELECT '=== 1) 三个权限元素已注册(应 3 行)===' AS "核对项"; +SELECT code, name, menu_code, element_type FROM sys_element + WHERE code IN ('material_list:productImageRemark','material_list:manualLinkRemark','material_list:remark_edit') + ORDER BY code; + +SELECT '=== 2) 读权限:应各 6 个角色,与 material_list:files 一致 ===' AS "核对项"; +SELECT target_code AS 权限码, count(*) AS 角色数, + string_agg(role_code, ', ' ORDER BY role_code) AS 角色 + FROM sys_role_permission + WHERE target_code IN ('material_list:files','material_list:productImageRemark','material_list:manualLinkRemark') + AND type = 'element' + GROUP BY target_code ORDER BY target_code; + +SELECT '=== 3) 写权限:应 3 个角色(SUPER_ADMIN / SUPERVISOR / WAREHOUSE_MGR)===' AS "核对项"; +SELECT role_code FROM sys_role_permission + WHERE target_code = 'material_list:remark_edit' AND type = 'element' ORDER BY role_code; + +SELECT '=== 4) 三组角色集合是否完全对齐(应全为 true)===' AS "核对项"; +SELECT + (SELECT bool_and(a = b) FROM ( + SELECT + (SELECT string_agg(role_code,',' ORDER BY role_code) FROM sys_role_permission + WHERE target_code='material_list:files' AND type='element') AS a, + (SELECT string_agg(role_code,',' ORDER BY role_code) FROM sys_role_permission + WHERE target_code='material_list:productImageRemark' AND type='element') AS b + ) t) AS 产品图备注读权与files一致, + (SELECT bool_and(a = b) FROM ( + SELECT + (SELECT string_agg(role_code,',' ORDER BY role_code) FROM sys_role_permission + WHERE target_code='material_list:files' AND type='element') AS a, + (SELECT string_agg(role_code,',' ORDER BY role_code) FROM sys_role_permission + WHERE target_code='material_list:manualLinkRemark' AND type='element') AS b + ) t) AS 说明书备注读权与files一致; + + +-- ============================================================================= +-- 回滚段 +-- ============================================================================= +-- BEGIN; +-- DELETE FROM sys_role_permission WHERE target_code = 'material_list:remark_edit'; +-- DELETE FROM sys_element WHERE code = 'material_list:remark_edit'; +-- DELETE FROM sys_role_permission +-- WHERE target_code IN ('material_list:productImageRemark','material_list:manualLinkRemark'); +-- DELETE FROM sys_element +-- WHERE code IN ('material_list:productImageRemark','material_list:manualLinkRemark'); +-- COMMIT;