Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Backport: fix: prevent default DCP identity extractor from being registered #1598

Merged
Show file tree
Hide file tree
Changes from 1 commit
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 @@ -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());
}

}
Loading