Skip to content

Commit 8003262

Browse files
authored
feat: first E2E transfer with DCP (#23)
feat: first E2E transfer with DCP enabled
1 parent 5b1e8fd commit 8003262

16 files changed

Lines changed: 911 additions & 120 deletions

gradle/libs.versions.toml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ edc-core-dataplane-signaling-transfer = { module = "org.eclipse.edc:transfer-dat
6060
# EDC Lib modules
6161
edc-lib-store = { module = "org.eclipse.edc:store-lib", version.ref = "edc" }
6262
edc-lib-query = { module = "org.eclipse.edc:query-lib", version.ref = "edc" }
63+
edc-lib-sql = { module = "org.eclipse.edc:sql-lib", version.ref = "edc" }
6364
edc-lib-jsonld = { module = "org.eclipse.edc:json-ld-lib", version.ref = "edc" }
6465
edc-lib-transform = { module = "org.eclipse.edc:transform-lib", version.ref = "edc" }
6566
edc-lib-controlplane-transform = { module = "org.eclipse.edc:control-plane-transform", version.ref = "edc" }
@@ -92,6 +93,13 @@ edc-bom-dataplane = { module = "org.eclipse.edc:dataplane-base-bom", version.ref
9293
## EDC Fixtures
9394
edc-fixtures-sql = { module = "org.eclipse.edc:sql-test-fixtures", version.ref = "edc" }
9495

96+
### IH for testing
97+
edc-ih-test-fixtures = { module = "org.eclipse.edc:identityhub-test-fixtures", version.ref = "edc" }
98+
edc-bom-identityhub = { module = "org.eclipse.edc:identityhub-bom", version.ref = "edc" }
99+
edc-bom-identityhub-sql = { module = "org.eclipse.edc:identityhub-feature-sql-bom", version.ref = "edc" }
100+
edc-bom-issuerservice = { module = "org.eclipse.edc:issuerservice-bom", version.ref = "edc" }
101+
edc-bom-issuerservice-sql = { module = "org.eclipse.edc:issuerservice-feature-sql-bom", version.ref = "edc" }
102+
95103
# Other libraries
96104
awaitility = { module = "org.awaitility:awaitility", version.ref = "awaitility" }
97105
restAssured = { module = "io.rest-assured:rest-assured", version.ref = "restAssured" }

settings.gradle.kts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,12 +59,16 @@ include(":system-tests:system-test-fixtures")
5959
include(":system-tests:runtimes:controlplane-base")
6060
include(":system-tests:runtimes:controlplane-memory")
6161
include(":system-tests:runtimes:controlplane-postgres")
62+
include(":system-tests:runtimes:issuer")
63+
include(":system-tests:runtimes:identity-hub")
6264
include(":system-tests:runtime-tests")
6365
include(":system-tests:dsp-tck-tests")
6466
include(":system-tests:extensions:v-tck-extension")
6567
include(":system-tests:runtimes:tck:tck-controlplane-memory")
6668
include(":system-tests:runtimes:tck:tck-controlplane-postgres")
6769
include(":system-tests:runtimes:e2e:e2e-controlplane-memory")
6870
include(":system-tests:runtimes:e2e:e2e-controlplane-postgres")
71+
include(":system-tests:runtimes:e2e:e2e-dcp-controlplane-postgres")
72+
6973

7074

system-tests/runtime-tests/build.gradle.kts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,14 +20,17 @@ dependencies {
2020
testImplementation(libs.awaitility)
2121
testImplementation(libs.edc.spi.dataplane)
2222
testImplementation(libs.edc.spi.jsonld)
23+
testImplementation(libs.edc.spi.transaction.datasource)
2324
testImplementation(libs.edc.spi.control.plane)
2425
testImplementation(libs.edc.spi.participantcontext.config)
2526
testImplementation(libs.edc.lib.jsonld)
27+
testImplementation(libs.edc.lib.sql)
2628
testImplementation(libs.edc.junit)
2729
testImplementation(libs.restAssured)
2830
testImplementation(testFixtures(libs.edc.fixtures.sql))
2931
testImplementation(testFixtures(project(":extensions:lib:nats-lib")))
3032
testImplementation(project(":system-tests:system-test-fixtures"))
33+
testImplementation(testFixtures(libs.edc.ih.test.fixtures))
3134
testImplementation(libs.testcontainers.junit)
3235
testImplementation(libs.testcontainers.vault)
3336
testImplementation(libs.testcontainers.postgres)

system-tests/runtime-tests/src/test/java/org/eclipse/edc/virtualized/Runtimes.java

Lines changed: 40 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -14,27 +14,19 @@
1414

1515
package org.eclipse.edc.virtualized;
1616

17-
import org.eclipse.edc.connector.controlplane.services.spi.asset.AssetService;
18-
import org.eclipse.edc.connector.controlplane.services.spi.catalog.CatalogService;
19-
import org.eclipse.edc.connector.controlplane.services.spi.contractdefinition.ContractDefinitionService;
20-
import org.eclipse.edc.connector.controlplane.services.spi.contractnegotiation.ContractNegotiationService;
21-
import org.eclipse.edc.connector.controlplane.services.spi.policydefinition.PolicyDefinitionService;
22-
import org.eclipse.edc.connector.controlplane.services.spi.transferprocess.TransferProcessService;
23-
import org.eclipse.edc.junit.extensions.ComponentRuntimeContext;
2417
import org.eclipse.edc.junit.utils.Endpoints;
25-
import org.eclipse.edc.participantcontext.spi.config.service.ParticipantContextConfigService;
26-
import org.eclipse.edc.participantcontext.spi.service.ParticipantContextService;
2718
import org.eclipse.edc.spi.system.configuration.Config;
2819
import org.eclipse.edc.spi.system.configuration.ConfigFactory;
29-
import org.eclipse.edc.virtualized.transfer.fixtures.VirtualConnector;
3020

3121
import java.net.URI;
3222
import java.util.HashMap;
23+
import java.util.Map;
24+
import java.util.Objects;
3325

3426
import static org.eclipse.edc.util.io.Ports.getFreePort;
3527

3628
public interface Runtimes {
37-
29+
3830
interface ControlPlane {
3931
String NAME = "controlplane";
4032

@@ -46,6 +38,10 @@ interface ControlPlane {
4638
":system-tests:runtimes:e2e:e2e-controlplane-postgres",
4739
};
4840

41+
String[] DCP_PG_MODULES = {
42+
":system-tests:runtimes:e2e:e2e-dcp-controlplane-postgres",
43+
};
44+
4945
Endpoints.Builder ENDPOINTS = Endpoints.Builder.newInstance()
5046
.endpoint("control", () -> URI.create("http://localhost:" + getFreePort() + "/control"))
5147
.endpoint("protocol", () -> URI.create("http://localhost:" + getFreePort() + "/protocol"));
@@ -58,19 +54,40 @@ static Config config() {
5854
}
5955
});
6056
}
57+
}
58+
59+
interface Issuer {
60+
String NAME = "issuer";
61+
62+
String[] MODULES = {
63+
":system-tests:runtimes:issuer",
64+
};
65+
}
66+
67+
interface IdentityHub {
68+
String NAME = "identityhub";
69+
70+
String[] MODULES = {
71+
":system-tests:runtimes:identity-hub",
72+
};
73+
74+
static String didFor(Endpoints endpoints, String participantContextId) {
75+
var didEndpoint = Objects.requireNonNull(endpoints.getEndpoint("did"));
76+
String didLocation = String.format("%s%%3A%s", didEndpoint.get().getHost(), didEndpoint.get().getPort());
77+
return String.format("did:web:%s:%s", didLocation, participantContextId);
78+
}
6179

62-
static VirtualConnector connector(ComponentRuntimeContext ctx) {
63-
return new VirtualConnector(
64-
ctx.getService(ParticipantContextService.class),
65-
ctx.getService(ParticipantContextConfigService.class),
66-
ctx.getService(AssetService.class),
67-
ctx.getService(PolicyDefinitionService.class),
68-
ctx.getService(ContractDefinitionService.class),
69-
ctx.getService(CatalogService.class),
70-
ctx.getService(ContractNegotiationService.class),
71-
ctx.getService(TransferProcessService.class),
72-
ctx.getEndpoint("protocol")
73-
);
80+
static Config dcpConfig(Endpoints endpoints, String participantContextId) {
81+
var did = didFor(endpoints, participantContextId);
82+
var stsEndpoint = Objects.requireNonNull(endpoints.getEndpoint("sts"));
83+
return ConfigFactory.fromMap(Map.of(
84+
"edc.participant.id", did,
85+
"edc.iam.issuer.id", did,
86+
"edc.iam.sts.oauth.client.id", did,
87+
"edc.iam.sts.oauth.client.secret.alias", did + "-alias",
88+
"edc.iam.sts.oauth.token.url", stsEndpoint.get().toString() + "/token",
89+
"edc.iam.did.web.use.https", "false"
90+
));
7491
}
7592
}
7693
}
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
/*
2+
* Copyright (c) 2025 Metaform Systems, Inc.
3+
*
4+
* This program and the accompanying materials are made available under the
5+
* terms of the Apache License, Version 2.0 which is available at
6+
* https://www.apache.org/licenses/LICENSE-2.0
7+
*
8+
* SPDX-License-Identifier: Apache-2.0
9+
*
10+
* Contributors:
11+
* Metaform Systems, Inc. - initial API and implementation
12+
*
13+
*/
14+
15+
package org.eclipse.edc.virtualized.extensions;
16+
17+
import org.eclipse.edc.connector.controlplane.catalog.spi.policy.CatalogPolicyContext;
18+
import org.eclipse.edc.connector.controlplane.contract.spi.policy.ContractNegotiationPolicyContext;
19+
import org.eclipse.edc.connector.controlplane.contract.spi.policy.TransferProcessPolicyContext;
20+
import org.eclipse.edc.policy.context.request.spi.RequestCatalogPolicyContext;
21+
import org.eclipse.edc.policy.context.request.spi.RequestContractNegotiationPolicyContext;
22+
import org.eclipse.edc.policy.context.request.spi.RequestTransferProcessPolicyContext;
23+
import org.eclipse.edc.policy.engine.spi.AtomicConstraintRuleFunction;
24+
import org.eclipse.edc.policy.engine.spi.PolicyContext;
25+
import org.eclipse.edc.policy.engine.spi.PolicyEngine;
26+
import org.eclipse.edc.policy.engine.spi.RuleBindingRegistry;
27+
import org.eclipse.edc.policy.model.Permission;
28+
import org.eclipse.edc.runtime.metamodel.annotation.Inject;
29+
import org.eclipse.edc.spi.system.ServiceExtension;
30+
import org.eclipse.edc.spi.system.ServiceExtensionContext;
31+
32+
import java.util.Set;
33+
34+
import static org.eclipse.edc.policy.model.OdrlNamespace.ODRL_SCHEMA;
35+
import static org.eclipse.edc.virtualized.extensions.MembershipCredentialEvaluationFunction.MEMBERSHIP_CONSTRAINT_KEY;
36+
37+
/**
38+
* Extension that provide configuration for testing DSP + DCP integration.
39+
*/
40+
public class DcpPatchExtension implements ServiceExtension {
41+
42+
@Inject
43+
private PolicyEngine policyEngine;
44+
45+
@Inject
46+
private RuleBindingRegistry ruleBindingRegistry;
47+
48+
@Override
49+
public void initialize(ServiceExtensionContext context) {
50+
51+
var contextMappingFunction = new DefaultScopeMappingFunction(Set.of("org.eclipse.edc.vc.type:MembershipCredential:read"));
52+
53+
policyEngine.registerPostValidator(RequestCatalogPolicyContext.class, contextMappingFunction::apply);
54+
policyEngine.registerPostValidator(RequestContractNegotiationPolicyContext.class, contextMappingFunction::apply);
55+
policyEngine.registerPostValidator(RequestTransferProcessPolicyContext.class, contextMappingFunction::apply);
56+
57+
bindPermissionFunction(MembershipCredentialEvaluationFunction.create(), TransferProcessPolicyContext.class, TransferProcessPolicyContext.TRANSFER_SCOPE, MEMBERSHIP_CONSTRAINT_KEY);
58+
bindPermissionFunction(MembershipCredentialEvaluationFunction.create(), ContractNegotiationPolicyContext.class, ContractNegotiationPolicyContext.NEGOTIATION_SCOPE, MEMBERSHIP_CONSTRAINT_KEY);
59+
bindPermissionFunction(MembershipCredentialEvaluationFunction.create(), CatalogPolicyContext.class, CatalogPolicyContext.CATALOG_SCOPE, MEMBERSHIP_CONSTRAINT_KEY);
60+
}
61+
62+
private <C extends PolicyContext> void bindPermissionFunction(AtomicConstraintRuleFunction<Permission, C> function, Class<C> contextClass, String scope, String constraintType) {
63+
ruleBindingRegistry.bind("use", scope);
64+
ruleBindingRegistry.bind(ODRL_SCHEMA + "use", scope);
65+
ruleBindingRegistry.bind(constraintType, scope);
66+
67+
policyEngine.registerFunction(contextClass, Permission.class, constraintType, function);
68+
}
69+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
/*
2+
* Copyright (c) 2025 Metaform Systems, Inc.
3+
*
4+
* This program and the accompanying materials are made available under the
5+
* terms of the Apache License, Version 2.0 which is available at
6+
* https://www.apache.org/licenses/LICENSE-2.0
7+
*
8+
* SPDX-License-Identifier: Apache-2.0
9+
*
10+
* Contributors:
11+
* Metaform Systems, Inc. - initial API and implementation
12+
*
13+
*/
14+
15+
package org.eclipse.edc.virtualized.extensions;
16+
17+
import org.eclipse.edc.policy.context.request.spi.RequestPolicyContext;
18+
import org.eclipse.edc.policy.engine.spi.PolicyValidatorRule;
19+
import org.eclipse.edc.policy.model.Policy;
20+
21+
import java.util.HashSet;
22+
import java.util.Set;
23+
24+
public class DefaultScopeMappingFunction implements PolicyValidatorRule<RequestPolicyContext> {
25+
private final Set<String> defaultScopes;
26+
27+
public DefaultScopeMappingFunction(Set<String> defaultScopes) {
28+
this.defaultScopes = defaultScopes;
29+
}
30+
31+
@Override
32+
public Boolean apply(Policy policy, RequestPolicyContext requestPolicyContext) {
33+
var requestScopeBuilder = requestPolicyContext.requestScopeBuilder();
34+
var rq = requestScopeBuilder.build();
35+
var existingScope = rq.getScopes();
36+
var newScopes = new HashSet<>(defaultScopes);
37+
newScopes.addAll(existingScope);
38+
requestScopeBuilder.scopes(newScopes);
39+
return true;
40+
}
41+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
/*
2+
* Copyright (c) 2025 Metaform Systems, Inc.
3+
*
4+
* This program and the accompanying materials are made available under the
5+
* terms of the Apache License, Version 2.0 which is available at
6+
* https://www.apache.org/licenses/LICENSE-2.0
7+
*
8+
* SPDX-License-Identifier: Apache-2.0
9+
*
10+
* Contributors:
11+
* Metaform Systems, Inc. - initial API and implementation
12+
*
13+
*/
14+
15+
package org.eclipse.edc.virtualized.extensions;
16+
17+
import org.eclipse.edc.iam.verifiablecredentials.spi.model.VerifiableCredential;
18+
import org.eclipse.edc.participant.spi.ParticipantAgent;
19+
import org.eclipse.edc.participant.spi.ParticipantAgentPolicyContext;
20+
import org.eclipse.edc.policy.engine.spi.AtomicConstraintRuleFunction;
21+
import org.eclipse.edc.policy.model.Operator;
22+
import org.eclipse.edc.policy.model.Permission;
23+
import org.eclipse.edc.spi.result.Result;
24+
25+
import java.time.Instant;
26+
import java.util.List;
27+
import java.util.Map;
28+
29+
public class MembershipCredentialEvaluationFunction<C extends ParticipantAgentPolicyContext> implements AtomicConstraintRuleFunction<Permission, C> {
30+
public static final String MEMBERSHIP_CONSTRAINT_KEY = "MembershipCredential";
31+
protected static final String MVD_NAMESPACE = "https://w3id.org/mvd/credentials/";
32+
private static final String VC_CLAIM = "vc";
33+
private static final String MEMBERSHIP_CLAIM = "membership";
34+
private static final String SINCE_CLAIM = "since";
35+
private static final String ACTIVE = "active";
36+
37+
private MembershipCredentialEvaluationFunction() {
38+
}
39+
40+
public static <C extends ParticipantAgentPolicyContext> MembershipCredentialEvaluationFunction<C> create() {
41+
return new MembershipCredentialEvaluationFunction<>() {
42+
};
43+
}
44+
45+
@SuppressWarnings("unchecked")
46+
@Override
47+
public boolean evaluate(Operator operator, Object rightOperand, Permission permission, C policyContext) {
48+
if (!operator.equals(Operator.EQ)) {
49+
policyContext.reportProblem("Invalid operator '%s', only accepts '%s'".formatted(operator, Operator.EQ));
50+
return false;
51+
}
52+
if (!ACTIVE.equals(rightOperand)) {
53+
policyContext.reportProblem("Right-operand must be equal to '%s', but was '%s'".formatted(ACTIVE, rightOperand));
54+
return false;
55+
}
56+
57+
var pa = policyContext.participantAgent();
58+
if (pa == null) {
59+
policyContext.reportProblem("No ParticipantAgent found on context.");
60+
return false;
61+
}
62+
var credentialResult = getCredentialList(pa);
63+
if (credentialResult.failed()) {
64+
policyContext.reportProblem(credentialResult.getFailureDetail());
65+
return false;
66+
}
67+
68+
return credentialResult.getContent()
69+
.stream()
70+
.filter(vc -> vc.getType().stream().anyMatch(t -> t.endsWith(MEMBERSHIP_CONSTRAINT_KEY)))
71+
.flatMap(vc -> vc.getCredentialSubject().stream().filter(cs -> cs.getClaims().containsKey(MEMBERSHIP_CLAIM)))
72+
.anyMatch(credential -> {
73+
var membershipClaim = (Map<String, ?>) credential.getClaim(MVD_NAMESPACE, MEMBERSHIP_CLAIM);
74+
var membershipStartDate = Instant.parse(membershipClaim.get(SINCE_CLAIM).toString());
75+
return membershipStartDate.isBefore(Instant.now());
76+
});
77+
}
78+
79+
protected Result<List<VerifiableCredential>> getCredentialList(ParticipantAgent agent) {
80+
var vcListClaim = agent.getClaims().get(VC_CLAIM);
81+
82+
if (vcListClaim == null) {
83+
return Result.failure("ParticipantAgent did not contain a '%s' claim.".formatted(VC_CLAIM));
84+
}
85+
if (!(vcListClaim instanceof List)) {
86+
return Result.failure("ParticipantAgent contains a '%s' claim, but the type is incorrect. Expected %s, received %s.".formatted(VC_CLAIM, List.class.getName(), vcListClaim.getClass().getName()));
87+
}
88+
var vcList = (List<VerifiableCredential>) vcListClaim;
89+
if (vcList.isEmpty()) {
90+
return Result.failure("ParticipantAgent contains a '%s' claim but it did not contain any VerifiableCredentials.".formatted(VC_CLAIM));
91+
}
92+
return Result.success(vcList);
93+
}
94+
}

0 commit comments

Comments
 (0)