diff --git a/storage-targets/cds-feature-attachments-oss/src/main/java/com/sap/cds/feature/attachments/oss/configuration/Registration.java b/storage-targets/cds-feature-attachments-oss/src/main/java/com/sap/cds/feature/attachments/oss/configuration/Registration.java index 3fbca05e..78c087e9 100644 --- a/storage-targets/cds-feature-attachments-oss/src/main/java/com/sap/cds/feature/attachments/oss/configuration/Registration.java +++ b/storage-targets/cds-feature-attachments-oss/src/main/java/com/sap/cds/feature/attachments/oss/configuration/Registration.java @@ -7,6 +7,7 @@ import com.sap.cds.feature.attachments.oss.client.OSClientFactory; import com.sap.cds.feature.attachments.oss.handler.OSSAttachmentsServiceHandler; import com.sap.cds.feature.attachments.oss.handler.TenantCleanupHandler; +import com.sap.cds.services.ServiceException; import com.sap.cds.services.environment.CdsEnvironment; import com.sap.cds.services.runtime.CdsRuntimeConfiguration; import com.sap.cds.services.runtime.CdsRuntimeConfigurer; @@ -30,6 +31,17 @@ public void eventHandlers(CdsRuntimeConfigurer configurer) { boolean multitenancyEnabled = isMultitenancyEnabled(env); String objectStoreKind = getObjectStoreKind(env); + // Fail fast on an insecure multitenant configuration: without a shared object store the + // object keys are not prefixed with the tenant ID, so tenants are not isolated in the + // bucket. Refuse to start rather than silently storing all tenants under the same keyspace. + if (multitenancyEnabled && !"shared".equals(objectStoreKind)) { + throw new ServiceException( + "Multitenancy is enabled but the object store kind is not 'shared' (was: '{}'). " + + "A shared object store is required to isolate tenants via tenant-prefixed object " + + "keys. Configure 'cds.attachments.objectStore.kind: shared'.", + objectStoreKind); + } + // Fixed thread pool for background I/O operations (upload, download, delete). // Default 16 is tuned for I/O-bound cloud storage calls, not CPU-bound work. int threadPoolSize = diff --git a/storage-targets/cds-feature-attachments-oss/src/test/java/com/sap/cds/feature/attachments/oss/configuration/RegistrationTest.java b/storage-targets/cds-feature-attachments-oss/src/test/java/com/sap/cds/feature/attachments/oss/configuration/RegistrationTest.java index 3987e1a7..09a517d1 100644 --- a/storage-targets/cds-feature-attachments-oss/src/test/java/com/sap/cds/feature/attachments/oss/configuration/RegistrationTest.java +++ b/storage-targets/cds-feature-attachments-oss/src/test/java/com/sap/cds/feature/attachments/oss/configuration/RegistrationTest.java @@ -3,6 +3,7 @@ */ package com.sap.cds.feature.attachments.oss.configuration; +import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; @@ -11,6 +12,7 @@ import static org.mockito.Mockito.when; import com.sap.cds.feature.attachments.oss.handler.OSSAttachmentsServiceHandler; +import com.sap.cds.services.ServiceException; import com.sap.cds.services.environment.CdsEnvironment; import com.sap.cds.services.runtime.CdsRuntime; import com.sap.cds.services.runtime.CdsRuntimeConfigurer; @@ -87,29 +89,31 @@ void testEventHandlersNoBindingDoesNotRegister() { } @Test - void testMtEnabledNonSharedKindRegistersOnlyOSSHandler() { + void testMtEnabledNonSharedKindFailsStartup() { when(environment.getServiceBindings()).thenReturn(Stream.of(awsBinding)); when(environment.getProperty("cds.multitenancy.enabled", Boolean.class, Boolean.FALSE)) .thenReturn(Boolean.TRUE); when(environment.getProperty("cds.attachments.objectStore.kind", String.class, null)) .thenReturn("dedicated"); - registration.eventHandlers(configurer); + assertThatThrownBy(() -> registration.eventHandlers(configurer)) + .isInstanceOf(ServiceException.class) + .hasMessageContaining("shared"); - verify(configurer, times(1)).eventHandler(any(OSSAttachmentsServiceHandler.class)); - verify(configurer, times(1)).eventHandler(any()); + verify(configurer, never()).eventHandler(any()); } @Test - void testMtEnabledNullKindRegistersOnlyOSSHandler() { + void testMtEnabledNullKindFailsStartup() { when(environment.getServiceBindings()).thenReturn(Stream.of(awsBinding)); when(environment.getProperty("cds.multitenancy.enabled", Boolean.class, Boolean.FALSE)) .thenReturn(Boolean.TRUE); - registration.eventHandlers(configurer); + assertThatThrownBy(() -> registration.eventHandlers(configurer)) + .isInstanceOf(ServiceException.class) + .hasMessageContaining("shared"); - verify(configurer, times(1)).eventHandler(any(OSSAttachmentsServiceHandler.class)); - verify(configurer, times(1)).eventHandler(any()); + verify(configurer, never()).eventHandler(any()); } @Test