Skip to content

Commit 838e763

Browse files
authored
feat: exchange identity extractor (eclipse-tractusx#2677)
* fix: remove didextractionfunction * chore: collapse membershipcredential-based extractors into one * fix: update e2e test expected return value
1 parent fe3ccdc commit 838e763

10 files changed

Lines changed: 122 additions & 344 deletions

File tree

edc-extensions/dataspace-protocol/cx-dataspace-protocol/src/main/java/org/eclipse/tractusx/edc/protocol/cx/identifier/BpnExtractionFunction.java

Lines changed: 54 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -21,31 +21,76 @@
2121
package org.eclipse.tractusx.edc.protocol.cx.identifier;
2222

2323
import org.eclipse.edc.iam.verifiablecredentials.spi.model.VerifiableCredential;
24+
import org.eclipse.edc.protocol.spi.ParticipantIdExtractionFunction;
25+
import org.eclipse.edc.spi.EdcException;
26+
import org.eclipse.edc.spi.iam.ClaimToken;
2427
import org.eclipse.edc.spi.monitor.Monitor;
25-
import org.eclipse.tractusx.edc.protocol.core.identifier.MembershipCredentialIdExtractionFunction;
28+
import org.eclipse.edc.spi.result.Result;
29+
import org.eclipse.tractusx.edc.core.utils.credentials.CredentialTypePredicate;
2630

31+
import java.util.List;
2732
import java.util.Map;
2833
import java.util.Optional;
2934

35+
import static org.eclipse.tractusx.edc.edr.spi.CoreConstants.CX_CREDENTIAL_NS;
36+
3037
/**
3138
* Extracts the BPN (= holderIdentifier property) from the MembershipCredential as the participant id.
3239
* Used to handle id extraction for DSP 0.8.
3340
*/
34-
public class BpnExtractionFunction extends MembershipCredentialIdExtractionFunction {
35-
41+
public class BpnExtractionFunction implements ParticipantIdExtractionFunction {
42+
43+
private static final String VC_CLAIM = "vc";
44+
private static final String IDENTITY_CREDENTIAL = "MembershipCredential";
3645
private static final String IDENTITY_PROPERTY = "holderIdentifier";
3746

47+
private final CredentialTypePredicate typePredicate = new CredentialTypePredicate(CX_CREDENTIAL_NS, IDENTITY_CREDENTIAL);
48+
private final Monitor monitor;
49+
3850
public BpnExtractionFunction(Monitor monitor) {
39-
super(monitor);
51+
this.monitor = monitor.withPrefix(getClass().getSimpleName());
4052
}
4153

4254
@Override
43-
public String identityProperty() {
44-
return IDENTITY_PROPERTY;
55+
public String apply(ClaimToken claimToken) {
56+
var credentials = getCredentialList(claimToken)
57+
.orElseThrow(failure -> new EdcException("Failed to fetch credentials from the claim token: %s".formatted(failure.getFailureDetail())));
58+
59+
return credentials.stream()
60+
.filter(typePredicate)
61+
.findFirst()
62+
.flatMap(this::getIdentifier)
63+
.orElseThrow(() -> {
64+
var msg = "Required credential type '%s' not present in ClaimToken, cannot extract property '%s'".formatted(IDENTITY_CREDENTIAL, IDENTITY_PROPERTY);
65+
monitor.warning(msg);
66+
return new EdcException(msg);
67+
});
4568
}
46-
47-
@Override
48-
public Optional<String> getIdentifier(VerifiableCredential vc) {
69+
70+
@SuppressWarnings("unchecked")
71+
private Result<List<VerifiableCredential>> getCredentialList(ClaimToken claimToken) {
72+
var vcListClaim = claimToken.getClaims().get(VC_CLAIM);
73+
74+
if (vcListClaim == null) {
75+
var msg = "ClaimToken did not contain a '%s' claim.".formatted(VC_CLAIM);
76+
monitor.warning(msg);
77+
return Result.failure(msg);
78+
}
79+
if (!(vcListClaim instanceof List)) {
80+
var msg = "ClaimToken contains a '%s' claim, but the type is incorrect. Expected %s, got %s.".formatted(VC_CLAIM, List.class.getName(), vcListClaim.getClass().getName());
81+
monitor.warning(msg);
82+
return Result.failure(msg);
83+
}
84+
var vcList = (List<VerifiableCredential>) vcListClaim;
85+
if (vcList.isEmpty()) {
86+
var msg = "ClaimToken contains a '%s' claim but it did not contain any VerifiableCredentials.".formatted(VC_CLAIM);
87+
monitor.warning(msg);
88+
return Result.failure(msg);
89+
}
90+
return Result.success(vcList);
91+
}
92+
93+
private Optional<String> getIdentifier(VerifiableCredential vc) {
4994
return vc.getCredentialSubject().stream()
5095
.flatMap(credentialSubject -> credentialSubject.getClaims().entrySet().stream())
5196
.filter(entry -> entry.getKey().endsWith(IDENTITY_PROPERTY))

edc-extensions/dataspace-protocol/cx-dataspace-protocol/src/test/java/org/eclipse/tractusx/edc/protocol/cx/identifier/BpnExtractionFunctionTest.java

Lines changed: 57 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
/*
2+
* Copyright (c) 2024 Bayerische Motoren Werke Aktiengesellschaft (BMW AG)
23
* Copyright (c) 2025 Cofinity-X GmbH
34
* Copyright (c) 2026 SAP SE
45
*
@@ -20,50 +21,95 @@
2021

2122
package org.eclipse.tractusx.edc.protocol.cx.identifier;
2223

24+
import org.eclipse.edc.iam.verifiablecredentials.spi.model.CredentialSubject;
25+
import org.eclipse.edc.iam.verifiablecredentials.spi.model.Issuer;
2326
import org.eclipse.edc.iam.verifiablecredentials.spi.model.VerifiableCredential;
27+
import org.eclipse.edc.spi.EdcException;
2428
import org.eclipse.edc.spi.iam.ClaimToken;
2529
import org.eclipse.edc.spi.monitor.Monitor;
26-
import org.eclipse.tractusx.edc.protocol.core.identifier.MembershipCredentialIdExtractionFunction;
27-
import org.eclipse.tractusx.edc.protocol.core.identifier.MembershipCredentialIdExtractionFunctionTest;
30+
import org.junit.jupiter.api.Test;
2831
import org.junit.jupiter.api.extension.ExtensionContext;
2932
import org.junit.jupiter.params.ParameterizedTest;
3033
import org.junit.jupiter.params.provider.Arguments;
3134
import org.junit.jupiter.params.provider.ArgumentsProvider;
3235
import org.junit.jupiter.params.provider.ArgumentsSource;
3336

37+
import java.time.Instant;
3438
import java.util.List;
3539
import java.util.Map;
3640
import java.util.stream.Stream;
3741

3842
import static org.assertj.core.api.Assertions.assertThat;
43+
import static org.assertj.core.api.Assertions.assertThatThrownBy;
3944
import static org.eclipse.tractusx.edc.edr.spi.CoreConstants.CX_CREDENTIAL_NS;
4045
import static org.mockito.ArgumentMatchers.anyString;
4146
import static org.mockito.Mockito.mock;
4247
import static org.mockito.Mockito.when;
4348

44-
public class BpnExtractionFunctionTest extends MembershipCredentialIdExtractionFunctionTest {
49+
public class BpnExtractionFunctionTest {
4550

51+
public static final String DID = "did:web:example";
4652
public static final String BPN = "bpn";
4753
public static final String ID_PROPERTY = "holderIdentifier";
4854

4955
private final Monitor monitor = mock();
5056

51-
@Override
52-
protected MembershipCredentialIdExtractionFunction extractionFunction() {
57+
private BpnExtractionFunction extractionFunction() {
5358
when(monitor.withPrefix(anyString())).thenReturn(monitor);
5459
return new BpnExtractionFunction(monitor);
5560
}
56-
57-
@Override
58-
protected String expectedId() {
59-
return BPN;
60-
}
6161

6262
@ParameterizedTest
6363
@ArgumentsSource(VerifiableCredentialArgumentProvider.class)
6464
void apply(VerifiableCredential credential) {
6565
var id = extractionFunction().apply(ClaimToken.Builder.newInstance().claim("vc", List.of(credential)).build());
66-
assertThat(id).isEqualTo(expectedId());
66+
assertThat(id).isEqualTo(BPN);
67+
}
68+
69+
@Test
70+
void apply_fails_WhenCredentialNotFound() {
71+
assertThatThrownBy(() -> extractionFunction().apply(ClaimToken.Builder.newInstance().claim("vc", List.of(vc("FooCredential", Map.of("foo", "bar")))).build()))
72+
.isInstanceOf(EdcException.class)
73+
.hasMessage("Required credential type 'MembershipCredential' not present in ClaimToken, cannot extract property '%s'", ID_PROPERTY);
74+
}
75+
76+
@Test
77+
void apply_fails_whenNoVcClaims() {
78+
assertThatThrownBy(() -> extractionFunction().apply(ClaimToken.Builder.newInstance().build()))
79+
.isInstanceOf(EdcException.class)
80+
.hasMessageContaining("Failed to fetch credentials from the claim token: ClaimToken did not contain a 'vc' claim");
81+
}
82+
83+
@Test
84+
void apply_fails_whenNullVcClaims() {
85+
assertThatThrownBy(() -> extractionFunction().apply(ClaimToken.Builder.newInstance().claim("vc", null).build()))
86+
.isInstanceOf(EdcException.class)
87+
.hasMessageContaining("Failed to fetch credentials from the claim token: ClaimToken did not contain a 'vc' claim");
88+
}
89+
90+
@Test
91+
void apply_fails_WhenVcClaimIsNotList() {
92+
assertThatThrownBy(() -> extractionFunction().apply(ClaimToken.Builder.newInstance().claim("vc", "wrong").build()))
93+
.isInstanceOf(EdcException.class)
94+
.hasMessageContaining("Failed to fetch credentials from the claim token: ClaimToken contains a 'vc' claim, but the type is incorrect. Expected java.util.List, got java.lang.String.");
95+
}
96+
97+
@Test
98+
void apply_fails_WhenVcClaimsIsEmptyList() {
99+
assertThatThrownBy(() -> extractionFunction().apply(ClaimToken.Builder.newInstance().claim("vc", List.of()).build()))
100+
.isInstanceOf(EdcException.class)
101+
.hasMessageContaining("Failed to fetch credentials from the claim token: ClaimToken contains a 'vc' claim but it did not contain any VerifiableCredentials.");
102+
}
103+
104+
private static VerifiableCredential vc(String type, Map<String, Object> claims) {
105+
return VerifiableCredential.Builder.newInstance().type(type)
106+
.issuanceDate(Instant.now())
107+
.issuer(new Issuer("issuer", Map.of()))
108+
.credentialSubject(CredentialSubject.Builder.newInstance()
109+
.id(claims.containsKey("id") ? claims.get("id").toString() : null)
110+
.claims(claims)
111+
.build())
112+
.build();
67113
}
68114

69115
private static class VerifiableCredentialArgumentProvider implements ArgumentsProvider {

edc-extensions/dataspace-protocol/dataspace-protocol-core/build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ dependencies {
3737

3838
implementation(libs.edc.spi.participant)
3939
implementation(libs.edc.spi.protocol)
40+
implementation(libs.edc.iam.decentralized.claims.core)
4041

4142
implementation(project(":spi:core-spi"))
4243
implementation(project(":core:core-utils"))

edc-extensions/dataspace-protocol/dataspace-protocol-core/src/main/java/org/eclipse/tractusx/edc/protocol/core/CoreDataspaceProtocolExtension.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020

2121
package org.eclipse.tractusx.edc.protocol.core;
2222

23+
import org.eclipse.edc.iam.decentralizedclaims.core.defaults.DefaultDcpParticipantIdExtractionFunction;
2324
import org.eclipse.edc.participantcontext.single.spi.SingleParticipantContextSupplier;
2425
import org.eclipse.edc.protocol.dsp.http.spi.api.DspBaseWebhookAddress;
2526
import org.eclipse.edc.protocol.spi.DataspaceProfileContext;
@@ -28,7 +29,6 @@
2829
import org.eclipse.edc.spi.monitor.Monitor;
2930
import org.eclipse.edc.spi.system.ServiceExtension;
3031
import org.eclipse.edc.spi.system.ServiceExtensionContext;
31-
import org.eclipse.tractusx.edc.protocol.core.identifier.DidExtractionFunction;
3232

3333
import static org.eclipse.edc.protocol.dsp.spi.type.Dsp2025Constants.DATASPACE_PROTOCOL_HTTP_V_2025_1;
3434
import static org.eclipse.edc.protocol.dsp.spi.type.Dsp2025Constants.V_2025_1;
@@ -47,6 +47,6 @@ public class CoreDataspaceProtocolExtension implements ServiceExtension {
4747

4848
@Override
4949
public void initialize(ServiceExtensionContext context) {
50-
contextRegistry.register(new DataspaceProfileContext(DATASPACE_PROTOCOL_HTTP_V_2025_1, V_2025_1, () -> dspWebhookAddress.get() + V_2025_1_PATH, new DidExtractionFunction(monitor)));
50+
contextRegistry.register(new DataspaceProfileContext(DATASPACE_PROTOCOL_HTTP_V_2025_1, V_2025_1, () -> dspWebhookAddress.get() + V_2025_1_PATH, new DefaultDcpParticipantIdExtractionFunction()));
5151
}
5252
}

edc-extensions/dataspace-protocol/dataspace-protocol-core/src/main/java/org/eclipse/tractusx/edc/protocol/core/identifier/DidExtractionFunction.java

Lines changed: 0 additions & 52 deletions
This file was deleted.

edc-extensions/dataspace-protocol/dataspace-protocol-core/src/main/java/org/eclipse/tractusx/edc/protocol/core/identifier/MembershipCredentialIdExtractionFunction.java

Lines changed: 0 additions & 95 deletions
This file was deleted.

0 commit comments

Comments
 (0)