diff --git a/extensions/common/trust-framework-policies/trust-framework-policies-core/README.md b/extensions/common/trust-framework-policies/trust-framework-policies-core/README.md index fc7ec2f..1b3dc9c 100644 --- a/extensions/common/trust-framework-policies/trust-framework-policies-core/README.md +++ b/extensions/common/trust-framework-policies/trust-framework-policies-core/README.md @@ -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! \ No newline at end of file +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/) diff --git a/extensions/common/trust-framework-policies/trust-framework-policies-core/src/main/java/org/eclipse/edc/trustframework/policy/core/GaiaxCorePoliciesExtension.java b/extensions/common/trust-framework-policies/trust-framework-policies-core/src/main/java/org/eclipse/edc/trustframework/policy/core/GaiaxCorePoliciesExtension.java new file mode 100644 index 0000000..436e370 --- /dev/null +++ b/extensions/common/trust-framework-policies/trust-framework-policies-core/src/main/java/org/eclipse/edc/trustframework/policy/core/GaiaxCorePoliciesExtension.java @@ -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); + } +} diff --git a/extensions/common/trust-framework-policies/trust-framework-policies-core/src/main/java/org/eclipse/edc/trustframework/policy/core/compliance/GaiaxComplianceConstraintFunction.java b/extensions/common/trust-framework-policies/trust-framework-policies-core/src/main/java/org/eclipse/edc/trustframework/policy/core/compliance/GaiaxComplianceConstraintFunction.java new file mode 100644 index 0000000..01970e7 --- /dev/null +++ b/extensions/common/trust-framework-policies/trust-framework-policies-core/src/main/java/org/eclipse/edc/trustframework/policy/core/compliance/GaiaxComplianceConstraintFunction.java @@ -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 { + + 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> complianceCredentials) { + return !complianceCredentials.isEmpty() && complianceCredentials.stream() + .noneMatch(credential -> credential.get(GAIAX_COMPLIANCE_ID).isBlank() || + credential.get(GAIAX_COMPLIANCE_INTEGRITY).isBlank()); + } + + private List> getComplianceCredentials(Map 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 buildComplianceObject(Object object) { + var complianceCredentialSubject = new HashMap(); + 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); + } + +} diff --git a/extensions/common/trust-framework-policies/trust-framework-policies-core/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension b/extensions/common/trust-framework-policies/trust-framework-policies-core/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension new file mode 100644 index 0000000..9b2f80f --- /dev/null +++ b/extensions/common/trust-framework-policies/trust-framework-policies-core/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension @@ -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 diff --git a/extensions/common/trust-framework-policies/trust-framework-policies-core/src/test/java/org/eclipse/edc/trustframework/policy/core/compliance/GaiaxComplianceConstraintFunctionTest.java b/extensions/common/trust-framework-policies/trust-framework-policies-core/src/test/java/org/eclipse/edc/trustframework/policy/core/compliance/GaiaxComplianceConstraintFunctionTest.java new file mode 100644 index 0000000..d36ab1e --- /dev/null +++ b/extensions/common/trust-framework-policies/trust-framework-policies-core/src/test/java/org/eclipse/edc/trustframework/policy/core/compliance/GaiaxComplianceConstraintFunctionTest.java @@ -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 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(); + } +} diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index ae04661..774fae8 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -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