Skip to content

Commit

Permalink
feat: credential based identity extractor (#1164)
Browse files Browse the repository at this point in the history
* feat: moves identity extractor from tests to iatp lib + tests

* chore: dependencies file
  • Loading branch information
wolf4ood authored Mar 27, 2024
1 parent 273c3d5 commit cfa8917
Show file tree
Hide file tree
Showing 13 changed files with 194 additions and 19 deletions.
1 change: 1 addition & 0 deletions core/core-utils/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,5 @@ plugins {

dependencies {
implementation(libs.edc.spi.core)
implementation(libs.edc.spi.identitytrust)
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
* SPDX-License-Identifier: Apache-2.0
********************************************************************************/

package org.eclipse.tractusx.edc.policy.cx.common;
package org.eclipse.tractusx.edc.core.utils.credentials;

import org.eclipse.edc.identitytrust.model.VerifiableCredential;

Expand Down
1 change: 1 addition & 0 deletions edc-extensions/cx-policy/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ plugins {
dependencies {
implementation(project(":spi:core-spi"))
implementation(project(":spi:ssi-spi"))
implementation(project(":core:core-utils"))
implementation(libs.edc.spi.policyengine)
implementation(libs.jakartaJson)
implementation(libs.edc.spi.identitytrust)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@
import org.eclipse.edc.policy.engine.spi.PolicyContext;
import org.eclipse.edc.policy.model.Operator;
import org.eclipse.edc.policy.model.Permission;
import org.eclipse.tractusx.edc.core.utils.credentials.CredentialTypePredicate;
import org.eclipse.tractusx.edc.policy.cx.common.AbstractDynamicCredentialConstraintFunction;
import org.eclipse.tractusx.edc.policy.cx.common.CredentialTypePredicate;
import org.jetbrains.annotations.NotNull;

import java.util.ArrayList;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@
import org.eclipse.edc.policy.model.Permission;
import org.eclipse.edc.spi.agent.ParticipantAgent;
import org.eclipse.edc.spi.result.Result;
import org.eclipse.tractusx.edc.core.utils.credentials.CredentialTypePredicate;
import org.eclipse.tractusx.edc.policy.cx.common.AbstractDynamicCredentialConstraintFunction;
import org.eclipse.tractusx.edc.policy.cx.common.CredentialTypePredicate;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@
import org.eclipse.edc.policy.model.Operator;
import org.eclipse.edc.policy.model.Permission;
import org.eclipse.edc.spi.agent.ParticipantAgent;
import org.eclipse.tractusx.edc.core.utils.credentials.CredentialTypePredicate;
import org.eclipse.tractusx.edc.policy.cx.common.AbstractDynamicCredentialConstraintFunction;
import org.eclipse.tractusx.edc.policy.cx.common.CredentialTypePredicate;

import static org.eclipse.tractusx.edc.edr.spi.CoreConstants.CX_CREDENTIAL_NS;
import static org.eclipse.tractusx.edc.edr.spi.CoreConstants.CX_POLICY_NS;
Expand Down
1 change: 1 addition & 0 deletions edc-extensions/iatp/tx-iatp/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ dependencies {
implementation(libs.edc.spi.policyengine)
implementation(libs.edc.identity.trust.spi)
implementation(project(":spi:core-spi"))
implementation(project(":core:core-utils"))

testImplementation(libs.edc.junit)
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,24 +17,33 @@
* SPDX-License-Identifier: Apache-2.0
********************************************************************************/

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

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.spi.system.ServiceExtension;
import org.eclipse.edc.spi.system.ServiceExtensionContext;
import org.eclipse.tractusx.edc.iam.iatp.identity.IatpIdentityExtractor;

@Extension("TX credential policy evaluation extension")
public class CredentialPolicyEvaluationExtension implements ServiceExtension {

import static org.eclipse.tractusx.edc.iam.iatp.IatpDefaultScopeExtension.NAME;

@Extension(NAME)
public class IatpIdentityExtension implements ServiceExtension {


static final String NAME = "Tractusx IATP identity extension";
@Inject
private ParticipantAgentService participantAgentService;

@Override
public void initialize(ServiceExtensionContext context) {
public String name() {
return NAME;
}

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

}
Original file line number Diff line number Diff line change
Expand Up @@ -17,35 +17,42 @@
* SPDX-License-Identifier: Apache-2.0
********************************************************************************/

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

import org.eclipse.edc.identitytrust.model.VerifiableCredential;
import org.eclipse.edc.spi.EdcException;
import org.eclipse.edc.spi.agent.ParticipantAgentServiceExtension;
import org.eclipse.edc.spi.iam.ClaimToken;
import org.eclipse.edc.spi.result.Result;
import org.eclipse.tractusx.edc.core.utils.credentials.CredentialTypePredicate;
import org.jetbrains.annotations.NotNull;

import java.util.List;
import java.util.Map;
import java.util.Optional;

import static org.eclipse.edc.spi.agent.ParticipantAgent.PARTICIPANT_IDENTITY;
import static org.eclipse.tractusx.edc.edr.spi.CoreConstants.CX_CREDENTIAL_NS;

// TODO this is just a test identity extractor, the real one can be inspired by this but with final Identity credential
public class IdentityExtractor implements ParticipantAgentServiceExtension {
/**
* Implementation of {@link ParticipantAgentServiceExtension} which extracts the identity of a participant
* from the MembershipCredential
*/
public class IatpIdentityExtractor implements ParticipantAgentServiceExtension {

private static final String VC_CLAIM = "vc";
private static final String IDENTITY_CREDENTIAL = "MembershipCredential";
private static final String IDENTITY_PROPERTY = "holderIdentifier";

private final CredentialTypePredicate typePredicate = new CredentialTypePredicate(CX_CREDENTIAL_NS, IDENTITY_CREDENTIAL);

@Override
public @NotNull Map<String, String> attributesFor(ClaimToken claimToken) {
var credentials = getCredentialList(claimToken)
.orElseThrow(failure -> new EdcException("Failed to fetch credentials from the claim token: %s".formatted(failure.getFailureDetail())));

return credentials.stream()
.filter(this::isIdentityCredential)
.filter(typePredicate)
.findFirst()
.flatMap(this::getIdentifier)
.map(identity -> Map.of(PARTICIPANT_IDENTITY, identity))
Expand All @@ -54,10 +61,6 @@ public class IdentityExtractor implements ParticipantAgentServiceExtension {

}

private boolean isIdentityCredential(VerifiableCredential verifiableCredential) {
return verifiableCredential.getType().stream().anyMatch(t -> t.endsWith(IDENTITY_CREDENTIAL));
}

private Optional<String> getIdentifier(VerifiableCredential verifiableCredential) {
return verifiableCredential.getCredentialSubject().stream()
.flatMap(credentialSubject -> credentialSubject.getClaims().entrySet().stream())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,6 @@
#################################################################################

org.eclipse.tractusx.edc.iam.iatp.IatpDefaultScopeExtension
org.eclipse.tractusx.edc.iam.iatp.IatpIdentityExtension
org.eclipse.tractusx.edc.iam.iatp.IatpScopeExtractorExtension

Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/********************************************************************************
* 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.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;

@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));
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
/********************************************************************************
* 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.iam.iatp.identity;

import org.eclipse.edc.identitytrust.model.CredentialSubject;
import org.eclipse.edc.identitytrust.model.Issuer;
import org.eclipse.edc.identitytrust.model.VerifiableCredential;
import org.eclipse.edc.spi.EdcException;
import org.eclipse.edc.spi.iam.ClaimToken;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.ArgumentsProvider;
import org.junit.jupiter.params.provider.ArgumentsSource;

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

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.eclipse.edc.spi.agent.ParticipantAgent.PARTICIPANT_IDENTITY;
import static org.eclipse.tractusx.edc.edr.spi.CoreConstants.CX_CREDENTIAL_NS;

class IatpIdentityExtractorTest {

private static final String IDENTITY = "identity";
private final IatpIdentityExtractor extractor = new IatpIdentityExtractor();

private static VerifiableCredential vc(String type, Map<String, Object> claims) {
return VerifiableCredential.Builder.newInstance().type(type)
.issuanceDate(Instant.now())
.issuer(new Issuer("issuer", Map.of()))
.credentialSubject(CredentialSubject.Builder.newInstance().claims(claims).build())
.build();
}

@ParameterizedTest
@ArgumentsSource(VerifiableCredentialArgumentProvider.class)
void attributesFor(VerifiableCredential credential) {
var attributes = extractor.attributesFor(ClaimToken.Builder.newInstance().claim("vc", List.of(credential)).build());
assertThat(attributes).containsEntry(PARTICIPANT_IDENTITY, IDENTITY);
}

@Test
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");
}

@Test
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() {

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() {
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() {
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.");
}

private static class VerifiableCredentialArgumentProvider implements ArgumentsProvider {
@Override
public Stream<? extends Arguments> provideArguments(ExtensionContext context) {
return Stream.of(
Arguments.of(vc("MembershipCredential", Map.of("holderIdentifier", IDENTITY))),
Arguments.of(vc(CX_CREDENTIAL_NS + "MembershipCredential", Map.of("holderIdentifier", IDENTITY))),
Arguments.of(vc(CX_CREDENTIAL_NS + "MembershipCredential", Map.of(CX_CREDENTIAL_NS + "holderIdentifier", IDENTITY))));
}

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,4 @@

org.eclipse.tractusx.edc.iatp.TestAudienceMapperExtension
org.eclipse.tractusx.edc.iatp.ih.IdentityHubExtension
org.eclipse.tractusx.edc.iatp.policy.CredentialPolicyEvaluationExtension
org.eclipse.tractusx.edc.iatp.CredentialsJsonLdExtension

0 comments on commit cfa8917

Please sign in to comment.