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 new file mode 100644 index 000000000..f49233c81 --- /dev/null +++ b/RuoYi-Vue/ruoyi-admin/src/test/java/com/ruoyi/web/controller/train/DmsAccessSafetyPackageAMigrationContractTest.java @@ -0,0 +1,64 @@ +package com.ruoyi.web.controller.train; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import org.junit.Test; + +/** + * 014 DMS 门禁安全与住宿权限 Migration 契约测试 (Package A) + */ +public class DmsAccessSafetyPackageAMigrationContractTest +{ + @Test + public void migration014CompliesWithPackageASecurityRequirements() throws Exception + { + String sql = new String(Files.readAllBytes(findMigration()), StandardCharsets.UTF_8).toLowerCase(); + + // 必须包含住宿分配权限与门禁管理页面注册 + assertContains(sql, + "2060", "'auth'", "'train/auth/index'", "train:access:query", + "2264", "train:accommodation:add", + "2191", "train:access:query", + "2192", "train:access:manage", + "2193", "train:access:reconcile"); + + // 必须授权给 train_admin (role_id = 3) + assertContains(sql, + "select 3, 2060", + "select 3, 2191", + "select 3, 2192", + "select 3, 2193", + "select 3, 2264"); + + // 必须包含后置断言防护 + assertContains(sql, + "do $$", "raise exception", "train:accommodation:remove"); + + // 严禁赋予 train_admin 任何删除住宿权限或远程开锁 + assertFalse("must not grant remove permissions", sql.contains("select 3, 2265") || sql.contains("train:accommodation:remove' into")); + assertFalse("must not contain remote unlock", sql.contains("remoteunlock") || sql.contains("remote-unlock")); + } + + private Path findMigration() + { + for (Path path = Paths.get("").toAbsolutePath(); path != null; path = path.getParent()) + { + Path candidate = path.resolve("sql/system/kingbase/014_dms_access_safety_and_accommodation_permissions.sql"); + if (Files.isRegularFile(candidate)) return candidate; + } + throw new IllegalStateException("014_dms_access_safety_and_accommodation_permissions.sql migration not found from working directory"); + } + + private void assertContains(String sql, String... tokens) + { + for (String token : tokens) + { + assertTrue("missing migration token: " + token, sql.contains(token)); + } + } +} diff --git a/RuoYi-Vue/sql/system/kingbase/014_dms_access_safety_and_accommodation_permissions.sql b/RuoYi-Vue/sql/system/kingbase/014_dms_access_safety_and_accommodation_permissions.sql new file mode 100644 index 000000000..6ce8e57c3 --- /dev/null +++ b/RuoYi-Vue/sql/system/kingbase/014_dms_access_safety_and_accommodation_permissions.sql @@ -0,0 +1,121 @@ +-- ============================================================================= +-- Migration: 014 DMS Access Safety & Accommodation Permissions +-- Target: KingbaseES, schema public +-- Scope: +-- 1. Register '门禁管理' menu (2060) under '教学设施' (2154) +-- 2. Register '分配住宿' button permission (2264) under '住宿管理' (2261) +-- 3. Ensure minimal operational access permissions (2191, 2192, 2193) exist +-- 4. Grant minimal necessary permissions to train_admin role (role_id = 3): +-- - 2060 (门禁管理页面) +-- - 2191 (train:access:query) +-- - 2192 (train:access:manage) +-- - 2193 (train:access:reconcile) +-- - 2264 (train:accommodation:add) +-- 5. Safety Guard: Explicitly verify train_admin DOES NOT have accommodation:remove or remote unlock +-- 6. Post-migration verification assertion block +-- ============================================================================= + +BEGIN; + +-- 1. Register '门禁管理' menu under '教学设施' (parent_id = 2154) +INSERT INTO public.sys_menu + (menu_id, menu_name, parent_id, order_num, path, component, query, is_frame, is_cache, + menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) +SELECT 2060, '门禁管理', 2154, 9, 'auth', 'train/auth/index', '', 1, 0, + 'C', '0', '0', 'train:access:query', 'lock', 'admin', CURRENT_TIMESTAMP, '', NULL, '门禁授权与设备状态管理' +WHERE NOT EXISTS (SELECT 1 FROM public.sys_menu WHERE menu_id = 2060); + +-- Update menu 2060 in case it existed with different params +UPDATE public.sys_menu +SET parent_id = 2154, path = 'auth', component = 'train/auth/index', perms = 'train:access:query', + status = '0', visible = '0', update_by = 'admin', update_time = CURRENT_TIMESTAMP +WHERE menu_id = 2060; + +-- 2. Ensure access permissions (2191, 2192, 2193) exist under parent 2060 +INSERT INTO public.sys_menu + (menu_id, menu_name, parent_id, order_num, path, component, query, is_frame, is_cache, + menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) +SELECT 2191, '门禁与资源映射查看', 2060, 1, '#', '', '', 1, 0, + 'F', '0', '0', 'train:access:query', '#', 'admin', CURRENT_TIMESTAMP, '', NULL, '门禁授权与外部资源映射查看权限' +WHERE NOT EXISTS (SELECT 1 FROM public.sys_menu WHERE menu_id = 2191); + +INSERT INTO public.sys_menu + (menu_id, menu_name, parent_id, order_num, path, component, query, is_frame, is_cache, + menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) +SELECT 2192, '门禁资源映射维护', 2060, 2, '#', '', '', 1, 0, + 'F', '0', '0', 'train:access:manage', '#', 'admin', CURRENT_TIMESTAMP, '', NULL, '门禁凭据下发、注销与映射维护权限' +WHERE NOT EXISTS (SELECT 1 FROM public.sys_menu WHERE menu_id = 2192); + +INSERT INTO public.sys_menu + (menu_id, menu_name, parent_id, order_num, path, component, query, is_frame, is_cache, + menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) +SELECT 2193, '门禁手动对账触发', 2060, 3, '#', '', '', 1, 0, + 'F', '0', '0', 'train:access:reconcile', '#', 'admin', CURRENT_TIMESTAMP, '', NULL, '门禁权限手动对账触发执行权限' +WHERE NOT EXISTS (SELECT 1 FROM public.sys_menu WHERE menu_id = 2193); + +-- 3. Register '分配住宿' permission under '住宿管理' (parent_id = 2261) +INSERT INTO public.sys_menu + (menu_id, menu_name, parent_id, order_num, path, component, query, is_frame, is_cache, + menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) +SELECT 2264, '分配住宿', 2261, 3, '#', '', '', 1, 0, + 'F', '0', '0', 'train:accommodation:add', '#', 'admin', CURRENT_TIMESTAMP, '', NULL, '分配学员住宿安排权限' +WHERE NOT EXISTS (SELECT 1 FROM public.sys_menu WHERE menu_id = 2264); + +-- 4. Grant minimal operational permissions to train_admin role (role_id = 3) +-- 门禁管理页面 +INSERT INTO public.sys_role_menu (role_id, menu_id) +SELECT 3, 2060 +WHERE NOT EXISTS (SELECT 1 FROM public.sys_role_menu WHERE role_id = 3 AND menu_id = 2060); + +-- 门禁与资源映射查看 +INSERT INTO public.sys_role_menu (role_id, menu_id) +SELECT 3, 2191 +WHERE NOT EXISTS (SELECT 1 FROM public.sys_role_menu WHERE role_id = 3 AND menu_id = 2191); + +-- 门禁资源维护与下发/注销 +INSERT INTO public.sys_role_menu (role_id, menu_id) +SELECT 3, 2192 +WHERE NOT EXISTS (SELECT 1 FROM public.sys_role_menu WHERE role_id = 3 AND menu_id = 2192); + +-- 门禁手动对账 +INSERT INTO public.sys_role_menu (role_id, menu_id) +SELECT 3, 2193 +WHERE NOT EXISTS (SELECT 1 FROM public.sys_role_menu WHERE role_id = 3 AND menu_id = 2193); + +-- 分配住宿 +INSERT INTO public.sys_role_menu (role_id, menu_id) +SELECT 3, 2264 +WHERE NOT EXISTS (SELECT 1 FROM public.sys_role_menu WHERE role_id = 3 AND menu_id = 2264); + +-- 5. Post-migration Verification Assertions +DO $$ +BEGIN + -- Verify 2060 exists and active + IF (SELECT count(*) FROM public.sys_menu WHERE menu_id = 2060 AND perms = 'train:access:query' AND status = '0') <> 1 THEN + RAISE EXCEPTION 'Menu 2060 verification failed'; + END IF; + + -- Verify 2191, 2192, 2193 exist + IF (SELECT count(*) FROM public.sys_menu WHERE menu_id IN (2191, 2192, 2193)) <> 3 THEN + RAISE EXCEPTION 'Access button permissions (2191, 2192, 2193) verification failed'; + END IF; + + -- Verify 2264 exists and active + IF (SELECT count(*) FROM public.sys_menu WHERE menu_id = 2264 AND perms = 'train:accommodation:add' AND status = '0') <> 1 THEN + RAISE EXCEPTION 'Menu 2264 verification failed'; + END IF; + + -- Verify role_id = 3 has 2060, 2191, 2192, 2193, 2264 + IF (SELECT count(*) FROM public.sys_role_menu WHERE role_id = 3 AND menu_id IN (2060, 2191, 2192, 2193, 2264)) <> 5 THEN + RAISE EXCEPTION 'Role assignment for train_admin verification failed'; + END IF; + + -- Security Guard: Verify train_admin (role_id=3) DOES NOT have any forbidden remove or mutation permissions + 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 IN ('train:course:remove', 'train:teacher:remove', 'train:student:remove', 'train:accommodation:remove')) > 0 THEN + RAISE EXCEPTION 'Security violation: train_admin role must not have remove permissions'; + END IF; +END $$; + +COMMIT;