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

feat(IdentityService): implements first skeleton of the SSI identity service #459

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
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 @@ -25,8 +25,8 @@
import org.eclipse.edc.policy.model.Operator;
import org.eclipse.edc.spi.agent.ParticipantAgent;
import org.eclipse.edc.spi.monitor.Monitor;
import org.jetbrains.annotations.Nullable;

import java.util.Map;
import java.util.Objects;

import static java.lang.String.format;
Expand Down Expand Up @@ -102,20 +102,8 @@ protected boolean evaluate(
monitor.debug(message);
return false;
}

final ParticipantAgent participantAgent = policyContext.getParticipantAgent();
final Map<String, Object> claims = participantAgent.getClaims();

if (!claims.containsKey(REFERRING_CONNECTOR_CLAIM)) {
return false;
}

Object referringConnectorClaimObject = claims.get(REFERRING_CONNECTOR_CLAIM);
String referringConnectorClaim = null;

if (referringConnectorClaimObject instanceof String) {
referringConnectorClaim = (String) referringConnectorClaimObject;
}

var referringConnectorClaim = getReferringConnectorClaim(policyContext.getParticipantAgent());

if (referringConnectorClaim == null || referringConnectorClaim.isEmpty()) {
return false;
Expand All @@ -131,6 +119,24 @@ protected boolean evaluate(
}
}

@Nullable
private String getReferringConnectorClaim(ParticipantAgent participantAgent) {
Object referringConnectorClaimObject = null;
String referringConnectorClaim = null;
var claims = participantAgent.getClaims();

referringConnectorClaimObject = claims.get(REFERRING_CONNECTOR_CLAIM);

if (referringConnectorClaimObject instanceof String) {
referringConnectorClaim = (String) referringConnectorClaimObject;
}
if (referringConnectorClaim == null) {
referringConnectorClaim = participantAgent.getIdentity();
}

return referringConnectorClaim;
}

private boolean isBusinessPartnerNumber(String referringConnectorClaim, Object businessPartnerNumber, PolicyContext policyContext) {
if (businessPartnerNumber == null) {
final String message = format(FAIL_EVALUATION_BECAUSE_RIGHT_VALUE_NOT_STRING, "null");
Expand Down
21 changes: 21 additions & 0 deletions edc-extensions/ssi/ssi-identity-core/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# SSI Core Identity Service Module

This module contains an implementation of the EDC identity service for SSI.
The SsiIdentityService contains a `SsiTokenValidationService` for validating the `JWT` token,
that uses an implementation of `SsiCredentialClient` for validating the JWT token and then check custom rules registered in the `SsiValidationRuleRegistry`

For obtaining the `JWT` token, the identity service also delegate to the `SsiCredentialClient` .

The default implementation according to the first milestone [here](https://github.com/eclipse-tractusx/ssi-docu/tree/main/docs/architecture/cx-3-2)
will rely on an MIW and the implementations in available in the module `:edc-extensions:ssi:ssi-miw-credential-client`.

The implementation also provide a rule registry `SsiValidationRuleRegistry` where custom rule can be registered for validating the `ClaimToken` extracted from the `JWT` token.

Custom rule could be like:

- Audience validation
- VP/VC validation
- Expiration
- ..etc

This module it's still in development, but it will likely to contain also the Identity extractor from the `ClaimToken`
27 changes: 27 additions & 0 deletions edc-extensions/ssi/ssi-identity-core/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/*
* Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG)
*
* 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
*
* SPDX-License-Identifier: Apache-2.0
*
* Contributors:
* Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation
*
*/

plugins {
`java-library`
`maven-publish`
}

dependencies {
implementation(project(":spi:ssi-spi"))
implementation(libs.edc.spi.core)
implementation(libs.edc.spi.jwt)
implementation(libs.edc.jwt.core)
implementation(libs.nimbus.jwt)
testImplementation(testFixtures(libs.edc.junit))
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/*
* Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG)
*
* 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
*
* SPDX-License-Identifier: Apache-2.0
*
* Contributors:
* Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation
*
*/

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

import org.eclipse.edc.jwt.spi.TokenValidationService;
import org.eclipse.edc.spi.iam.ClaimToken;
import org.eclipse.edc.spi.iam.IdentityService;
import org.eclipse.edc.spi.iam.TokenParameters;
import org.eclipse.edc.spi.iam.TokenRepresentation;
import org.eclipse.edc.spi.result.Result;
import org.eclipse.tractusx.edc.iam.ssi.spi.SsiCredentialClient;

public class SsiIdentityService implements IdentityService {

private final TokenValidationService tokenValidationService;

private final SsiCredentialClient client;

public SsiIdentityService(TokenValidationService tokenValidationService, SsiCredentialClient client) {
this.tokenValidationService = tokenValidationService;
this.client = client;
}

@Override
public Result<TokenRepresentation> obtainClientCredentials(TokenParameters parameters) {
return client.obtainClientCredentials(parameters);
}

@Override
public Result<ClaimToken> verifyJwtToken(TokenRepresentation tokenRepresentation, String audience) {
return tokenValidationService.validate(tokenRepresentation);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/*
* Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG)
*
* 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
*
* SPDX-License-Identifier: Apache-2.0
*
* Contributors:
* Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation
*
*/

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

import org.eclipse.edc.runtime.metamodel.annotation.Extension;
import org.eclipse.edc.runtime.metamodel.annotation.Inject;
import org.eclipse.edc.runtime.metamodel.annotation.Provides;
import org.eclipse.edc.spi.iam.IdentityService;
import org.eclipse.edc.spi.system.ServiceExtension;
import org.eclipse.edc.spi.system.ServiceExtensionContext;
import org.eclipse.tractusx.edc.iam.ssi.spi.SsiCredentialClient;
import org.eclipse.tractusx.edc.iam.ssi.spi.SsiValidationRuleRegistry;

@Provides({IdentityService.class, SsiValidationRuleRegistry.class})
@Extension(SsiIdentityServiceExtension.EXTENSION_NAME)
public class SsiIdentityServiceExtension implements ServiceExtension {

public static final String EXTENSION_NAME = "SSI Identity Service";

@Inject
private SsiCredentialClient credentialClient;

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

@Override
public void initialize(ServiceExtensionContext context) {
var validationRulesRegistry = new SsiValidationRulesRegistryImpl();
context.registerService(SsiValidationRuleRegistry.class, validationRulesRegistry);

var identityService = new SsiIdentityService(new SsiTokenValidationService(validationRulesRegistry, credentialClient), credentialClient);

context.registerService(IdentityService.class, identityService);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/*
* Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG)
*
* 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
*
* SPDX-License-Identifier: Apache-2.0
*
* Contributors:
* Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation
*
*/

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

import org.eclipse.edc.jwt.spi.TokenValidationRulesRegistry;
import org.eclipse.edc.jwt.spi.TokenValidationService;
import org.eclipse.edc.spi.iam.ClaimToken;
import org.eclipse.edc.spi.iam.TokenRepresentation;
import org.eclipse.edc.spi.result.Result;
import org.eclipse.tractusx.edc.iam.ssi.spi.SsiCredentialClient;
import org.jetbrains.annotations.Nullable;

import java.util.Collection;
import java.util.Map;
import java.util.stream.Collectors;

public class SsiTokenValidationService implements TokenValidationService {

private final TokenValidationRulesRegistry rulesRegistry;
private final SsiCredentialClient credentialClient;

public SsiTokenValidationService(TokenValidationRulesRegistry rulesRegistry, SsiCredentialClient credentialClient) {
this.rulesRegistry = rulesRegistry;
this.credentialClient = credentialClient;
}

@Override
public Result<ClaimToken> validate(TokenRepresentation tokenRepresentation) {
return credentialClient.validate(tokenRepresentation)
.compose(claimToken -> checkRules(claimToken, tokenRepresentation.getAdditional()));
}

private Result<ClaimToken> checkRules(ClaimToken claimToken, @Nullable Map<String, Object> additional) {
var errors = rulesRegistry.getRules().stream()
.map(r -> r.checkRule(claimToken, additional))
.filter(Result::failed)
.map(Result::getFailureMessages)
.flatMap(Collection::stream)
.collect(Collectors.toList());

if (!errors.isEmpty()) {
return Result.failure(errors);
}
return Result.success(claimToken);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/*
* Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG)
*
* 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
*
* SPDX-License-Identifier: Apache-2.0
*
* Contributors:
* Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation
*
*/

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

import org.eclipse.edc.jwt.TokenValidationRulesRegistryImpl;
import org.eclipse.tractusx.edc.iam.ssi.spi.SsiValidationRuleRegistry;

public class SsiValidationRulesRegistryImpl extends TokenValidationRulesRegistryImpl implements SsiValidationRuleRegistry {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
#
# Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG)
#
# 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
#
# SPDX-License-Identifier: Apache-2.0
#
# Contributors:
# Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation
#
#

org.eclipse.tractusx.edc.iam.ssi.identity.SsiIdentityServiceExtension
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/*
* Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG)
*
* 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
*
* SPDX-License-Identifier: Apache-2.0
*
* Contributors:
* Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation
*
*/

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

import org.eclipse.edc.junit.extensions.DependencyInjectionExtension;
import org.eclipse.edc.spi.iam.IdentityService;
import org.eclipse.edc.spi.system.ServiceExtensionContext;
import org.eclipse.edc.spi.system.injection.ObjectFactory;
import org.eclipse.tractusx.edc.iam.ssi.spi.SsiCredentialClient;
import org.eclipse.tractusx.edc.iam.ssi.spi.SsiValidationRuleRegistry;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;

@ExtendWith(DependencyInjectionExtension.class)
public class SsiIdentityServiceExtensionTest {

SsiIdentityServiceExtension extension;

ServiceExtensionContext context;

@BeforeEach
void setup(ObjectFactory factory, ServiceExtensionContext context) {
this.context = spy(context);
context.registerService(SsiCredentialClient.class, mock(SsiCredentialClient.class));
extension = factory.constructInstance(SsiIdentityServiceExtension.class);
}

@Test
void initialize() {
extension.initialize(context);

assertThat(context.getService(IdentityService.class)).isNotNull().isInstanceOf(SsiIdentityService.class);
assertThat(context.getService(SsiValidationRuleRegistry.class)).isNotNull().isInstanceOf(SsiValidationRulesRegistryImpl.class);
}
}
Loading