Просмотр исходного кода

fix(e09): 报告删除接口补归属校验,防越权删除

Agnes Agent 1 день назад
Родитель
Сommit
dde41be731

+ 16 - 1
cfc-backend/src/main/java/com/etotem/cfc/controller/HealthReportController.java

@@ -763,11 +763,26 @@ public class HealthReportController {
     @Operation(summary = "删除健康报告")
     @PostMapping("/report/delete")
     public Result<String> deleteReport(
-            @RequestBody Map<String, Object> params) {
+            @RequestBody Map<String, Object> params,
+            @RequestAttribute("userId") Long userId) {
         Long reportId = ParamUtils.getLong(params.get("reportId"));
         if (reportId == null) {
             return Result.error("reportId不能为空");
         }
+        if (userId == null) {
+            return Result.error("用户未登录");
+        }
+        HealthReport report = healthReportService.getReportById(reportId);
+        if (report == null) {
+            return Result.error("报告不存在");
+        }
+        // E09 越权修复:仅上传者本人或家庭管理员可删除
+        boolean isOwner = report.getUserId() != null && report.getUserId().equals(userId);
+        boolean isAdmin = report.getFamilyId() != null
+                && reportMemberPermissionService.isFamilyAdmin(userId, report.getFamilyId());
+        if (!isOwner && !isAdmin) {
+            return Result.error("无权删除该报告");
+        }
         healthReportService.deleteReport(reportId);
         return Result.success("已删除");
     }

+ 114 - 0
cfc-backend/src/test/java/com/etotem/cfc/controller/HealthReportControllerDeleteAuthzTest.java

@@ -0,0 +1,114 @@
+package com.etotem.cfc.controller;
+
+import com.etotem.cfc.common.Result;
+import com.etotem.cfc.entity.HealthReport;
+import com.etotem.cfc.service.HealthReportService;
+import com.etotem.cfc.service.ReportMemberPermissionService;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import java.lang.reflect.Field;
+import java.util.HashMap;
+import java.util.Map;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+/**
+ * E09 越权修复契约单测:POST /api/health/report/delete 删除健康报告 的归属/权限校验。
+ *
+ * 契约(对应 HealthReportController.deleteReport):
+ * - userId 未注入 → Result.error("用户未登录")
+ * - reportId 不存在 → Result.error("报告不存在"),不触发 healthReportService.deleteReport
+ * - 上传者本人(report.userId == userId)→ Result.success("已删除")
+ * - 家庭管理员(report.familyId 匹配且 isFamilyAdmin=true)→ Result.success("已删除")
+ * - 其他登录用户(非本人、非管理员)→ Result.error("无权删除该报告"),不触发 deleteReport
+ *
+ * 测试不依赖数据库/外部服务:healthReportService / reportMemberPermissionService 全 mock;
+ * 不启 Spring 上下文,沿用 HealthPlanDeleteControllerTest 的直接调用模式。
+ */
+class HealthReportControllerDeleteAuthzTest {
+
+    private HealthReportController controller;
+    private HealthReportService healthReportService;
+    private ReportMemberPermissionService reportMemberPermissionService;
+
+    @BeforeEach
+    void setUp() throws Exception {
+        controller = new HealthReportController();
+        healthReportService = mock(HealthReportService.class);
+        reportMemberPermissionService = mock(ReportMemberPermissionService.class);
+        inject("healthReportService", healthReportService);
+        inject("reportMemberPermissionService", reportMemberPermissionService);
+    }
+
+    private void inject(String name, Object value) throws Exception {
+        Field f = controller.getClass().getDeclaredField(name);
+        f.setAccessible(true);
+        f.set(controller, value);
+    }
+
+    private Map<String, Object> params(Object reportId) {
+        Map<String, Object> p = new HashMap<>();
+        p.put("reportId", reportId);
+        return p;
+    }
+
+    private HealthReport report(Long reportId, Long ownerUserId, Long familyId) {
+        HealthReport r = new HealthReport();
+        r.setId(reportId);
+        r.setUserId(ownerUserId);
+        r.setFamilyId(familyId);
+        return r;
+    }
+
+    @Test
+    void 未登录_返回用户未登录不触发删除() {
+        Result<?> r = controller.deleteReport(params(1L), null);
+        assertEquals("用户未登录", r.getMessage());
+        verify(healthReportService, never()).deleteReport(anyLong());
+    }
+
+    @Test
+    void 报告不存在_返回报告不存在不触发删除() {
+        when(healthReportService.getReportById(99L)).thenReturn(null);
+        Result<?> r = controller.deleteReport(params(99L), 1L);
+        assertEquals("报告不存在", r.getMessage());
+        verify(healthReportService, never()).deleteReport(anyLong());
+    }
+
+    @Test
+    void 上传者本人_可删除自己报告() {
+        when(healthReportService.getReportById(100L)).thenReturn(report(100L, 10L, 7L));
+        Result<?> r = controller.deleteReport(params(100L), 10L);
+        assertEquals(200, r.getCode());
+        assertEquals("已删除", r.getData());
+        verify(healthReportService).deleteReport(100L);
+    }
+
+    @Test
+    void 家庭管理员_可删除家庭内报告() {
+        when(healthReportService.getReportById(100L)).thenReturn(report(100L, 10L, 7L));
+        when(reportMemberPermissionService.isFamilyAdmin(20L, 7L)).thenReturn(true);
+        Result<?> r = controller.deleteReport(params(100L), 20L);
+        assertEquals(200, r.getCode());
+        assertEquals("已删除", r.getData());
+        verify(healthReportService).deleteReport(100L);
+        verify(reportMemberPermissionService).isFamilyAdmin(20L, 7L);
+    }
+
+    @Test
+    void 非本人非管理员_拒绝删除() {
+        when(healthReportService.getReportById(100L)).thenReturn(report(100L, 10L, 7L));
+        when(reportMemberPermissionService.isFamilyAdmin(30L, 7L)).thenReturn(false);
+        Result<?> r = controller.deleteReport(params(100L), 30L);
+        assertEquals("无权删除该报告", r.getMessage());
+        assertNotEquals(200, r.getCode());
+        verify(healthReportService, never()).deleteReport(anyLong());
+    }
+}