diff --git a/RuoYi-Vue/ruoyi-admin/src/test/java/com/ruoyi/web/controller/train/DmsAccessSafetyPackageAMigrationContractTest.java b/RuoYi-Vue/ruoyi-admin/src/test/java/com/ruoyi/web/controller/train/DmsAccessSafetyPackageAMigrationContractTest.java index f49233c81..a0734cdb8 100644 --- a/RuoYi-Vue/ruoyi-admin/src/test/java/com/ruoyi/web/controller/train/DmsAccessSafetyPackageAMigrationContractTest.java +++ b/RuoYi-Vue/ruoyi-admin/src/test/java/com/ruoyi/web/controller/train/DmsAccessSafetyPackageAMigrationContractTest.java @@ -1,6 +1,7 @@ package com.ruoyi.web.controller.train; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import java.nio.charset.StandardCharsets; @@ -44,6 +45,42 @@ public class DmsAccessSafetyPackageAMigrationContractTest assertFalse("must not contain remote unlock", sql.contains("remoteunlock") || sql.contains("remote-unlock")); } + @Test + public void migration015PreservesAccommodationEditForTrainAdmin() throws Exception + { + Path migration015Path = null; + for (Path path = Paths.get("").toAbsolutePath(); path != null; path = path.getParent()) + { + Path candidate = path.resolve("sql/system/kingbase/015_dms_package_a_hardening.sql"); + if (Files.isRegularFile(candidate)) { + migration015Path = candidate; + break; + } + } + if (migration015Path == null) { + for (Path path = Paths.get("").toAbsolutePath(); path != null; path = path.getParent()) + { + Path candidate = path.resolve("RuoYi-Vue/sql/system/kingbase/015_dms_package_a_hardening.sql"); + if (Files.isRegularFile(candidate)) { + migration015Path = candidate; + break; + } + } + } + assertNotNull("015_dms_package_a_hardening.sql migration not found", migration015Path); + String sql = new String(Files.readAllBytes(migration015Path), StandardCharsets.UTF_8).toLowerCase(); + + // 必须确保授予或保留 2263 (train:accommodation:edit) + assertContains(sql, "select 3, 2263"); + // 绝不得包含对 2263 的 delete 语句 + assertFalse("015 严禁删除 train_admin 的 2263 权限", + java.util.regex.Pattern.compile("delete\\s+from\\s+public\\.sys_role_menu[^;]*?menu_id\\s*=\\s*2263").matcher(sql).find()); + // 必须断言 role 3 拥有 2263 + assertContains(sql, "where role_id = 3 and menu_id = 2263) <> 1"); + // 必须断言 role 3 严禁拥有 train:accommodation:remove + assertContains(sql, "perms = 'train:accommodation:remove') <> 0"); + } + private Path findMigration() { for (Path path = Paths.get("").toAbsolutePath(); path != null; path = path.getParent()) diff --git a/RuoYi-Vue/sql/system/kingbase/015_dms_package_a_hardening.sql b/RuoYi-Vue/sql/system/kingbase/015_dms_package_a_hardening.sql index 1c7f8ed68..9573dde8e 100644 --- a/RuoYi-Vue/sql/system/kingbase/015_dms_package_a_hardening.sql +++ b/RuoYi-Vue/sql/system/kingbase/015_dms_package_a_hardening.sql @@ -1,7 +1,7 @@ -- ============================================================================ -- Migration: 015_dms_package_a_hardening.sql -- Description: Package A.1 DMS 一致性加固与最小权限拆分 --- 1. 权限最小化拆分:新增 2194 (train:access:credential),从 train_admin 移除 2192 (train:access:manage) 与 2263 (train:accommodation:remove) +-- 1. 权限最小化拆分:新增 2194 (train:access:credential),从 train_admin 移除 2192 (train:access:manage) 并保证保留 2263 (train:accommodation:edit) -- 2. 外部操作状态加固:确保 train_external_operation 支持 DISPATCHING 与 AMBIGUOUS -- 3. 幂等与断言校验 -- ============================================================================ @@ -33,9 +33,10 @@ WHERE NOT EXISTS (SELECT 1 FROM public.sys_role_menu WHERE role_id = 3 AND menu_ DELETE FROM public.sys_role_menu WHERE role_id = 3 AND menu_id = 2192; --- 从 train_admin (role_id = 3) 回收 2263 (train:accommodation:remove,物理删除住宿历史权限) -DELETE FROM public.sys_role_menu -WHERE role_id = 3 AND menu_id = 2263; +-- 确保培训管理员 (role_id = 3) 拥有 2263 (train:accommodation:edit,办理入住/退宿/调房) +INSERT INTO public.sys_role_menu (role_id, menu_id) +SELECT 3, 2263 +WHERE NOT EXISTS (SELECT 1 FROM public.sys_role_menu WHERE role_id = 3 AND menu_id = 2263); @@ -64,9 +65,16 @@ BEGIN RAISE EXCEPTION 'Assertion failed: role 3 still has menu 2192 (train:access:manage)'; END IF; - -- 断言 4: train_admin (role_id = 3) 不得拥有 2263 (train:accommodation:remove) - IF (SELECT count(*) FROM public.sys_role_menu WHERE role_id = 3 AND menu_id = 2263) <> 0 THEN - RAISE EXCEPTION 'Assertion failed: role 3 has forbidden menu 2263 (train:accommodation:remove)'; + -- 断言 4: train_admin (role_id = 3) 必须拥有 2263 (train:accommodation:edit,办理入住/退宿/调房) + IF (SELECT count(*) FROM public.sys_role_menu WHERE role_id = 3 AND menu_id = 2263) <> 1 THEN + RAISE EXCEPTION 'Assertion failed: role 3 missing menu 2263 (train:accommodation:edit)'; + END IF; + + -- 断言 4b: train_admin (role_id = 3) 严禁拥有破坏性删除权限 train:accommodation:remove + IF (SELECT count(*) FROM public.sys_role_menu rm + JOIN public.sys_menu m ON m.menu_id = rm.menu_id + WHERE rm.role_id = 3 AND m.perms = 'train:accommodation:remove') <> 0 THEN + RAISE EXCEPTION 'Security violation: role 3 has forbidden permission train:accommodation:remove'; END IF; -- 断言 5: train_admin 拥有 2264 (train:accommodation:add)