Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(IATP): pluggable constraint to scope mapping #3593

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/*
* Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG)
*
* This program and the accompanying materials are made available under the
* terms of the Apache License, Version 2.0 which is available at
* https://www.apache.org/licenses/LICENSE-2.0
*
* SPDX-License-Identifier: Apache-2.0
*
* Contributors:
* Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation
*
*/

package org.eclipse.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";
wolf4ood marked this conversation as resolved.
Show resolved Hide resolved

@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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
@@ -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<Policy, PolicyContext, Boolean> {

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;
}
}
}
Original file line number Diff line number Diff line change
@@ -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<ScopeExtractor> extractors = new ArrayList<>();

@Override
public void registerScopeExtractor(ScopeExtractor extractor) {
extractors.add(extractor);
}

@Override
public Result<Set<String>> 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);
}

}
Original file line number Diff line number Diff line change
@@ -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<Set<String>>, Rule.Visitor<Set<String>>, Constraint.Visitor<Set<String>>, Expression.Visitor<Object> {

private final List<ScopeExtractor> mappers;
private final PolicyContext policyContext;

public IatpScopeExtractorVisitor(List<ScopeExtractor> mappers, PolicyContext policyContext) {
this.mappers = mappers;
this.policyContext = policyContext;
}

@Override
public Set<String> visitAndConstraint(AndConstraint andConstraint) {
return visitMultiplicityConstraint(andConstraint);
}

@Override
public Set<String> visitOrConstraint(OrConstraint orConstraint) {
return visitMultiplicityConstraint(orConstraint);
}

@Override
public Set<String> visitXoneConstraint(XoneConstraint constraint) {
return visitMultiplicityConstraint(constraint);
}

@Override
public Set<String> 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<String> visitPolicy(Policy policy) {
var scopes = new HashSet<String>();
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<String> 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<String> visitProhibition(Prohibition policy) {
return visitRule(policy);
}

@Override
public Set<String> visitDuty(Duty policy) {
return visitRule(policy);
}

private Set<String> visitRule(Rule rule) {
return rule.getConstraints().stream()
.map(constraint -> constraint.accept(this))
.flatMap(Collection::stream)
.collect(Collectors.toSet());
}

private Set<String> visitMultiplicityConstraint(MultiplicityConstraint multiplicityConstraint) {
return multiplicityConstraint.getConstraints().stream()
.map(constraint -> constraint.accept(this))
.flatMap(Collection::stream)
.collect(Collectors.toSet());
}
}
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
}
}
Loading
Loading