From 0d6086df44abd774b73b718b51f7430af4bf2aa2 Mon Sep 17 00:00:00 2001 From: Enrico Risa Date: Fri, 10 Nov 2023 09:53:27 +0100 Subject: [PATCH] feat(IATP): pluggable constraint to scope mapping (#3593) * feat(IATP): pluggable constraint to scope mapping * pr remarks --- .../core/IatpDefaultServicesExtension.java | 9 ++ .../core/IatpScopeExtractorExtension.java | 58 +++++++ .../DefaultTrustedIssuerRegistry.java | 2 +- .../InMemorySignatureSuiteRegistry.java | 2 +- .../scope/IatpScopeExtractorFunction.java | 59 +++++++ .../scope/IatpScopeExtractorRegistry.java | 46 ++++++ .../core/scope/IatpScopeExtractorVisitor.java | 127 +++++++++++++++ ...rg.eclipse.edc.spi.system.ServiceExtension | 17 ++ .../IatpDefaultServicesExtensionTest.java | 10 ++ .../core/IatpScopeExtractorExtensionTest.java | 55 +++++++ .../DefaultTrustedIssuerRegistryTest.java | 2 +- .../scope/IatpScopeExtractorFunctionTest.java | 82 ++++++++++ .../scope/IatpScopeExtractorRegistryTest.java | 153 ++++++++++++++++++ .../core/scope/ScopeTestFunctions.java | 92 +++++++++++ .../identity-trust-spi/build.gradle.kts | 1 + .../identitytrust/scope/ScopeExtractor.java | 40 +++++ .../scope/ScopeExtractorRegistry.java | 45 ++++++ 17 files changed, 797 insertions(+), 3 deletions(-) create mode 100644 extensions/common/iam/identity-trust/identity-trust-core/src/main/java/org/eclipse/edc/iam/identitytrust/core/IatpScopeExtractorExtension.java rename extensions/common/iam/identity-trust/identity-trust-core/src/main/java/org/eclipse/edc/iam/identitytrust/core/{ => defaults}/DefaultTrustedIssuerRegistry.java (95%) rename extensions/common/iam/identity-trust/identity-trust-core/src/main/java/org/eclipse/edc/iam/identitytrust/core/{ => defaults}/InMemorySignatureSuiteRegistry.java (95%) create mode 100644 extensions/common/iam/identity-trust/identity-trust-core/src/main/java/org/eclipse/edc/iam/identitytrust/core/scope/IatpScopeExtractorFunction.java create mode 100644 extensions/common/iam/identity-trust/identity-trust-core/src/main/java/org/eclipse/edc/iam/identitytrust/core/scope/IatpScopeExtractorRegistry.java create mode 100644 extensions/common/iam/identity-trust/identity-trust-core/src/main/java/org/eclipse/edc/iam/identitytrust/core/scope/IatpScopeExtractorVisitor.java create mode 100644 extensions/common/iam/identity-trust/identity-trust-core/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension create mode 100644 extensions/common/iam/identity-trust/identity-trust-core/src/test/java/org/eclipse/edc/iam/identitytrust/core/IatpScopeExtractorExtensionTest.java rename extensions/common/iam/identity-trust/identity-trust-core/src/test/java/org/eclipse/edc/iam/identitytrust/core/{ => defaults}/DefaultTrustedIssuerRegistryTest.java (97%) create mode 100644 extensions/common/iam/identity-trust/identity-trust-core/src/test/java/org/eclipse/edc/iam/identitytrust/core/scope/IatpScopeExtractorFunctionTest.java create mode 100644 extensions/common/iam/identity-trust/identity-trust-core/src/test/java/org/eclipse/edc/iam/identitytrust/core/scope/IatpScopeExtractorRegistryTest.java create mode 100644 extensions/common/iam/identity-trust/identity-trust-core/src/test/java/org/eclipse/edc/iam/identitytrust/core/scope/ScopeTestFunctions.java create mode 100644 spi/common/identity-trust-spi/src/main/java/org/eclipse/edc/identitytrust/scope/ScopeExtractor.java create mode 100644 spi/common/identity-trust-spi/src/main/java/org/eclipse/edc/identitytrust/scope/ScopeExtractorRegistry.java diff --git a/extensions/common/iam/identity-trust/identity-trust-core/src/main/java/org/eclipse/edc/iam/identitytrust/core/IatpDefaultServicesExtension.java b/extensions/common/iam/identity-trust/identity-trust-core/src/main/java/org/eclipse/edc/iam/identitytrust/core/IatpDefaultServicesExtension.java index b533d56e36..cd971c10db 100644 --- a/extensions/common/iam/identity-trust/identity-trust-core/src/main/java/org/eclipse/edc/iam/identitytrust/core/IatpDefaultServicesExtension.java +++ b/extensions/common/iam/identity-trust/identity-trust-core/src/main/java/org/eclipse/edc/iam/identitytrust/core/IatpDefaultServicesExtension.java @@ -14,9 +14,13 @@ package org.eclipse.edc.iam.identitytrust.core; +import org.eclipse.edc.iam.identitytrust.core.defaults.DefaultTrustedIssuerRegistry; +import org.eclipse.edc.iam.identitytrust.core.defaults.InMemorySignatureSuiteRegistry; +import org.eclipse.edc.iam.identitytrust.core.scope.IatpScopeExtractorRegistry; import org.eclipse.edc.iam.identitytrust.sts.embedded.EmbeddedSecureTokenService; import org.eclipse.edc.identitytrust.SecureTokenService; import org.eclipse.edc.identitytrust.TrustedIssuerRegistry; +import org.eclipse.edc.identitytrust.scope.ScopeExtractorRegistry; import org.eclipse.edc.identitytrust.verification.SignatureSuiteRegistry; import org.eclipse.edc.jwt.TokenGenerationServiceImpl; import org.eclipse.edc.runtime.metamodel.annotation.Extension; @@ -78,6 +82,11 @@ public SignatureSuiteRegistry createSignatureSuiteRegistry() { return new InMemorySignatureSuiteRegistry(); } + @Provider(isDefault = true) + public ScopeExtractorRegistry scopeExtractorRegistry() { + return new IatpScopeExtractorRegistry(); + } + private KeyPair keyPairFromConfig(ServiceExtensionContext context) { var pubKeyAlias = context.getSetting(STS_PUBLIC_KEY_ALIAS, null); var privKeyAlias = context.getSetting(STS_PRIVATE_KEY_ALIAS, null); diff --git a/extensions/common/iam/identity-trust/identity-trust-core/src/main/java/org/eclipse/edc/iam/identitytrust/core/IatpScopeExtractorExtension.java b/extensions/common/iam/identity-trust/identity-trust-core/src/main/java/org/eclipse/edc/iam/identitytrust/core/IatpScopeExtractorExtension.java new file mode 100644 index 0000000000..e1a83807e8 --- /dev/null +++ b/extensions/common/iam/identity-trust/identity-trust-core/src/main/java/org/eclipse/edc/iam/identitytrust/core/IatpScopeExtractorExtension.java @@ -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.edc.iam.identitytrust.core; + +import org.eclipse.edc.iam.identitytrust.core.scope.IatpScopeExtractorFunction; +import org.eclipse.edc.identitytrust.scope.ScopeExtractorRegistry; +import org.eclipse.edc.policy.engine.spi.PolicyEngine; +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 static org.eclipse.edc.iam.identitytrust.core.IatpScopeExtractorExtension.NAME; + +@Extension(NAME) +public class IatpScopeExtractorExtension implements ServiceExtension { + + public static final String NAME = "IATP scope extractor extension"; + + public static final String CATALOG_REQUEST_SCOPE = "request.catalog"; + public static final String NEGOTIATION_REQUEST_SCOPE = "request.contract.negotiation"; + public static final String TRANSFER_PROCESS_REQUEST_SCOPE = "request.transfer.process"; + + @Inject + private PolicyEngine policyEngine; + + @Inject + private ScopeExtractorRegistry scopeExtractorRegistry; + + @Inject + private Monitor monitor; + + @Override + public String name() { + return NAME; + } + + @Override + public void initialize(ServiceExtensionContext context) { + var contextMappingFunction = new IatpScopeExtractorFunction(scopeExtractorRegistry, monitor); + policyEngine.registerPreValidator(CATALOG_REQUEST_SCOPE, contextMappingFunction); + policyEngine.registerPreValidator(NEGOTIATION_REQUEST_SCOPE, contextMappingFunction); + policyEngine.registerPreValidator(TRANSFER_PROCESS_REQUEST_SCOPE, contextMappingFunction); + } +} diff --git a/extensions/common/iam/identity-trust/identity-trust-core/src/main/java/org/eclipse/edc/iam/identitytrust/core/DefaultTrustedIssuerRegistry.java b/extensions/common/iam/identity-trust/identity-trust-core/src/main/java/org/eclipse/edc/iam/identitytrust/core/defaults/DefaultTrustedIssuerRegistry.java similarity index 95% rename from extensions/common/iam/identity-trust/identity-trust-core/src/main/java/org/eclipse/edc/iam/identitytrust/core/DefaultTrustedIssuerRegistry.java rename to extensions/common/iam/identity-trust/identity-trust-core/src/main/java/org/eclipse/edc/iam/identitytrust/core/defaults/DefaultTrustedIssuerRegistry.java index cd3dcf9e75..2a01f9062d 100644 --- a/extensions/common/iam/identity-trust/identity-trust-core/src/main/java/org/eclipse/edc/iam/identitytrust/core/DefaultTrustedIssuerRegistry.java +++ b/extensions/common/iam/identity-trust/identity-trust-core/src/main/java/org/eclipse/edc/iam/identitytrust/core/defaults/DefaultTrustedIssuerRegistry.java @@ -12,7 +12,7 @@ * */ -package org.eclipse.edc.iam.identitytrust.core; +package org.eclipse.edc.iam.identitytrust.core.defaults; import org.eclipse.edc.identitytrust.TrustedIssuerRegistry; import org.eclipse.edc.identitytrust.model.Issuer; diff --git a/extensions/common/iam/identity-trust/identity-trust-core/src/main/java/org/eclipse/edc/iam/identitytrust/core/InMemorySignatureSuiteRegistry.java b/extensions/common/iam/identity-trust/identity-trust-core/src/main/java/org/eclipse/edc/iam/identitytrust/core/defaults/InMemorySignatureSuiteRegistry.java similarity index 95% rename from extensions/common/iam/identity-trust/identity-trust-core/src/main/java/org/eclipse/edc/iam/identitytrust/core/InMemorySignatureSuiteRegistry.java rename to extensions/common/iam/identity-trust/identity-trust-core/src/main/java/org/eclipse/edc/iam/identitytrust/core/defaults/InMemorySignatureSuiteRegistry.java index 412c8c2e13..1b76fa02b0 100644 --- a/extensions/common/iam/identity-trust/identity-trust-core/src/main/java/org/eclipse/edc/iam/identitytrust/core/InMemorySignatureSuiteRegistry.java +++ b/extensions/common/iam/identity-trust/identity-trust-core/src/main/java/org/eclipse/edc/iam/identitytrust/core/defaults/InMemorySignatureSuiteRegistry.java @@ -12,7 +12,7 @@ * */ -package org.eclipse.edc.iam.identitytrust.core; +package org.eclipse.edc.iam.identitytrust.core.defaults; import com.apicatalog.ld.signature.SignatureSuite; import org.eclipse.edc.identitytrust.verification.SignatureSuiteRegistry; diff --git a/extensions/common/iam/identity-trust/identity-trust-core/src/main/java/org/eclipse/edc/iam/identitytrust/core/scope/IatpScopeExtractorFunction.java b/extensions/common/iam/identity-trust/identity-trust-core/src/main/java/org/eclipse/edc/iam/identitytrust/core/scope/IatpScopeExtractorFunction.java new file mode 100644 index 0000000000..75e8b07d94 --- /dev/null +++ b/extensions/common/iam/identity-trust/identity-trust-core/src/main/java/org/eclipse/edc/iam/identitytrust/core/scope/IatpScopeExtractorFunction.java @@ -0,0 +1,59 @@ +/* + * 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.edc.iam.identitytrust.core.scope; + +import org.eclipse.edc.identitytrust.scope.ScopeExtractor; +import org.eclipse.edc.identitytrust.scope.ScopeExtractorRegistry; +import org.eclipse.edc.policy.engine.spi.PolicyContext; +import org.eclipse.edc.policy.model.Policy; +import org.eclipse.edc.spi.EdcException; +import org.eclipse.edc.spi.iam.TokenParameters; +import org.eclipse.edc.spi.monitor.Monitor; + +import java.util.function.BiFunction; + +import static java.lang.String.format; + +/** + * IATP pre-validator function for extracting scopes from a {@link Policy} using the registered {@link ScopeExtractor} + * in the {@link ScopeExtractorRegistry}. + */ +public class IatpScopeExtractorFunction implements BiFunction { + + private final ScopeExtractorRegistry registry; + private final Monitor monitor; + + public IatpScopeExtractorFunction(ScopeExtractorRegistry registry, Monitor monitor) { + this.registry = registry; + this.monitor = monitor; + } + + @Override + public Boolean apply(Policy policy, PolicyContext context) { + var params = context.getContextData(TokenParameters.Builder.class); + if (params == null) { + throw new EdcException(format("%s not set in policy context", TokenParameters.Builder.class.getName())); + } + var results = registry.extractScopes(policy, context).map(scopes -> String.join(" ", scopes)); + + if (results.succeeded()) { + params.scope(results.getContent()); + return true; + } else { + monitor.warning("Failed to extract scopes from a policy"); + return false; + } + } +} diff --git a/extensions/common/iam/identity-trust/identity-trust-core/src/main/java/org/eclipse/edc/iam/identitytrust/core/scope/IatpScopeExtractorRegistry.java b/extensions/common/iam/identity-trust/identity-trust-core/src/main/java/org/eclipse/edc/iam/identitytrust/core/scope/IatpScopeExtractorRegistry.java new file mode 100644 index 0000000000..85e688166d --- /dev/null +++ b/extensions/common/iam/identity-trust/identity-trust-core/src/main/java/org/eclipse/edc/iam/identitytrust/core/scope/IatpScopeExtractorRegistry.java @@ -0,0 +1,46 @@ +/* + * 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.edc.iam.identitytrust.core.scope; + +import org.eclipse.edc.identitytrust.scope.ScopeExtractor; +import org.eclipse.edc.identitytrust.scope.ScopeExtractorRegistry; +import org.eclipse.edc.policy.engine.spi.PolicyContext; +import org.eclipse.edc.policy.model.Policy; +import org.eclipse.edc.spi.result.Result; + +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + +public class IatpScopeExtractorRegistry implements ScopeExtractorRegistry { + + private final List extractors = new ArrayList<>(); + + @Override + public void registerScopeExtractor(ScopeExtractor extractor) { + extractors.add(extractor); + } + + @Override + public Result> extractScopes(Policy policy, PolicyContext policyContext) { + var visitor = new IatpScopeExtractorVisitor(extractors, policyContext); + var policies = policy.accept(visitor); + if (policyContext.hasProblems()) { + return Result.failure(policyContext.getProblems()); + } + return Result.success(policies); + } + +} diff --git a/extensions/common/iam/identity-trust/identity-trust-core/src/main/java/org/eclipse/edc/iam/identitytrust/core/scope/IatpScopeExtractorVisitor.java b/extensions/common/iam/identity-trust/identity-trust-core/src/main/java/org/eclipse/edc/iam/identitytrust/core/scope/IatpScopeExtractorVisitor.java new file mode 100644 index 0000000000..957e5d6c70 --- /dev/null +++ b/extensions/common/iam/identity-trust/identity-trust-core/src/main/java/org/eclipse/edc/iam/identitytrust/core/scope/IatpScopeExtractorVisitor.java @@ -0,0 +1,127 @@ +/* + * 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.edc.iam.identitytrust.core.scope; + +import org.eclipse.edc.identitytrust.scope.ScopeExtractor; +import org.eclipse.edc.policy.engine.spi.PolicyContext; +import org.eclipse.edc.policy.model.AndConstraint; +import org.eclipse.edc.policy.model.AtomicConstraint; +import org.eclipse.edc.policy.model.Constraint; +import org.eclipse.edc.policy.model.Duty; +import org.eclipse.edc.policy.model.Expression; +import org.eclipse.edc.policy.model.LiteralExpression; +import org.eclipse.edc.policy.model.MultiplicityConstraint; +import org.eclipse.edc.policy.model.OrConstraint; +import org.eclipse.edc.policy.model.Permission; +import org.eclipse.edc.policy.model.Policy; +import org.eclipse.edc.policy.model.Prohibition; +import org.eclipse.edc.policy.model.Rule; +import org.eclipse.edc.policy.model.XoneConstraint; + +import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +/** + * IATP scope visitor for invoking {@link ScopeExtractor}s during the pre-validation phase. + */ +public class IatpScopeExtractorVisitor implements Policy.Visitor>, Rule.Visitor>, Constraint.Visitor>, Expression.Visitor { + + private final List mappers; + private final PolicyContext policyContext; + + public IatpScopeExtractorVisitor(List mappers, PolicyContext policyContext) { + this.mappers = mappers; + this.policyContext = policyContext; + } + + @Override + public Set visitAndConstraint(AndConstraint andConstraint) { + return visitMultiplicityConstraint(andConstraint); + } + + @Override + public Set visitOrConstraint(OrConstraint orConstraint) { + return visitMultiplicityConstraint(orConstraint); + } + + @Override + public Set visitXoneConstraint(XoneConstraint constraint) { + return visitMultiplicityConstraint(constraint); + } + + @Override + public Set visitAtomicConstraint(AtomicConstraint constraint) { + var rightValue = constraint.getRightExpression().accept(this); + var leftRawValue = constraint.getLeftExpression().accept(this); + + return mappers.stream() + .map(mapper -> mapper.extractScopes(leftRawValue, constraint.getOperator(), rightValue, policyContext)) + .flatMap(Collection::stream) + .collect(Collectors.toSet()); + + } + + @Override + public Object visitLiteralExpression(LiteralExpression expression) { + return expression.getValue(); + } + + @Override + public Set visitPolicy(Policy policy) { + var scopes = new HashSet(); + policy.getPermissions().forEach(permission -> scopes.addAll(permission.accept(this))); + policy.getProhibitions().forEach(prohibition -> scopes.addAll(prohibition.accept(this))); + policy.getObligations().forEach(duty -> scopes.addAll(duty.accept(this))); + return scopes; + } + + @Override + public Set visitPermission(Permission policy) { + var scopes = policy.getDuties().stream() + .map(duty -> duty.accept(this)) + .flatMap(Collection::stream) + .collect(Collectors.toSet()); + + scopes.addAll(visitRule(policy)); + return scopes; + } + + @Override + public Set visitProhibition(Prohibition policy) { + return visitRule(policy); + } + + @Override + public Set visitDuty(Duty policy) { + return visitRule(policy); + } + + private Set visitRule(Rule rule) { + return rule.getConstraints().stream() + .map(constraint -> constraint.accept(this)) + .flatMap(Collection::stream) + .collect(Collectors.toSet()); + } + + private Set visitMultiplicityConstraint(MultiplicityConstraint multiplicityConstraint) { + return multiplicityConstraint.getConstraints().stream() + .map(constraint -> constraint.accept(this)) + .flatMap(Collection::stream) + .collect(Collectors.toSet()); + } +} diff --git a/extensions/common/iam/identity-trust/identity-trust-core/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension b/extensions/common/iam/identity-trust/identity-trust-core/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension new file mode 100644 index 0000000000..c025a0a2c8 --- /dev/null +++ b/extensions/common/iam/identity-trust/identity-trust-core/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension @@ -0,0 +1,17 @@ +# +# 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.edc.iam.identitytrust.core.IatpDefaultServicesExtension +org.eclipse.edc.iam.identitytrust.core.IatpScopeExtractorExtension +org.eclipse.edc.iam.identitytrust.core.IdentityAndTrustExtension \ No newline at end of file diff --git a/extensions/common/iam/identity-trust/identity-trust-core/src/test/java/org/eclipse/edc/iam/identitytrust/core/IatpDefaultServicesExtensionTest.java b/extensions/common/iam/identity-trust/identity-trust-core/src/test/java/org/eclipse/edc/iam/identitytrust/core/IatpDefaultServicesExtensionTest.java index 495dfe78a4..b8996b71f2 100644 --- a/extensions/common/iam/identity-trust/identity-trust-core/src/test/java/org/eclipse/edc/iam/identitytrust/core/IatpDefaultServicesExtensionTest.java +++ b/extensions/common/iam/identity-trust/identity-trust-core/src/test/java/org/eclipse/edc/iam/identitytrust/core/IatpDefaultServicesExtensionTest.java @@ -17,6 +17,8 @@ import com.nimbusds.jose.JOSEException; import com.nimbusds.jose.jwk.KeyUse; import com.nimbusds.jose.jwk.gen.RSAKeyGenerator; +import org.eclipse.edc.iam.identitytrust.core.defaults.DefaultTrustedIssuerRegistry; +import org.eclipse.edc.iam.identitytrust.core.scope.IatpScopeExtractorRegistry; import org.eclipse.edc.iam.identitytrust.sts.embedded.EmbeddedSecureTokenService; import org.eclipse.edc.junit.extensions.DependencyInjectionExtension; import org.eclipse.edc.spi.monitor.Monitor; @@ -103,4 +105,12 @@ void verify_defaultIssuerRegistry(ServiceExtensionContext context, ObjectFactory assertThat(ext.createInMemoryIssuerRegistry()).isInstanceOf(DefaultTrustedIssuerRegistry.class); } + + @Test + void verify_defaultCredentialMapperRegistry(ServiceExtensionContext context, IatpDefaultServicesExtension ext) { + Monitor mockedMonitor = mock(); + context.registerService(Monitor.class, mockedMonitor); + + assertThat(ext.scopeExtractorRegistry()).isInstanceOf(IatpScopeExtractorRegistry.class); + } } \ No newline at end of file diff --git a/extensions/common/iam/identity-trust/identity-trust-core/src/test/java/org/eclipse/edc/iam/identitytrust/core/IatpScopeExtractorExtensionTest.java b/extensions/common/iam/identity-trust/identity-trust-core/src/test/java/org/eclipse/edc/iam/identitytrust/core/IatpScopeExtractorExtensionTest.java new file mode 100644 index 0000000000..0d7af855f7 --- /dev/null +++ b/extensions/common/iam/identity-trust/identity-trust-core/src/test/java/org/eclipse/edc/iam/identitytrust/core/IatpScopeExtractorExtensionTest.java @@ -0,0 +1,55 @@ +/* + * 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.edc.iam.identitytrust.core; + +import org.eclipse.edc.iam.identitytrust.core.scope.IatpScopeExtractorFunction; +import org.eclipse.edc.junit.extensions.DependencyInjectionExtension; +import org.eclipse.edc.policy.engine.spi.PolicyEngine; +import org.eclipse.edc.spi.system.ServiceExtensionContext; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; + +import static org.eclipse.edc.iam.identitytrust.core.IatpScopeExtractorExtension.CATALOG_REQUEST_SCOPE; +import static org.eclipse.edc.iam.identitytrust.core.IatpScopeExtractorExtension.NEGOTIATION_REQUEST_SCOPE; +import static org.eclipse.edc.iam.identitytrust.core.IatpScopeExtractorExtension.TRANSFER_PROCESS_REQUEST_SCOPE; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.isA; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; + +@ExtendWith(DependencyInjectionExtension.class) +class IatpScopeExtractorExtensionTest { + + + private final PolicyEngine policyEngine = mock(); + + @BeforeEach + void setup(ServiceExtensionContext context) { + context.registerService(PolicyEngine.class, policyEngine); + } + + @Test + void initialize(ServiceExtensionContext context, IatpScopeExtractorExtension ext) { + + ext.initialize(context); + + verify(policyEngine).registerPreValidator(eq(CATALOG_REQUEST_SCOPE), isA(IatpScopeExtractorFunction.class)); + verify(policyEngine).registerPreValidator(eq(NEGOTIATION_REQUEST_SCOPE), isA(IatpScopeExtractorFunction.class)); + verify(policyEngine).registerPreValidator(eq(TRANSFER_PROCESS_REQUEST_SCOPE), isA(IatpScopeExtractorFunction.class)); + } + + +} \ No newline at end of file diff --git a/extensions/common/iam/identity-trust/identity-trust-core/src/test/java/org/eclipse/edc/iam/identitytrust/core/DefaultTrustedIssuerRegistryTest.java b/extensions/common/iam/identity-trust/identity-trust-core/src/test/java/org/eclipse/edc/iam/identitytrust/core/defaults/DefaultTrustedIssuerRegistryTest.java similarity index 97% rename from extensions/common/iam/identity-trust/identity-trust-core/src/test/java/org/eclipse/edc/iam/identitytrust/core/DefaultTrustedIssuerRegistryTest.java rename to extensions/common/iam/identity-trust/identity-trust-core/src/test/java/org/eclipse/edc/iam/identitytrust/core/defaults/DefaultTrustedIssuerRegistryTest.java index 7a7c07bdf7..b8915f726a 100644 --- a/extensions/common/iam/identity-trust/identity-trust-core/src/test/java/org/eclipse/edc/iam/identitytrust/core/DefaultTrustedIssuerRegistryTest.java +++ b/extensions/common/iam/identity-trust/identity-trust-core/src/test/java/org/eclipse/edc/iam/identitytrust/core/defaults/DefaultTrustedIssuerRegistryTest.java @@ -12,7 +12,7 @@ * */ -package org.eclipse.edc.iam.identitytrust.core; +package org.eclipse.edc.iam.identitytrust.core.defaults; import org.eclipse.edc.identitytrust.model.Issuer; import org.junit.jupiter.api.Test; diff --git a/extensions/common/iam/identity-trust/identity-trust-core/src/test/java/org/eclipse/edc/iam/identitytrust/core/scope/IatpScopeExtractorFunctionTest.java b/extensions/common/iam/identity-trust/identity-trust-core/src/test/java/org/eclipse/edc/iam/identitytrust/core/scope/IatpScopeExtractorFunctionTest.java new file mode 100644 index 0000000000..74ce6e8340 --- /dev/null +++ b/extensions/common/iam/identity-trust/identity-trust-core/src/test/java/org/eclipse/edc/iam/identitytrust/core/scope/IatpScopeExtractorFunctionTest.java @@ -0,0 +1,82 @@ +/* + * 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.edc.iam.identitytrust.core.scope; + +import org.eclipse.edc.identitytrust.scope.ScopeExtractorRegistry; +import org.eclipse.edc.policy.engine.spi.PolicyContext; +import org.eclipse.edc.policy.model.Policy; +import org.eclipse.edc.spi.EdcException; +import org.eclipse.edc.spi.iam.TokenParameters; +import org.eclipse.edc.spi.monitor.Monitor; +import org.eclipse.edc.spi.result.Result; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.util.Set; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class IatpScopeExtractorFunctionTest { + + private final ScopeExtractorRegistry registry = mock(); + private final PolicyContext policyContext = mock(); + private IatpScopeExtractorFunction function; + + @BeforeEach + void setup() { + function = new IatpScopeExtractorFunction(registry, mock(Monitor.class)); + } + + @Test + void apply() { + var policy = Policy.Builder.newInstance().build(); + var tokenParamBuilder = TokenParameters.Builder.newInstance().audience("testAud"); + + when(policyContext.getContextData(TokenParameters.Builder.class)).thenReturn(tokenParamBuilder); + when(registry.extractScopes(eq(policy), any())).thenReturn(Result.success(Set.of("scope1", "scope2"))); + + assertThat(function.apply(policy, policyContext)).isTrue(); + assertThat(tokenParamBuilder.build()) + .extracting(TokenParameters::getScope) + .extracting(scope -> scope.split(" ")) + .satisfies(scopes -> assertThat(scopes).contains("scope1", "scope2")); + } + + @Test + void apply_shouldThrow_whenTokenBuilderMissing() { + var policy = Policy.Builder.newInstance().build(); + + when(registry.extractScopes(eq(policy), any())).thenReturn(Result.success(Set.of("scope1", "scope2"))); + + assertThatThrownBy(() -> function.apply(policy, policyContext)).isInstanceOf(EdcException.class); + } + + @Test + void apply_fail_whenScopeExtractorFails() { + var policy = Policy.Builder.newInstance().build(); + var tokenParamBuilder = TokenParameters.Builder.newInstance().audience("testAud"); + when(policyContext.getContextData(TokenParameters.Builder.class)).thenReturn(tokenParamBuilder); + + when(registry.extractScopes(eq(policy), any())).thenReturn(Result.failure("failure")); + + assertThat(function.apply(policy, policyContext)).isFalse(); + + } +} diff --git a/extensions/common/iam/identity-trust/identity-trust-core/src/test/java/org/eclipse/edc/iam/identitytrust/core/scope/IatpScopeExtractorRegistryTest.java b/extensions/common/iam/identity-trust/identity-trust-core/src/test/java/org/eclipse/edc/iam/identitytrust/core/scope/IatpScopeExtractorRegistryTest.java new file mode 100644 index 0000000000..f96b762035 --- /dev/null +++ b/extensions/common/iam/identity-trust/identity-trust-core/src/test/java/org/eclipse/edc/iam/identitytrust/core/scope/IatpScopeExtractorRegistryTest.java @@ -0,0 +1,153 @@ +/* + * 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.edc.iam.identitytrust.core.scope; + +import org.eclipse.edc.identitytrust.scope.ScopeExtractor; +import org.eclipse.edc.policy.engine.spi.PolicyContext; +import org.eclipse.edc.policy.model.Constraint; +import org.eclipse.edc.policy.model.Operator; +import org.eclipse.edc.policy.model.Policy; +import org.eclipse.edc.spi.result.Failure; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.ArgumentsProvider; +import org.junit.jupiter.params.provider.ArgumentsSource; + +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.IntStream; +import java.util.stream.Stream; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.eclipse.edc.iam.identitytrust.core.scope.ScopeTestFunctions.andConstraint; +import static org.eclipse.edc.iam.identitytrust.core.scope.ScopeTestFunctions.atomicConstraint; +import static org.eclipse.edc.iam.identitytrust.core.scope.ScopeTestFunctions.dutyPolicy; +import static org.eclipse.edc.iam.identitytrust.core.scope.ScopeTestFunctions.orConstraint; +import static org.eclipse.edc.iam.identitytrust.core.scope.ScopeTestFunctions.permissionPolicy; +import static org.eclipse.edc.iam.identitytrust.core.scope.ScopeTestFunctions.prohibitionPolicy; +import static org.eclipse.edc.iam.identitytrust.core.scope.ScopeTestFunctions.xoneConstraint; +import static org.eclipse.edc.junit.assertions.AbstractResultAssert.assertThat; +import static org.junit.jupiter.params.provider.Arguments.of; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +public class IatpScopeExtractorRegistryTest { + + private final IatpScopeExtractorRegistry registry = new IatpScopeExtractorRegistry(); + private ScopeExtractor extractor; + private PolicyContext ctx; + + @BeforeEach + void setup() { + extractor = mock(ScopeExtractor.class); + ctx = mock(PolicyContext.class); + registry.registerScopeExtractor(extractor); + } + + @ParameterizedTest + @ArgumentsSource(SingleConstraintProvider.class) + void extractScopes(Policy policy, ConstraintData data) { + + when(extractor.extractScopes(data.left(), data.operator(), data.right, ctx)).thenReturn(Set.of(data.left())); + + assertThat(registry.extractScopes(policy, ctx)) + .isSucceeded() + .satisfies(scopes -> assertThat(scopes).contains(data.left)); + + verify(extractor).extractScopes(data.left(), data.operator(), data.right(), ctx); + } + + @ParameterizedTest + @ArgumentsSource(MultipleConstraintProvider.class) + void extractScopes_withMultipleConstraints(Policy policy, List constraintData) { + + var scopes = constraintData.stream().map(ConstraintData::left).collect(Collectors.toSet()); + + constraintData.forEach(data -> when(extractor.extractScopes(data.left(), data.operator(), data.right, ctx)).thenReturn(Set.of(data.left()))); + + + assertThat(registry.extractScopes(policy, ctx)) + .isSucceeded() + .satisfies(returnedScopes -> assertThat(returnedScopes).containsAll(scopes)); + + constraintData.forEach(data -> verify(extractor).extractScopes(data.left(), data.operator(), data.right(), ctx)); + } + + @Test + void extractScopes_fails_whenContextProblem() { + + var left = "left"; + var right = "right"; + var operator = Operator.EQ; + var problems = List.of("problem"); + when(extractor.extractScopes(left, operator, right, ctx)).thenReturn(Set.of()); + when(ctx.hasProblems()).thenReturn(true); + when(ctx.getProblems()).thenReturn(problems); + + var policy = permissionPolicy(atomicConstraint(left, operator, right)); + + assertThat(registry.extractScopes(policy, ctx)) + .isFailed() + .extracting(Failure::getMessages) + .isEqualTo(problems); + + verify(extractor).extractScopes(left, operator, right, ctx); + } + + private record ConstraintData(String left, Operator operator, Object right) { + } + + private static class SingleConstraintProvider implements ArgumentsProvider { + + @Override + public Stream provideArguments(ExtensionContext context) throws Exception { + var left = "left"; + var right = "right"; + var operator = Operator.EQ; + var constraint = atomicConstraint(left, operator, right); + var constraintData = new ConstraintData(left, operator, right); + return Stream.of( + of(permissionPolicy(constraint), constraintData), + of(dutyPolicy(constraint), constraintData), + of(prohibitionPolicy(constraint), constraintData) + ); + } + } + + private static class MultipleConstraintProvider implements ArgumentsProvider { + + @Override + public Stream provideArguments(ExtensionContext context) throws Exception { + var operator = Operator.EQ; + var data = IntStream.range(0, 5).mapToObj(i -> new ConstraintData("left" + i, operator, "right" + i)).toList(); + + var constraints = data.stream().map(d -> atomicConstraint(d.left(), operator, d.right())) + .map(Constraint.class::cast).toList(); + + return Stream.of( + of(permissionPolicy(andConstraint(constraints)), data), + of(permissionPolicy(orConstraint(constraints)), data), + of(permissionPolicy(xoneConstraint(constraints)), data) + ); + } + } + + +} diff --git a/extensions/common/iam/identity-trust/identity-trust-core/src/test/java/org/eclipse/edc/iam/identitytrust/core/scope/ScopeTestFunctions.java b/extensions/common/iam/identity-trust/identity-trust-core/src/test/java/org/eclipse/edc/iam/identitytrust/core/scope/ScopeTestFunctions.java new file mode 100644 index 0000000000..fcf635016c --- /dev/null +++ b/extensions/common/iam/identity-trust/identity-trust-core/src/test/java/org/eclipse/edc/iam/identitytrust/core/scope/ScopeTestFunctions.java @@ -0,0 +1,92 @@ +/* + * 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.edc.iam.identitytrust.core.scope; + +import org.eclipse.edc.policy.model.AndConstraint; +import org.eclipse.edc.policy.model.AtomicConstraint; +import org.eclipse.edc.policy.model.Constraint; +import org.eclipse.edc.policy.model.Duty; +import org.eclipse.edc.policy.model.LiteralExpression; +import org.eclipse.edc.policy.model.Operator; +import org.eclipse.edc.policy.model.OrConstraint; +import org.eclipse.edc.policy.model.Permission; +import org.eclipse.edc.policy.model.Policy; +import org.eclipse.edc.policy.model.Prohibition; +import org.eclipse.edc.policy.model.XoneConstraint; + +import java.util.List; + +public class ScopeTestFunctions { + + public static Policy permissionPolicy(Constraint constraint) { + var permission = Permission.Builder.newInstance() + .constraint(constraint) + .build(); + + return Policy.Builder.newInstance() + .permission(permission) + .build(); + } + + public static AtomicConstraint atomicConstraint(Object left, Operator operator, Object right) { + return AtomicConstraint.Builder.newInstance() + .leftExpression(new LiteralExpression(left)) + .operator(operator) + .rightExpression(new LiteralExpression(right)) + .build(); + + } + + public static AndConstraint andConstraint(List constraints) { + return AndConstraint.Builder.newInstance() + .constraints(constraints) + .build(); + + } + + public static OrConstraint orConstraint(List constraints) { + return OrConstraint.Builder.newInstance() + .constraints(constraints) + .build(); + + } + + public static XoneConstraint xoneConstraint(List constraints) { + return XoneConstraint.Builder.newInstance() + .constraints(constraints) + .build(); + + } + + public static Policy dutyPolicy(Constraint constraint) { + var duty = Duty.Builder.newInstance() + .constraint(constraint) + .build(); + + return Policy.Builder.newInstance() + .duty(duty) + .build(); + } + + public static Policy prohibitionPolicy(Constraint constraint) { + var prohibition = Prohibition.Builder.newInstance() + .constraint(constraint) + .build(); + + return Policy.Builder.newInstance() + .prohibition(prohibition) + .build(); + } +} diff --git a/spi/common/identity-trust-spi/build.gradle.kts b/spi/common/identity-trust-spi/build.gradle.kts index 3a91dd60aa..910f729684 100644 --- a/spi/common/identity-trust-spi/build.gradle.kts +++ b/spi/common/identity-trust-spi/build.gradle.kts @@ -20,6 +20,7 @@ plugins { dependencies { api(project(":spi:common:core-spi")) + api(project(":spi:common:policy-engine-spi")) api(libs.iron.vc) { //this is not on MavenCentral, and we don't really need it anyway exclude("com.github.multiformats") diff --git a/spi/common/identity-trust-spi/src/main/java/org/eclipse/edc/identitytrust/scope/ScopeExtractor.java b/spi/common/identity-trust-spi/src/main/java/org/eclipse/edc/identitytrust/scope/ScopeExtractor.java new file mode 100644 index 0000000000..59554b8162 --- /dev/null +++ b/spi/common/identity-trust-spi/src/main/java/org/eclipse/edc/identitytrust/scope/ScopeExtractor.java @@ -0,0 +1,40 @@ +/* + * 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.edc.identitytrust.scope; + +import org.eclipse.edc.policy.engine.spi.PolicyContext; +import org.eclipse.edc.policy.engine.spi.PolicyEngine; +import org.eclipse.edc.policy.model.AtomicConstraint; +import org.eclipse.edc.policy.model.Operator; + +import java.util.Set; + +/** + * Invoked during the pre-validation phase in the {@link PolicyEngine} for extracting the scopes needed for the IATP flow from an {@link AtomicConstraint} . + * Extractors can be registered in {@link ScopeExtractorRegistry} + */ +@FunctionalInterface +public interface ScopeExtractor { + + /** + * Performs the extraction of the scopes. + * + * @param leftValue the left-side expression for the constraint. + * @param operator the operator. + * @param rightValue the right-side expression for the constraint. + * @param context the policy context + */ + Set extractScopes(Object leftValue, Operator operator, Object rightValue, PolicyContext context); +} diff --git a/spi/common/identity-trust-spi/src/main/java/org/eclipse/edc/identitytrust/scope/ScopeExtractorRegistry.java b/spi/common/identity-trust-spi/src/main/java/org/eclipse/edc/identitytrust/scope/ScopeExtractorRegistry.java new file mode 100644 index 0000000000..960a720e90 --- /dev/null +++ b/spi/common/identity-trust-spi/src/main/java/org/eclipse/edc/identitytrust/scope/ScopeExtractorRegistry.java @@ -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.edc.identitytrust.scope; + +import org.eclipse.edc.policy.engine.spi.PolicyContext; +import org.eclipse.edc.policy.model.Policy; +import org.eclipse.edc.runtime.metamodel.annotation.ExtensionPoint; +import org.eclipse.edc.spi.result.Result; + +import java.util.Set; + +/** + * Registry for {@link ScopeExtractor} + */ +@ExtensionPoint +public interface ScopeExtractorRegistry { + + /** + * Register a scope extractor + * + * @param extractor The extractor + */ + void registerScopeExtractor(ScopeExtractor extractor); + + /** + * Extract scopes from a policy. + * + * @param policy The input policy + * @param policyContext The policy context + * @return The set of scopes to use if succeeded, otherwise failure + */ + Result> extractScopes(Policy policy, PolicyContext policyContext); +}