Skip to content

Commit

Permalink
feat(IdentityService): implements first skeleton of the SSI identity …
Browse files Browse the repository at this point in the history
…service (#459)

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

* pr remarks

* pr remarks
  • Loading branch information
wolf4ood authored Jun 13, 2023
1 parent cd7f0c4 commit 2f81217
Show file tree
Hide file tree
Showing 36 changed files with 1,594 additions and 19 deletions.
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

0 comments on commit 2f81217

Please sign in to comment.