-
Notifications
You must be signed in to change notification settings - Fork 7
Add independent MalwareScanner feature #785
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Schmarvinius
merged 14 commits into
main
from
503-feature-provide-a-malwarescannerservice-independant-of-the-attachmentservice
Apr 14, 2026
Merged
Changes from all commits
Commits
Show all changes
14 commits
Select commit
Hold shift + click to select a range
5629dc2
add new independent MalwareScanner
Schmarvinius c82df9a
spotless
Schmarvinius 2878d9b
Merge branch 'main' into 503-feature-provide-a-malwarescannerservice-…
Schmarvinius d09a8e3
Add ENCRYPTED status test for DefaultMalwareScannerServiceHandler
Schmarvinius 3d63488
formatting
Schmarvinius 11899c4
alignment
Schmarvinius 1bc761d
add ITests
Schmarvinius ba96194
adapt to oss ITest structure
Schmarvinius a34abb7
add mtls tests
Schmarvinius 2cf732c
Merge branch 'main' into 503-feature-provide-a-malwarescannerservice-…
Schmarvinius f0193d7
add new credentials to pipeline
Schmarvinius 3518f6e
minor improvements
Schmarvinius 0881fb8
parameterize tests
Schmarvinius 5b2b5f1
Merge remote-tracking branch 'origin/main' into 503-feature-provide-a…
Schmarvinius File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
31 changes: 31 additions & 0 deletions
31
...achments/src/main/java/com/sap/cds/feature/attachments/service/MalwareScannerService.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,31 @@ | ||
| /* | ||
| * © 2026 SAP SE or an SAP affiliate company and cds-feature-attachments contributors. | ||
| */ | ||
| package com.sap.cds.feature.attachments.service; | ||
|
|
||
| import com.sap.cds.feature.attachments.service.malware.client.MalwareScanResultStatus; | ||
| import com.sap.cds.services.Service; | ||
| import java.io.InputStream; | ||
|
|
||
| /** | ||
| * The {@link MalwareScannerService} provides malware scanning capabilities independent of the | ||
| * {@link AttachmentService}. It can be injected from the CAP service catalog and used to scan | ||
| * arbitrary content for malware. | ||
| */ | ||
| public interface MalwareScannerService extends Service { | ||
|
|
||
| /** The {@link MalwareScannerService} uses this name in the service catalog */ | ||
| String DEFAULT_NAME = "MalwareScannerService$Default"; | ||
|
|
||
| /** This event is emitted when content shall be scanned for malware */ | ||
| String EVENT_SCAN_CONTENT = "SCAN_CONTENT"; | ||
|
|
||
| /** | ||
| * Scans the given content for malware. | ||
| * | ||
| * @param content the {@link InputStream} of the content to scan | ||
| * @return the result of the malware scan | ||
| * @see MalwareScanResultStatus | ||
| */ | ||
| MalwareScanResultStatus scanContent(InputStream content); | ||
| } | ||
31 changes: 31 additions & 0 deletions
31
...ents/src/main/java/com/sap/cds/feature/attachments/service/MalwareScannerServiceImpl.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,31 @@ | ||
| /* | ||
| * © 2026 SAP SE or an SAP affiliate company and cds-feature-attachments contributors. | ||
| */ | ||
| package com.sap.cds.feature.attachments.service; | ||
|
|
||
| import com.sap.cds.feature.attachments.service.malware.client.MalwareScanResultStatus; | ||
| import com.sap.cds.feature.attachments.service.model.servicehandler.MalwareScanEventContext; | ||
| import com.sap.cds.services.ServiceDelegator; | ||
| import java.io.InputStream; | ||
|
|
||
| /** | ||
| * Implementation of the {@link MalwareScannerService} interface. The main purpose of this class is | ||
| * to set data in the corresponding context and to call the emit method for the | ||
| * MalwareScannerService. | ||
| */ | ||
| public class MalwareScannerServiceImpl extends ServiceDelegator implements MalwareScannerService { | ||
|
|
||
| public MalwareScannerServiceImpl() { | ||
| super(DEFAULT_NAME); | ||
| } | ||
|
|
||
| @Override | ||
| public MalwareScanResultStatus scanContent(InputStream content) { | ||
| var scanContext = MalwareScanEventContext.create(); | ||
| scanContext.setContent(content); | ||
|
|
||
| emit(scanContext); | ||
|
|
||
| return scanContext.getScanResult(); | ||
Schmarvinius marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| } | ||
| } | ||
63 changes: 63 additions & 0 deletions
63
.../com/sap/cds/feature/attachments/service/handler/DefaultMalwareScannerServiceHandler.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,63 @@ | ||
| /* | ||
| * © 2026 SAP SE or an SAP affiliate company and cds-feature-attachments contributors. | ||
| */ | ||
| package com.sap.cds.feature.attachments.service.handler; | ||
|
|
||
| import com.sap.cds.feature.attachments.service.MalwareScannerService; | ||
| import com.sap.cds.feature.attachments.service.malware.client.MalwareScanClient; | ||
| import com.sap.cds.feature.attachments.service.malware.client.MalwareScanResultStatus; | ||
| import com.sap.cds.feature.attachments.service.model.servicehandler.MalwareScanEventContext; | ||
| import com.sap.cds.services.handler.EventHandler; | ||
| import com.sap.cds.services.handler.annotations.HandlerOrder; | ||
| import com.sap.cds.services.handler.annotations.On; | ||
| import com.sap.cds.services.handler.annotations.ServiceName; | ||
| import org.slf4j.Logger; | ||
| import org.slf4j.LoggerFactory; | ||
|
|
||
| /** | ||
| * The class {@link DefaultMalwareScannerServiceHandler} is the default event handler for the {@link | ||
| * MalwareScannerService}. It delegates to the {@link MalwareScanClient} to perform the actual | ||
| * malware scan. | ||
| */ | ||
| @ServiceName(value = "*", type = MalwareScannerService.class) | ||
| public class DefaultMalwareScannerServiceHandler implements EventHandler { | ||
|
|
||
| private static final int DEFAULT_ON = 10 * HandlerOrder.AFTER + HandlerOrder.LATE; | ||
Schmarvinius marked this conversation as resolved.
Show resolved
Hide resolved
Schmarvinius marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| private static final Logger logger = | ||
| LoggerFactory.getLogger(DefaultMalwareScannerServiceHandler.class); | ||
|
|
||
| private final MalwareScanClient malwareScanClient; | ||
|
|
||
| /** | ||
| * Constructs a new instance of {@link DefaultMalwareScannerServiceHandler}. | ||
| * | ||
| * @param malwareScanClient an optional {@link MalwareScanClient} instance, may be {@code null} if | ||
| * no malware scanner service binding is available | ||
| */ | ||
| public DefaultMalwareScannerServiceHandler(MalwareScanClient malwareScanClient) { | ||
| this.malwareScanClient = malwareScanClient; | ||
| } | ||
|
|
||
| @On | ||
| @HandlerOrder(DEFAULT_ON) | ||
| void scanContent(MalwareScanEventContext context) { | ||
| if (malwareScanClient == null) { | ||
| logger.info("No malware scanner service binding available. Returning NO_SCANNER status."); | ||
| context.setScanResult(MalwareScanResultStatus.NO_SCANNER); | ||
| context.setCompleted(); | ||
| return; | ||
| } | ||
|
|
||
| try { | ||
| logger.debug("Starting malware scan of content."); | ||
| MalwareScanResultStatus result = malwareScanClient.scanContent(context.getContent()); | ||
Schmarvinius marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| context.setScanResult(result); | ||
| logger.debug("Malware scan completed with status '{}'.", result); | ||
| } catch (Exception e) { | ||
| logger.error("Error during malware scan.", e); | ||
| context.setScanResult(MalwareScanResultStatus.FAILED); | ||
| } | ||
| context.setCompleted(); | ||
| } | ||
| } | ||
49 changes: 49 additions & 0 deletions
49
...com/sap/cds/feature/attachments/service/model/servicehandler/MalwareScanEventContext.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,49 @@ | ||
| /* | ||
| * © 2026 SAP SE or an SAP affiliate company and cds-feature-attachments contributors. | ||
| */ | ||
| package com.sap.cds.feature.attachments.service.model.servicehandler; | ||
|
|
||
| import com.sap.cds.feature.attachments.service.MalwareScannerService; | ||
| import com.sap.cds.feature.attachments.service.malware.client.MalwareScanResultStatus; | ||
| import com.sap.cds.services.EventContext; | ||
| import com.sap.cds.services.EventName; | ||
| import java.io.InputStream; | ||
|
|
||
| /** The {@link MalwareScanEventContext} is used to store the context of a malware scan event. */ | ||
| @EventName(MalwareScannerService.EVENT_SCAN_CONTENT) | ||
| public interface MalwareScanEventContext extends EventContext { | ||
|
|
||
| /** | ||
| * Creates an {@link EventContext} already overlaid with this interface. The event is set to be | ||
| * {@link MalwareScannerService#EVENT_SCAN_CONTENT} | ||
| * | ||
| * @return the {@link MalwareScanEventContext} | ||
| */ | ||
| static MalwareScanEventContext create() { | ||
| return EventContext.create(MalwareScanEventContext.class, null); | ||
| } | ||
|
|
||
| /** | ||
| * @return the content {@link InputStream} to scan for malware | ||
| */ | ||
| InputStream getContent(); | ||
|
|
||
| /** | ||
| * Sets the content to scan for malware | ||
| * | ||
| * @param content the content {@link InputStream} to scan | ||
| */ | ||
| void setContent(InputStream content); | ||
|
|
||
| /** | ||
| * @return the result of the malware scan or {@code null} if not yet scanned | ||
| */ | ||
| MalwareScanResultStatus getScanResult(); | ||
|
|
||
| /** | ||
| * Sets the result of the malware scan | ||
| * | ||
| * @param scanResult the result of the malware scan | ||
| */ | ||
| void setScanResult(MalwareScanResultStatus scanResult); | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
67 changes: 67 additions & 0 deletions
67
.../src/test/java/com/sap/cds/feature/attachments/service/MalwareScannerServiceImplTest.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,67 @@ | ||
| /* | ||
| * © 2026 SAP SE or an SAP affiliate company and cds-feature-attachments contributors. | ||
| */ | ||
| package com.sap.cds.feature.attachments.service; | ||
|
|
||
| import static org.assertj.core.api.Assertions.assertThat; | ||
| import static org.mockito.ArgumentMatchers.any; | ||
| import static org.mockito.Mockito.doAnswer; | ||
| import static org.mockito.Mockito.mock; | ||
| import static org.mockito.Mockito.when; | ||
|
|
||
| import com.sap.cds.feature.attachments.service.malware.client.MalwareScanResultStatus; | ||
| import com.sap.cds.feature.attachments.service.model.servicehandler.MalwareScanEventContext; | ||
| import com.sap.cds.services.environment.CdsEnvironment; | ||
| import com.sap.cds.services.environment.CdsProperties; | ||
| import com.sap.cds.services.handler.Handler; | ||
| import com.sap.cds.services.impl.ServiceSPI; | ||
| import com.sap.cds.services.runtime.CdsRuntime; | ||
| import java.io.InputStream; | ||
| import java.util.concurrent.atomic.AtomicReference; | ||
| import org.junit.jupiter.api.BeforeEach; | ||
| import org.junit.jupiter.params.ParameterizedTest; | ||
| import org.junit.jupiter.params.provider.EnumSource; | ||
|
|
||
| class MalwareScannerServiceImplTest { | ||
|
|
||
| private MalwareScannerServiceImpl cut; | ||
| private Handler handler; | ||
| private ServiceSPI serviceSpi; | ||
|
|
||
| @BeforeEach | ||
| void setup() { | ||
| cut = new MalwareScannerServiceImpl(); | ||
|
|
||
| CdsEnvironment env = mock(CdsEnvironment.class); | ||
| CdsProperties props = new CdsProperties(); | ||
| when(env.getCdsProperties()).thenReturn(props); | ||
| CdsRuntime runtime = mock(CdsRuntime.class); | ||
| when(runtime.getEnvironment()).thenReturn(env); | ||
| handler = mock(Handler.class); | ||
| serviceSpi = (ServiceSPI) cut.getDelegatedService(); | ||
| serviceSpi.setCdsRuntime(runtime); | ||
| } | ||
|
|
||
| @ParameterizedTest | ||
| @EnumSource(MalwareScanResultStatus.class) | ||
| void scanContentReturnsCorrectStatus(MalwareScanResultStatus expectedStatus) { | ||
| var contextReference = new AtomicReference<MalwareScanEventContext>(); | ||
| var stream = mock(InputStream.class); | ||
| doAnswer( | ||
| input -> { | ||
| var context = (MalwareScanEventContext) input.getArgument(0); | ||
| contextReference.set(context); | ||
| context.setCompleted(); | ||
| context.setScanResult(expectedStatus); | ||
| return null; | ||
| }) | ||
| .when(handler) | ||
| .process(any()); | ||
| serviceSpi.on(MalwareScannerService.EVENT_SCAN_CONTENT, "", handler); | ||
|
|
||
| var result = cut.scanContent(stream); | ||
|
|
||
| assertThat(result).isEqualTo(expectedStatus); | ||
| assertThat(contextReference.get().getContent()).isEqualTo(stream); | ||
| } | ||
| } |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.