Skip to content

Commit

Permalink
feat(policy): add constraint function to evaluate gaia-x compliance
Browse files Browse the repository at this point in the history
  • Loading branch information
YassirSellami committed Jul 4, 2023
1 parent fbfb0f1 commit ee3c0f1
Show file tree
Hide file tree
Showing 6 changed files with 295 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -137,4 +137,11 @@ The EDC policy engine will indeed use this left expression for retrieving the ev
This `PolicyDefinition` object can be created at runtime by leveraging the Management API exposed by the EDC control plane.

Finally, we can reference the newly created Policy into the Contract Offer(s) for which we want to put access/usage control in place.
EDC takes care of the rest!
EDC takes care of the rest!

### Using the GAIA-X Compliance evaluation function

Also provided in this extension, an evaluation function for Gaia-x Compliance Credentials `GaiaxComplianceConstraintFunction`, which can used same as described above.
This function also verifies the validity of a Gaia-x Compliance:
-More info on the credential format: [Gaia-X Credential format](https://gaia-x.gitlab.io/technical-committee/federation-services/icam/credential_format/#gaia-x-compliance-inputoutput)
-This simple wizard can used to create a credential: [Wizard](https://wizard.lab.gaia-x.eu/)
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/*
* Copyright (c) 2023 GAIA-X
*
* 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:
* GAIA-X - initial API and implementation
*
*/

package org.eclipse.edc.trustframework.policy.core;

import org.eclipse.edc.policy.engine.spi.PolicyEngine;
import org.eclipse.edc.policy.engine.spi.RuleBindingRegistry;
import org.eclipse.edc.policy.model.Permission;
import org.eclipse.edc.runtime.metamodel.annotation.Extension;
import org.eclipse.edc.runtime.metamodel.annotation.Inject;
import org.eclipse.edc.spi.monitor.Monitor;
import org.eclipse.edc.spi.system.ServiceExtension;
import org.eclipse.edc.spi.system.ServiceExtensionContext;
import org.eclipse.edc.trustframework.policy.core.compliance.GaiaxComplianceConstraintFunction;

import static org.eclipse.edc.policy.engine.spi.PolicyEngine.ALL_SCOPES;

@Extension(value = GaiaxCorePoliciesExtension.GAIA_X_CORE_POLICIES_NAME)
public class GaiaxCorePoliciesExtension implements ServiceExtension {

private static final String ALL_SCOPE = "*";

public static final String GAIA_X_CORE_POLICIES_NAME = "Gaia-X Core Policies";

private static final String GAIAX_COMPLIANCE = "gx:compliance";

@Inject
private PolicyEngine policyEngine;

@Inject
private RuleBindingRegistry ruleBindingRegistry;

@Inject
private Monitor monitor;

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

@Override
public void initialize(ServiceExtensionContext context) {
policyEngine.registerFunction(ALL_SCOPES, Permission.class, GAIAX_COMPLIANCE, new GaiaxComplianceConstraintFunction(monitor));
ruleBindingRegistry.bind(GAIAX_COMPLIANCE, ALL_SCOPE);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
/*
* Copyright (c) 2023 GAIA-X
*
* 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:
* GAIA-X - initial implementation
*
*/

package org.eclipse.edc.trustframework.policy.core.compliance;

import org.eclipse.edc.identityhub.spi.credentials.model.Credential;
import org.eclipse.edc.policy.engine.spi.AtomicConstraintFunction;
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.edc.spi.monitor.Monitor;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
* A function for evaluating Gaia-X compliance credentials.
*/
public class GaiaxComplianceConstraintFunction implements AtomicConstraintFunction<Permission> {

private static final String GAIAX_COMPLIANCE_ID = "id";
private static final String GAIAX_COMPLIANCE_TYPE = "type";
private static final String GAIAX_COMPLIANCE_INTEGRITY = "integrity";
private static final String GAIAX_COMPLIANCE_TYPE_VALUE = "gx:compliance";
private final Monitor monitor;

public GaiaxComplianceConstraintFunction(Monitor monitor) {
this.monitor = monitor;
}

/**
* Performs the evaluation
*
* @param operator the operator
* @param rightValue the right-side expression for the constraint; the concrete type may be a string, primitive or object such as a JSON-LD encoded collection.
* @param rule the rule associated with the constraint
* @param context the policy context
* @return match with right value and operator and validity of Gaia-x compliance credential
*/
@Override
public boolean evaluate(Operator operator, Object rightValue, Permission rule, PolicyContext context) {
var complianceCredentials = getComplianceCredentials(context.getParticipantAgent().getClaims());
return switch (operator) {
case EQ -> rightValue.equals(areComplianceCredentialsValid(complianceCredentials));
case NEQ -> !rightValue.equals(areComplianceCredentialsValid(complianceCredentials));
default -> {
monitor.warning("Provided operator is not implemented");
yield false;
}
};
}

private boolean areComplianceCredentialsValid(List<Map<String, String>> complianceCredentials) {
return !complianceCredentials.isEmpty() && complianceCredentials.stream()
.noneMatch(credential -> credential.get(GAIAX_COMPLIANCE_ID).isBlank() ||
credential.get(GAIAX_COMPLIANCE_INTEGRITY).isBlank());
}

private List<Map<String, String>> getComplianceCredentials(Map<String, Object> claims) {
return claims.values().stream()
.map(this::buildComplianceObject)
.filter(vc -> GAIAX_COMPLIANCE_TYPE_VALUE.equals(vc.get(GAIAX_COMPLIANCE_TYPE)))
.filter(vc -> vc.get(GAIAX_COMPLIANCE_ID) != null &&
vc.get(GAIAX_COMPLIANCE_INTEGRITY) != null)
.toList();
}

private Map<String, String> buildComplianceObject(Object object) {
var complianceCredentialSubject = new HashMap<String, String>();
var credential = (Credential) object;
complianceCredentialSubject.put(GAIAX_COMPLIANCE_ID, credential.getCredentialSubject().getId());
complianceCredentialSubject.put(GAIAX_COMPLIANCE_TYPE, getCredentialProperty(credential, GAIAX_COMPLIANCE_TYPE));
complianceCredentialSubject.put(GAIAX_COMPLIANCE_INTEGRITY, getCredentialProperty(credential, GAIAX_COMPLIANCE_INTEGRITY));
return complianceCredentialSubject;
}

private String getCredentialProperty(Object object, String claimKey) {
var credential = (Credential) object;
var claims = credential.getCredentialSubject().getClaims();
return (String) claims.get(claimKey);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
#
# Copyright (c) 2023 GAIA-X
#
# 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:
# GAIA-X - initial API and implementation
#
#

org.eclipse.edc.trustframework.policy.core.GaiaxCorePoliciesExtension
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
/*
* Copyright (c) 2023 GAIA-X
*
* 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:
* GAIA-X - initial implementation
*
*/

package org.eclipse.edc.trustframework.policy.core.compliance;

import org.eclipse.edc.identityhub.spi.credentials.model.Credential;
import org.eclipse.edc.identityhub.spi.credentials.model.CredentialSubject;
import org.eclipse.edc.identityhub.spi.credentials.model.VerifiableCredential;
import org.eclipse.edc.policy.engine.PolicyContextImpl;
import org.eclipse.edc.policy.model.Operator;
import org.eclipse.edc.policy.model.Permission;
import org.eclipse.edc.spi.agent.ParticipantAgent;
import org.eclipse.edc.spi.monitor.Monitor;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

import java.sql.Date;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.stream.Collectors;

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

class GaiaxComplianceConstraintFunctionTest {

private static final Monitor MONITOR = mock(Monitor.class);
private GaiaxComplianceConstraintFunction function;

@BeforeEach
public void setUp() {
function = new GaiaxComplianceConstraintFunction(MONITOR);
}

@Test
void shouldBeTrueWhenValidCredentialIsPresent() {
var credentialSubject = CredentialSubject.Builder.newInstance()
.id("did:web:did-host:company1")
.claim("integrity", "sha256-40f94d5a0da1afb4b48cb5472dd04407123dbd60a0c8ab30cf2b00b1c9ea42f4")
.claim("type", "gx:compliance")
.build();
var policyContext = createPolicyContext(List.of(buildCredential(credentialSubject)));

var result = function.evaluate(Operator.EQ, true, anyPermission(), policyContext);

assertThat(result).isTrue();
}

@Test
void shouldBeFalseWhenNoCredentialIsPresent() {
var policyContext = createPolicyContext(new ArrayList<>());

var result = function.evaluate(Operator.EQ, true, anyPermission(), policyContext);

assertThat(result).isFalse();
}

@Test
void shouldBeFalseWhenCredentialsAreNotValid() {
var credentialSubject = CredentialSubject.Builder.newInstance()
.id("did:web:did-host:company1")
.claim("type", "gx:compliance")
.build();
var policyContext = createPolicyContext(List.of(buildCredential(credentialSubject)));

var result = function.evaluate(Operator.EQ, true, anyPermission(), policyContext);

assertThat(result).isFalse();
}

@Test
void shouldBeFalseWhenCredentialsTypeIsNotPresent() {
var credentialSubject = CredentialSubject.Builder.newInstance()
.id("did:web:did-host:company1")
.claim("integrity", "sha256-40f94d5a0da1afb4b48cb5472dd04407123dbd60a0c8ab30cf2b00b1c9ea42f4")
.build();
var policyContext = createPolicyContext(List.of(buildCredential(credentialSubject)));

var result = function.evaluate(Operator.EQ, true, anyPermission(), policyContext);

assertThat(result).isFalse();
}

private static PolicyContextImpl createPolicyContext(List<Credential> credentials) {
var credentialsMap = credentials.stream()
.collect(Collectors.toMap(Credential::getId, credential -> (Object) credential));
var participantAgent = new ParticipantAgent(credentialsMap, Map.of());
return new PolicyContextImpl(participantAgent, Map.of());
}

private Credential buildCredential(CredentialSubject credentialSubject) {
return Credential.Builder.newInstance()
.id(UUID.randomUUID().toString())
.context(VerifiableCredential.DEFAULT_CONTEXT)
.issuer("issuer")
.issuanceDate(Date.from(Instant.now().truncatedTo(ChronoUnit.SECONDS)))
.credentialSubject(credentialSubject)
.build();
}

private Permission anyPermission() {
return Permission.Builder.newInstance().build();
}
}
2 changes: 1 addition & 1 deletion gradle/wrapper/gradle-wrapper.properties
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.1-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

0 comments on commit ee3c0f1

Please sign in to comment.