Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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
*
*/

Expand All @@ -23,13 +24,7 @@
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;
Expand All @@ -39,14 +34,7 @@
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;
Expand Down Expand Up @@ -84,12 +72,31 @@ public ResponseEntity<Void> 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,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looking at the dataAccessService, this will always create Permission objects. Since we're pulling this up into the API, it would be better to accept a PolicyDefinition object rather than just constraints for full flexibility

@RequestPart("file") MultipartFile file) {

try {
var publicMetadataMap = objectMapper.readValue(publicMetadata, new TypeReference<Map<String, Object>>() {});
var privateMetadataMap = objectMapper.readValue(privateMetadata, new TypeReference<Map<String, Object>>() {});
dataAccessService.uploadFileForParticipant(participantId, publicMetadataMap, privateMetadataMap, file.getInputStream(), file.getContentType(), file.getOriginalFilename());
List<CelExpression> celExpressionList = null;
if (celExpressions != null) {
celExpressionList = objectMapper.readValue(celExpressions, new TypeReference<>() {});
}
List<PolicySet.Constraint> constraintList = null;
if (constraints != null) {
constraintList = objectMapper.readValue(constraints, new TypeReference<>() {});
}
dataAccessService.uploadFileForParticipant(
participantId,
publicMetadataMap,
privateMetadataMap,
file.getInputStream(),
file.getContentType(),
file.getOriginalFilename(),
celExpressionList,
constraintList
);
} catch (IOException e) {
return ResponseEntity.internalServerError().build();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,40 +9,17 @@
*
* Contributors:
* Metaform Systems, Inc. - initial API and implementation
* Fraunhofer-Gesellschaft zur Förderung der angewandten Forschung e.V. - refactoring
*
*/

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");
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
*
*/

Expand All @@ -21,13 +22,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;
Expand All @@ -39,20 +34,12 @@
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;

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.*;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we don't do wildcard imports. please leave individual imports in place.


@Service
public class DataAccessService {
Expand All @@ -72,11 +59,23 @@ public DataAccessService(DataPlaneApiClient dataPlaneApiClient, WebDidResolver w
}

@Transactional
public void uploadFileForParticipant(Long participantId, Map<String, Object> publicMetadata, Map<String, Object> privateMetadata, InputStream fileStream, String contentType, String originalFilename) {
public void uploadFileForParticipant(Long participantId, Map<String, Object> publicMetadata, Map<String, Object> privateMetadata, InputStream fileStream, String contentType, String originalFilename, List<CelExpression> celExpressions, List<PolicySet.Constraint> constraints) {

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);
Expand Down Expand Up @@ -105,16 +104,23 @@ public void uploadFileForParticipant(Long participantId, Map<String, Object> pub
}

//2. create policy
var policy = MEMBERSHIP_POLICY;
policy.setId(UUID.randomUUID().toString());
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);
}
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());
managementApiClient.createContractDefinition(participantContextId, contractDef);
var contractDef = NewContractDefinition.Builder.aNewContractDefinition();
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

use builder fluently

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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
*
*/

Expand All @@ -25,6 +26,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;
Expand All @@ -39,6 +41,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;
Expand Down Expand Up @@ -80,11 +83,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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
*
* Contributors:
* Metaform Systems, Inc. - initial API and implementation
* Fraunhofer-Gesellschaft zur Förderung der angewandten Forschung e.V. - improve tests
*
*/

Expand Down Expand Up @@ -168,17 +169,24 @@ 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()
.setResponseCode(200)
.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
Expand All @@ -191,15 +199,15 @@ 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()
.hasValueSatisfying(p -> assertThat(p.getUploadedFiles()).hasSize(1));
}

@Test
void shouldUploadFile_whenPolicyAndContractDefExist() throws Exception {
void shouldFailUploadFile_whenPolicyAndContractDefExist() throws Exception {
// Create a tenant and participant
var tenant = new Tenant();
tenant.setName("Test Tenant");
Expand All @@ -225,31 +233,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());
}

Expand Down Expand Up @@ -367,4 +374,4 @@ void shouldRequestContractWithConstraints() throws Exception {
.content(objectMapper.writeValueAsString(contractRequest)))
.andExpect(status().isOk());
}
}
}
Loading
Loading