Skip to content

Commit

Permalink
feat: improve bpn validation (#687)
Browse files Browse the repository at this point in the history
* feat: improve BPN validation

* add SQL persistence

* add base test class, inmem tests

* revert build

* deprecation

* documentation, cleanup

* cleanup

* added migration

* renamed extension

* pr remarks

* add D-R

* md lint
  • Loading branch information
paullatzelsperger authored Aug 3, 2023
1 parent cd2466e commit d8b54dd
Show file tree
Hide file tree
Showing 29 changed files with 1,292 additions and 8 deletions.
4 changes: 4 additions & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,10 @@ allprojects {
configure<org.eclipse.edc.plugins.autodoc.AutodocExtension> {
processorVersion.set(annotationProcessorVersion)
outputDirectory.set(project.buildDir)
// uncomment the following lines to enable the Autodoc-2-Markdown converter
// only available with EDC 0.2.1 SNAPSHOT
// additionalInputDirectory.set(downloadDir.asFile)
// downloadDirectory.set(downloadDir.asFile)
}

configure<org.eclipse.edc.plugins.edcbuild.extensions.BuildExtension> {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# Removal of manually curated CHANGELOG.md

## Decision

The BPN validation extension will be improved in the following aspects:

1. Instead of hard-coding them on policies, BPNs are stored in a database (in-mem + Postgres)
2. BPNs are grouped to enable stable policies
3. More `Operator`s are supported
4. Database entries can be manipulated using a REST API

## Rationale

Hard-coding BPNs on policies is quite inflexible and does not scale, because when a new business partner joins or leaves
the network, all participants would have to update all their policies, which is a significant migration effort. Instead,
a structure has to be defined where that situation can be handled in a less intrusive and involved way. This effectively
will remove the need to update/migrate policies.

## Approach

Every BPN is associated with one or more groups, for example `BPN0000001` -> `["gold_member"]`. It is important to note,
that these groups are _internal_ tags that every participant maintains on their own, they are not claims in
VerifiableCredentials (the BPN would be a claim, however). A new policy constraint is introduced, that looks like this:

```json
{
"constraint": {
"leftOperand": "https://w3id.org/tractusx/v0.0.1/ns/BusinessPartnerGroup",
"operator": "isAnyOf",
"rightOperand": [
"gold_customer",
"platin_partner"
]
}
}
```

NB: the `leftOperand` must be an IRI as mandated by ODRL, thus it must either be prefixed with the namespace (as shown
in the example), or using a vocabulary entry in the JSON-LD context, i.e. `tx:BusinessPartnerGroup`. Supported operators
will be: `eq, neq, in, isAllOf, isAnyOf, isNoneOf`.

Manipulating the BPN -> group associations can be done through a REST API.
36 changes: 36 additions & 0 deletions edc-extensions/bpn-validation/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/*
* Copyright (c) 2021,2022 Contributors to the Eclipse Foundation
*
* 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`
`maven-publish`
`java-test-fixtures`
}

dependencies {
implementation(project(":spi:core-spi"))
api(libs.edc.spi.core)
implementation(libs.edc.spi.policy)
implementation(libs.edc.spi.contract)
implementation(libs.edc.spi.policyengine)

testFixturesImplementation(libs.edc.junit)
testFixturesImplementation(libs.junit.jupiter.api)
testFixturesImplementation(libs.assertj)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
/*
* 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.tractusx.edc.validation.businesspartner;

import org.eclipse.edc.policy.engine.spi.PolicyEngine;
import org.eclipse.edc.policy.engine.spi.RuleBindingRegistry;
import org.eclipse.edc.policy.model.Permission;
import org.eclipse.edc.runtime.metamodel.annotation.Extension;
import org.eclipse.edc.runtime.metamodel.annotation.Inject;
import org.eclipse.edc.spi.system.ServiceExtension;
import org.eclipse.edc.spi.system.ServiceExtensionContext;
import org.eclipse.tractusx.edc.validation.businesspartner.functions.BusinessPartnerGroupFunction;
import org.eclipse.tractusx.edc.validation.businesspartner.spi.BusinessPartnerGroupStore;

import static org.eclipse.edc.connector.contract.spi.offer.ContractDefinitionResolver.CATALOGING_SCOPE;
import static org.eclipse.edc.connector.contract.spi.validation.ContractValidationService.NEGOTIATION_SCOPE;
import static org.eclipse.edc.connector.contract.spi.validation.ContractValidationService.TRANSFER_SCOPE;

/**
* Registers a {@link org.eclipse.tractusx.edc.validation.businesspartner.functions.BusinessPartnerGroupFunction} for the following scopes:
* <ul>
* <li>{@link org.eclipse.edc.connector.contract.spi.offer.ContractDefinitionResolver#CATALOGING_SCOPE}</li>
* <li>{@link org.eclipse.edc.connector.contract.spi.validation.ContractValidationService#NEGOTIATION_SCOPE}</li>
* <li>{@link org.eclipse.edc.connector.contract.spi.validation.ContractValidationService#TRANSFER_SCOPE}</li>
* </ul>
* The rule to which the function is bound is {@link BusinessPartnerGroupFunction#BUSINESS_PARTNER_CONSTRAINT_KEY}. That means, that policies that are bound to these scopes look
* like this:
* <pre>
* {
* "constraint": {
* "leftOperand": "https://w3id.org/tractusx/v0.0.1/ns/BusinessPartnerGroup",
* "operator": "isAnyOf",
* "rightOperand": ["gold_customer","platin_partner"]
* }
* }
* </pre>
* <p>
* Note that the {@link BusinessPartnerGroupFunction} is an {@link org.eclipse.edc.policy.engine.spi.AtomicConstraintFunction}, thus it is registered with the {@link PolicyEngine} for the {@link Permission} class.
*/
@Extension(value = "Registers a function to evaluate whether a BPN number is covered by a certain policy or not", categories = {"policy", "contract"})
public class BusinessPartnerEvaluationExtension implements ServiceExtension {

private static final String USE = "USE";
@Inject
private RuleBindingRegistry ruleBindingRegistry;
@Inject
private PolicyEngine policyEngine;
@Inject
private BusinessPartnerGroupStore store;

@Override
public void initialize(ServiceExtensionContext context) {
var function = new BusinessPartnerGroupFunction(store);

bindToScope(function, TRANSFER_SCOPE);
bindToScope(function, NEGOTIATION_SCOPE);
bindToScope(function, CATALOGING_SCOPE);
}

private void bindToScope(BusinessPartnerGroupFunction function, String scope) {
ruleBindingRegistry.bind(USE, scope);
ruleBindingRegistry.bind(BusinessPartnerGroupFunction.BUSINESS_PARTNER_CONSTRAINT_KEY, scope);

policyEngine.registerFunction(scope, Permission.class, BusinessPartnerGroupFunction.BUSINESS_PARTNER_CONSTRAINT_KEY, function);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/*
* 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.tractusx.edc.validation.businesspartner.defaults;

import org.eclipse.edc.runtime.metamodel.annotation.Extension;
import org.eclipse.edc.runtime.metamodel.annotation.Provider;
import org.eclipse.edc.spi.system.ServiceExtension;
import org.eclipse.tractusx.edc.validation.businesspartner.spi.BusinessPartnerGroupStore;

@Extension("Provides a default BusinessPartnerGroupStore")
public class DefaultStoreProviderExtension implements ServiceExtension {

@Provider(isDefault = true)
public BusinessPartnerGroupStore createInMemStore() {
return new InMemoryBusinessPartnerGroupStore();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
/*
* 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.tractusx.edc.validation.businesspartner.defaults;

import org.eclipse.edc.spi.result.StoreResult;
import org.eclipse.tractusx.edc.validation.businesspartner.spi.BusinessPartnerGroupStore;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class InMemoryBusinessPartnerGroupStore implements BusinessPartnerGroupStore {
private final Map<String, List<String>> cache = new HashMap<>();

@Override
public StoreResult<List<String>> resolveForBpn(String businessPartnerNumber) {
var entry = cache.get(businessPartnerNumber);
return entry == null ?
StoreResult.notFound(NOT_FOUND_TEMPLATE.formatted(businessPartnerNumber)) :
StoreResult.success(entry);
}

@Override
public StoreResult<Void> save(String businessPartnerNumber, List<String> groups) {
//to maintain behavioural consistency with the SQL store
if (cache.containsKey(businessPartnerNumber)) {
return StoreResult.alreadyExists(ALREADY_EXISTS_TEMPLATE.formatted(businessPartnerNumber));
}
cache.put(businessPartnerNumber, groups);
return StoreResult.success();
}

@Override
public StoreResult<Void> delete(String businessPartnerNumber) {

return cache.remove(businessPartnerNumber) == null ?
StoreResult.notFound(NOT_FOUND_TEMPLATE.formatted(businessPartnerNumber)) :
StoreResult.success();
}

@Override
public StoreResult<Void> update(String businessPartnerNumber, List<String> groups) {
if (cache.containsKey(businessPartnerNumber)) {
cache.put(businessPartnerNumber, groups);
return StoreResult.success();
}
return StoreResult.notFound(NOT_FOUND_TEMPLATE.formatted(businessPartnerNumber));
}
}
Loading

0 comments on commit d8b54dd

Please sign in to comment.