Skip to content

Commit 80f5b43

Browse files
Fix usage of malware scanner with non-HANA storage (#557)
Co-authored-by: Markus Ofterdinger <[email protected]>
1 parent 4c77f3b commit 80f5b43

File tree

8 files changed

+54
-35
lines changed

8 files changed

+54
-35
lines changed

cds-feature-attachments/src/main/java/com/sap/cds/feature/attachments/handler/applicationservice/ReadAttachmentsHandler.java

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -95,11 +95,10 @@ void processAfter(CdsReadEventContext context, List<CdsData> data) {
9595
(path, element, value) -> {
9696
Attachments attachment = Attachments.of(path.target().values());
9797
InputStream content = attachment.getContent();
98-
boolean contentExists = nonNull(content);
99-
if (nonNull(attachment.getContentId()) || contentExists) {
100-
verifyStatus(path, attachment, contentExists);
98+
if (nonNull(attachment.getContentId())) {
99+
verifyStatus(path, attachment);
101100
Supplier<InputStream> supplier =
102-
contentExists
101+
nonNull(content)
103102
? () -> content
104103
: () -> attachmentService.readAttachment(attachment.getContentId());
105104
return new LazyProxyInputStream(supplier, statusValidator, attachment.getStatus());
@@ -147,19 +146,20 @@ private List<String> getAttachmentAssociations(
147146
return associationNames;
148147
}
149148

150-
private void verifyStatus(Path path, Attachments attachment, boolean contentExists) {
149+
private void verifyStatus(Path path, Attachments attachment) {
151150
if (areKeysEmpty(path.target().keys())) {
151+
String currentStatus = attachment.getStatus();
152152
logger.debug(
153153
"In verify status for content id {} and status {}",
154154
attachment.getContentId(),
155-
attachment.getStatus());
156-
if ((StatusCode.UNSCANNED.equals(attachment.getStatus())
157-
|| StatusCode.SCANNING.equals(attachment.getStatus()))
158-
&& contentExists) {
155+
currentStatus);
156+
if (StatusCode.UNSCANNED.equals(currentStatus)
157+
|| StatusCode.SCANNING.equals(currentStatus)
158+
|| currentStatus == null) {
159159
logger.debug(
160160
"Scanning content with ID {} for malware, has current status {}",
161161
attachment.getContentId(),
162-
attachment.getStatus());
162+
currentStatus);
163163
scanExecutor.scanAsync(path.target().entity(), attachment.getContentId());
164164
}
165165
statusValidator.verifyStatus(attachment.getStatus());

cds-feature-attachments/src/main/java/com/sap/cds/feature/attachments/service/handler/DefaultAttachmentsServiceHandler.java

Lines changed: 24 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,11 @@
1313
import com.sap.cds.feature.attachments.service.model.servicehandler.AttachmentRestoreEventContext;
1414
import com.sap.cds.services.changeset.ChangeSetListener;
1515
import com.sap.cds.services.handler.EventHandler;
16+
import com.sap.cds.services.handler.annotations.After;
1617
import com.sap.cds.services.handler.annotations.HandlerOrder;
1718
import com.sap.cds.services.handler.annotations.On;
1819
import com.sap.cds.services.handler.annotations.ServiceName;
20+
import java.util.Objects;
1921
import org.slf4j.Logger;
2022
import org.slf4j.LoggerFactory;
2123

@@ -35,33 +37,43 @@ public class DefaultAttachmentsServiceHandler implements EventHandler {
3537
private static final Logger logger =
3638
LoggerFactory.getLogger(DefaultAttachmentsServiceHandler.class);
3739

38-
private final EndTransactionMalwareScanProvider endTransactionMalwareScanProvider;
40+
private final EndTransactionMalwareScanProvider malwareScanProvider;
3941

40-
public DefaultAttachmentsServiceHandler(
41-
EndTransactionMalwareScanProvider endTransactionMalwareScanProvider) {
42-
this.endTransactionMalwareScanProvider = endTransactionMalwareScanProvider;
42+
public DefaultAttachmentsServiceHandler(EndTransactionMalwareScanProvider malwareScanProvider) {
43+
this.malwareScanProvider =
44+
Objects.requireNonNull(malwareScanProvider, "malwareScanProvider must not be null");
4345
}
4446

4547
@On
4648
@HandlerOrder(DEFAULT_ON)
47-
public void createAttachment(AttachmentCreateEventContext context) {
49+
void createAttachment(AttachmentCreateEventContext context) {
4850
logger.debug(
4951
"Default Attachment Service handler called for creating attachment for entity '{}'",
5052
context.getAttachmentEntity().getQualifiedName());
5153
String contentId = (String) context.getAttachmentIds().get(Attachments.ID);
5254
context.getData().setStatus(StatusCode.SCANNING);
53-
ChangeSetListener listener =
54-
endTransactionMalwareScanProvider.getChangeSetListener(
55-
context.getAttachmentEntity(), contentId);
56-
context.getChangeSetContext().register(listener);
5755
context.setIsInternalStored(true);
5856
context.setContentId(contentId);
5957
context.setCompleted();
6058
}
6159

60+
/**
61+
* After the attachment is created, register a {@link ChangeSetListener} to perform a malware scan
62+
* at the end of the transaction.
63+
*
64+
* @param context the attachment creation event context
65+
*/
66+
@After
67+
void afterCreateAttachment(AttachmentCreateEventContext context) {
68+
ChangeSetListener listener =
69+
malwareScanProvider.getChangeSetListener(
70+
context.getAttachmentEntity(), context.getContentId());
71+
context.getChangeSetContext().register(listener);
72+
}
73+
6274
@On
6375
@HandlerOrder(DEFAULT_ON)
64-
public void markAttachmentAsDeleted(AttachmentMarkAsDeletedEventContext context) {
76+
void markAttachmentAsDeleted(AttachmentMarkAsDeletedEventContext context) {
6577
logger.debug(
6678
"Default Attachment Service handler called for marking attachment as deleted with document id {}",
6779
context.getContentId());
@@ -72,7 +84,7 @@ public void markAttachmentAsDeleted(AttachmentMarkAsDeletedEventContext context)
7284

7385
@On
7486
@HandlerOrder(DEFAULT_ON)
75-
public void restoreAttachment(AttachmentRestoreEventContext context) {
87+
void restoreAttachment(AttachmentRestoreEventContext context) {
7688
logger.debug(
7789
"Default Attachment Service handler called for restoring attachment for timestamp {}",
7890
context.getRestoreTimestamp());
@@ -83,7 +95,7 @@ public void restoreAttachment(AttachmentRestoreEventContext context) {
8395

8496
@On
8597
@HandlerOrder(DEFAULT_ON)
86-
public void readAttachment(AttachmentReadEventContext context) {
98+
void readAttachment(AttachmentReadEventContext context) {
8799
logger.debug(
88100
"Default Attachment Service handler called for reading attachment with document id {}",
89101
context.getContentId());

cds-feature-attachments/src/main/java/com/sap/cds/feature/attachments/service/handler/transaction/EndTransactionMalwareScanRunner.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,8 +61,7 @@ private void startScanning(CdsEntity attachmentEntityToScan, String contentId) {
6161
runner.run(
6262
resourceCtx -> {
6363
// ensure that DB transaction is still active until the content is completely read
64-
// from InputStream and
65-
// scanned by malware scanner
64+
// from InputStream and scanned by malware scanner
6665
runtime
6766
.changeSetContext()
6867
.run(

cds-feature-attachments/src/test/java/com/sap/cds/feature/attachments/handler/applicationservice/ReadAttachmentsHandlerTest.java

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -128,9 +128,10 @@ void dataFilledWithDeepStructure() throws IOException {
128128
item4.setId("item id4");
129129
item4.setAttachments(List.of(attachmentWithStreamAsContent));
130130
var attachmentWithStreamContentButWithoutContentId = Attachments.create();
131-
attachmentWithStreamContentButWithoutContentId.setContent(mock(InputStream.class));
131+
var inputMock = mock(InputStream.class);
132+
attachmentWithStreamContentButWithoutContentId.setContent(inputMock);
132133
var item5 = Items.create();
133-
item5.setId("item id4");
134+
item5.setId("item id5");
134135
item5.setAttachments(List.of(attachmentWithStreamContentButWithoutContentId));
135136
var root1 = RootTable.create();
136137
root1.setItemTable(List.of(item2, item1, item4, item5));
@@ -147,8 +148,7 @@ void dataFilledWithDeepStructure() throws IOException {
147148
assertThat(attachmentWithoutContentField.getContent()).isNull();
148149
assertThat(attachmentWithStreamAsContent.getContent())
149150
.isInstanceOf(LazyProxyInputStream.class);
150-
assertThat(attachmentWithStreamContentButWithoutContentId.getContent())
151-
.isInstanceOf(LazyProxyInputStream.class);
151+
assertThat(attachmentWithStreamContentButWithoutContentId.getContent()).isEqualTo(inputMock);
152152
verifyNoInteractions(attachmentService);
153153
}
154154
}
@@ -227,7 +227,7 @@ void scannerCalledForUnscannedAttachments() {
227227
}
228228

229229
@Test
230-
void scannerNotCalledForUnscannedAttachmentsIfNoContentProvided() {
230+
void scannerCalledForUnscannedAttachmentsIfNoContentProvided() {
231231
mockEventContext(Attachment_.CDS_NAME, mock(CqnSelect.class));
232232
var attachment = Attachments.create();
233233
attachment.setContentId("some ID");
@@ -236,7 +236,8 @@ void scannerNotCalledForUnscannedAttachmentsIfNoContentProvided() {
236236

237237
cut.processAfter(readEventContext, List.of(attachment));
238238

239-
verifyNoInteractions(asyncMalwareScanExecutor);
239+
verify(asyncMalwareScanExecutor)
240+
.scanAsync(readEventContext.getTarget(), attachment.getContentId());
240241
}
241242

242243
@Test

cds-feature-attachments/src/test/java/com/sap/cds/feature/attachments/service/handler/AttachmentsServiceImplHandlerTest.java

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ void classHasCorrectAnnotation() {
103103
@Test
104104
void createMethodHasCorrectAnnotation() throws NoSuchMethodException {
105105
var createMethod =
106-
cut.getClass().getMethod("createAttachment", AttachmentCreateEventContext.class);
106+
cut.getClass().getDeclaredMethod("createAttachment", AttachmentCreateEventContext.class);
107107
var onAnnotation = createMethod.getAnnotation(On.class);
108108
var handlerOrderAnnotation = createMethod.getAnnotation(HandlerOrder.class);
109109

@@ -114,7 +114,7 @@ void createMethodHasCorrectAnnotation() throws NoSuchMethodException {
114114
@Test
115115
void restoreAttachmentMethodHasCorrectAnnotation() throws NoSuchMethodException {
116116
var updateMethod =
117-
cut.getClass().getMethod("restoreAttachment", AttachmentRestoreEventContext.class);
117+
cut.getClass().getDeclaredMethod("restoreAttachment", AttachmentRestoreEventContext.class);
118118
var onAnnotation = updateMethod.getAnnotation(On.class);
119119
var handlerOrderAnnotation = updateMethod.getAnnotation(HandlerOrder.class);
120120

@@ -126,7 +126,8 @@ void restoreAttachmentMethodHasCorrectAnnotation() throws NoSuchMethodException
126126
void deleteMethodHasCorrectAnnotation() throws NoSuchMethodException {
127127
var deleteMethod =
128128
cut.getClass()
129-
.getMethod("markAttachmentAsDeleted", AttachmentMarkAsDeletedEventContext.class);
129+
.getDeclaredMethod(
130+
"markAttachmentAsDeleted", AttachmentMarkAsDeletedEventContext.class);
130131
var onAnnotation = deleteMethod.getAnnotation(On.class);
131132
var handlerOrderAnnotation = deleteMethod.getAnnotation(HandlerOrder.class);
132133

@@ -136,7 +137,8 @@ void deleteMethodHasCorrectAnnotation() throws NoSuchMethodException {
136137

137138
@Test
138139
void readMethodHasCorrectAnnotation() throws NoSuchMethodException {
139-
var readMethod = cut.getClass().getMethod("readAttachment", AttachmentReadEventContext.class);
140+
var readMethod =
141+
cut.getClass().getDeclaredMethod("readAttachment", AttachmentReadEventContext.class);
140142
var onAnnotation = readMethod.getAnnotation(On.class);
141143
var handlerOrderAnnotation = readMethod.getAnnotation(HandlerOrder.class);
142144

@@ -156,6 +158,7 @@ void malwareScannerRegisteredForEndOfTransaction() {
156158
ChangeSetContextImpl.open(false);
157159

158160
cut.createAttachment(createContext);
161+
cut.afterCreateAttachment(createContext);
159162

160163
verify(malwareScanProvider).getChangeSetListener(entity, "contentId");
161164
}

doc/CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/).
1414

1515
### Fixed
1616

17+
- Fixed an issue where the Malware Scanning Service is not triggered after uploading an Attachment, if the target storage is other than the default DB storage. (#557)
18+
1719
## Version 1.2.0 - 2025-08-26
1820

1921
### Added

storage-targets/cds-feature-attachments-fs/src/main/java/com/sap/cds/feature/attachments/fs/handler/FSAttachmentsServiceHandler.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ void createAttachment(AttachmentCreateEventContext context) throws IOException {
5656
String contentId = (String) context.getAttachmentIds().get(Attachments.ID);
5757

5858
MediaData data = context.getData();
59-
data.setStatus(StatusCode.CLEAN);
59+
data.setStatus(StatusCode.SCANNING);
6060

6161
try (InputStream input = data.getContent()) {
6262
Path contentPath = getContentPath(context, contentId);

storage-targets/cds-feature-attachments-oss/src/main/java/com/sap/cds/feature/attachments/oss/handler/OSSAttachmentsServiceHandler.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
import com.sap.cds.feature.attachments.generated.cds4j.sap.attachments.Attachments;
77
import com.sap.cds.feature.attachments.generated.cds4j.sap.attachments.MediaData;
8+
import com.sap.cds.feature.attachments.generated.cds4j.sap.attachments.StatusCode;
89
import com.sap.cds.feature.attachments.oss.client.AWSClient;
910
import com.sap.cds.feature.attachments.oss.client.AzureClient;
1011
import com.sap.cds.feature.attachments.oss.client.GoogleClient;
@@ -124,6 +125,7 @@ void createAttachment(AttachmentCreateEventContext context)
124125
try {
125126
osClient.uploadContent(data.getContent(), contentId, data.getMimeType()).get();
126127
logger.info("Uploaded file {}", fileName);
128+
context.getData().setStatus(StatusCode.SCANNING);
127129
context.setIsInternalStored(false);
128130
context.setContentId(contentId);
129131
context.setCompleted();

0 commit comments

Comments
 (0)