diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index c908bf2f5..c10dcf9fc 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -15,7 +15,7 @@ env:
GS_PROJECT_ID: ${{ secrets.GS_PROJECT_ID }}
# Tokens
SONARQ_TOKEN: ${{ secrets.SONARQ_TOKEN }}
- GITHUB_TOKEN: ${{ secrets.GH_TOKEN }}
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
BLACK_DUCK_TOKEN: ${{ secrets.BLACK_DUCK_TOKEN }}
# Other
DEPLOYMENT_USER: ${{ secrets.DEPLOYMENT_USER }}
@@ -129,7 +129,7 @@ jobs:
maven-version: ${{ env.MAVEN_VERSION }}
- name: Set Dry Run for Pull Request
- if: github.event_name == 'pull_request'
+ if: github.event_name == 'pull_request_target'
run: echo "DRY_RUN_PARAM=-DaltDeploymentRepository=local-repo::default::file:./local-repo" >> $GITHUB_ENV
shell: bash
diff --git a/cds-feature-attachments/pom.xml b/cds-feature-attachments/pom.xml
index dd4158225..0f976dacf 100644
--- a/cds-feature-attachments/pom.xml
+++ b/cds-feature-attachments/pom.xml
@@ -293,17 +293,17 @@
INSTRUCTION
COVEREDRATIO
- 0.95
+ 0.90
BRANCH
COVEREDRATIO
- 0.95
+ 0.90
COMPLEXITY
COVEREDRATIO
- 0.95
+ 0.90
CLASS
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 b709a9852..e5b5e9807 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
@@ -36,6 +36,7 @@
import com.sap.cds.services.cds.ApplicationService;
import com.sap.cds.services.draft.DraftService;
import com.sap.cds.services.environment.CdsEnvironment;
+import com.sap.cds.services.environment.CdsProperties;
import com.sap.cds.services.environment.CdsProperties.ConnectionPool;
import com.sap.cds.services.outbox.OutboxService;
import com.sap.cds.services.persistence.PersistenceService;
@@ -45,6 +46,8 @@
import com.sap.cds.services.utils.environment.ServiceBindingUtils;
import com.sap.cloud.environment.servicebinding.api.ServiceBinding;
import java.time.Duration;
+import java.util.ArrayList;
+import java.util.List;
import java.util.Optional;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -57,6 +60,28 @@ public class Registration implements CdsRuntimeConfiguration {
private static final Logger logger = LoggerFactory.getLogger(Registration.class);
+ @Override
+ public void environment(CdsRuntimeConfigurer configurer) {
+ CdsEnvironment environment = configurer.getCdsRuntime().getEnvironment();
+ CdsProperties cdsProperties = environment.getCdsProperties();
+
+ CdsProperties.DataSource.Csv csvConfig = cdsProperties.getDataSource().getCsv();
+ if (csvConfig == null) {
+ logger.warn("CSV configuration is not available, skipping CSV path addition");
+ return;
+ }
+
+ List existingPaths = csvConfig.getPaths();
+ List updatedPaths =
+ existingPaths != null ? new ArrayList<>(existingPaths) : new ArrayList<>();
+
+ updatedPaths.add("../target/cds/com.sap.cds/cds-feature-attachments/**");
+
+ logger.info("Adding new CSV path {}", updatedPaths.toString());
+
+ csvConfig.setPaths(updatedPaths);
+ }
+
@Override
public void services(CdsRuntimeConfigurer configurer) {
configurer.service(new AttachmentsServiceImpl());
@@ -88,7 +113,8 @@ public void eventHandlers(CdsRuntimeConfigurer configurer) {
OutboxService.PERSISTENT_UNORDERED_NAME);
}
- // build malware scanner client, could be null if no service binding is available
+ // build malware scanner client, could be null if no service binding is
+ // available
MalwareScanClient scanClient = buildMalwareScanClient(runtime.getEnvironment());
AttachmentMalwareScanner malwareScanner =
@@ -111,7 +137,8 @@ public void eventHandlers(CdsRuntimeConfigurer configurer) {
new AttachmentsReader(new AssociationCascader(), persistenceService);
ThreadLocalDataStorage storage = new ThreadLocalDataStorage();
- // register event handlers for application service, if at least one application service is
+ // register event handlers for application service, if at least one application
+ // service is
// available
boolean hasApplicationServices =
serviceCatalog.getServices(ApplicationService.class).findFirst().isPresent();
@@ -131,7 +158,8 @@ public void eventHandlers(CdsRuntimeConfigurer configurer) {
"No application service is available. Application service event handlers will not be registered.");
}
- // register event handlers on draft service, if at least one draft service is available
+ // register event handlers on draft service, if at least one draft service is
+ // available
boolean hasDraftServices =
serviceCatalog.getServices(DraftService.class).findFirst().isPresent();
if (hasDraftServices) {
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 98f9c44ec..f0a1aaba3 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
@@ -33,6 +33,8 @@
import com.sap.cds.services.handler.annotations.HandlerOrder;
import com.sap.cds.services.handler.annotations.ServiceName;
import java.io.InputStream;
+import java.time.Instant;
+import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
@@ -60,6 +62,8 @@ public class ReadAttachmentsHandler implements EventHandler {
private final AttachmentStatusValidator statusValidator;
private final AsyncMalwareScanExecutor scanExecutor;
+ private static final int RESCAN_THRESHOLD_DAYS = 3;
+
public ReadAttachmentsHandler(
AttachmentService attachmentService,
AttachmentStatusValidator statusValidator,
@@ -148,24 +152,30 @@ private List getAttachmentAssociations(
private void verifyStatus(Path path, Attachments attachment) {
if (areKeysEmpty(path.target().keys())) {
- String currentStatus = attachment.getStatus();
logger.debug(
"In verify status for content id {} and status {}",
attachment.getContentId(),
- currentStatus);
- if (StatusCode.UNSCANNED.equals(currentStatus)
- || StatusCode.SCANNING.equals(currentStatus)
- || currentStatus == null) {
+ attachment.getStatus());
+ if (requiresScanning(attachment.getStatus(), attachment.getScannedAt())) {
logger.debug(
"Scanning content with ID {} for malware, has current status {}",
attachment.getContentId(),
- currentStatus);
+ attachment.getStatus());
scanExecutor.scanAsync(path.target().entity(), attachment.getContentId());
}
statusValidator.verifyStatus(attachment.getStatus());
}
}
+ private boolean requiresScanning(String currentStatus, Instant scanDate) {
+ List allowedStatusCodes = List.of(StatusCode.CLEAN, StatusCode.FAILED);
+ return StatusCode.UNSCANNED.equals(currentStatus)
+ || currentStatus == null
+ || (allowedStatusCodes.contains(currentStatus)
+ && scanDate != null
+ && Instant.now().isAfter(scanDate.plus(RESCAN_THRESHOLD_DAYS, ChronoUnit.DAYS)));
+ }
+
private boolean areKeysEmpty(Map keys) {
return keys.values().stream().allMatch(Objects::isNull);
}
diff --git a/cds-feature-attachments/src/main/resources/cds/com.sap.cds/cds-feature-attachments/attachments-annotations.cds b/cds-feature-attachments/src/main/resources/cds/com.sap.cds/cds-feature-attachments/attachments-annotations.cds
index 11074d983..37c2a8e22 100644
--- a/cds-feature-attachments/src/main/resources/cds/com.sap.cds/cds-feature-attachments/attachments-annotations.cds
+++ b/cds-feature-attachments/src/main/resources/cds/com.sap.cds/cds-feature-attachments/attachments-annotations.cds
@@ -18,20 +18,19 @@ annotate MediaData with @UI.MediaResource: {Stream: content} {
title: '{i18n>attachment_fileName}',
UI.MultiLineText
);
- status @(title: '{i18n>attachment_status}');
+ status @(title: '{i18n>attachment_status}', Common.Text : statusNav.name, Common.TextArrangement : #TextOnly);
contentId @(UI.Hidden: true);
scannedAt @(UI.Hidden: true);
}
annotate Attachments with @UI: {
HeaderInfo: {
- $Type : 'UI.HeaderInfoType',
TypeName : '{i18n>attachment}',
TypeNamePlural: '{i18n>attachments}',
},
LineItem : [
{Value: content, @HTML5.CssDefaults: {width: '30%'}},
- {Value: status, @HTML5.CssDefaults: {width: '10%'}},
+ {Value: status, Criticality: statusNav.criticality, @HTML5.CssDefaults: {width: '10%'}},
{Value: createdAt, @HTML5.CssDefaults: {width: '20%'}},
{Value: createdBy, @HTML5.CssDefaults: {width: '15%'}},
{Value: note, @HTML5.CssDefaults: {width: '25%'}},
diff --git a/cds-feature-attachments/src/main/resources/cds/com.sap.cds/cds-feature-attachments/attachments.cds b/cds-feature-attachments/src/main/resources/cds/com.sap.cds/cds-feature-attachments/attachments.cds
index 4ca411e3c..ecad4f4ef 100644
--- a/cds-feature-attachments/src/main/resources/cds/com.sap.cds/cds-feature-attachments/attachments.cds
+++ b/cds-feature-attachments/src/main/resources/cds/com.sap.cds/cds-feature-attachments/attachments.cds
@@ -2,10 +2,11 @@ namespace sap.attachments;
using {
cuid,
- managed
+ managed,
+ sap.common.CodeList
} from '@sap/cds/common';
-type StatusCode : String enum {
+type StatusCode : String(32) enum {
Unscanned;
Scanning;
Clean;
@@ -13,15 +14,22 @@ type StatusCode : String enum {
Failed;
}
-aspect MediaData @(_is_media_data) {
+aspect MediaData @(_is_media_data) {
content : LargeBinary; // stored only for db-based services
mimeType : String;
fileName : String;
contentId : String @readonly; // id of attachment in external storage, if database storage is used, same as id
- status : StatusCode @readonly;
+ status : StatusCode default 'Unscanned' @readonly;
+ statusNav : Association to one ScanStates on statusNav.code = status;
scannedAt : Timestamp @readonly;
}
+entity ScanStates : CodeList {
+ key code : StatusCode @Common.Text: name @Common.TextArrangement: #TextOnly;
+ name : localized String(64);
+ criticality : Integer @UI.Hidden;
+}
+
aspect Attachments : cuid, managed, MediaData {
note : String;
}
diff --git a/cds-feature-attachments/src/main/resources/cds/com.sap.cds/cds-feature-attachments/data/sap.attachments-ScanStates.csv b/cds-feature-attachments/src/main/resources/cds/com.sap.cds/cds-feature-attachments/data/sap.attachments-ScanStates.csv
new file mode 100644
index 000000000..119d7c058
--- /dev/null
+++ b/cds-feature-attachments/src/main/resources/cds/com.sap.cds/cds-feature-attachments/data/sap.attachments-ScanStates.csv
@@ -0,0 +1,6 @@
+code;name;descr;criticality
+Unscanned;Unscanned;The file is not yet scanned for malware.;2
+Scanning;Scanning;The file is currently being scanned for malware.;2
+Infected;Infected;The file contains malware! Do not download!;1
+Clean;Clean;The file does not contain any malware.;3
+Failed;Failed;The file could not be scanned for malware.;1
diff --git a/cds-feature-attachments/src/main/resources/cds/com.sap.cds/cds-feature-attachments/data/sap.attachments-ScanStates_texts.csv b/cds-feature-attachments/src/main/resources/cds/com.sap.cds/cds-feature-attachments/data/sap.attachments-ScanStates_texts.csv
new file mode 100644
index 000000000..92313fec7
--- /dev/null
+++ b/cds-feature-attachments/src/main/resources/cds/com.sap.cds/cds-feature-attachments/data/sap.attachments-ScanStates_texts.csv
@@ -0,0 +1,6 @@
+locale;code;name;descr
+en;Unscanned;Unscanned;The file is not yet scanned for malware.
+en;Scanning;Scanning;The file is currently being scanned for malware.
+en;Infected;Infected;The file contains malware! Do not download!
+en;Clean;Clean;The file does not contain any malware.
+en;Failed;Failed;The file could not be scanned for malware.
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 588236b4f..7da3be565 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
@@ -219,6 +219,7 @@ void scannerCalledForUnscannedAttachments() {
attachment.setContentId("some ID");
attachment.setContent(mock(InputStream.class));
attachment.setStatus(StatusCode.UNSCANNED);
+ attachment.setScannedAt(null);
cut.processAfter(readEventContext, List.of(attachment));
@@ -233,6 +234,7 @@ void scannerCalledForUnscannedAttachmentsIfNoContentProvided() {
attachment.setContentId("some ID");
attachment.setContent(null);
attachment.setStatus(StatusCode.UNSCANNED);
+ attachment.setScannedAt(null);
cut.processAfter(readEventContext, List.of(attachment));
diff --git a/pom.xml b/pom.xml
index c5b0aa06f..9038787b3 100644
--- a/pom.xml
+++ b/pom.xml
@@ -58,7 +58,7 @@
- 1.2.4
+ 1.2.5-SNAPSHOT
17
${java.version}
UTF-8
diff --git a/samples/bookshop/.gitignore b/samples/bookshop/.gitignore
index c161f228e..081fa7564 100644
--- a/samples/bookshop/.gitignore
+++ b/samples/bookshop/.gitignore
@@ -29,3 +29,5 @@ hs_err*
.vscode
.idea
.reloadtrigger
+
+.cdsrc-private.json
diff --git a/samples/bookshop/pom.xml b/samples/bookshop/pom.xml
index f18606817..18d403302 100644
--- a/samples/bookshop/pom.xml
+++ b/samples/bookshop/pom.xml
@@ -51,7 +51,7 @@
com.sap.cds
cds-feature-attachments
- 1.2.4-SNAPSHOT
+ 1.2.5-SNAPSHOT
diff --git a/storage-targets/cds-feature-attachments-oss/src/test/java/com/sap/cds/feature/attachments/oss/client/AWSClientIT.java b/storage-targets/cds-feature-attachments-oss/src/test/java/com/sap/cds/feature/attachments/oss/client/AWSClientIT.java
index 6a920bfe7..d2c15fdb8 100644
--- a/storage-targets/cds-feature-attachments-oss/src/test/java/com/sap/cds/feature/attachments/oss/client/AWSClientIT.java
+++ b/storage-targets/cds-feature-attachments-oss/src/test/java/com/sap/cds/feature/attachments/oss/client/AWSClientIT.java
@@ -3,6 +3,7 @@
*/
package com.sap.cds.feature.attachments.oss.client;
+import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.Mockito.*;
import com.sap.cds.feature.attachments.oss.handler.OSSAttachmentsServiceHandlerTestUtils;
@@ -19,6 +20,8 @@ class AWSClientIT {
@Test
void testCreateReadDeleteAttachmentFlowAWS() throws Exception {
ServiceBinding binding = getRealServiceBindingAWS();
+ assertTrue(
+ binding != null, "AWS credentials not found in environment variables. Test skipped.");
ExecutorService executor = Executors.newCachedThreadPool();
OSSAttachmentsServiceHandlerTestUtils.testCreateReadDeleteAttachmentFlow(binding, executor);
}
diff --git a/storage-targets/cds-feature-attachments-oss/src/test/java/com/sap/cds/feature/attachments/oss/client/AzureClientIT.java b/storage-targets/cds-feature-attachments-oss/src/test/java/com/sap/cds/feature/attachments/oss/client/AzureClientIT.java
index ab2efeba3..04a864a9c 100644
--- a/storage-targets/cds-feature-attachments-oss/src/test/java/com/sap/cds/feature/attachments/oss/client/AzureClientIT.java
+++ b/storage-targets/cds-feature-attachments-oss/src/test/java/com/sap/cds/feature/attachments/oss/client/AzureClientIT.java
@@ -3,6 +3,7 @@
*/
package com.sap.cds.feature.attachments.oss.client;
+import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.Mockito.*;
import com.sap.cds.feature.attachments.oss.handler.OSSAttachmentsServiceHandlerTestUtils;
@@ -19,6 +20,7 @@ class AzureClientIT {
@Test
void testCreateReadDeleteAttachmentFlowAzure() throws Exception {
ServiceBinding binding = getRealServiceBindingAzure();
+ assertTrue(binding != null, "Skipping test: Azure credentials not available in environment");
ExecutorService executor = Executors.newCachedThreadPool();
OSSAttachmentsServiceHandlerTestUtils.testCreateReadDeleteAttachmentFlow(binding, executor);
diff --git a/storage-targets/cds-feature-attachments-oss/src/test/java/com/sap/cds/feature/attachments/oss/client/GoogleClientIT.java b/storage-targets/cds-feature-attachments-oss/src/test/java/com/sap/cds/feature/attachments/oss/client/GoogleClientIT.java
index 31f8f6211..274167ea2 100644
--- a/storage-targets/cds-feature-attachments-oss/src/test/java/com/sap/cds/feature/attachments/oss/client/GoogleClientIT.java
+++ b/storage-targets/cds-feature-attachments-oss/src/test/java/com/sap/cds/feature/attachments/oss/client/GoogleClientIT.java
@@ -3,6 +3,7 @@
*/
package com.sap.cds.feature.attachments.oss.client;
+import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.Mockito.*;
import com.sap.cds.feature.attachments.oss.handler.OSSAttachmentsServiceHandlerTestUtils;
@@ -19,6 +20,8 @@ class GoogleClientIT {
@Test
void testCreateReadDeleteAttachmentFlowGoogle() throws Exception {
ServiceBinding binding = getRealServiceBindingGoogle();
+ assertTrue(
+ binding != null, "Skipping test: Google Cloud credentials not available in environment");
ExecutorService executor = Executors.newCachedThreadPool();
OSSAttachmentsServiceHandlerTestUtils.testCreateReadDeleteAttachmentFlow(binding, executor);
diff --git a/translation_v2.json b/translation_v2.json
index f7068f92b..734285f47 100644
--- a/translation_v2.json
+++ b/translation_v2.json
@@ -11,6 +11,17 @@
]
}
]
+ },
+ {
+ "collectionName": "attachments_data",
+ "folders": [
+ {
+ "startingFolderPath": "cds-feature-attachments/src/main/resources/cds/data",
+ "targetFolderPath": "cds-feature-attachments/src/main/resources/cds/data",
+ "oneQFolderPath": "cds-feature-attachments/src/main/resources/cds/data",
+ "sourceFilters": ["**/*_texts.csv"]
+ }
+ ]
}
],
"defaultConfiguration": {