Skip to content

Commit

Permalink
Merge pull request #829 from dsmf/chore/542-default-policy-config-imp…
Browse files Browse the repository at this point in the history
…roved

Chore/542 default policy config improved
  • Loading branch information
ds-jhartmann authored Jul 19, 2024
2 parents 493f8eb + 2e4dc25 commit 04ace25
Show file tree
Hide file tree
Showing 13 changed files with 252 additions and 100 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ _**For better traceability add the corresponding GitHub issue number in each cha

## [Unreleased]

### Changed
- Default policies are now configured using JSON in accordance with the ODRL schema. #542

## [5.3.0] - 2024-07-15

### Added
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -118,12 +118,7 @@ data:
submodel-suffix: {{ tpl (.Values.edc.submodel.suffix | default "/$value") . | quote }}
catalog:
acceptedPolicies:
{{- range .Values.edc.catalog.acceptedPolicies}}
- leftOperand: {{ .leftOperand | quote }}
operator: {{ .operator | quote }}
rightOperand: {{ .rightOperand | quote }}
{{- end }}
acceptedPolicies: {{ .Values.edc.catalog.acceptedPolicies | trim | b64enc | quote }}
discoveryFinderClient:
cacheTTL: {{ .Values.edc.discoveryFinderClient.cacheTTL | quote }}
connectorEndpointService:
Expand Down
43 changes: 34 additions & 9 deletions charts/item-relationship-service/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -187,15 +187,40 @@ edc:
urnprefix: /urn
suffix: /$value
catalog:
# IRS will only negotiate contracts for offers with a policy as defined in the allowedNames list.
# If a requested asset does not provide one of these policies, a tombstone will be created and this node will not be processed.
acceptedPolicies:
- leftOperand: "https://w3id.org/catenax/policy/FrameworkAgreement"
operator: "eq"
rightOperand: "traceability:1.0"
- leftOperand: "https://w3id.org/catenax/policy/UsagePurpose"
operator: "eq"
rightOperand: "cx.core.industrycore:1"
# IRS will only negotiate contracts for offers with a policy as defined in the Policy Store.
# The following configuration value allows the definition of default policies to be used
# if no policy has been defined via the Policy Store API.
# If the policy check fails, a tombstone will be created and this node will not be processed.
# Configure the default policies as JSON array using multiline string here.
acceptedPolicies: >
[{
"policyId": "default-policy",
"createdOn": "2024-07-17T16:15:14.12345678Z",
"validUntil": "9999-01-01T00:00:00.00000000Z",
"permissions": [
{
"action": "use",
"constraint": {
"and": [
{
"leftOperand": "https://w3id.org/catenax/policy/FrameworkAgreement",
"operator": {
"@id": "eq"
},
"rightOperand": "traceability:1.0"
},
{
"leftOperand": "https://w3id.org/catenax/policy/UsagePurpose",
"operator": {
"@id": "eq"
},
"rightOperand": "cx.core.industrycore:1"
}
]
}
}
]
}]
discoveryFinderClient:
cacheTTL: PT24H # Time to live for DiscoveryFinderClient for findDiscoveryEndpoints method cache
connectorEndpointService:
Expand Down
2 changes: 1 addition & 1 deletion docs/src/docs/administration/configuration.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ include::irs-spring-config.adoc[leveloffset=+1]
[source,yaml]
----
include::../../../../charts/item-relationship-service/values.yaml[lines=104..302]
include::../../../../charts/item-relationship-service/values.yaml[lines=104..338]
----
<1> Use this to enable or disable the monitoring components
Expand Down
15 changes: 6 additions & 9 deletions irs-api/src/main/resources/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -154,15 +154,12 @@ irs-edc-client:
connect: PT90S # HTTP connect timeout for the submodel client

catalog:
# IRS will only negotiate contracts for offers with a policy as defined in the acceptedPolicies list.
# If a requested asset does not provide one of these policies, a tombstone will be created and this node will not be processed.
acceptedPolicies:
- leftOperand: "cx-policy:FrameworkAgreement"
operator: "eq"
rightOperand: "traceability:1.0"
- leftOperand: "cx-policy:UsagePurpose"
operator: "eq"
rightOperand: "cx.core.industrycore:1"
# IRS will only negotiate contracts for offers with a policy as defined in the Policy Store.
# The following configuration value allows the definition of default policies to be used
# if no policy has been defined via the Policy Store API.
# If the policy check fails, a tombstone will be created and this node will not be processed.
# The value must be Base64 encoded here. See decoded value in charts/item-relationship-service/values.yaml.
acceptedPolicies: "W3sKICAgICJwb2xpY3lJZCI6ICJkZWZhdWx0LXBvbGljeSIsCiAgICAiY3JlYXRlZE9uIjogIjIwMjQtMDctMTdUMTY6MTU6MTQuMTIzNDU2NzhaIiwKICAgICJ2YWxpZFVudGlsIjogIjk5OTktMDEtMDFUMDA6MDA6MDAuMDAwMDAwMDBaIiwKICAgICJwZXJtaXNzaW9ucyI6IFsKICAgICAgICB7CiAgICAgICAgICAgICJhY3Rpb24iOiAidXNlIiwKICAgICAgICAgICAgImNvbnN0cmFpbnQiOiB7CiAgICAgICAgICAgICAgICAiYW5kIjogWwogICAgICAgICAgICAgICAgICAgIHsKICAgICAgICAgICAgICAgICAgICAgICAgImxlZnRPcGVyYW5kIjogImh0dHBzOi8vdzNpZC5vcmcvY2F0ZW5heC9wb2xpY3kvRnJhbWV3b3JrQWdyZWVtZW50IiwKICAgICAgICAgICAgICAgICAgICAgICAgIm9wZXJhdG9yIjogewogICAgICAgICAgICAgICAgICAgICAgICAgICAgIkBpZCI6ICJlcSIKICAgICAgICAgICAgICAgICAgICAgICAgfSwKICAgICAgICAgICAgICAgICAgICAgICAgInJpZ2h0T3BlcmFuZCI6ICJ0cmFjZWFiaWxpdHk6MS4wIgogICAgICAgICAgICAgICAgICAgIH0sCiAgICAgICAgICAgICAgICAgICAgewogICAgICAgICAgICAgICAgICAgICAgICAibGVmdE9wZXJhbmQiOiAiaHR0cHM6Ly93M2lkLm9yZy9jYXRlbmF4L3BvbGljeS9Vc2FnZVB1cnBvc2UiLAogICAgICAgICAgICAgICAgICAgICAgICAib3BlcmF0b3IiOiB7CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAiQGlkIjogImVxIgogICAgICAgICAgICAgICAgICAgICAgICB9LAogICAgICAgICAgICAgICAgICAgICAgICAicmlnaHRPcGVyYW5kIjogImN4LmNvcmUuaW5kdXN0cnljb3JlOjEiCiAgICAgICAgICAgICAgICAgICAgfQogICAgICAgICAgICAgICAgXQogICAgICAgICAgICB9CiAgICAgICAgfQogICAgXQp9XQ=="
discoveryFinderClient:
cacheTTL: PT24H # Time to live for DiscoveryFinderClient for findDiscoveryEndpoints method cache
connectorEndpointService:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -337,8 +337,7 @@ void shouldCreateDetailedTombstoneForEdcErrors() {
assertThat(jobForJobId.getTombstones()).hasSize(1);
final Tombstone actualTombstone = jobForJobId.getTombstones().get(0);
assertThat(actualTombstone.getProcessingError().getRootCauses()).hasSize(1);
assertThat(actualTombstone.getProcessingError().getRootCauses().get(0)).contains(
"502 Bad Gateway");
assertThat(actualTombstone.getProcessingError().getRootCauses().get(0)).contains("502 Bad Gateway");
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,22 @@
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;

import java.nio.charset.StandardCharsets;
import java.time.OffsetDateTime;
import java.util.Base64;
import java.util.List;

import com.fasterxml.jackson.core.type.TypeReference;
import org.eclipse.tractusx.irs.component.Bpn;
import org.eclipse.tractusx.irs.component.Description;
import org.eclipse.tractusx.irs.data.JsonParseException;
import org.eclipse.tractusx.irs.data.StringMapper;
import org.eclipse.tractusx.irs.edc.client.policy.Constraint;
import org.eclipse.tractusx.irs.edc.client.policy.Constraints;
import org.eclipse.tractusx.irs.edc.client.policy.OperatorType;
import org.eclipse.tractusx.irs.edc.client.policy.Permission;
import org.eclipse.tractusx.irs.edc.client.policy.Policy;
import org.eclipse.tractusx.irs.edc.client.policy.PolicyType;
import org.junit.jupiter.api.Test;

class StringMapperTest {
Expand All @@ -44,8 +56,7 @@ void mapToString() {
@Test
void shouldThrowParseExceptionWhenMappingToString() {
final var object = new Object();
assertThatThrownBy(() -> StringMapper.mapToString(object)).isInstanceOf(
JsonParseException.class);
assertThatThrownBy(() -> StringMapper.mapToString(object)).isInstanceOf(JsonParseException.class);
}

@Test
Expand All @@ -61,4 +72,84 @@ void shouldThrowParseExceptionWhenMappingFromString() {
assertThatThrownBy(() -> StringMapper.mapFromString("test", Description.class)).isInstanceOf(
JsonParseException.class);
}

@Test
void shouldMapFromBase64StringUsingTypeReference() {

// ARRANGE
final TypeReference<List<Policy>> listOfPoliciesType = new TypeReference<>() {
};

final String originalJsonStr = """
[{
"policyId": "default-trace-policy",
"createdOn": "2024-07-17T16:15:14.12345678Z",
"validUntil": "9999-01-01T00:00:00.00000000Z",
"permissions": [
{
"action": "use",
"constraint": {
"and": [
{
"leftOperand": "https://w3id.org/catenax/policy/FrameworkAgreement",
"operator": {
"@id": "eq"
},
"rightOperand": "traceability:1.0"
},
{
"leftOperand": "https://w3id.org/catenax/policy/UsagePurpose",
"operator": {
"@id": "eq"
},
"rightOperand": "cx.core.industrycore:1"
}
]
}
}
]
}]
""";
final String originalJsonBase64 = new String(
Base64.getEncoder().encode(originalJsonStr.getBytes(StandardCharsets.UTF_8)));

// ACT
// convert back andConstraints forth to facilitate comparison
final List<Policy> listOfPolicies = StringMapper.mapFromBase64String(originalJsonBase64, listOfPoliciesType);
final String backToString = StringMapper.mapToString(listOfPolicies);
final List<Policy> backToObj = StringMapper.mapFromString(backToString, listOfPoliciesType);

// ASSERT
{
assertThat(listOfPolicies).hasSize(1);
assertThat(backToObj).hasSize(1);
assertThat(backToObj).usingRecursiveComparison().isEqualTo(listOfPolicies);

final Policy policy = listOfPolicies.get(0);
assertThat(policy.getPolicyId()).isEqualTo("default-trace-policy");
assertThat(policy.getValidUntil()).isEqualTo(OffsetDateTime.parse("9999-01-01T00:00:00.00000000Z"));
assertThat(policy.getCreatedOn()).isEqualTo(OffsetDateTime.parse("2024-07-17T16:15:14.12345678Z"));
assertThat(policy.getPermissions()).hasSize(1);

final Permission permission = policy.getPermissions().get(0);
assertThat(permission.getAction()).isEqualTo(PolicyType.USE);

final Constraints constraints = permission.getConstraint();
final List<Constraint> andConstraints = constraints.getAnd();
assertThat(andConstraints).hasSize(2);
{
final Constraint constraint = andConstraints.get(0);
assertThat(constraint.getLeftOperand()).isEqualTo("https://w3id.org/catenax/policy/FrameworkAgreement");
assertThat(constraint.getOperator().getOperatorType()).isEqualTo(OperatorType.EQ);
assertThat(constraint.getRightOperand()).isEqualTo("traceability:1.0");
}
{
final Constraint constraint = andConstraints.get(1);
assertThat(constraint.getLeftOperand()).isEqualTo("https://w3id.org/catenax/policy/UsagePurpose");
assertThat(constraint.getOperator().getOperatorType()).isEqualTo(OperatorType.EQ);
assertThat(constraint.getRightOperand()).isEqualTo("cx.core.industrycore:1");
}
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,13 @@
********************************************************************************/
package org.eclipse.tractusx.irs.data;

import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import java.util.Objects;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
Expand All @@ -39,6 +45,9 @@
@Slf4j
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class StringMapper {

private static final Charset CHARSET = StandardCharsets.UTF_8;

private static final ObjectMapper MAPPER = new ObjectMapper();

static {
Expand All @@ -56,6 +65,13 @@ public static String mapToString(final Object value) {
}
}

public static <T> T mapFromBase64String(final String value, final TypeReference<T> typeReference) {
if (value == null) {
return null;
}
return mapFromString(fromBase64(value), typeReference);
}

public static <T> T mapFromString(final String value, final Class<T> clazz) {
try {
return MAPPER.readValue(value, clazz);
Expand All @@ -64,4 +80,22 @@ public static <T> T mapFromString(final String value, final Class<T> clazz) {
}
}

public static <T> T mapFromString(final String value, final TypeReference<T> typeReference) {
try {
return MAPPER.readValue(value, typeReference);
} catch (final JsonProcessingException e) {
throw new JsonParseException(e);
}
}

public static String toBase64(final String str) {
Objects.requireNonNull(str);
return new String(Base64.getEncoder().encode(str.trim().getBytes(CHARSET)));
}

public static String fromBase64(final String value) {
Objects.requireNonNull(value);
return new String(Base64.getDecoder().decode(value.trim()), CHARSET);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,6 @@
********************************************************************************/
package org.eclipse.tractusx.irs.policystore.config;

import java.util.List;

import lombok.AllArgsConstructor;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
Expand All @@ -37,7 +35,11 @@
@ConfigurationProperties(prefix = "irs-edc-client.catalog")
@Data
public class DefaultAcceptedPoliciesConfig {
private List<AcceptedPolicy> acceptedPolicies;

/**
* Accepted policies as a Base64 encoded string.
*/
private String acceptedPolicies;

/**
* Accepted Policy for
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@
import java.time.Clock;
import java.time.OffsetDateTime;
import java.util.AbstractMap;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.LinkedList;
import java.util.List;
Expand All @@ -38,18 +37,14 @@
import java.util.stream.Collectors;
import java.util.stream.Stream;

import com.fasterxml.jackson.core.type.TypeReference;
import jakarta.json.JsonObject;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.eclipse.tractusx.irs.data.StringMapper;
import org.eclipse.tractusx.irs.edc.client.policy.AcceptedPoliciesProvider;
import org.eclipse.tractusx.irs.edc.client.policy.AcceptedPolicy;
import org.eclipse.tractusx.irs.edc.client.policy.Constraint;
import org.eclipse.tractusx.irs.edc.client.policy.Constraints;
import org.eclipse.tractusx.irs.edc.client.policy.Operator;
import org.eclipse.tractusx.irs.edc.client.policy.OperatorType;
import org.eclipse.tractusx.irs.edc.client.policy.Permission;
import org.eclipse.tractusx.irs.edc.client.policy.Policy;
import org.eclipse.tractusx.irs.edc.client.policy.PolicyType;
import org.eclipse.tractusx.irs.edc.client.transformer.EdcTransformer;
import org.eclipse.tractusx.irs.policystore.config.DefaultAcceptedPoliciesConfig;
import org.eclipse.tractusx.irs.policystore.exceptions.PolicyStoreException;
Expand All @@ -71,6 +66,9 @@
})
public class PolicyStoreService implements AcceptedPoliciesProvider {

private static final TypeReference<List<Policy>> LIST_OF_POLICIES_TYPE = new TypeReference<>() {
};

private final List<Policy> allowedPoliciesFromConfig;

private final PolicyPersistence persistence;
Expand Down Expand Up @@ -200,7 +198,8 @@ public void deletePolicy(final String policyId) {
throw new ResponseStatusException(HttpStatus.NOT_FOUND,
"Policy with id '%s' not found".formatted(policyId));
} else if (bpnsContainingPolicyId.stream().noneMatch(StringUtils::isNotEmpty)) {
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "A configured default policy cannot be deleted. "
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, //
"A configured default policy cannot be deleted. "
+ "It can be overridden by defining a default policy via the API instead.");
} else {
try {
Expand Down Expand Up @@ -297,26 +296,8 @@ private AcceptedPolicy toAcceptedPolicy(final Policy policy) {
return new AcceptedPolicy(policy, policy.getValidUntil());
}

private List<Policy> createDefaultPolicyFromConfig(
final DefaultAcceptedPoliciesConfig defaultAcceptedPoliciesConfig) {

final List<Constraint> constraints = new ArrayList<>();
defaultAcceptedPoliciesConfig.getAcceptedPolicies()
.forEach(acceptedPolicy -> constraints.add(
new Constraint(acceptedPolicy.getLeftOperand(),
new Operator(OperatorType.fromValue(acceptedPolicy.getOperator())),
acceptedPolicy.getRightOperand())));

final OffsetDateTime now = OffsetDateTime.now(clock);
return List.of(Policy.builder()
.policyId(ConfiguredDefaultPolicy.DEFAULT_POLICY_ID)
.createdOn(now)
.validUntil(now.plusYears(ConfiguredDefaultPolicy.DEFAULT_POLICY_LIFETIME_YEARS))
.permissions(List.of(Permission.builder()
.action(PolicyType.USE)
.constraint(new Constraints(constraints, constraints))
.build()))
.build());
private List<Policy> createDefaultPolicyFromConfig(final DefaultAcceptedPoliciesConfig defaultPoliciesConfig) {
return StringMapper.mapFromBase64String(defaultPoliciesConfig.getAcceptedPolicies(), LIST_OF_POLICIES_TYPE);
}

}
Loading

0 comments on commit 04ace25

Please sign in to comment.