From 2346edec4d42a94994d4433a34ebcbea0d86892f Mon Sep 17 00:00:00 2001 From: Marvin Lindner Date: Thu, 9 Apr 2026 11:29:17 +0200 Subject: [PATCH 1/2] fix: skip rescan-on-download when no malware scanner is bound --- .../configuration/Registration.java | 3 +- .../ReadAttachmentsHandler.java | 7 +- .../ReadAttachmentsHandlerTest.java | 76 ++++++++++++++++++- 3 files changed, 82 insertions(+), 4 deletions(-) diff --git a/cds-feature-attachments/src/main/java/com/sap/cds/feature/attachments/configuration/Registration.java b/cds-feature-attachments/src/main/java/com/sap/cds/feature/attachments/configuration/Registration.java index b04f726e9..7ecf07d97 100644 --- a/cds-feature-attachments/src/main/java/com/sap/cds/feature/attachments/configuration/Registration.java +++ b/cds-feature-attachments/src/main/java/com/sap/cds/feature/attachments/configuration/Registration.java @@ -160,7 +160,8 @@ public void eventHandlers(CdsRuntimeConfigurer configurer) { new EndTransactionMalwareScanRunner(null, null, malwareScanner, runtime); configurer.eventHandler( new ReadAttachmentsHandler( - attachmentService, new AttachmentStatusValidator(), scanRunner, persistenceService)); + attachmentService, new AttachmentStatusValidator(), scanRunner, persistenceService, + scanClient != null)); } else { logger.debug( "No application service is available. Application service event handlers will not be registered."); diff --git a/cds-feature-attachments/src/main/java/com/sap/cds/feature/attachments/handler/applicationservice/ReadAttachmentsHandler.java b/cds-feature-attachments/src/main/java/com/sap/cds/feature/attachments/handler/applicationservice/ReadAttachmentsHandler.java index 810152ec3..7f98bc15b 100644 --- a/cds-feature-attachments/src/main/java/com/sap/cds/feature/attachments/handler/applicationservice/ReadAttachmentsHandler.java +++ b/cds-feature-attachments/src/main/java/com/sap/cds/feature/attachments/handler/applicationservice/ReadAttachmentsHandler.java @@ -77,18 +77,21 @@ public class ReadAttachmentsHandler implements EventHandler { private final AttachmentStatusValidator statusValidator; private final AsyncMalwareScanExecutor scanExecutor; private final PersistenceService persistenceService; + private final boolean scannerAvailable; public ReadAttachmentsHandler( AttachmentService attachmentService, AttachmentStatusValidator statusValidator, AsyncMalwareScanExecutor scanExecutor, - PersistenceService persistenceService) { + PersistenceService persistenceService, + boolean scannerAvailable) { this.attachmentService = requireNonNull(attachmentService, "attachmentService must not be null"); this.statusValidator = requireNonNull(statusValidator, "statusValidator must not be null"); this.scanExecutor = requireNonNull(scanExecutor, "scanExecutor must not be null"); this.persistenceService = requireNonNull(persistenceService, "persistenceService must not be null"); + this.scannerAvailable = scannerAvailable; } @Before @@ -174,7 +177,7 @@ private void verifyStatus(Path path, Attachments attachment) { "In verify status for content id {} and status {}", attachment.getContentId(), currentStatus); - if (needsScan(currentStatus, attachment.getScannedAt())) { + if (scannerAvailable && needsScan(currentStatus, attachment.getScannedAt())) { if (StatusCode.CLEAN.equals(currentStatus)) { transitionToScanning(path.target().entity(), attachment); } diff --git a/cds-feature-attachments/src/test/java/com/sap/cds/feature/attachments/handler/applicationservice/ReadAttachmentsHandlerTest.java b/cds-feature-attachments/src/test/java/com/sap/cds/feature/attachments/handler/applicationservice/ReadAttachmentsHandlerTest.java index 0e70d7380..6c24fa20e 100644 --- a/cds-feature-attachments/src/test/java/com/sap/cds/feature/attachments/handler/applicationservice/ReadAttachmentsHandlerTest.java +++ b/cds-feature-attachments/src/test/java/com/sap/cds/feature/attachments/handler/applicationservice/ReadAttachmentsHandlerTest.java @@ -82,7 +82,8 @@ void setup() { attachmentService, attachmentStatusValidator, asyncMalwareScanExecutor, - persistenceService); + persistenceService, + true); readEventContext = mock(CdsReadEventContext.class); } @@ -423,6 +424,79 @@ void emptyContentIdAndEmptyContentReturnNullContent() { assertThat(attachment.getContent()).isNull(); } + @Test + void scannerNotAvailable_staleCleanAttachmentIsNotRescanned() { + var handlerWithoutScanner = + new ReadAttachmentsHandler( + attachmentService, + attachmentStatusValidator, + asyncMalwareScanExecutor, + persistenceService, + false); + mockEventContext(Attachment_.CDS_NAME, mock(CqnSelect.class)); + var attachment = Attachments.create(); + attachment.setContentId("some ID"); + attachment.setContent(mock(InputStream.class)); + attachment.setStatus(StatusCode.CLEAN); + attachment.setScannedAt(Instant.now().minus(4, ChronoUnit.DAYS)); + + handlerWithoutScanner.processAfter(readEventContext, List.of(attachment)); + + verifyNoInteractions(asyncMalwareScanExecutor); + verifyNoInteractions(persistenceService); + assertThat(attachment.getStatus()).isEqualTo(StatusCode.CLEAN); + } + + @Test + void scannerNotAvailable_cleanAttachmentWithNullScannedAtIsNotRescanned() { + var handlerWithoutScanner = + new ReadAttachmentsHandler( + attachmentService, + attachmentStatusValidator, + asyncMalwareScanExecutor, + persistenceService, + false); + mockEventContext(Attachment_.CDS_NAME, mock(CqnSelect.class)); + var attachment = Attachments.create(); + attachment.setContentId("some ID"); + attachment.setContent(mock(InputStream.class)); + attachment.setStatus(StatusCode.CLEAN); + attachment.setScannedAt(null); + + handlerWithoutScanner.processAfter(readEventContext, List.of(attachment)); + + verifyNoInteractions(asyncMalwareScanExecutor); + verifyNoInteractions(persistenceService); + assertThat(attachment.getStatus()).isEqualTo(StatusCode.CLEAN); + } + + @Test + void scannerNotAvailable_unscannedAttachmentStillFailsValidation() { + var handlerWithoutScanner = + new ReadAttachmentsHandler( + attachmentService, + attachmentStatusValidator, + asyncMalwareScanExecutor, + persistenceService, + false); + mockEventContext(Attachment_.CDS_NAME, mock(CqnSelect.class)); + var attachment = Attachments.create(); + attachment.setContentId("some ID"); + attachment.setContent(mock(InputStream.class)); + attachment.setStatus(StatusCode.UNSCANNED); + doThrow(AttachmentStatusException.class) + .when(attachmentStatusValidator) + .verifyStatus(StatusCode.UNSCANNED); + + List attachments = List.of(attachment); + assertThrows( + AttachmentStatusException.class, + () -> handlerWithoutScanner.processAfter(readEventContext, attachments)); + + verifyNoInteractions(asyncMalwareScanExecutor); + verifyNoInteractions(persistenceService); + } + private void mockEventContext(String entityName, CqnSelect select) { var serviceEntity = runtime.getCdsModel().findEntity(entityName); when(readEventContext.getTarget()).thenReturn(serviceEntity.orElseThrow()); From cb2a768eec24f3d05a64eeaa494794e5e697abfe Mon Sep 17 00:00:00 2001 From: Marvin Lindner Date: Thu, 9 Apr 2026 11:30:32 +0200 Subject: [PATCH 2/2] spotless --- .../cds/feature/attachments/configuration/Registration.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/cds-feature-attachments/src/main/java/com/sap/cds/feature/attachments/configuration/Registration.java b/cds-feature-attachments/src/main/java/com/sap/cds/feature/attachments/configuration/Registration.java index 7ecf07d97..9f5d72e93 100644 --- a/cds-feature-attachments/src/main/java/com/sap/cds/feature/attachments/configuration/Registration.java +++ b/cds-feature-attachments/src/main/java/com/sap/cds/feature/attachments/configuration/Registration.java @@ -160,7 +160,10 @@ public void eventHandlers(CdsRuntimeConfigurer configurer) { new EndTransactionMalwareScanRunner(null, null, malwareScanner, runtime); configurer.eventHandler( new ReadAttachmentsHandler( - attachmentService, new AttachmentStatusValidator(), scanRunner, persistenceService, + attachmentService, + new AttachmentStatusValidator(), + scanRunner, + persistenceService, scanClient != null)); } else { logger.debug(