Skip to content

Commit

Permalink
fix: prevent default identity extractor from being registered (eclips…
Browse files Browse the repository at this point in the history
…e-tractusx#1597)

* fix(dcp): prevent default identity extractor from being registered

* fix unit tests
  • Loading branch information
paullatzelsperger committed Oct 1, 2024
1 parent f33b729 commit 181cd2e
Show file tree
Hide file tree
Showing 5 changed files with 106 additions and 32 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,10 @@

package org.eclipse.tractusx.edc.iam.iatp;

import org.eclipse.edc.iam.identitytrust.spi.DcpParticipantAgentServiceExtension;
import org.eclipse.edc.runtime.metamodel.annotation.Extension;
import org.eclipse.edc.runtime.metamodel.annotation.Inject;
import org.eclipse.edc.spi.agent.ParticipantAgentService;
import org.eclipse.edc.runtime.metamodel.annotation.Provider;
import org.eclipse.edc.spi.system.ServiceExtension;
import org.eclipse.edc.spi.system.ServiceExtensionContext;
import org.eclipse.tractusx.edc.iam.iatp.identity.IatpIdentityExtractor;

import static org.eclipse.tractusx.edc.iam.iatp.IatpDefaultScopeExtension.NAME;
Expand All @@ -33,17 +32,21 @@ public class IatpIdentityExtension implements ServiceExtension {


static final String NAME = "Tractusx IATP identity extension";
@Inject
private ParticipantAgentService participantAgentService;
private final IatpIdentityExtractor iatpIdentityExtractor = new IatpIdentityExtractor();

@Override
public String name() {
return NAME;
}

@Override
public void initialize(ServiceExtensionContext context) {
participantAgentService.register(new IatpIdentityExtractor());

/**
* This provider method is mandatory, because it prevents the {@code DefaultDcpParticipantAgentServiceExtension} from being
* registered, which would cause a race condition in the identity extractors
*/
@Provider
public DcpParticipantAgentServiceExtension extractor() {
return iatpIdentityExtractor;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@

package org.eclipse.tractusx.edc.iam.iatp.identity;

import org.eclipse.edc.iam.identitytrust.spi.DcpParticipantAgentServiceExtension;
import org.eclipse.edc.iam.verifiablecredentials.spi.model.VerifiableCredential;
import org.eclipse.edc.spi.EdcException;
import org.eclipse.edc.spi.agent.ParticipantAgentServiceExtension;
Expand All @@ -38,7 +39,7 @@
* Implementation of {@link ParticipantAgentServiceExtension} which extracts the identity of a participant
* from the MembershipCredential
*/
public class IatpIdentityExtractor implements ParticipantAgentServiceExtension {
public class IatpIdentityExtractor implements DcpParticipantAgentServiceExtension {

private static final String VC_CLAIM = "vc";
private static final String IDENTITY_CREDENTIAL = "MembershipCredential";
Expand All @@ -56,7 +57,7 @@ public class IatpIdentityExtractor implements ParticipantAgentServiceExtension {
.findFirst()
.flatMap(this::getIdentifier)
.map(identity -> Map.of(PARTICIPANT_IDENTITY, identity))
.orElseThrow(() -> new EdcException("Failed to fetch %s property from %s credential".formatted(IDENTITY_PROPERTY, IDENTITY_CREDENTIAL)));
.orElseThrow(() -> new EdcException("Required credential type '%s' not present in ClaimToken, cannot extract property '%s'".formatted(IDENTITY_CREDENTIAL, IDENTITY_PROPERTY)));


}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,31 +20,18 @@
package org.eclipse.tractusx.edc.iam.iatp;

import org.eclipse.edc.junit.extensions.DependencyInjectionExtension;
import org.eclipse.edc.spi.agent.ParticipantAgentService;
import org.eclipse.edc.spi.system.ServiceExtensionContext;
import org.eclipse.tractusx.edc.iam.iatp.identity.IatpIdentityExtractor;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;

import static org.mockito.ArgumentMatchers.isA;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.assertj.core.api.Assertions.assertThat;

@ExtendWith(DependencyInjectionExtension.class)
public class IatpIdentityExtensionTest {

private final ParticipantAgentService participantAgentService = mock();

@BeforeEach
void setup(ServiceExtensionContext context) {
context.registerService(ParticipantAgentService.class, participantAgentService);
}

@Test
void initialize(ServiceExtensionContext context, IatpIdentityExtension extension) {
extension.initialize(context);
verify(participantAgentService).register(isA(IatpIdentityExtractor.class));
void initialize(IatpIdentityExtension extension) {
assertThat(extension.extractor()).isInstanceOf(IatpIdentityExtractor.class);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -62,36 +62,36 @@ void attributesFor(VerifiableCredential credential) {
}

@Test
void attributesFor_Fails_WhenCredentialNotFound() {
void attributesFor_fails_WhenCredentialNotFound() {
assertThatThrownBy(() -> extractor.attributesFor(ClaimToken.Builder.newInstance().claim("vc", List.of(vc("FooCredential", Map.of("foo", "bar")))).build()))
.isInstanceOf(EdcException.class)
.hasMessageContaining("Failed to fetch");
.hasMessage("Required credential type 'MembershipCredential' not present in ClaimToken, cannot extract property 'holderIdentifier'");
}

@Test
void attributesFor_Fails_whenNoVcClaims() {
void attributesFor_fails_whenNoVcClaims() {
assertThatThrownBy(() -> extractor.attributesFor(ClaimToken.Builder.newInstance().build()))
.isInstanceOf(EdcException.class)
.hasMessageContaining("Failed to fetch credentials from the claim token: ClaimToken did not contain a 'vc' claim");
}

@Test
void attributesFor_Fails_whenNullVcClaims() {
void attributesFor_fails_whenNullVcClaims() {

assertThatThrownBy(() -> extractor.attributesFor(ClaimToken.Builder.newInstance().claim("vc", null).build()))
.isInstanceOf(EdcException.class)
.hasMessageContaining("Failed to fetch credentials from the claim token: ClaimToken did not contain a 'vc' claim");
}

@Test
void attributesFor_Fails_WhenVcClaimIsNotList() {
void attributesFor_fails_WhenVcClaimIsNotList() {
assertThatThrownBy(() -> extractor.attributesFor(ClaimToken.Builder.newInstance().claim("vc", "wrong").build()))
.isInstanceOf(EdcException.class)
.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.");
}

@Test
void attributesFor_Fails_WhenVcClaimsIsEmptyList() {
void attributesFor_fails_WhenVcClaimsIsEmptyList() {
assertThatThrownBy(() -> extractor.attributesFor(ClaimToken.Builder.newInstance().claim("vc", List.of()).build()))
.isInstanceOf(EdcException.class)
.hasMessageContaining("Failed to fetch credentials from the claim token: ClaimToken contains a 'vc' claim but it did not contain any VerifiableCredentials.");
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
/********************************************************************************
* Copyright (c) 2024 Bayerische Motoren Werke Aktiengesellschaft (BMW AG)
*
* See the NOTICE file(s) distributed with this work for additional
* information regarding copyright ownership.
*
* This program and the accompanying materials are made available under the
* terms of the Apache License, Version 2.0 which is available at
* https://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* SPDX-License-Identifier: Apache-2.0
********************************************************************************/

package org.eclipse.tractusx.edc.tests.transfer;

import org.eclipse.edc.iam.verifiablecredentials.spi.model.CredentialSubject;
import org.eclipse.edc.iam.verifiablecredentials.spi.model.Issuer;
import org.eclipse.edc.iam.verifiablecredentials.spi.model.VerifiableCredential;
import org.eclipse.edc.junit.annotations.EndToEndTest;
import org.eclipse.edc.junit.extensions.RuntimeExtension;
import org.eclipse.edc.spi.EdcException;
import org.eclipse.edc.spi.agent.ParticipantAgentService;
import org.eclipse.edc.spi.iam.ClaimToken;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;

import java.time.Instant;
import java.util.List;
import java.util.Map;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.eclipse.tractusx.edc.tests.transfer.iatp.runtime.Runtimes.iatpRuntime;

/**
* This test asserts that the ParticipantAgent's identity is determined by the "credentialSubject.holderIdentifier" property.
* Due to how the extractors are used and registered, this must be tested using a fully-fledged runtime.
*/
@EndToEndTest
public class IdentityExtractorTest implements IatpParticipants {

@RegisterExtension
protected static final RuntimeExtension CONSUMER_RUNTIME = iatpRuntime(CONSUMER.getName(), CONSUMER.iatpConfiguration(PROVIDER), CONSUMER.getKeyPair());

@Test
void verifyCorrectParticipantAgentId(ParticipantAgentService participantAgentService) {
var claimtoken = ClaimToken.Builder.newInstance()
.claim("vc", List.of(createCredential().build()))
.build();
var agent = participantAgentService.createFor(claimtoken);

assertThat(agent.getIdentity()).isEqualTo("the-holder");
}

@Test
void verifyAgentId_whenNoMembershipCredential(ParticipantAgentService participantAgentService) {
var claimtoken = ClaimToken.Builder.newInstance()
.claim("vc", List.of(createCredential().types(List.of("VerifiableCredential")).build()))
.build();
assertThatThrownBy(() -> participantAgentService.createFor(claimtoken)).isInstanceOf(EdcException.class)
.hasMessage("Required credential type 'MembershipCredential' not present in ClaimToken, cannot extract property 'holderIdentifier'");
}

@SuppressWarnings({ "unchecked", "rawtypes" })
private VerifiableCredential.Builder createCredential() {
return VerifiableCredential.Builder.newInstance()
.types(List.of("VerifiableCredential", "MembershipCredential"))
.id("test-id")
.issuanceDate(Instant.now())
.issuer(new Issuer("test-issuer", Map.of()))
.credentialSubject(CredentialSubject.Builder.newInstance()
.id("test-id")
.claim("holderIdentifier", "the-holder")
.build());
}

}

0 comments on commit 181cd2e

Please sign in to comment.