Skip to content

Commit

Permalink
feat: policy scope extractors for contract negotiation ingress calls (e…
Browse files Browse the repository at this point in the history
…clipse-edc#3834)

* refactor: notify* messages

* refactor: notifyRequested message refactor

* pr suggestions
  • Loading branch information
wolf4ood authored Feb 8, 2024
1 parent 21ab10d commit 8569352
Show file tree
Hide file tree
Showing 13 changed files with 881 additions and 428 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@
import org.eclipse.edc.connector.contract.spi.negotiation.ProviderContractNegotiationManager;
import org.eclipse.edc.connector.contract.spi.negotiation.observe.ContractNegotiationObservable;
import org.eclipse.edc.connector.contract.spi.negotiation.store.ContractNegotiationStore;
import org.eclipse.edc.connector.contract.spi.offer.ContractDefinitionResolver;
import org.eclipse.edc.connector.contract.spi.types.negotiation.ContractNegotiation;
import org.eclipse.edc.connector.contract.spi.validation.ContractValidationService;
import org.eclipse.edc.connector.contract.validation.ContractValidationServiceImpl;
Expand Down Expand Up @@ -140,9 +139,6 @@ public class ContractCoreExtension implements ServiceExtension {
@Inject
private ProtocolWebhook protocolWebhook;

@Inject
private ContractDefinitionResolver contractDefinitionResolver;

@Inject
private ContractNegotiationObservable observable;

Expand Down Expand Up @@ -184,7 +180,7 @@ private void registerServices(ServiceExtensionContext context) {
var participantId = context.getParticipantId();

var policyEquality = new PolicyEquality(typeManager);
var validationService = new ContractValidationServiceImpl(agentService, contractDefinitionResolver, assetIndex, policyStore, policyEngine, policyEquality);
var validationService = new ContractValidationServiceImpl(agentService, assetIndex, policyEngine, policyEquality);
context.registerService(ContractValidationService.class, validationService);

// bind/register rule to evaluate contract expiry
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,13 @@
package org.eclipse.edc.connector.contract;

import org.eclipse.edc.connector.contract.observe.ContractNegotiationObservableImpl;
import org.eclipse.edc.connector.contract.offer.ConsumerOfferResolverImpl;
import org.eclipse.edc.connector.contract.offer.ContractDefinitionResolverImpl;
import org.eclipse.edc.connector.contract.policy.PolicyArchiveImpl;
import org.eclipse.edc.connector.contract.spi.negotiation.ContractNegotiationPendingGuard;
import org.eclipse.edc.connector.contract.spi.negotiation.observe.ContractNegotiationObservable;
import org.eclipse.edc.connector.contract.spi.negotiation.store.ContractNegotiationStore;
import org.eclipse.edc.connector.contract.spi.offer.ConsumerOfferResolver;
import org.eclipse.edc.connector.contract.spi.offer.ContractDefinitionResolver;
import org.eclipse.edc.connector.contract.spi.offer.store.ContractDefinitionStore;
import org.eclipse.edc.connector.policy.spi.store.PolicyArchive;
Expand Down Expand Up @@ -56,6 +58,11 @@ public ContractDefinitionResolver contractDefinitionResolver(ServiceExtensionCon
return new ContractDefinitionResolverImpl(context.getMonitor(), contractDefinitionStore, policyEngine, policyStore);
}

@Provider
public ConsumerOfferResolver consumerOfferResolver() {
return new ConsumerOfferResolverImpl(contractDefinitionStore, policyStore);
}

@Provider
public ContractNegotiationObservable observable() {
return new ContractNegotiationObservableImpl();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
/*
* Copyright (c) 2024 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.connector.contract.offer;

import org.eclipse.edc.connector.contract.spi.ContractOfferId;
import org.eclipse.edc.connector.contract.spi.offer.ConsumerOfferResolver;
import org.eclipse.edc.connector.contract.spi.offer.store.ContractDefinitionStore;
import org.eclipse.edc.connector.contract.spi.validation.ValidatableConsumerOffer;
import org.eclipse.edc.connector.policy.spi.store.PolicyDefinitionStore;
import org.eclipse.edc.spi.result.ServiceResult;
import org.jetbrains.annotations.NotNull;

import static java.lang.String.format;

public class ConsumerOfferResolverImpl implements ConsumerOfferResolver {

private final ContractDefinitionStore contractDefinitionStore;
private final PolicyDefinitionStore policyDefinitionStore;


public ConsumerOfferResolverImpl(ContractDefinitionStore contractDefinitionStore, PolicyDefinitionStore policyDefinitionStore) {
this.contractDefinitionStore = contractDefinitionStore;
this.policyDefinitionStore = policyDefinitionStore;
}

@Override
public @NotNull ServiceResult<ValidatableConsumerOffer> resolveOffer(String offerId) {
var parsedResult = ContractOfferId.parseId(offerId);

if (parsedResult.failed()) {
return ServiceResult.badRequest(parsedResult.getFailureMessages());
} else {
var definitionId = parsedResult.getContent().definitionPart();
var contractDefinition = contractDefinitionStore.findById(definitionId);
if (contractDefinition == null) {
return ServiceResult.notFound(format("Contract definition with id %s not found", definitionId));
}

var accessPolicy = policyDefinitionStore.findById(contractDefinition.getAccessPolicyId());
if (accessPolicy == null) {
return ServiceResult.notFound(format("Policy with id %s not found", contractDefinition.getAccessPolicyId()));
}

var contractPolicy = policyDefinitionStore.findById(contractDefinition.getContractPolicyId());
if (contractPolicy == null) {
return ServiceResult.notFound(format("Policy with id %s not found", contractDefinition.getContractPolicyId()));
}

return ServiceResult.success(ValidatableConsumerOffer.Builder.newInstance()
.contractDefinition(contractDefinition)
.accessPolicy(accessPolicy.getPolicy())
.contractPolicy(contractPolicy.getPolicy())
.offerId(parsedResult.getContent())
.build());

}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,10 @@

import org.eclipse.edc.connector.contract.policy.PolicyEquality;
import org.eclipse.edc.connector.contract.spi.ContractOfferId;
import org.eclipse.edc.connector.contract.spi.offer.ContractDefinitionResolver;
import org.eclipse.edc.connector.contract.spi.types.negotiation.ContractNegotiation;
import org.eclipse.edc.connector.contract.spi.validation.ContractValidationService;
import org.eclipse.edc.connector.contract.spi.validation.ValidatableConsumerOffer;
import org.eclipse.edc.connector.contract.spi.validation.ValidatedConsumerOffer;
import org.eclipse.edc.connector.policy.spi.store.PolicyDefinitionStore;
import org.eclipse.edc.policy.engine.spi.PolicyContextImpl;
import org.eclipse.edc.policy.engine.spi.PolicyEngine;
import org.eclipse.edc.policy.model.Policy;
Expand All @@ -43,6 +42,7 @@
import java.util.Optional;

import static java.lang.String.format;
import static org.eclipse.edc.connector.contract.spi.offer.ContractDefinitionResolver.CATALOGING_SCOPE;
import static org.eclipse.edc.spi.result.Result.failure;
import static org.eclipse.edc.spi.result.Result.success;

Expand All @@ -52,45 +52,28 @@
public class ContractValidationServiceImpl implements ContractValidationService {

private final ParticipantAgentService agentService;
private final ContractDefinitionResolver contractDefinitionResolver;
private final AssetIndex assetIndex;
private final PolicyDefinitionStore policyStore;
private final PolicyEngine policyEngine;
private final PolicyEquality policyEquality;

public ContractValidationServiceImpl(ParticipantAgentService agentService,
ContractDefinitionResolver contractDefinitionResolver,
AssetIndex assetIndex,
PolicyDefinitionStore policyStore,
PolicyEngine policyEngine,
PolicyEquality policyEquality) {
this.agentService = agentService;
this.contractDefinitionResolver = contractDefinitionResolver;
this.assetIndex = assetIndex;
this.policyStore = policyStore;
this.policyEngine = policyEngine;
this.policyEquality = policyEquality;
}

@Override
@NotNull
public Result<ValidatedConsumerOffer> validateInitialOffer(ClaimToken token, ContractOffer offer) {
return validateInitialOffer(token, offer.getId());
}

@Override
public @NotNull Result<ValidatedConsumerOffer> validateInitialOffer(ClaimToken token, String offerId) {
return ContractOfferId.parseId(offerId)
.compose(contractId -> {
var agent = agentService.createFor(token);

return validateInitialOffer(contractId, agent)
.map(sanitizedPolicy -> {
var offer = createContractOffer(sanitizedPolicy, contractId);
return new ValidatedConsumerOffer(agent.getIdentity(), offer);
});
public @NotNull Result<ValidatedConsumerOffer> validateInitialOffer(ClaimToken token, ValidatableConsumerOffer consumerOffer) {
var agent = agentService.createFor(token);
return validateInitialOffer(consumerOffer, agent)
.map(sanitizedPolicy -> {
var offer = createContractOffer(sanitizedPolicy, consumerOffer.getOfferId());
return new ValidatedConsumerOffer(agent.getIdentity(), offer);
});

}

@Override
Expand Down Expand Up @@ -154,40 +137,40 @@ public Result<Void> validateConfirmed(ClaimToken token, ContractAgreement agreem
* Validates an initial contract offer, ensuring that the referenced asset exists, is selected by the corresponding policy definition and the agent fulfills the contract policy.
* A sanitized policy definition is returned to avoid clients injecting manipulated policies.
*/
private Result<Policy> validateInitialOffer(ContractOfferId contractOfferId, ParticipantAgent agent) {
private Result<Policy> validateInitialOffer(ValidatableConsumerOffer consumerOffer, ParticipantAgent agent) {
var consumerIdentity = agent.getIdentity();
if (consumerIdentity == null) {
return failure("Invalid consumer identity");
}

var contractDefinition = contractDefinitionResolver.definitionFor(agent, contractOfferId.definitionPart());
if (contractDefinition == null) {
return failure("The ContractDefinition with id %s either does not exist or the access to it is not granted.");
var accessPolicyResult = evaluatePolicy(consumerOffer.getAccessPolicy(), CATALOGING_SCOPE, agent, consumerOffer.getOfferId());

if (accessPolicyResult.failed()) {
return accessPolicyResult;
}

// verify the target asset exists
var targetAsset = assetIndex.findById(contractOfferId.assetIdPart());
var targetAsset = assetIndex.findById(consumerOffer.getOfferId().assetIdPart());
if (targetAsset == null) {
return failure("Invalid target: " + contractOfferId.assetIdPart());
return failure("Invalid target: " + consumerOffer.getOfferId().assetIdPart());
}

// verify that the asset in the offer is actually in the contract definition
var testCriteria = new ArrayList<>(contractDefinition.getAssetsSelector());
testCriteria.add(new Criterion(Asset.PROPERTY_ID, "=", contractOfferId.assetIdPart()));
var testCriteria = new ArrayList<>(consumerOffer.getContractDefinition().getAssetsSelector());
testCriteria.add(new Criterion(Asset.PROPERTY_ID, "=", consumerOffer.getOfferId().assetIdPart()));
if (assetIndex.countAssets(testCriteria) <= 0) {
return failure("Asset ID from the ContractOffer is not included in the ContractDefinition");
}

var policyDefinition = policyStore.findById(contractDefinition.getContractPolicyId());
if (policyDefinition == null) {
return failure(format("Policy %s not found", contractDefinition.getContractPolicyId()));
}
var contractPolicy = consumerOffer.getContractPolicy().withTarget(consumerOffer.getOfferId().assetIdPart());
return evaluatePolicy(contractPolicy, NEGOTIATION_SCOPE, agent, consumerOffer.getOfferId());
}

var policy = policyDefinition.getPolicy().withTarget(contractOfferId.assetIdPart());
private Result<Policy> evaluatePolicy(Policy policy, String scope, ParticipantAgent agent, ContractOfferId offerId) {
var policyContext = PolicyContextImpl.Builder.newInstance().additional(ParticipantAgent.class, agent).build();
var policyResult = policyEngine.evaluate(NEGOTIATION_SCOPE, policy, policyContext);
var policyResult = policyEngine.evaluate(scope, policy, policyContext);
if (policyResult.failed()) {
return failure(format("Policy %s not fulfilled", policyDefinition.getUid()));
return failure(format("Policy in scope %s not fulfilled for offer %s", scope, offerId.toString()));
}
return Result.success(policy);
}
Expand Down
Loading

0 comments on commit 8569352

Please sign in to comment.