diff --git a/muse-cloud/muse-module-member/muse-module-member-server/src/main/java/cn/iocoder/muse/module/member/application/account/AccountExportServiceImpl.java b/muse-cloud/muse-module-member/muse-module-member-server/src/main/java/cn/iocoder/muse/module/member/application/account/AccountExportServiceImpl.java index 447b5819..a28f7532 100644 --- a/muse-cloud/muse-module-member/muse-module-member-server/src/main/java/cn/iocoder/muse/module/member/application/account/AccountExportServiceImpl.java +++ b/muse-cloud/muse-module-member/muse-module-member-server/src/main/java/cn/iocoder/muse/module/member/application/account/AccountExportServiceImpl.java @@ -53,7 +53,6 @@ public class AccountExportServiceImpl implements AccountExportService { private static final String STATUS_COMPLETED = "completed"; private static final String STATUS_FAILED = "failed"; private static final String STATUS_IDEMPOTENT_HIT = "idempotent_hit"; - private static final int SAFE_DATE_RANGE_DAYS = 31; private static final Set EXPORT_TYPES = Set.of("profile", "usage", "security_events", "purchases", "licenses"); private static final Set DATE_RANGE_REQUIRED_TYPES = Set.of("usage", "security_events"); private static final Set SENSITIVE_EXPORT_TYPES = Set.of("security_events", "usage"); @@ -212,10 +211,9 @@ public class AccountExportServiceImpl implements AccountExportService { if (startDate != null && endDate != null && startDate.isAfter(endDate)) { throw invalidParamException("dateRange.startDate 不能晚于 endDate"); } - long rangeDays = startDate == null || endDate == null ? 0 : endDate.toEpochDay() - startDate.toEpochDay() + 1; - if (sensitiveExportType(exportType) && rangeDays > SAFE_DATE_RANGE_DAYS) { - // OpenAPI 当前没有 step-up 证明字段,大范围高敏导出必须 fail-closed,避免静默放开敏感数据导出。 - throw invalidParamException("高敏导出超过 {} 天需要 step-up,当前请求缺少 step-up 证明", SAFE_DATE_RANGE_DAYS); + if (sensitiveExportType(exportType)) { + // OpenAPI 只声明高敏导出需要 step-up,但当前请求体没有可信 step-up proof 字段;服务端必须失败关闭。 + throw invalidParamException("{} 高敏导出当前缺少 step-up 证明,暂不可执行", exportType); } return new ExportRequest(exportType, startDate, endDate, desensitizationRule); } diff --git a/muse-cloud/muse-module-member/muse-module-member-server/src/test/java/cn/iocoder/muse/module/member/application/account/AccountExportServiceTest.java b/muse-cloud/muse-module-member/muse-module-member-server/src/test/java/cn/iocoder/muse/module/member/application/account/AccountExportServiceTest.java index f445fb8c..11c0f0c1 100644 --- a/muse-cloud/muse-module-member/muse-module-member-server/src/test/java/cn/iocoder/muse/module/member/application/account/AccountExportServiceTest.java +++ b/muse-cloud/muse-module-member/muse-module-member-server/src/test/java/cn/iocoder/muse/module/member/application/account/AccountExportServiceTest.java @@ -9,7 +9,6 @@ import cn.iocoder.muse.module.member.controller.app.account.vo.AccountExportTask import cn.iocoder.muse.module.member.dal.dataobject.account.AccountCommandDO; import cn.iocoder.muse.module.member.dal.dataobject.account.AccountDownloadCredentialDO; import cn.iocoder.muse.module.member.dal.dataobject.account.AccountExportTaskDO; -import cn.iocoder.muse.module.member.dal.dataobject.account.MemberSecurityEventDO; import cn.iocoder.muse.module.member.dal.dataobject.user.MemberUserDO; import cn.iocoder.muse.module.member.dal.mysql.account.AccountDownloadCredentialMapper; import cn.iocoder.muse.module.member.dal.mysql.account.AccountExportTaskMapper; @@ -57,8 +56,8 @@ class AccountExportServiceTest extends BaseMockitoUnitTest { private AccountFileServiceFacade fileServiceFacade; @Test - void should_createQueuedSecurityExport_andWriteSensitiveSecurityEvent_whenFileServiceUnavailable() { - AccountExportTaskCreateReqVO reqVO = exportReq("security_events", "cmd-export-1"); + void should_createQueuedProfileExport_whenFileServiceUnavailable() { + AccountExportTaskCreateReqVO reqVO = exportReq("profile", "cmd-export-1"); when(memberUserMapper.selectById(1001L)).thenReturn(user(1001L)); when(commandService.buildRequestHash(any())).thenReturn("hash-export"); when(commandService.reserveCommand(any())).thenReturn(null); @@ -77,17 +76,12 @@ class AccountExportServiceTest extends BaseMockitoUnitTest { assertNotEquals("pending", result.getStatus()); verify(exportTaskMapper).insert(argThat((AccountExportTaskDO task) -> Long.valueOf(1001L).equals(task.getAccountUserId()) - && "security_events".equals(task.getExportType()) + && "profile".equals(task.getExportType()) && "queued".equals(task.getStatus()) && task.getFileRef() == null && Boolean.FALSE.equals(task.getSourceBlocked()))); verify(downloadCredentialMapper, never()).insert(any(AccountDownloadCredentialDO.class)); - verify(securityEventMapper).insert(argThat((MemberSecurityEventDO event) -> - Long.valueOf(1001L).equals(event.getAccountUserId()) - && "sensitive_export".equals(event.getEventType()) - && "warning".equals(event.getSeverity()) - && event.getDeviceInfo() != null - && !event.getDeviceInfo().contains("credential"))); + verifyNoInteractions(securityEventMapper); verify(commandService).recordSucceeded(any(), argThat(snapshot -> snapshot.contains("\"status\":\"queued\""))); verify(auditService).record(argThat(req -> "appCreateExportTask".equals(req.getOperationId()) @@ -114,10 +108,21 @@ class AccountExportServiceTest extends BaseMockitoUnitTest { } @Test - void should_failClosed_whenSecurityExportRangeRequiresStepUpButNoStepUpFieldExists() { + void should_failClosed_whenSecurityEventsExportLacksStepUpProof() { AccountExportTaskCreateReqVO reqVO = exportReq("security_events", "cmd-export-1"); - reqVO.getDateRange().setStartDate(LocalDate.of(2026, 1, 1)); - reqVO.getDateRange().setEndDate(LocalDate.of(2026, 5, 28)); + when(memberUserMapper.selectById(1001L)).thenReturn(user(1001L)); + + ServiceException exception = assertThrows(ServiceException.class, + () -> exportService.appCreateExportTask(1001L, reqVO)); + + assertEquals(400, exception.getCode()); + assertTrue(exception.getMessage().contains("step-up")); + verifyNoInteractions(commandService, fileServiceFacade, exportTaskMapper); + } + + @Test + void should_failClosed_whenUsageExportLacksStepUpProof() { + AccountExportTaskCreateReqVO reqVO = exportReq("usage", "cmd-export-1"); when(memberUserMapper.selectById(1001L)).thenReturn(user(1001L)); ServiceException exception = assertThrows(ServiceException.class,