Skip to content

Commit

Permalink
Add stable contract offer id
Browse files Browse the repository at this point in the history
  • Loading branch information
Christophe '116' Loiseau committed Feb 13, 2024
1 parent 78d92b0 commit 3ef07d0
Show file tree
Hide file tree
Showing 7 changed files with 92 additions and 8 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ please see [changelog_updates.md](docs/dev/changelog_updates.md).

#### Minor Changes
- Add new MDS fields and migrate existing MDS asset keys to mobilityDCAT-AP
- Introduce a stable ID for contracts offers

#### Patch Changes
- Docs: Enhanced starting a Http-Pull over the EDC-Ui documentation
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,7 @@ public static WrapperExtensionContext buildContext(
policyDefinitionService,
policyMapper
);
var dataOfferBuilder = new DspDataOfferBuilder(jsonLd);
var dataOfferBuilder = new DspDataOfferBuilder(jsonLd, monitor);
var dspCatalogService = new DspCatalogService(catalogService, dataOfferBuilder);
var catalogApiService = new CatalogApiService(
assetMapper,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@
import de.sovity.edc.ext.wrapper.api.ui.pages.transferhistory.TransferProcessStateService;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.edc.connector.contract.spi.types.agreement.ContractAgreement;
import org.eclipse.edc.connector.contract.spi.types.negotiation.ContractNegotiation;
import org.eclipse.edc.connector.transfer.spi.types.TransferProcess;
Expand All @@ -35,7 +34,6 @@
import static de.sovity.edc.ext.wrapper.utils.EdcDateUtils.utcMillisToOffsetDateTime;
import static de.sovity.edc.ext.wrapper.utils.EdcDateUtils.utcSecondsToOffsetDateTime;

@Slf4j
@RequiredArgsConstructor
public class ContractAgreementPageCardBuilder {
private final PolicyMapper policyMapper;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,23 @@
import de.sovity.edc.utils.jsonld.vocab.Prop;
import jakarta.json.Json;
import jakarta.json.JsonObject;
import jakarta.json.JsonString;
import lombok.RequiredArgsConstructor;
import lombok.val;
import org.eclipse.edc.jsonld.spi.JsonLd;
import org.eclipse.edc.spi.monitor.Monitor;
import org.jetbrains.annotations.NotNull;

import java.io.StringWriter;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Base64;

@RequiredArgsConstructor
public class DspDataOfferBuilder {

private final JsonLd jsonLd;
private final Monitor monitor;

public DspCatalog buildDataOffers(String endpoint, JsonObject json) {
json = jsonLd.expand(json).orElseThrow(DspCatalogServiceException::ofFailure);
Expand Down Expand Up @@ -65,7 +74,50 @@ private DspDataOffer buildDataOffer(JsonObject dataset) {
}

@NotNull
private DspContractOffer buildContractOffer(JsonObject json) {
return new DspContractOffer(JsonLdUtils.id(json), json);
DspContractOffer buildContractOffer(JsonObject json) {
/*
* /!\ Workaround
* TODO: can't reference a private repo in a public repo
* https://github.com/sovity/edc-broker-server-extension/issues/278
* https://github.com/sovity/edc-broker-server-extension/issues/409
*
* The Eclipse EDC uses a new random ID for each contract offer that it returns.
* This can't be used as an id.
* As a workaround, we must introduce our own ID.
* For a first iteration, we will assume that the content of the policy remains the same (same content, same order)
* and hash it to use it as a key.
*/

String idFieldName = "@id";
val id = json.get(idFieldName);
val idAsString = (JsonString) id;
val parts = idAsString.getString().split(":");
if (parts.length != 3) {
throw new RuntimeException("Can't use " + idAsString + ": wrong format, must be made of 3 parts.");
}

val sw = new StringWriter();
try (val writer = Json.createWriter(sw)) {
// FIXME: This doesn't enforce any property order and may cause trouble if the returned policy schema is not consistent
// Use canonical form if needed later.
val noId = Json.createObjectBuilder(json).remove(idFieldName).build();
writer.write(noId);
val policyJsonString = sw.toString();
try {
val hash = MessageDigest.getInstance("sha-1").digest(policyJsonString.getBytes());
val b64 = Base64.getEncoder().encode(hash);
val contractId = parts[0];
val assetId = parts[1];
val policyId = new String(b64);
val stableId = contractId + ":" + assetId + ":" + policyId;

val copy = Json.createObjectBuilder(json).remove(idFieldName).add(idFieldName, stableId).build();

return new DspContractOffer(stableId, copy);
} catch (NoSuchAlgorithmException e) {
monitor.severe("Failed to hash with sha-1", e);
throw new RuntimeException(e);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,8 @@ private DspCatalogService newDspCatalogService(String resultJsonFilename) {

var result = CompletableFuture.completedFuture(StatusResult.success(catalogJson.getBytes(StandardCharsets.UTF_8)));
when(catalogService.requestCatalog(eq(endpoint), eq("dataspace-protocol-http"), eq(QuerySpec.max()))).thenReturn(result);
var dataOfferBuilder = new DspDataOfferBuilder(new TitaniumJsonLd(mock(Monitor.class)));
var monitor = mock(Monitor.class);
var dataOfferBuilder = new DspDataOfferBuilder(new TitaniumJsonLd(monitor), monitor);

return new DspCatalogService(catalogService, dataOfferBuilder);
}
Expand All @@ -71,7 +72,7 @@ void testCatalogMapping() {

assertThat(offer.getContractOffers()).hasSize(1);
var co = offer.getContractOffers().get(0);
assertThat(co.getContractOfferId()).isEqualTo("policy-1");
assertThat(co.getContractOfferId()).isEqualTo("contract-id:asset-id:gsA8LIxJakmI9clojmLCfhsh0A4=");
assertThat(toJson(co.getPolicyJsonLd())).contains("ALWAYS_TRUE");

assertThat(offer.getDistributions()).hasSize(1);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package de.sovity.edc.utils.catalog.mapper;

import com.apicatalog.jsonld.JsonLd;
import jakarta.json.Json;
import lombok.val;
import org.eclipse.edc.jsonld.TitaniumJsonLd;
import org.eclipse.edc.spi.monitor.Monitor;
import org.junit.jupiter.api.Test;

import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.mock;

class DspDataOfferBuilderTest {
@Test
void testCanConvertTheEdcIdToAStableId() {
// arrange
val contractOffer = Json.createObjectBuilder()
.add("@id", "part1:part2:part3")
.add("somefield", "somevalue")
.build();

// act
val monitor = mock(Monitor.class);
val result = new DspDataOfferBuilder(new TitaniumJsonLd(monitor), monitor).buildContractOffer(contractOffer);

// assert
val stableId = "part1:part2:KbdwJ8MGwX3y7K9mi3lhzplluhc=";
assertThat(result.getContractOfferId()).isEqualTo(stableId);
assertThat(result.getPolicyJsonLd().getString("@id")).isEqualTo(stableId);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"@id": "test-1.0",
"@type": "dcat:Dataset",
"odrl:hasPolicy": {
"@id": "policy-1",
"@id": "contract-id:asset-id:policy-id",
"@type": "odrl:Set",
"odrl:permission": {
"odrl:target": "test-1.0",
Expand Down

0 comments on commit 3ef07d0

Please sign in to comment.