From 71557131c67fb4c133b103e5a663849c0a661bd6 Mon Sep 17 00:00:00 2001 From: Tim Dahlmanns Date: Thu, 29 Jan 2026 14:55:38 +0100 Subject: [PATCH 1/8] feat: file upload cel and permissions body part --- .../api/controller/EdcDataController.java | 28 ++++++++++----- .../domain/service/DataAccessService.java | 34 ++++++++++++------- 2 files changed, 41 insertions(+), 21 deletions(-) diff --git a/src/main/java/com/metaformsystems/redline/api/controller/EdcDataController.java b/src/main/java/com/metaformsystems/redline/api/controller/EdcDataController.java index a0c751b..fffc285 100644 --- a/src/main/java/com/metaformsystems/redline/api/controller/EdcDataController.java +++ b/src/main/java/com/metaformsystems/redline/api/controller/EdcDataController.java @@ -18,21 +18,17 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.metaformsystems.redline.api.dto.request.ContractRequest; import com.metaformsystems.redline.api.dto.request.CounterPartyIdWrapper; +import com.metaformsystems.redline.api.dto.request.FileUploadRequest; import com.metaformsystems.redline.api.dto.request.TransferProcessRequest; import com.metaformsystems.redline.api.dto.response.Contract; import com.metaformsystems.redline.api.dto.response.ContractNegotiation; import com.metaformsystems.redline.api.dto.response.FileResource; import com.metaformsystems.redline.domain.service.DataAccessService; -import com.metaformsystems.redline.infrastructure.client.management.dto.Catalog; -import com.metaformsystems.redline.infrastructure.client.management.dto.Constraint; -import com.metaformsystems.redline.infrastructure.client.management.dto.Obligation; -import com.metaformsystems.redline.infrastructure.client.management.dto.Offer; -import com.metaformsystems.redline.infrastructure.client.management.dto.Permission; -import com.metaformsystems.redline.infrastructure.client.management.dto.Prohibition; -import com.metaformsystems.redline.infrastructure.client.management.dto.TransferProcess; +import com.metaformsystems.redline.infrastructure.client.management.dto.*; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Encoding; import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.responses.ApiResponses; @@ -51,6 +47,7 @@ import java.io.IOException; import java.time.Instant; +import java.util.Collection; import java.util.List; import java.util.Map; @@ -84,12 +81,27 @@ public ResponseEntity uploadFile(@PathVariable Long participantId, @PathVariable Long providerId, @RequestPart("publicMetadata") String publicMetadata, @RequestPart("privateMetadata") String privateMetadata, + @RequestPart(value = "celExpressions", required = false) String celExpressions, + @RequestPart(value = "permissions", required = false) PolicySet permissions, @RequestPart("file") MultipartFile file) { try { var publicMetadataMap = objectMapper.readValue(publicMetadata, new TypeReference>() {}); var privateMetadataMap = objectMapper.readValue(privateMetadata, new TypeReference>() {}); - dataAccessService.uploadFileForParticipant(participantId, publicMetadataMap, privateMetadataMap, file.getInputStream(), file.getContentType(), file.getOriginalFilename()); + List celExpressionList = null; + if (celExpressions != null) { + celExpressionList = objectMapper.readValue(celExpressions, new TypeReference<>() {}); + } + dataAccessService.uploadFileForParticipant( + participantId, + publicMetadataMap, + privateMetadataMap, + file.getInputStream(), + file.getContentType(), + file.getOriginalFilename(), + celExpressionList, + permissions + ); } catch (IOException e) { return ResponseEntity.internalServerError().build(); } diff --git a/src/main/java/com/metaformsystems/redline/domain/service/DataAccessService.java b/src/main/java/com/metaformsystems/redline/domain/service/DataAccessService.java index 59cafac..24502ab 100644 --- a/src/main/java/com/metaformsystems/redline/domain/service/DataAccessService.java +++ b/src/main/java/com/metaformsystems/redline/domain/service/DataAccessService.java @@ -21,13 +21,7 @@ import com.metaformsystems.redline.domain.repository.ParticipantRepository; import com.metaformsystems.redline.infrastructure.client.dataplane.DataPlaneApiClient; import com.metaformsystems.redline.infrastructure.client.management.ManagementApiClient; -import com.metaformsystems.redline.infrastructure.client.management.dto.Asset; -import com.metaformsystems.redline.infrastructure.client.management.dto.Catalog; -import com.metaformsystems.redline.infrastructure.client.management.dto.CelExpression; -import com.metaformsystems.redline.infrastructure.client.management.dto.ContractNegotiation; -import com.metaformsystems.redline.infrastructure.client.management.dto.ContractRequest; -import com.metaformsystems.redline.infrastructure.client.management.dto.TransferProcess; -import com.metaformsystems.redline.infrastructure.client.management.dto.TransferRequest; +import com.metaformsystems.redline.infrastructure.client.management.dto.*; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Service; @@ -39,11 +33,7 @@ import java.io.InputStream; import java.time.Duration; import java.time.Instant; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.UUID; +import java.util.*; import java.util.regex.Pattern; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -72,11 +62,23 @@ public DataAccessService(DataPlaneApiClient dataPlaneApiClient, WebDidResolver w } @Transactional - public void uploadFileForParticipant(Long participantId, Map publicMetadata, Map privateMetadata, InputStream fileStream, String contentType, String originalFilename) { + public void uploadFileForParticipant(Long participantId, Map publicMetadata, Map privateMetadata, InputStream fileStream, String contentType, String originalFilename, List celExpressions, PolicySet permissions) { var participant = participantRepository.findById(participantId).orElseThrow(() -> new ObjectNotFoundException("Participant not found with id: " + participantId)); var participantContextId = participant.getParticipantContextId(); + + //-1. create CEL expressions + if (celExpressions != null) { + celExpressions.forEach(celExpression -> { + try { + managementApiClient.createCelExpression(celExpression); + } catch (WebClientResponseException.Conflict e) { + //do nothing, CEL expression already exists + } + }); + } + //0. upload file to data plane var assetId = UUID.randomUUID().toString(); publicMetadata.put("assetId", assetId); @@ -106,6 +108,11 @@ public void uploadFileForParticipant(Long participantId, Map pub //2. create policy var policy = MEMBERSHIP_POLICY; + if (permissions != null) { + var permissionList = new ArrayList<>(policy.getPolicy().getPermission()); + permissionList.addAll(permissions.getPermission()); + policy.getPolicy().setPermission(permissionList); + } policy.setId(UUID.randomUUID().toString()); managementApiClient.createPolicy(participantContextId, policy); @@ -114,6 +121,7 @@ public void uploadFileForParticipant(Long participantId, Map pub contractDef.setId(UUID.randomUUID().toString()); contractDef.setContractPolicyId(policy.getId()); contractDef.setAccessPolicyId(policy.getId()); + contractDef.setAssetsSelector(Set.of(new Criterion("id", "=", assetId))); managementApiClient.createContractDefinition(participantContextId, contractDef); From 0ab4d48572253eece7e408bfb65cc752d244ca6a Mon Sep 17 00:00:00 2001 From: Tim Dahlmanns Date: Fri, 30 Jan 2026 12:06:19 +0100 Subject: [PATCH 2/8] fix: policy definition --- .../api/controller/EdcDataController.java | 20 ++++------- .../redline/domain/service/Constants.java | 26 +-------------- .../domain/service/DataAccessService.java | 33 +++++++++---------- 3 files changed, 23 insertions(+), 56 deletions(-) diff --git a/src/main/java/com/metaformsystems/redline/api/controller/EdcDataController.java b/src/main/java/com/metaformsystems/redline/api/controller/EdcDataController.java index fffc285..57cc251 100644 --- a/src/main/java/com/metaformsystems/redline/api/controller/EdcDataController.java +++ b/src/main/java/com/metaformsystems/redline/api/controller/EdcDataController.java @@ -18,7 +18,6 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.metaformsystems.redline.api.dto.request.ContractRequest; import com.metaformsystems.redline.api.dto.request.CounterPartyIdWrapper; -import com.metaformsystems.redline.api.dto.request.FileUploadRequest; import com.metaformsystems.redline.api.dto.request.TransferProcessRequest; import com.metaformsystems.redline.api.dto.response.Contract; import com.metaformsystems.redline.api.dto.response.ContractNegotiation; @@ -28,26 +27,17 @@ import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.media.Content; -import io.swagger.v3.oas.annotations.media.Encoding; import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.responses.ApiResponses; import io.swagger.v3.oas.annotations.tags.Tag; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestHeader; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestPart; -import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; import java.io.IOException; import java.time.Instant; -import java.util.Collection; import java.util.List; import java.util.Map; @@ -82,7 +72,7 @@ public ResponseEntity uploadFile(@PathVariable Long participantId, @RequestPart("publicMetadata") String publicMetadata, @RequestPart("privateMetadata") String privateMetadata, @RequestPart(value = "celExpressions", required = false) String celExpressions, - @RequestPart(value = "permissions", required = false) PolicySet permissions, + @RequestPart(value = "constraints", required = false) String constraints, @RequestPart("file") MultipartFile file) { try { @@ -92,6 +82,10 @@ public ResponseEntity uploadFile(@PathVariable Long participantId, if (celExpressions != null) { celExpressionList = objectMapper.readValue(celExpressions, new TypeReference<>() {}); } + List constraintList = null; + if (constraints != null) { + constraintList = objectMapper.readValue(constraints, new TypeReference<>() {}); + } dataAccessService.uploadFileForParticipant( participantId, publicMetadataMap, @@ -100,7 +94,7 @@ public ResponseEntity uploadFile(@PathVariable Long participantId, file.getContentType(), file.getOriginalFilename(), celExpressionList, - permissions + constraintList ); } catch (IOException e) { return ResponseEntity.internalServerError().build(); diff --git a/src/main/java/com/metaformsystems/redline/domain/service/Constants.java b/src/main/java/com/metaformsystems/redline/domain/service/Constants.java index 2817b78..0d8187f 100644 --- a/src/main/java/com/metaformsystems/redline/domain/service/Constants.java +++ b/src/main/java/com/metaformsystems/redline/domain/service/Constants.java @@ -14,35 +14,11 @@ package com.metaformsystems.redline.domain.service; -import com.metaformsystems.redline.infrastructure.client.management.dto.Criterion; -import com.metaformsystems.redline.infrastructure.client.management.dto.NewContractDefinition; -import com.metaformsystems.redline.infrastructure.client.management.dto.NewPolicyDefinition; import com.metaformsystems.redline.infrastructure.client.management.dto.PolicySet; -import java.util.List; -import java.util.Set; - public interface Constants { String ASSET_PERMISSION = "membership_asset"; - String MEMBERSHIP_POLICY_ID = "membership_policy"; String MEMBERSHIP_EXPRESSION_ID = "membership_expr"; String MEMBERSHIP_EXPRESSION = "ctx.agent.claims.vc.filter(c, c.type.exists(t, t == 'MembershipCredential')).exists(c, c.credentialSubject.exists(cs, timestamp(cs.membershipStartDate) < now))"; - String CONTRACT_DEFINITION_ID = "membership_contract_definition"; - - // all files that are uploaded fall under this policy: the MembershipCredential must be presented to view the EDC asset - NewPolicyDefinition MEMBERSHIP_POLICY = NewPolicyDefinition.Builder.aNewPolicyDefinition() - .id(MEMBERSHIP_POLICY_ID) - .policy(new PolicySet(List.of(new PolicySet.Permission("use", - List.of(new PolicySet.Constraint("MembershipCredential", "eq", "active")))))) - .build(); - - // all new assets must have privateProperties: "permission" - "membership_asset", so that they are affected by this contract def - NewContractDefinition MEMBERSHIP_CONTRACT_DEFINITION = NewContractDefinition.Builder.aNewContractDefinition() - .id(CONTRACT_DEFINITION_ID) - .accessPolicyId(MEMBERSHIP_POLICY_ID) - .contractPolicyId(MEMBERSHIP_POLICY_ID) - .assetsSelector(Set.of(new Criterion("privateProperties.'https://w3id.org/edc/v0.0.1/ns/permission'", "=", ASSET_PERMISSION))) - .build(); - - + PolicySet.Constraint MEMBERSHIP_CONSTRAINT = new PolicySet.Constraint("MembershipCredential", "eq", "active"); } diff --git a/src/main/java/com/metaformsystems/redline/domain/service/DataAccessService.java b/src/main/java/com/metaformsystems/redline/domain/service/DataAccessService.java index 24502ab..13fb2ef 100644 --- a/src/main/java/com/metaformsystems/redline/domain/service/DataAccessService.java +++ b/src/main/java/com/metaformsystems/redline/domain/service/DataAccessService.java @@ -38,11 +38,7 @@ import java.util.stream.Collectors; import java.util.stream.Stream; -import static com.metaformsystems.redline.domain.service.Constants.ASSET_PERMISSION; -import static com.metaformsystems.redline.domain.service.Constants.MEMBERSHIP_CONTRACT_DEFINITION; -import static com.metaformsystems.redline.domain.service.Constants.MEMBERSHIP_EXPRESSION; -import static com.metaformsystems.redline.domain.service.Constants.MEMBERSHIP_EXPRESSION_ID; -import static com.metaformsystems.redline.domain.service.Constants.MEMBERSHIP_POLICY; +import static com.metaformsystems.redline.domain.service.Constants.*; @Service public class DataAccessService { @@ -62,7 +58,7 @@ public DataAccessService(DataPlaneApiClient dataPlaneApiClient, WebDidResolver w } @Transactional - public void uploadFileForParticipant(Long participantId, Map publicMetadata, Map privateMetadata, InputStream fileStream, String contentType, String originalFilename, List celExpressions, PolicySet permissions) { + public void uploadFileForParticipant(Long participantId, Map publicMetadata, Map privateMetadata, InputStream fileStream, String contentType, String originalFilename, List celExpressions, List constraints) { var participant = participantRepository.findById(participantId).orElseThrow(() -> new ObjectNotFoundException("Participant not found with id: " + participantId)); var participantContextId = participant.getParticipantContextId(); @@ -107,22 +103,23 @@ public void uploadFileForParticipant(Long participantId, Map pub } //2. create policy - var policy = MEMBERSHIP_POLICY; - if (permissions != null) { - var permissionList = new ArrayList<>(policy.getPolicy().getPermission()); - permissionList.addAll(permissions.getPermission()); - policy.getPolicy().setPermission(permissionList); + var policy = NewPolicyDefinition.Builder.aNewPolicyDefinition() + .id(UUID.randomUUID().toString()) + .policy(new PolicySet(List.of(new PolicySet.Permission("use", + new ArrayList<>(List.of(MEMBERSHIP_CONSTRAINT)) + )))).build(); + if (constraints != null) { + policy.getPolicy().getPermission().getFirst().getConstraint().addAll(constraints); } - policy.setId(UUID.randomUUID().toString()); managementApiClient.createPolicy(participantContextId, policy); //3. create contract definition if none exists - var contractDef = MEMBERSHIP_CONTRACT_DEFINITION; - contractDef.setId(UUID.randomUUID().toString()); - contractDef.setContractPolicyId(policy.getId()); - contractDef.setAccessPolicyId(policy.getId()); - contractDef.setAssetsSelector(Set.of(new Criterion("id", "=", assetId))); - managementApiClient.createContractDefinition(participantContextId, contractDef); + var contractDef = NewContractDefinition.Builder.aNewContractDefinition(); + contractDef.id(UUID.randomUUID().toString()); + contractDef.contractPolicyId(policy.getId()); + contractDef.accessPolicyId(policy.getId()); + contractDef.assetsSelector(Set.of(new Criterion("id", "=", assetId))); + managementApiClient.createContractDefinition(participantContextId, contractDef.build()); //2. track uploaded file in DB From 014df3464a8aadfeba7ea1ba5eb36216742b21e5 Mon Sep 17 00:00:00 2001 From: Tim Dahlmanns Date: Fri, 30 Jan 2026 14:27:33 +0100 Subject: [PATCH 3/8] e2e: include cel expression and constraint --- .../redline/TransferEndToEndTest.java | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/test/java/com/metaformsystems/redline/TransferEndToEndTest.java b/src/test/java/com/metaformsystems/redline/TransferEndToEndTest.java index 714a345..30a80bf 100644 --- a/src/test/java/com/metaformsystems/redline/TransferEndToEndTest.java +++ b/src/test/java/com/metaformsystems/redline/TransferEndToEndTest.java @@ -25,6 +25,7 @@ import com.metaformsystems.redline.api.dto.response.Participant; import com.metaformsystems.redline.api.dto.response.Tenant; import com.metaformsystems.redline.infrastructure.client.management.dto.Catalog; +import com.metaformsystems.redline.infrastructure.client.management.dto.CelExpression; import com.metaformsystems.redline.infrastructure.client.management.dto.Constraint; import com.metaformsystems.redline.infrastructure.client.management.dto.TransferProcess; import io.restassured.http.ContentType; @@ -39,6 +40,7 @@ import java.util.Arrays; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.concurrent.atomic.AtomicReference; import static io.restassured.RestAssured.given; @@ -80,11 +82,21 @@ void testTransferFile() throws Exception { log.info("uploading file to provider"); // upload file for consumer - this creates asset, policy, contract-def, etc. + var celExpressions = List.of(CelExpression.Builder.aNewCelExpression() + .id("counter-party-id-" + slug) + .leftOperand("CounterPartyId") + .description("Counter Party Access Policy") + .expression("ctx.agent.id == this.rightOperand") + .scopes(Set.of("catalog", "contract.negotiation", "transfer.process")) + .build()); + var constraints = List.of(new Constraint("CounterPartyId", "eq", consumerDid)); baseRequest() .contentType(ContentType.MULTIPART) .multiPart("file", "testfile.txt", "This is a test file.".getBytes()) .multiPart("publicMetadata", "{\"slug\": \"%s\"}".formatted(slug), "application/json") .multiPart("privateMetadata", "{\"privateSlug\": \"%s\"}".formatted(slug), "application/json") + .multiPart("celExpressions", new com.fasterxml.jackson.databind.ObjectMapper().writeValueAsString(celExpressions), "application/json") + .multiPart("constraints", new com.fasterxml.jackson.databind.ObjectMapper().writeValueAsString(constraints), "application/json") .post("/api/ui/service-providers/%s/tenants/%s/participants/%s/files".formatted(SERVICE_PROVIDER_ID, provider.tenantId(), provider.participantId())) .then() .statusCode(200); From 9cd32af893b40ff448e7c63ae17172c73ab1dc6e Mon Sep 17 00:00:00 2001 From: Tim Dahlmanns Date: Fri, 30 Jan 2026 14:39:44 +0100 Subject: [PATCH 4/8] test: update and add tests --- .../api/controller/EdcDataControllerTest.java | 44 ++++++++------- .../DataAccessServiceIntegrationTest.java | 53 +++++++++++++++++++ 2 files changed, 78 insertions(+), 19 deletions(-) diff --git a/src/test/java/com/metaformsystems/redline/api/controller/EdcDataControllerTest.java b/src/test/java/com/metaformsystems/redline/api/controller/EdcDataControllerTest.java index 89bfdb8..b36b5c6 100644 --- a/src/test/java/com/metaformsystems/redline/api/controller/EdcDataControllerTest.java +++ b/src/test/java/com/metaformsystems/redline/api/controller/EdcDataControllerTest.java @@ -168,6 +168,13 @@ void shouldUploadFile() throws Exception { publicMetadata.getHeaders().setContentType(MediaType.APPLICATION_JSON); var privateMetadata = new MockPart("privateMetadata", "{\"private\": \"value\"}".getBytes()); privateMetadata.getHeaders().setContentType(MediaType.APPLICATION_JSON); + var celExpressions = new MockPart("celExpressions", "[{\"id\":\"custom-expression\",\"leftOperand\":\"CustomCredential\",\"description\":\"Custom expression\",\"expression\":\"true\",\"scopes\":[\"catalog\"]}]".getBytes()); + celExpressions.getHeaders().setContentType(MediaType.APPLICATION_JSON); + var constraints = new MockPart("constraints", "[{\"leftOperand\":\"purpose\",\"operator\":\"eq\",\"rightOperand\":\"test\"}]".getBytes()); + constraints.getHeaders().setContentType(MediaType.APPLICATION_JSON); + + // mock create-cel-expression (custom) + mockWebServer.enqueue(new MockResponse().setResponseCode(200)); // Mock the upload response from the dataplane mockWebServer.enqueue(new MockResponse() @@ -175,10 +182,10 @@ void shouldUploadFile() throws Exception { .setBody("{\"id\": \"generated-file-id-123\"}") .addHeader("Content-Type", "application/json")); - // mock create-cel-expression + // mock create-asset mockWebServer.enqueue(new MockResponse().setResponseCode(200)); - // mock create-asset + // mock create-cel-expression (membership) mockWebServer.enqueue(new MockResponse().setResponseCode(200)); // mock create-policy @@ -191,7 +198,7 @@ void shouldUploadFile() throws Exception { mockMvc.perform(multipart("/api/ui/service-providers/{providerId}/tenants/{tenantId}/participants/{participantId}/files", serviceProvider.getId(), tenant.getId(), participant.getId()) .file(mockFile) - .part(publicMetadata, privateMetadata)) + .part(publicMetadata, privateMetadata, celExpressions, constraints)) .andExpect(status().isOk()); assertThat(participantRepository.findById(participant.getId())).isPresent() @@ -199,7 +206,7 @@ void shouldUploadFile() throws Exception { } @Test - void shouldUploadFile_whenPolicyAndContractDefExist() throws Exception { + void shouldFailUploadFile_whenPolicyAndContractDefExist() throws Exception { // Create a tenant and participant var tenant = new Tenant(); tenant.setName("Test Tenant"); @@ -225,31 +232,30 @@ void shouldUploadFile_whenPolicyAndContractDefExist() throws Exception { ); // Create metadata - var metadataPart = new MockPart("metadata", "{\"foo\": \"bar\"}".getBytes()); - metadataPart.getHeaders().setContentType(MediaType.APPLICATION_JSON); + var publicMetadata = new MockPart("publicMetadata", "{\"foo\": \"bar\"}".getBytes()); + publicMetadata.getHeaders().setContentType(MediaType.APPLICATION_JSON); + var privateMetadata = new MockPart("privateMetadata", "{\"private\": \"value\"}".getBytes()); + privateMetadata.getHeaders().setContentType(MediaType.APPLICATION_JSON); - // mock create-cel-expression - mockWebServer.enqueue(new MockResponse().setResponseCode(200)); + // Mock the upload response from the dataplane + mockWebServer.enqueue(new MockResponse() + .setResponseCode(200) + .setBody("{\"id\": \"generated-file-id-123\"}") + .addHeader("Content-Type", "application/json")); // mock create-asset mockWebServer.enqueue(new MockResponse().setResponseCode(200)); - // mock create-policy - mockWebServer.enqueue(new MockResponse().setResponseCode(409)); + // mock create-cel-expression (membership) + mockWebServer.enqueue(new MockResponse().setResponseCode(200)); - //mock create-contractdef + // mock create-policy mockWebServer.enqueue(new MockResponse().setResponseCode(409)); - // Mock the upload response from the dataplane - mockWebServer.enqueue(new MockResponse() - .setResponseCode(200) - .setBody("{\"id\": \"generated-file-id-123\"}") - .addHeader("Content-Type", "application/json")); - mockMvc.perform(multipart("/api/ui/service-providers/{providerId}/tenants/{tenantId}/participants/{participantId}/files", serviceProvider.getId(), tenant.getId(), participant.getId()) .file(mockFile) - .part(metadataPart)) + .part(publicMetadata, privateMetadata)) .andExpect(status().isInternalServerError()); } @@ -367,4 +373,4 @@ void shouldRequestContractWithConstraints() throws Exception { .content(objectMapper.writeValueAsString(contractRequest))) .andExpect(status().isOk()); } -} \ No newline at end of file +} diff --git a/src/test/java/com/metaformsystems/redline/domain/service/DataAccessServiceIntegrationTest.java b/src/test/java/com/metaformsystems/redline/domain/service/DataAccessServiceIntegrationTest.java index 076cef7..be47d5a 100644 --- a/src/test/java/com/metaformsystems/redline/domain/service/DataAccessServiceIntegrationTest.java +++ b/src/test/java/com/metaformsystems/redline/domain/service/DataAccessServiceIntegrationTest.java @@ -27,8 +27,10 @@ import com.metaformsystems.redline.domain.repository.TenantRepository; import com.metaformsystems.redline.infrastructure.client.management.dto.Constraint; import com.metaformsystems.redline.infrastructure.client.management.dto.ContractRequest; +import com.metaformsystems.redline.infrastructure.client.management.dto.CelExpression; import com.metaformsystems.redline.infrastructure.client.management.dto.Obligation; import com.metaformsystems.redline.infrastructure.client.management.dto.Offer; +import com.metaformsystems.redline.infrastructure.client.management.dto.PolicySet; import okhttp3.mockwebserver.MockResponse; import okhttp3.mockwebserver.MockWebServer; import org.junit.jupiter.api.AfterEach; @@ -48,6 +50,7 @@ import java.util.ArrayList; import java.util.List; import java.util.Map; +import java.util.Set; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.anyString; @@ -153,6 +156,56 @@ void shouldRequestCatalog_andBypassCacheWithNoCache() { assertThat(mockWebServer.getRequestCount()).isEqualTo(2); } + @Test + void shouldUploadFileWithCelExpressionsAndConstraints() { + var participant = createAndSaveParticipant("ctx-upload-1", "did:web:me"); + + // custom CEL expression + mockWebServer.enqueue(new MockResponse().setResponseCode(200)); + + // dataplane upload response + mockWebServer.enqueue(new MockResponse() + .setBody("{\"id\": \"generated-file-id-123\"}") + .addHeader("Content-Type", "application/json")); + + // asset creation + mockWebServer.enqueue(new MockResponse().setResponseCode(200)); + + // membership CEL expression + mockWebServer.enqueue(new MockResponse().setResponseCode(200)); + + // policy creation + mockWebServer.enqueue(new MockResponse().setResponseCode(200)); + + // contract definition + mockWebServer.enqueue(new MockResponse().setResponseCode(200)); + + var celExpressions = List.of(CelExpression.Builder.aNewCelExpression() + .id("custom-expression") + .leftOperand("CustomCredential") + .description("Custom expression") + .expression("true") + .scopes(Set.of("catalog")) + .build()); + + var constraints = List.of(new PolicySet.Constraint("purpose", "eq", "test")); + + dataAccessService.uploadFileForParticipant( + participant.getId(), + new java.util.HashMap<>(Map.of("foo", "bar")), + new java.util.HashMap<>(Map.of("private", "value")), + new java.io.ByteArrayInputStream("file-data".getBytes()), + "text/plain", + "file.txt", + celExpressions, + constraints + ); + + assertThat(participantRepository.findById(participant.getId())) + .isPresent() + .hasValueSatisfying(p -> assertThat(p.getUploadedFiles()).hasSize(1)); + } + @Test void shouldRequestCatalog_andRefreshWhenMaxAgeIsZero() { var participant = createAndSaveParticipant("ctx-3", "did:web:me"); From 1cb5a4579a6cccc0fbfa401614bfff612ca24c20 Mon Sep 17 00:00:00 2001 From: Tim Dahlmanns Date: Fri, 30 Jan 2026 15:02:51 +0100 Subject: [PATCH 5/8] test: fix dataplane url --- .../domain/service/DataAccessServiceIntegrationTest.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/test/java/com/metaformsystems/redline/domain/service/DataAccessServiceIntegrationTest.java b/src/test/java/com/metaformsystems/redline/domain/service/DataAccessServiceIntegrationTest.java index be47d5a..c14015e 100644 --- a/src/test/java/com/metaformsystems/redline/domain/service/DataAccessServiceIntegrationTest.java +++ b/src/test/java/com/metaformsystems/redline/domain/service/DataAccessServiceIntegrationTest.java @@ -94,6 +94,8 @@ static void configureProperties(DynamicPropertyRegistry registry) { registry.add("tenant-manager.url", () -> "http://%s:%s/tm".formatted(mockBackEndHost, mockBackEndPort)); registry.add("vault.url", () -> "http://%s:%s/vault".formatted(mockBackEndHost, mockBackEndPort)); registry.add("controlplane.url", () -> "http://%s:%s/cp".formatted(mockBackEndHost, mockBackEndPort)); + registry.add("dataplane.url", () -> "http://%s:%s/dataplane".formatted(mockBackEndHost, mockBackEndPort)); + registry.add("dataplane.internal.url", () -> "http://%s:%s/dataplane".formatted(mockBackEndHost, mockBackEndPort)); } @AfterEach From 675cd0843bb1496217331005917107b3f3668513 Mon Sep 17 00:00:00 2001 From: Tim Dahlmanns Date: Fri, 30 Jan 2026 15:10:02 +0100 Subject: [PATCH 6/8] chore: update license header --- .../redline/api/controller/EdcDataController.java | 1 + .../com/metaformsystems/redline/domain/service/Constants.java | 1 + .../redline/domain/service/DataAccessService.java | 1 + .../java/com/metaformsystems/redline/TransferEndToEndTest.java | 1 + .../redline/api/controller/EdcDataControllerTest.java | 1 + .../redline/domain/service/DataAccessServiceIntegrationTest.java | 1 + 6 files changed, 6 insertions(+) diff --git a/src/main/java/com/metaformsystems/redline/api/controller/EdcDataController.java b/src/main/java/com/metaformsystems/redline/api/controller/EdcDataController.java index 57cc251..f8f6cfe 100644 --- a/src/main/java/com/metaformsystems/redline/api/controller/EdcDataController.java +++ b/src/main/java/com/metaformsystems/redline/api/controller/EdcDataController.java @@ -9,6 +9,7 @@ * * Contributors: * Metaform Systems, Inc. - initial API and implementation + * Fraunhofer-Gesellschaft zur Förderung der angewandten Forschung e.V. - OpenAPI and file upload * */ diff --git a/src/main/java/com/metaformsystems/redline/domain/service/Constants.java b/src/main/java/com/metaformsystems/redline/domain/service/Constants.java index 0d8187f..e116b4f 100644 --- a/src/main/java/com/metaformsystems/redline/domain/service/Constants.java +++ b/src/main/java/com/metaformsystems/redline/domain/service/Constants.java @@ -9,6 +9,7 @@ * * Contributors: * Metaform Systems, Inc. - initial API and implementation + * Fraunhofer-Gesellschaft zur Förderung der angewandten Forschung e.V. - refactoring * */ diff --git a/src/main/java/com/metaformsystems/redline/domain/service/DataAccessService.java b/src/main/java/com/metaformsystems/redline/domain/service/DataAccessService.java index 13fb2ef..46bd42d 100644 --- a/src/main/java/com/metaformsystems/redline/domain/service/DataAccessService.java +++ b/src/main/java/com/metaformsystems/redline/domain/service/DataAccessService.java @@ -9,6 +9,7 @@ * * Contributors: * Metaform Systems, Inc. - initial API and implementation + * Fraunhofer-Gesellschaft zur Förderung der angewandten Forschung e.V. - CEL and policy creation for uploaded file * */ diff --git a/src/test/java/com/metaformsystems/redline/TransferEndToEndTest.java b/src/test/java/com/metaformsystems/redline/TransferEndToEndTest.java index 30a80bf..be9df37 100644 --- a/src/test/java/com/metaformsystems/redline/TransferEndToEndTest.java +++ b/src/test/java/com/metaformsystems/redline/TransferEndToEndTest.java @@ -9,6 +9,7 @@ * * Contributors: * Metaform Systems, Inc. - initial API and implementation + * Fraunhofer-Gesellschaft zur Förderung der angewandten Forschung e.V. - Add CEL and access constraint * */ diff --git a/src/test/java/com/metaformsystems/redline/api/controller/EdcDataControllerTest.java b/src/test/java/com/metaformsystems/redline/api/controller/EdcDataControllerTest.java index b36b5c6..9b73322 100644 --- a/src/test/java/com/metaformsystems/redline/api/controller/EdcDataControllerTest.java +++ b/src/test/java/com/metaformsystems/redline/api/controller/EdcDataControllerTest.java @@ -9,6 +9,7 @@ * * Contributors: * Metaform Systems, Inc. - initial API and implementation + * Fraunhofer-Gesellschaft zur Förderung der angewandten Forschung e.V. - improve tests * */ diff --git a/src/test/java/com/metaformsystems/redline/domain/service/DataAccessServiceIntegrationTest.java b/src/test/java/com/metaformsystems/redline/domain/service/DataAccessServiceIntegrationTest.java index c14015e..239a285 100644 --- a/src/test/java/com/metaformsystems/redline/domain/service/DataAccessServiceIntegrationTest.java +++ b/src/test/java/com/metaformsystems/redline/domain/service/DataAccessServiceIntegrationTest.java @@ -9,6 +9,7 @@ * * Contributors: * Metaform Systems, Inc. - initial API and implementation + * Fraunhofer-Gesellschaft zur Förderung der angewandten Forschung e.V. - CEL and Constraint * */ From 68bd601c95c1a38805a3fea3647351761fe6ae79 Mon Sep 17 00:00:00 2001 From: Tim Dahlmanns Date: Mon, 2 Feb 2026 09:53:15 +0100 Subject: [PATCH 7/8] refactor: upload replace constraints with policy set --- .../api/controller/EdcDataController.java | 20 +++++------ .../domain/service/DataAccessService.java | 36 +++++++++++-------- .../redline/TransferEndToEndTest.java | 12 +++---- .../DataAccessServiceIntegrationTest.java | 5 +-- 4 files changed, 40 insertions(+), 33 deletions(-) diff --git a/src/main/java/com/metaformsystems/redline/api/controller/EdcDataController.java b/src/main/java/com/metaformsystems/redline/api/controller/EdcDataController.java index f8f6cfe..fdadd08 100644 --- a/src/main/java/com/metaformsystems/redline/api/controller/EdcDataController.java +++ b/src/main/java/com/metaformsystems/redline/api/controller/EdcDataController.java @@ -72,20 +72,20 @@ public ResponseEntity uploadFile(@PathVariable Long participantId, @PathVariable Long providerId, @RequestPart("publicMetadata") String publicMetadata, @RequestPart("privateMetadata") String privateMetadata, - @RequestPart(value = "celExpressions", required = false) String celExpressions, - @RequestPart(value = "constraints", required = false) String constraints, + @RequestPart(value = "celExpressions", required = false) String celExpressionsString, + @RequestPart(value = "policySet", required = false) String policySetString, @RequestPart("file") MultipartFile file) { try { var publicMetadataMap = objectMapper.readValue(publicMetadata, new TypeReference>() {}); var privateMetadataMap = objectMapper.readValue(privateMetadata, new TypeReference>() {}); - List celExpressionList = null; - if (celExpressions != null) { - celExpressionList = objectMapper.readValue(celExpressions, new TypeReference<>() {}); + List celExpressions = null; + if (celExpressionsString != null) { + celExpressions = objectMapper.readValue(celExpressionsString, new TypeReference<>() {}); } - List constraintList = null; - if (constraints != null) { - constraintList = objectMapper.readValue(constraints, new TypeReference<>() {}); + PolicySet policySet = null; + if (policySetString != null) { + policySet = objectMapper.readValue(policySetString, new TypeReference<>() {}); } dataAccessService.uploadFileForParticipant( participantId, @@ -94,8 +94,8 @@ public ResponseEntity uploadFile(@PathVariable Long participantId, file.getInputStream(), file.getContentType(), file.getOriginalFilename(), - celExpressionList, - constraintList + celExpressions, + policySet ); } catch (IOException e) { return ResponseEntity.internalServerError().build(); diff --git a/src/main/java/com/metaformsystems/redline/domain/service/DataAccessService.java b/src/main/java/com/metaformsystems/redline/domain/service/DataAccessService.java index 46bd42d..8de7d5c 100644 --- a/src/main/java/com/metaformsystems/redline/domain/service/DataAccessService.java +++ b/src/main/java/com/metaformsystems/redline/domain/service/DataAccessService.java @@ -39,7 +39,10 @@ import java.util.stream.Collectors; import java.util.stream.Stream; -import static com.metaformsystems.redline.domain.service.Constants.*; +import static com.metaformsystems.redline.domain.service.Constants.ASSET_PERMISSION; +import static com.metaformsystems.redline.domain.service.Constants.MEMBERSHIP_CONSTRAINT; +import static com.metaformsystems.redline.domain.service.Constants.MEMBERSHIP_EXPRESSION; +import static com.metaformsystems.redline.domain.service.Constants.MEMBERSHIP_EXPRESSION_ID; @Service public class DataAccessService { @@ -59,7 +62,7 @@ public DataAccessService(DataPlaneApiClient dataPlaneApiClient, WebDidResolver w } @Transactional - public void uploadFileForParticipant(Long participantId, Map publicMetadata, Map privateMetadata, InputStream fileStream, String contentType, String originalFilename, List celExpressions, List constraints) { + public void uploadFileForParticipant(Long participantId, Map publicMetadata, Map privateMetadata, InputStream fileStream, String contentType, String originalFilename, List celExpressions, PolicySet policySet) { var participant = participantRepository.findById(participantId).orElseThrow(() -> new ObjectNotFoundException("Participant not found with id: " + participantId)); var participantContextId = participant.getParticipantContextId(); @@ -104,23 +107,28 @@ public void uploadFileForParticipant(Long participantId, Map pub } //2. create policy + if (policySet != null) { + var constraints = new ArrayList<>(List.of(MEMBERSHIP_CONSTRAINT)); + constraints.addAll(policySet.getPermission().getFirst().getConstraint()); + policySet.getPermission().getFirst().setConstraint(constraints); + } else { + policySet = new PolicySet(List.of(new PolicySet.Permission("use", + new ArrayList<>(List.of(MEMBERSHIP_CONSTRAINT)) + ))); + } var policy = NewPolicyDefinition.Builder.aNewPolicyDefinition() .id(UUID.randomUUID().toString()) - .policy(new PolicySet(List.of(new PolicySet.Permission("use", - new ArrayList<>(List.of(MEMBERSHIP_CONSTRAINT)) - )))).build(); - if (constraints != null) { - policy.getPolicy().getPermission().getFirst().getConstraint().addAll(constraints); - } + .policy(policySet).build(); managementApiClient.createPolicy(participantContextId, policy); //3. create contract definition if none exists - var contractDef = NewContractDefinition.Builder.aNewContractDefinition(); - contractDef.id(UUID.randomUUID().toString()); - contractDef.contractPolicyId(policy.getId()); - contractDef.accessPolicyId(policy.getId()); - contractDef.assetsSelector(Set.of(new Criterion("id", "=", assetId))); - managementApiClient.createContractDefinition(participantContextId, contractDef.build()); + var contractDef = NewContractDefinition.Builder.aNewContractDefinition() + .id(UUID.randomUUID().toString()) + .contractPolicyId(policy.getId()) + .accessPolicyId(policy.getId()) + .assetsSelector(Set.of(new Criterion("id", "=", assetId))) + .build(); + managementApiClient.createContractDefinition(participantContextId, contractDef); //2. track uploaded file in DB diff --git a/src/test/java/com/metaformsystems/redline/TransferEndToEndTest.java b/src/test/java/com/metaformsystems/redline/TransferEndToEndTest.java index be9df37..5c5b86e 100644 --- a/src/test/java/com/metaformsystems/redline/TransferEndToEndTest.java +++ b/src/test/java/com/metaformsystems/redline/TransferEndToEndTest.java @@ -25,10 +25,7 @@ import com.metaformsystems.redline.api.dto.response.FileResource; import com.metaformsystems.redline.api.dto.response.Participant; import com.metaformsystems.redline.api.dto.response.Tenant; -import com.metaformsystems.redline.infrastructure.client.management.dto.Catalog; -import com.metaformsystems.redline.infrastructure.client.management.dto.CelExpression; -import com.metaformsystems.redline.infrastructure.client.management.dto.Constraint; -import com.metaformsystems.redline.infrastructure.client.management.dto.TransferProcess; +import com.metaformsystems.redline.infrastructure.client.management.dto.*; import io.restassured.http.ContentType; import io.restassured.specification.RequestSpecification; import org.junit.jupiter.api.Test; @@ -50,7 +47,7 @@ /** * This test runs through a full participant deployment for consumer and provider plus a data transfer between them. - * For this test a running instance of JAD is required, and the Redline API Server must be reachable at http://redline.localhost. + * For this test a running instance of JAD is required, and the Redline API Server must be reachable at http://redline.localhost:8080. */ @EnabledIfEnvironmentVariable(named = "ENABLE_E2E_TESTS", matches = "true", disabledReason = "This can only run if ENABLE_E2E_TESTS=true is set in the environment.") public class TransferEndToEndTest { @@ -90,14 +87,15 @@ void testTransferFile() throws Exception { .expression("ctx.agent.id == this.rightOperand") .scopes(Set.of("catalog", "contract.negotiation", "transfer.process")) .build()); - var constraints = List.of(new Constraint("CounterPartyId", "eq", consumerDid)); + var policySet = new PolicySet(List.of(new PolicySet.Permission("use", + List.of(new PolicySet.Constraint("CounterPartyId", "eq", consumerDid))))); baseRequest() .contentType(ContentType.MULTIPART) .multiPart("file", "testfile.txt", "This is a test file.".getBytes()) .multiPart("publicMetadata", "{\"slug\": \"%s\"}".formatted(slug), "application/json") .multiPart("privateMetadata", "{\"privateSlug\": \"%s\"}".formatted(slug), "application/json") .multiPart("celExpressions", new com.fasterxml.jackson.databind.ObjectMapper().writeValueAsString(celExpressions), "application/json") - .multiPart("constraints", new com.fasterxml.jackson.databind.ObjectMapper().writeValueAsString(constraints), "application/json") + .multiPart("constraints", new com.fasterxml.jackson.databind.ObjectMapper().writeValueAsString(policySet), "application/json") .post("/api/ui/service-providers/%s/tenants/%s/participants/%s/files".formatted(SERVICE_PROVIDER_ID, provider.tenantId(), provider.participantId())) .then() .statusCode(200); diff --git a/src/test/java/com/metaformsystems/redline/domain/service/DataAccessServiceIntegrationTest.java b/src/test/java/com/metaformsystems/redline/domain/service/DataAccessServiceIntegrationTest.java index 239a285..713c83a 100644 --- a/src/test/java/com/metaformsystems/redline/domain/service/DataAccessServiceIntegrationTest.java +++ b/src/test/java/com/metaformsystems/redline/domain/service/DataAccessServiceIntegrationTest.java @@ -191,7 +191,8 @@ void shouldUploadFileWithCelExpressionsAndConstraints() { .scopes(Set.of("catalog")) .build()); - var constraints = List.of(new PolicySet.Constraint("purpose", "eq", "test")); + var policySet = new PolicySet(List.of(new PolicySet.Permission("use", + List.of(new PolicySet.Constraint("purpose", "eq", "test"))))); dataAccessService.uploadFileForParticipant( participant.getId(), @@ -201,7 +202,7 @@ void shouldUploadFileWithCelExpressionsAndConstraints() { "text/plain", "file.txt", celExpressions, - constraints + policySet ); assertThat(participantRepository.findById(participant.getId())) From 3b82569249f5f53a200a8ee81966cf1708c58424 Mon Sep 17 00:00:00 2001 From: Tim Dahlmanns Date: Mon, 2 Feb 2026 11:14:56 +0100 Subject: [PATCH 8/8] refactor: controller policy set type --- .../redline/api/controller/EdcDataController.java | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/main/java/com/metaformsystems/redline/api/controller/EdcDataController.java b/src/main/java/com/metaformsystems/redline/api/controller/EdcDataController.java index fdadd08..e371822 100644 --- a/src/main/java/com/metaformsystems/redline/api/controller/EdcDataController.java +++ b/src/main/java/com/metaformsystems/redline/api/controller/EdcDataController.java @@ -73,7 +73,7 @@ public ResponseEntity uploadFile(@PathVariable Long participantId, @RequestPart("publicMetadata") String publicMetadata, @RequestPart("privateMetadata") String privateMetadata, @RequestPart(value = "celExpressions", required = false) String celExpressionsString, - @RequestPart(value = "policySet", required = false) String policySetString, + @RequestPart(value = "policySet", required = false) PolicySet policySet, @RequestPart("file") MultipartFile file) { try { @@ -83,10 +83,6 @@ public ResponseEntity uploadFile(@PathVariable Long participantId, if (celExpressionsString != null) { celExpressions = objectMapper.readValue(celExpressionsString, new TypeReference<>() {}); } - PolicySet policySet = null; - if (policySetString != null) { - policySet = objectMapper.readValue(policySetString, new TypeReference<>() {}); - } dataAccessService.uploadFileForParticipant( participantId, publicMetadataMap,