From aa7e15087ba4efc4cf9b48bdc5647e82e3516033 Mon Sep 17 00:00:00 2001 From: let5sne Date: Tue, 14 Jul 2026 12:54:39 +0800 Subject: [PATCH] fix(aihr): protect demo sop review bypass --- .../dromara/aihr/controller/AihrSopController.java | 12 +++++++++++- .../aihr/service/AihrSopSeedServiceTest.java | 14 ++++++++++++++ docs/BRD_IMPLEMENTATION_AUDIT.md | 1 + scripts/demo-check.sh | 2 ++ 4 files changed, 28 insertions(+), 1 deletion(-) diff --git a/backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/controller/AihrSopController.java b/backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/controller/AihrSopController.java index 9d48465e..581ff8b3 100644 --- a/backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/controller/AihrSopController.java +++ b/backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/controller/AihrSopController.java @@ -4,6 +4,7 @@ import cn.dev33.satoken.annotation.SaCheckLogin; import cn.dev33.satoken.annotation.SaCheckRole; import cn.dev33.satoken.annotation.SaIgnore; import cn.dev33.satoken.annotation.SaMode; +import cn.dev33.satoken.stp.StpUtil; import lombok.RequiredArgsConstructor; import org.dromara.aihr.domain.AihrSopDto.AnswerFeedbackItemResponse; import org.dromara.aihr.domain.AihrSopDto.AnswerFeedbackRequest; @@ -52,6 +53,7 @@ import java.util.List; public class AihrSopController { private static final String HR_OPERATOR_ROLE = "hr_operator"; + private static final String DEMO_VERIFY_SOURCE = "verify_demo_questions"; private final AihrSopSeedService sopSeedService; private final AihrUploadQueueService uploadQueueService; @@ -80,7 +82,15 @@ public class AihrSopController { @SaCheckLogin @PostMapping("/search") public R search(@RequestBody SearchRequest request) { - return R.ok(sopSeedService.search(request)); + return R.ok(sopSeedService.search(restrictDemoVerificationSource(request))); + } + + private SearchRequest restrictDemoVerificationSource(SearchRequest request) { + if (request == null || !DEMO_VERIFY_SOURCE.equals(request.source()) + || StpUtil.hasRole(TenantConstants.SUPER_ADMIN_ROLE_KEY)) { + return request; + } + return new SearchRequest(request.queryText(), request.category(), request.position(), "knowledge_search", request.limit()); } @GetMapping("/position-sop") diff --git a/backend/ruoyi-modules/ruoyi-aihr/src/test/java/org/dromara/aihr/service/AihrSopSeedServiceTest.java b/backend/ruoyi-modules/ruoyi-aihr/src/test/java/org/dromara/aihr/service/AihrSopSeedServiceTest.java index c04121a7..b3e4b6a2 100644 --- a/backend/ruoyi-modules/ruoyi-aihr/src/test/java/org/dromara/aihr/service/AihrSopSeedServiceTest.java +++ b/backend/ruoyi-modules/ruoyi-aihr/src/test/java/org/dromara/aihr/service/AihrSopSeedServiceTest.java @@ -139,6 +139,20 @@ public class AihrSopSeedServiceTest { assertTrue(code.contains("reviewSearch(id, request, operator)")); } + @Test + @Tag("dev") + public void demoVerificationSourceIsRestrictedToSuperAdmin() throws Exception { + Path source = Path.of("src/main/java/org/dromara/aihr/controller/AihrSopController.java"); + if (!Files.exists(source)) { + source = Path.of("ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/controller/AihrSopController.java"); + } + String code = Files.readString(source); + + assertTrue(code.contains("DEMO_VERIFY_SOURCE = \"verify_demo_questions\"")); + assertTrue(code.contains("StpUtil.hasRole(TenantConstants.SUPER_ADMIN_ROLE_KEY)")); + assertTrue(code.contains("restrictDemoVerificationSource(request)")); + } + @Test @Tag("dev") public void answerFeedbackReviewUsesAuthenticatedOperatorForAuditTrail() throws Exception { diff --git a/docs/BRD_IMPLEMENTATION_AUDIT.md b/docs/BRD_IMPLEMENTATION_AUDIT.md index 03e6331d..4a7ad7c5 100644 --- a/docs/BRD_IMPLEMENTATION_AUDIT.md +++ b/docs/BRD_IMPLEMENTATION_AUDIT.md @@ -249,3 +249,4 @@ - 2026-07-14 发布预检卫生复核:根目录 `node_modules/` 确认为构建工具生成物,已加入 `.gitignore`,避免依赖目录阻塞发布检查;`RELEASE_VERIFY_REMOTE_MATCH=true` 的预检仍因两份用户未提交的 Figma 文档退出,说明未绕过“发布批次必须干净”的保护。 - 2026-07-14 BRD G3 匿名入口复核:扫描 AIHR 全部控制器后,当前仅保留两个 `@SaIgnore` 方法:公开移动首页 `/api/aihr/mobile/home/{role}` 和岗位-SOP 导航 `/api/knowledge/position-sop`;训练、搜索、上传、复盘、模型、组织同步及音频读取均有登录或角色门禁,未发现新的匿名业务数据入口。本轮无新增代码修复。 - 2026-07-14 BRD M5 导出指标语义复核:`formal_org_login_count` 原名会把按安全手机号映射统计的在职组织身份误读为登录次数;现统一更名为 `formal_org_identity_count`,统计 SQL 不变,CSV 契约、严格校验和验证脚本同步更新。AIHR 目标测试 `34/34`、CSV 契约测试 `5/5` 通过;不改变真实试点数据门槛,也未发布生产。 +- 2026-07-14 BRD G3/M5 评审样本绕过复核:SOP 搜索的 `source=verify_demo_questions` 原由客户端直接控制,普通登录用户可借此跳过评审样本写入,稀释正式可用率分母;现仅超级管理员保留该内部验证来源,其他请求自动归一为普通 `knowledge_search`,并补预检标记与回归测试。未修改生产环境。 diff --git a/scripts/demo-check.sh b/scripts/demo-check.sh index 693c3684..799a3f52 100755 --- a/scripts/demo-check.sh +++ b/scripts/demo-check.sh @@ -372,6 +372,8 @@ contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/service contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/controller/AihrInterviewController.java "@SaCheckLogin" contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/controller/AihrSpeechController.java "@SaCheckLogin" contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/controller/AihrCaseController.java "@SaCheckLogin" +contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/controller/AihrSopController.java "restrictDemoVerificationSource(request)" +contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/controller/AihrSopController.java "StpUtil.hasRole(TenantConstants.SUPER_ADMIN_ROLE_KEY)" contains mobile-uni/src/pages/candidate/interview/index.vue "startCandidateInterview" contains mobile-uni/src/pages/candidate/materials/index.vue "uploadCandidateMaterial" contains mobile-uni/src/pages/candidate/progress/index.vue "buildCandidateFlow"