根因(比上一轮报告的更深一层)
----
field_permissions.py 的读过滤要求两个权限码:
material_list:productImageRemark / material_list:manualLinkRemark
而这两个码**在 sys_element 里根本不存在**,也从未授予任何角色 —— 是「幽灵权限」。
除 SUPER_ADMIN(走 material_list:* 通配符绕过)外,没有任何人能通过读过滤。
配合写侧的「不在映射中 → 默认允许」兜底,形成:
谁都能写、除超管没人能读 —— 填了存进去了,回显却被抹成 null,
看起来就是「保存不了」。
本次改动
----
一、读侧:注册这两个元素,授予与 material_list:files **完全相同的 6 个角色**
(INBOUND / OUTBOUND / SALES / SUPERVISOR / WAREHOUSE_MGR / SUPER_ADMIN)
依据:备注依附于图片,「能看图的人就应该能看备注」。
二、写侧:新建 material_list:remark_edit,只授予 3 个核心管理角色
(SUPER_ADMIN / SUPERVISOR / WAREHOUSE_MGR)。
★ 为何不复用 material_list:operation:它授予了 5 个角色(含 INBOUND /
OUTBOUND),比业务要求的 3 个更宽,达不到「只有核心管理角色可改」。
⚠ 该码只做精确匹配,**切勿用 @permission_required 包裹** ——
_expand_operation_perms 会前缀桥接,把 material_list:operation 的持有者
一并放行,等于把刚收紧的口子又捅开。已在迁移与代码注释中双处警示。
幂等,可重复执行;脚本文本不含 psql 元命令,DataGrip 可直接整段执行。
137 lines
7.4 KiB
PL/PgSQL
137 lines
7.4 KiB
PL/PgSQL
-- =============================================================================
|
||
-- 基础信息 · 附件备注的读写权限修复
|
||
--
|
||
-- 现象
|
||
-- 主管(SUPERVISOR)在基础信息里填「产品图备注 / 说明书备注」,点确定后
|
||
-- 刷新页面又是空的 —— 看起来像「保存不了」。
|
||
--
|
||
-- 根因:写通、读断,且读侧引用的是**两个从未注册过的权限码**
|
||
-- 写侧:PUT /inbound/base/<id> 的 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;
|