diff --git a/edc-extensions/constructx-policy/README.md b/edc-extensions/constructx-policy/README.md new file mode 100644 index 0000000000..3fcf4d7fad --- /dev/null +++ b/edc-extensions/constructx-policy/README.md @@ -0,0 +1,7 @@ +# EDC Control-Plane Base Module + +## Building + +```shell +./gradlew :edc-extensions:constructx-policy:build +``` diff --git a/edc-extensions/constructx-policy/build.gradle.kts b/edc-extensions/constructx-policy/build.gradle.kts new file mode 100644 index 0000000000..f33e2fd7a8 --- /dev/null +++ b/edc-extensions/constructx-policy/build.gradle.kts @@ -0,0 +1,49 @@ +/******************************************************************************** + * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * Copyright (c) 2025 Fraunhofer-Gesellschaft zur Förderung der angewandten Forschung e.V. + * + * 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 + ********************************************************************************/ + +plugins { + `java-library` + `java-test-fixtures` +} + +dependencies { + api(project(":edc-extensions:bpn-validation:bpn-validation-spi")) + implementation(project(":spi:core-spi")) + implementation(project(":core:core-utils")) + implementation(project(":spi:bdrs-client-spi")) + implementation(libs.edc.core.policy.monitor) + implementation(libs.edc.spi.catalog) + implementation(libs.edc.spi.contract) + implementation(libs.edc.spi.decentralized.claims) + implementation(libs.edc.spi.policyengine) + implementation(libs.edc.spi.vc) + implementation(libs.jakartaJson) + implementation(libs.edc.spi.jsonld) + testImplementation(libs.jacksonJsonP) + testImplementation(libs.titaniumJsonLd) + + //validator dependencies + api(libs.edc.spi.controlplane) + implementation(libs.edc.lib.validator) + + testImplementation(libs.edc.junit) + testFixturesImplementation(libs.edc.junit) + testFixturesImplementation(libs.edc.spi.jsonld) +} diff --git a/edc-extensions/constructx-policy/src/main/java/org/constructx/edc/policy/constructx/ConstructxPolicyConstants.java b/edc-extensions/constructx-policy/src/main/java/org/constructx/edc/policy/constructx/ConstructxPolicyConstants.java new file mode 100644 index 0000000000..39c46d9725 --- /dev/null +++ b/edc-extensions/constructx-policy/src/main/java/org/constructx/edc/policy/constructx/ConstructxPolicyConstants.java @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2026 Materna SE + * + * 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.constructx.edc.policy.constructx; + +/** + * Constants used across Construct-X EDC policy extension. + */ +public final class ConstructxPolicyConstants { + + public static final String CONSTRUCTX_POLICY_NS = "https://w3id.org/constructx/policy/v1.0/"; + public static final String CONSTRUCTX_POLICY_PREFIX = "constructx-policy"; + public static final String CONSTRUCTX_POLICY_CONTEXT = CONSTRUCTX_POLICY_NS + "context.jsonld"; + + public static final String CONSTRUCTX_CONTEXT = "https://w3id.org/constructx/edc/v0.0.1"; + + public static final String CONSTRUCTX_CREDENTIAL_NS = "https://w3id.org/constructx/credentials/v1.0/"; + public static final String CONSTRUCTX_CREDENTIAL_PREFIX = "constructx-credentials"; + public static final String CONSTRUCTX_CREDENTIAL_CONTEXT = CONSTRUCTX_CREDENTIAL_NS + "context.jsonld"; + + private ConstructxPolicyConstants() { + } +} \ No newline at end of file diff --git a/edc-extensions/constructx-policy/src/main/java/org/constructx/edc/policy/constructx/ConstructxPolicyExtension.java b/edc-extensions/constructx-policy/src/main/java/org/constructx/edc/policy/constructx/ConstructxPolicyExtension.java new file mode 100644 index 0000000000..6388a08e31 --- /dev/null +++ b/edc-extensions/constructx-policy/src/main/java/org/constructx/edc/policy/constructx/ConstructxPolicyExtension.java @@ -0,0 +1,58 @@ +/******************************************************************************** + * Copyright (c) 2024 T-Systems International GmbH + * + * 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.constructx.edc.policy.constructx; + +import org.eclipse.edc.policy.engine.spi.PolicyEngine; +import org.eclipse.edc.policy.engine.spi.RuleBindingRegistry; +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 static org.constructx.edc.policy.constructx.ConstructxPolicyRegistration.registerBindings; +import static org.constructx.edc.policy.constructx.ConstructxPolicyRegistration.registerFunctions; + + +/** + * Provides implementations of standard Construct-X usage policies. + */ +public class ConstructxPolicyExtension implements ServiceExtension { + private static final String NAME = "Construct-X Policy"; + + @Inject + private PolicyEngine policyEngine; + + @Inject + private Monitor monitor; + + @Inject + private RuleBindingRegistry bindingRegistry; + + @Override + public String name() { + return NAME; + } + + @Override + public void initialize(ServiceExtensionContext context) { + registerFunctions(policyEngine, monitor); + registerBindings(bindingRegistry); + } +} diff --git a/edc-extensions/constructx-policy/src/main/java/org/constructx/edc/policy/constructx/ConstructxPolicyRegistration.java b/edc-extensions/constructx-policy/src/main/java/org/constructx/edc/policy/constructx/ConstructxPolicyRegistration.java new file mode 100644 index 0000000000..d2a80c702b --- /dev/null +++ b/edc-extensions/constructx-policy/src/main/java/org/constructx/edc/policy/constructx/ConstructxPolicyRegistration.java @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2024 T-Systems International GmbH + * Copyright (c) 2025 SAP SE + * + * 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.constructx.edc.policy.constructx; + +import org.constructx.edc.policy.constructx.membership.MembershipCredentialConstraintFunction; +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.spi.monitor.Monitor; + +import java.util.Set; +import java.util.stream.Stream; + +import static org.constructx.edc.policy.constructx.ConstructxPolicyConstants.CONSTRUCTX_POLICY_NS; +import static org.constructx.edc.policy.constructx.common.PolicyScopes.CATALOG_REQUEST_SCOPE; +import static org.constructx.edc.policy.constructx.common.PolicyScopes.CATALOG_SCOPE; +import static org.constructx.edc.policy.constructx.common.PolicyScopes.CATALOG_SCOPE_CLASS; +import static org.constructx.edc.policy.constructx.common.PolicyScopes.NEGOTIATION_REQUEST_SCOPE; +import static org.constructx.edc.policy.constructx.common.PolicyScopes.NEGOTIATION_SCOPE; +import static org.constructx.edc.policy.constructx.common.PolicyScopes.NEGOTIATION_SCOPE_CLASS; +import static org.constructx.edc.policy.constructx.common.PolicyScopes.TRANSFER_PROCESS_REQUEST_SCOPE; +import static org.constructx.edc.policy.constructx.common.PolicyScopes.TRANSFER_PROCESS_SCOPE; +import static org.constructx.edc.policy.constructx.common.PolicyScopes.TRANSFER_PROCESS_SCOPE_CLASS; +import static org.constructx.edc.policy.constructx.membership.MembershipCredentialConstraintFunction.CONSTRUCTX_MEMBERSHIP_LITERAL; +import static org.constructx.edc.policy.constructx.membership.MembershipCredentialConstraintFunction.MEMBERSHIP_LITERAL; +import static org.eclipse.edc.policy.model.OdrlNamespace.ODRL_SCHEMA; + +/** + * Registers Construct-X policy constraints to the EDC. + */ +public class ConstructxPolicyRegistration { + private static final Set FUNCTION_SCOPES_CLASSES = + Set.of(CATALOG_SCOPE_CLASS, NEGOTIATION_SCOPE_CLASS, TRANSFER_PROCESS_SCOPE_CLASS); + + private static final Set RULE_SCOPES = + Set.of(CATALOG_REQUEST_SCOPE, NEGOTIATION_REQUEST_SCOPE, TRANSFER_PROCESS_REQUEST_SCOPE, + CATALOG_SCOPE, NEGOTIATION_SCOPE, TRANSFER_PROCESS_SCOPE); + + public static void registerFunctions(PolicyEngine engine, Monitor monitor) { + FUNCTION_SCOPES_CLASSES.forEach(scope -> + engine.registerFunction(scope, Permission.class, new MembershipCredentialConstraintFunction<>(monitor)) + ); + } + + public static void registerBindings(RuleBindingRegistry registry) { + registry.dynamicBind(s -> { + if (Stream.of(MEMBERSHIP_LITERAL, CONSTRUCTX_MEMBERSHIP_LITERAL) + .anyMatch(postfix -> s.startsWith(CONSTRUCTX_POLICY_NS + postfix))) { + return RULE_SCOPES; + } + return Set.of(); + }); + + registry.bind(ODRL_SCHEMA + "use", CATALOG_SCOPE); + registry.bind(ODRL_SCHEMA + "use", NEGOTIATION_SCOPE); + registry.bind(ODRL_SCHEMA + "use", TRANSFER_PROCESS_SCOPE); + } +} \ No newline at end of file diff --git a/edc-extensions/constructx-policy/src/main/java/org/constructx/edc/policy/constructx/common/AbstractDynamicCredentialConstraintFunction.java b/edc-extensions/constructx-policy/src/main/java/org/constructx/edc/policy/constructx/common/AbstractDynamicCredentialConstraintFunction.java new file mode 100644 index 0000000000..b27fbc1f22 --- /dev/null +++ b/edc-extensions/constructx-policy/src/main/java/org/constructx/edc/policy/constructx/common/AbstractDynamicCredentialConstraintFunction.java @@ -0,0 +1,112 @@ +/******************************************************************************** + * Copyright (c) 2024 T-Systems International GmbH + * + * 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.constructx.edc.policy.constructx.common; + +import org.eclipse.edc.iam.verifiablecredentials.spi.model.VerifiableCredential; +import org.eclipse.edc.participant.spi.ParticipantAgent; +import org.eclipse.edc.participant.spi.ParticipantAgentPolicyContext; +import org.eclipse.edc.policy.engine.spi.DynamicAtomicConstraintRuleFunction; +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.result.Result; + +import java.util.Collection; +import java.util.List; + +/** + * This is a base class for dynamically bound Construct-X constraint evaluation functions that implements some basic common functionality and defines some + * common constants + */ +public abstract class AbstractDynamicCredentialConstraintFunction implements DynamicAtomicConstraintRuleFunction { + /** + * Verifiable credential key to extract values + */ + public static final String VC_CLAIM = "vc"; + + /** + * Expected right operand of membership. Inactive members cannot participate. + */ + public static final String ACTIVE = "active"; + + /** + * Credential Literal used to identify credentials and extract from credentialScopeExtractor + */ + public static final String CREDENTIAL_LITERAL = "Credential"; + + /** + * Expected ODRL operators to check fx policy + */ + protected static final Collection EQUALITY_OPERATORS = List.of(Operator.EQ, Operator.NEQ); + + /** + * checks acceptability of ODRL operator passed for constraint validation. + * + * @param actual operator from request + * @param context context of the policy + * @param expectedOperators collection of allowed operators + * @return true/false based on validity of the operator + */ + protected boolean checkOperator(Operator actual, PolicyContext context, Collection expectedOperators) { + if (!expectedOperators.contains(actual)) { + context.reportProblem("Invalid operator: this constraint only allows the following operators: %s, but received '%s'.".formatted(EQUALITY_OPERATORS, actual)); + return false; + } + return true; + } + + /** + * extracts participant agent from the policy context. + * + * @param context policy context from which participant is extracted. + * @return participant agent needed to validate policies. + */ + protected Result extractParticipantAgent(C context) { + // make sure the ParticipantAgent is there + var participantAgent = context.participantAgent(); + if (participantAgent == null) { + return Result.failure("Required PolicyContext data not found: " + ParticipantAgent.class.getName()); + } + return Result.success(participantAgent); + } + + /** + * Extracts a {@link List} of {@link VerifiableCredential} objects from the {@link ParticipantAgent}. Credentials must be + * stored in the agent's claims map using the "vc" key. + * + * @param agent participantAgent which contains information on the participant. + * @return list of verifiable credentials extracted from the participant agent + */ + protected Result> getCredentialList(ParticipantAgent agent) { + var vcListClaim = agent.getClaims().get(VC_CLAIM); + + if (vcListClaim == null) { + return Result.failure("ParticipantAgent did not contain a '%s' claim.".formatted(VC_CLAIM)); + } + if (!(vcListClaim instanceof List)) { + return Result.failure("ParticipantAgent contains a '%s' claim, but the type is incorrect. Expected %s, received %s.".formatted(VC_CLAIM, List.class.getName(), vcListClaim.getClass().getName())); + } + var vcList = (List) vcListClaim; + if (vcList.isEmpty()) { + return Result.failure("ParticipantAgent contains a '%s' claim but it did not contain any VerifiableCredentials.".formatted(VC_CLAIM)); + } + return Result.success(vcList); + } +} diff --git a/edc-extensions/constructx-policy/src/main/java/org/constructx/edc/policy/constructx/common/PolicyScopes.java b/edc-extensions/constructx-policy/src/main/java/org/constructx/edc/policy/constructx/common/PolicyScopes.java new file mode 100644 index 0000000000..ebf7d22c21 --- /dev/null +++ b/edc-extensions/constructx-policy/src/main/java/org/constructx/edc/policy/constructx/common/PolicyScopes.java @@ -0,0 +1,74 @@ +/******************************************************************************** + * Copyright (c) 2023 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.constructx.edc.policy.constructx.common; + +import org.eclipse.edc.connector.controlplane.catalog.spi.policy.CatalogPolicyContext; +import org.eclipse.edc.connector.controlplane.contract.spi.policy.ContractNegotiationPolicyContext; +import org.eclipse.edc.connector.controlplane.contract.spi.policy.TransferProcessPolicyContext; + +/** + * Defines standard EDC policy scopes. + */ +public interface PolicyScopes { + /** + * scope of catalog requests to edc + */ + String CATALOG_REQUEST_SCOPE = "request.catalog"; + + /** + * scope of contract negotiation requests to edc + */ + String NEGOTIATION_REQUEST_SCOPE = "request.contract.negotiation"; + + /** + * scope of transfer process requests to edc + */ + String TRANSFER_PROCESS_REQUEST_SCOPE = "request.transfer.process"; + + /** + * Scope for assets, policies + */ + String CATALOG_SCOPE = "catalog"; + + /** + * Scope for negotiations + */ + String NEGOTIATION_SCOPE = "contract.negotiation"; + + /** + * Scope for transfer of data + */ + String TRANSFER_PROCESS_SCOPE = "transfer.process"; + + /** + * Scope class for catalog + */ + Class CATALOG_SCOPE_CLASS = CatalogPolicyContext.class; + + /** + * Scope for contract negotiation + */ + Class NEGOTIATION_SCOPE_CLASS = ContractNegotiationPolicyContext.class; + + /** + * Scope for transfer process + */ + Class TRANSFER_PROCESS_SCOPE_CLASS = TransferProcessPolicyContext.class; +} diff --git a/edc-extensions/constructx-policy/src/main/java/org/constructx/edc/policy/constructx/membership/MembershipCredentialConstraintFunction.java b/edc-extensions/constructx-policy/src/main/java/org/constructx/edc/policy/constructx/membership/MembershipCredentialConstraintFunction.java new file mode 100644 index 0000000000..4259ac162b --- /dev/null +++ b/edc-extensions/constructx-policy/src/main/java/org/constructx/edc/policy/constructx/membership/MembershipCredentialConstraintFunction.java @@ -0,0 +1,96 @@ +/* + * Copyright (c) 2024 T-Systems International GmbH + * Copyright (c) 2025 SAP SE + * Copyright (c) 2026 Materna SE + * + * 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.constructx.edc.policy.constructx.membership; + +import org.constructx.edc.policy.constructx.common.AbstractDynamicCredentialConstraintFunction; +import org.eclipse.edc.iam.verifiablecredentials.spi.model.VerifiableCredential; +import org.eclipse.edc.participant.spi.ParticipantAgent; +import org.eclipse.edc.participant.spi.ParticipantAgentPolicyContext; +import org.eclipse.edc.policy.model.Operator; +import org.eclipse.edc.policy.model.Permission; +import org.eclipse.edc.spi.monitor.Monitor; +import org.eclipse.tractusx.edc.core.utils.credentials.CredentialTypePredicate; + +import static org.constructx.edc.policy.constructx.ConstructxPolicyConstants.CONSTRUCTX_CREDENTIAL_NS; +import static org.constructx.edc.policy.constructx.ConstructxPolicyConstants.CONSTRUCTX_POLICY_NS; + + +/** + * This constraint function checks that a MembershipCredential is present in a list of {@link VerifiableCredential} + * objects extracted from a {@link ParticipantAgent} which is expected to be present on the {@link ParticipantAgentPolicyContext}. + */ +public class MembershipCredentialConstraintFunction extends AbstractDynamicCredentialConstraintFunction { + + /** + * key of the membership credential constraint + * + * @deprecated Use {@value CONSTRUCTX_MEMBERSHIP_LITERAL} instead. + */ + @Deprecated(since = "0.0.4", forRemoval = true) + public static final String MEMBERSHIP_LITERAL = "Membership"; + + /** + * key for constructx-membership credential constraint + */ + public static final String CONSTRUCTX_MEMBERSHIP_LITERAL = "ConstructXMembership"; + + private final Monitor monitor; + + public MembershipCredentialConstraintFunction(Monitor monitor) { + this.monitor = monitor; + } + + @Override + public boolean evaluate(Object leftOperand, Operator operator, Object rightOperand, Permission permission, C context) { + if (!ACTIVE.equals(rightOperand)) { + context.reportProblem("Right-operand must be equal to '%s', but was '%s'".formatted(ACTIVE, rightOperand)); + return false; + } + + if ((CONSTRUCTX_POLICY_NS + MEMBERSHIP_LITERAL).equals(leftOperand)) { + monitor.warning("The %s%s Policy is deprecated since version 0.0.4 and will be removed in future releases. Please use %s%s Policy instead." + .formatted(CONSTRUCTX_POLICY_NS, MEMBERSHIP_LITERAL, CONSTRUCTX_POLICY_NS, CONSTRUCTX_MEMBERSHIP_LITERAL)); + } + + // make sure the ParticipantAgent is there + var participantAgent = extractParticipantAgent(context); + if (participantAgent.failed()) { + context.reportProblem(participantAgent.getFailureDetail()); + return false; + } + + var credentialResult = getCredentialList(participantAgent.getContent()); + if (credentialResult.failed()) { + context.reportProblem(credentialResult.getFailureDetail()); + return false; + } + return credentialResult.getContent() + .stream() + .anyMatch(new CredentialTypePredicate(CONSTRUCTX_CREDENTIAL_NS, CONSTRUCTX_MEMBERSHIP_LITERAL + CREDENTIAL_LITERAL) + .or(new CredentialTypePredicate(CONSTRUCTX_CREDENTIAL_NS, MEMBERSHIP_LITERAL + CREDENTIAL_LITERAL))); + } + + @Override + public boolean canHandle(Object leftOperand) { + return (CONSTRUCTX_POLICY_NS + CONSTRUCTX_MEMBERSHIP_LITERAL).equals(leftOperand) || (CONSTRUCTX_POLICY_NS + MEMBERSHIP_LITERAL).equals(leftOperand); + } +} diff --git a/edc-extensions/constructx-policy/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension b/edc-extensions/constructx-policy/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension new file mode 100644 index 0000000000..a435c7e317 --- /dev/null +++ b/edc-extensions/constructx-policy/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension @@ -0,0 +1,20 @@ +################################################################################# +# 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 +################################################################################# + +org.constructx.edc.policy.constructx.ConstructxPolicyExtension diff --git a/edc-extensions/constructx-policy/src/test/java/org/constructx/edc/policy/constructx/ConstructxPolicyRegistrationTest.java b/edc-extensions/constructx-policy/src/test/java/org/constructx/edc/policy/constructx/ConstructxPolicyRegistrationTest.java new file mode 100644 index 0000000000..9b154d93c7 --- /dev/null +++ b/edc-extensions/constructx-policy/src/test/java/org/constructx/edc/policy/constructx/ConstructxPolicyRegistrationTest.java @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2026 Materna SE + * + * 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.constructx.edc.policy.constructx; + +import org.constructx.edc.policy.constructx.membership.MembershipCredentialConstraintFunction; +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.spi.monitor.Monitor; +import org.junit.jupiter.api.Test; + +import static org.constructx.edc.policy.constructx.common.PolicyScopes.CATALOG_SCOPE; +import static org.constructx.edc.policy.constructx.common.PolicyScopes.NEGOTIATION_SCOPE; +import static org.constructx.edc.policy.constructx.common.PolicyScopes.TRANSFER_PROCESS_SCOPE; +import static org.eclipse.edc.policy.model.OdrlNamespace.ODRL_SCHEMA; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.atLeastOnce; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; + +class ConstructxPolicyRegistrationTest { + + @Test + void registerFunctions_registersMembershipFunction() { + var engine = mock(PolicyEngine.class); + var monitor = mock(Monitor.class); + + ConstructxPolicyRegistration.registerFunctions(engine, monitor); + + verify(engine, atLeastOnce()).registerFunction( + any(), + eq(Permission.class), + any(MembershipCredentialConstraintFunction.class) + ); + } + + @Test + void registerBindings_bindsOdrlUse() { + var registry = mock(RuleBindingRegistry.class); + + ConstructxPolicyRegistration.registerBindings(registry); + + verify(registry).bind(ODRL_SCHEMA + "use", CATALOG_SCOPE); + verify(registry).bind(ODRL_SCHEMA + "use", NEGOTIATION_SCOPE); + verify(registry).bind(ODRL_SCHEMA + "use", TRANSFER_PROCESS_SCOPE); + } +} diff --git a/edc-extensions/constructx-policy/src/test/java/org/constructx/edc/policy/constructx/CredentialFunctions.java b/edc-extensions/constructx-policy/src/test/java/org/constructx/edc/policy/constructx/CredentialFunctions.java new file mode 100644 index 0000000000..edc23af804 --- /dev/null +++ b/edc-extensions/constructx-policy/src/test/java/org/constructx/edc/policy/constructx/CredentialFunctions.java @@ -0,0 +1,70 @@ +/******************************************************************************** + * Copyright (c) 2024 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * Copyright (c) 2025 SAP SE + * Copyright (c) 2026 Materna SE + * + * 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.constructx.edc.policy.constructx; + +import org.eclipse.edc.iam.verifiablecredentials.spi.model.CredentialSubject; +import org.eclipse.edc.iam.verifiablecredentials.spi.model.Issuer; +import org.eclipse.edc.iam.verifiablecredentials.spi.model.VerifiableCredential; + +import java.time.Instant; +import java.time.temporal.ChronoUnit; +import java.util.List; +import java.util.Map; +import java.util.UUID; + +import static org.constructx.edc.policy.constructx.ConstructxPolicyConstants.CONSTRUCTX_CREDENTIAL_NS; + +public class CredentialFunctions { + + public static VerifiableCredential.Builder createCredential(String type) { + return VerifiableCredential.Builder.newInstance() + .types(List.of(CONSTRUCTX_CREDENTIAL_NS + "VerifiableCredential", CONSTRUCTX_CREDENTIAL_NS + type)) + .id(UUID.randomUUID().toString()) + .issuer(new Issuer(UUID.randomUUID().toString(), Map.of("prop1", "val1"))) + .expirationDate(Instant.now().plus(365, ChronoUnit.DAYS)) + .issuanceDate(Instant.now()) + .credentialSubject(CredentialSubject.Builder.newInstance() + .id("subject-id") + .claim("arbitrary", "claim") + .build()); + } + + public static VerifiableCredential.Builder createMembershipCredential() { + return VerifiableCredential.Builder.newInstance() + .types(List.of( + CONSTRUCTX_CREDENTIAL_NS + "VerifiableCredential", + CONSTRUCTX_CREDENTIAL_NS + "MembershipCredential", + CONSTRUCTX_CREDENTIAL_NS + "ConstructXMembershipCredential" + )) + .id(UUID.randomUUID().toString()) + .issuer(new Issuer(UUID.randomUUID().toString(), Map.of("prop1", "val1"))) + .expirationDate(Instant.now().plus(365, ChronoUnit.DAYS)) + .issuanceDate(Instant.now()) + .credentialSubject(CredentialSubject.Builder.newInstance() + .id("subject-id") + .claim("holderIdentifier", "did:web:holder") + .build()); + } + + private CredentialFunctions() { + } +} diff --git a/edc-extensions/constructx-policy/src/test/java/org/constructx/edc/policy/constructx/membership/MembershipConstraintFunctionTest.java b/edc-extensions/constructx-policy/src/test/java/org/constructx/edc/policy/constructx/membership/MembershipConstraintFunctionTest.java new file mode 100644 index 0000000000..14ad4b5479 --- /dev/null +++ b/edc-extensions/constructx-policy/src/test/java/org/constructx/edc/policy/constructx/membership/MembershipConstraintFunctionTest.java @@ -0,0 +1,148 @@ +/* + * Copyright (c) 2024 T-Systems International GmbH + * Copyright (c) 2025 SAP SE + * Copyright (c) 2026 Materna SE + * + * 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.constructx.edc.policy.constructx.membership; + +import org.eclipse.edc.participant.spi.ParticipantAgent; +import org.eclipse.edc.participant.spi.ParticipantAgentPolicyContext; +import org.eclipse.edc.policy.model.Operator; +import org.eclipse.edc.spi.monitor.Monitor; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.util.List; +import java.util.Map; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.constructx.edc.policy.constructx.ConstructxPolicyConstants.CONSTRUCTX_POLICY_NS; +import static org.constructx.edc.policy.constructx.CredentialFunctions.createCredential; +import static org.constructx.edc.policy.constructx.CredentialFunctions.createMembershipCredential; +import static org.constructx.edc.policy.constructx.membership.MembershipCredentialConstraintFunction.CONSTRUCTX_MEMBERSHIP_LITERAL; +import static org.constructx.edc.policy.constructx.membership.MembershipCredentialConstraintFunction.MEMBERSHIP_LITERAL; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +class MembershipConstraintFunctionTest { + + private final Monitor monitor = mock(); + private final MembershipCredentialConstraintFunction function = new MembershipCredentialConstraintFunction<>(monitor); + private final ParticipantAgentPolicyContext context = mock(); + private final ParticipantAgent participantAgent = mock(); + + @BeforeEach + void setup() { + when(context.participantAgent()).thenReturn(participantAgent); + } + + @Test + void evaluate_noParticipantAgentOnContext() { + when(context.participantAgent()).thenReturn(null); + + assertThat(function.evaluate(CONSTRUCTX_POLICY_NS + MEMBERSHIP_LITERAL, Operator.EQ, "active", null, context)).isFalse(); + + verify(monitor).warning(eq("The %s%s Policy is deprecated since version 0.0.4 and will be removed in future releases. Please use %s%s Policy instead." + .formatted(CONSTRUCTX_POLICY_NS, MEMBERSHIP_LITERAL, CONSTRUCTX_POLICY_NS, CONSTRUCTX_MEMBERSHIP_LITERAL))); + verify(context).reportProblem("Required PolicyContext data not found: org.eclipse.edc.participant.spi.ParticipantAgent"); + } + + @Test + void evaluate_noVcClaimOnParticipantAgent() { + assertThat(function.evaluate(CONSTRUCTX_POLICY_NS + CONSTRUCTX_MEMBERSHIP_LITERAL, Operator.EQ, "active", null, context)).isFalse(); + + verify(context).reportProblem(eq("ParticipantAgent did not contain a 'vc' claim.")); + } + + @Test + void evaluate_vcClaimEmpty() { + when(participantAgent.getClaims()).thenReturn(Map.of("vc", List.of())); + + assertThat(function.evaluate(CONSTRUCTX_POLICY_NS + CONSTRUCTX_MEMBERSHIP_LITERAL, Operator.EQ, "active", null, context)).isFalse(); + + verify(context).reportProblem(eq("ParticipantAgent contains a 'vc' claim but it did not contain any VerifiableCredentials.")); + } + + @Test + void evaluate_vcClaimNotList() { + when(participantAgent.getClaims()).thenReturn(Map.of("vc", new Object())); + + assertThat(function.evaluate(CONSTRUCTX_POLICY_NS + CONSTRUCTX_MEMBERSHIP_LITERAL, Operator.EQ, "active", null, context)).isFalse(); + + verify(context).reportProblem(eq("ParticipantAgent contains a 'vc' claim, but the type is incorrect. Expected java.util.List, received java.lang.Object.")); + } + + @Test + void evaluate_rightOperandNotActive() { + when(participantAgent.getClaims()).thenReturn(Map.of("vc", List.of(createMembershipCredential().build()))); + + assertThat(function.evaluate(CONSTRUCTX_POLICY_NS + CONSTRUCTX_MEMBERSHIP_LITERAL, Operator.EQ, "invalid", null, context)).isFalse(); + + verify(context).reportProblem(eq("Right-operand must be equal to 'active', but was 'invalid'")); + } + + @Test + void evaluate_whenConstructxMembershipCredentialFound() { + when(participantAgent.getClaims()).thenReturn(Map.of("vc", List.of(createMembershipCredential().build()))); + + assertThat(function.evaluate(CONSTRUCTX_POLICY_NS + CONSTRUCTX_MEMBERSHIP_LITERAL, Operator.EQ, "active", null, context)).isTrue(); + } + + @Test + void evaluate_whenLegacyMembershipCredentialFound() { + when(participantAgent.getClaims()).thenReturn(Map.of("vc", List.of(createMembershipCredential().build()))); + + assertThat(function.evaluate(CONSTRUCTX_POLICY_NS + MEMBERSHIP_LITERAL, Operator.EQ, "active", null, context)).isTrue(); + + verify(monitor).warning(eq("The %s%s Policy is deprecated since version 0.0.4 and will be removed in future releases. Please use %s%s Policy instead." + .formatted(CONSTRUCTX_POLICY_NS, MEMBERSHIP_LITERAL, CONSTRUCTX_POLICY_NS, CONSTRUCTX_MEMBERSHIP_LITERAL))); + } + + @Test + void evaluate_whenMultipleCredentialsFound() { + when(participantAgent.getClaims()).thenReturn(Map.of("vc", List.of( + createMembershipCredential().build(), + createMembershipCredential().build(), + createCredential("BogusCredential").build() + ))); + + assertThat(function.evaluate(CONSTRUCTX_POLICY_NS + CONSTRUCTX_MEMBERSHIP_LITERAL, Operator.EQ, "active", null, context)).isTrue(); + } + + @Test + void evaluate_whenCredentialNotFound() { + when(participantAgent.getClaims()).thenReturn(Map.of("vc", List.of(createCredential("BogusCredential").build()))); + + assertThat(function.evaluate(CONSTRUCTX_POLICY_NS + CONSTRUCTX_MEMBERSHIP_LITERAL, Operator.EQ, "active", null, context)).isFalse(); + } + + @Test + void test_canHandle() { + assertThat(function.canHandle(List.of())).isFalse(); + assertThat(function.canHandle("AnyLiteral")).isFalse(); + + assertThat(function.canHandle(CONSTRUCTX_MEMBERSHIP_LITERAL)).isFalse(); + assertThat(function.canHandle(CONSTRUCTX_POLICY_NS + CONSTRUCTX_MEMBERSHIP_LITERAL)).isTrue(); + + assertThat(function.canHandle(MEMBERSHIP_LITERAL)).isFalse(); + assertThat(function.canHandle(CONSTRUCTX_POLICY_NS + MEMBERSHIP_LITERAL)).isTrue(); + } +} diff --git a/settings.gradle.kts b/settings.gradle.kts index 80387478e4..7fb5128a1f 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -80,6 +80,7 @@ include(":edc-extensions:event-subscriber") include(":edc-extensions:edr:edr-api-v2") include(":edc-extensions:edr:edr-callback") include(":edc-extensions:edr:edr-index-lock-sql") +include(":edc-extensions:constructx-policy") include(":edc-extensions:cx-policy") include(":edc-extensions:cx-policy-legacy") include(":edc-extensions:dcp:cx-dcp")