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: improve bpn validation #687

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
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