Skip to content

Commit 3387dd6

Browse files
committed
add custom reimplementation of contract expiry policy validation to be used in negotiation scope
1 parent 018a34b commit 3387dd6

File tree

2 files changed

+147
-0
lines changed

2 files changed

+147
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
/*
2+
* Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG)
3+
*
4+
* This program and the accompanying materials are made available under the
5+
* terms of the Apache License, Version 2.0 which is available at
6+
* https://www.apache.org/licenses/LICENSE-2.0
7+
*
8+
* SPDX-License-Identifier: Apache-2.0
9+
*
10+
* Contributors:
11+
* Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation
12+
* Dataport AöR for the POSSIBLE project - Custom reimplementation for negotiation scope
13+
*
14+
*/
15+
16+
package org.eclipse.edc.extension.possiblepolicy;
17+
18+
import org.eclipse.edc.connector.core.policy.ContractExpiryCheckFunction;
19+
import org.eclipse.edc.policy.engine.spi.AtomicConstraintFunction;
20+
import org.eclipse.edc.policy.engine.spi.PolicyContext;
21+
import org.eclipse.edc.policy.model.Operator;
22+
import org.eclipse.edc.policy.model.Rule;
23+
import org.eclipse.edc.spi.monitor.Monitor;
24+
25+
import java.time.Instant;
26+
import java.time.format.DateTimeParseException;
27+
28+
import static java.lang.String.format;
29+
30+
/**
31+
* Custom reimplementation of the {@link ContractExpiryCheckFunction} from the Connector core to allow it to be used in the negotiation scope.
32+
* <p>
33+
* Constraint function that evaluates a time-based constraint. That is a constraint that either uses the "inForceDate" operand
34+
* with a fixed time (ISO-8061 UTC) as:
35+
* <pre>
36+
* {
37+
* "leftOperand": "edc:inForceDate",
38+
* "operator": "GEQ",
39+
* "rightOperand": "2024-01-01T00:00:01Z"
40+
* }
41+
* </pre>
42+
* Alternatively, it is possible to use a duration expression:
43+
* <pre>
44+
* {
45+
* "leftOperand": "edc:inForceDate",
46+
* "operator": "GEQ",
47+
* "rightOperand": "contractAgreement+365d"
48+
* }
49+
* </pre>
50+
* following the following schema: {@code <offset> + <numeric value>s|m|h|d} where {@code offset} must be equal to {@code "contractAgreement"}
51+
* (not case-sensitive) and refers to the signing date of the contract in Epoch seconds. Omitting the {@code offset} is not permitted.
52+
* The numeric value can have negative values.
53+
* Thus, the following examples would be valid:
54+
* <ul>
55+
* <li>contractAgreement+15s</li>
56+
* <li>contractAgreement+7d</li>
57+
* <li>contractAgreement+1h</li>
58+
* <li>contractAgreement+-5m (means "5 minutes before the signing of the contract")</li>
59+
* </ul>
60+
* Please note that all {@link Operator}s except {@link Operator#IN} are supported.
61+
*/
62+
public class NegotiationContractExpiryCheckFunction<R extends Rule> implements AtomicConstraintFunction<R> {
63+
64+
private final Monitor monitor;
65+
private static final String EXPRESSION_REGEX = "(contract[A,a]greement)\\+(-?[0-9]+)(s|m|h|d)";
66+
67+
public NegotiationContractExpiryCheckFunction(Monitor monitor) {
68+
69+
this.monitor = monitor;
70+
}
71+
72+
@Override
73+
public boolean evaluate(Operator operator, Object rightValue, R rule, PolicyContext context) {
74+
if (!(rightValue instanceof String)) {
75+
context.reportProblem("Right-value expected to be String but was " + rightValue.getClass());
76+
return false;
77+
}
78+
79+
try {
80+
Instant now = Instant.now(); // "now" is not in context during negotiation, use current time
81+
82+
var rightValueStr = (String) rightValue;
83+
var bound = asInstant(rightValueStr);
84+
if (bound != null) {
85+
monitor.info(format("Validating time constraint %s %s %s", now, operator, bound));
86+
return checkFixedPeriod(now, operator, bound);
87+
}
88+
89+
if (rightValueStr.matches(EXPRESSION_REGEX)) {
90+
monitor.info(format("Offset constraints are ignored during negotiation. %s %s %s",
91+
ContractExpiryCheckFunction.CONTRACT_EXPIRY_EVALUATION_KEY, operator, rightValueStr));
92+
return true;
93+
}
94+
95+
context.reportProblem(format("Unsupported right-value, expected either an ISO-8061 String or a expression matching '%s', but got '%s'",
96+
ContractExpiryCheckFunction.CONTRACT_EXPIRY_EVALUATION_KEY, rightValueStr));
97+
} catch (NullPointerException | DateTimeParseException ex) {
98+
monitor.warning(format("Exception during time policy evaluation: %s %s", ex.getClass().getCanonicalName(), ex.getMessage()));
99+
context.reportProblem(ex.getMessage());
100+
}
101+
return false;
102+
}
103+
104+
private boolean checkFixedPeriod(Instant now, Operator operator, Instant bound) {
105+
var comparison = now.compareTo(bound);
106+
107+
return switch (operator) {
108+
case EQ -> comparison == 0;
109+
case NEQ -> comparison != 0;
110+
case GT -> comparison > 0;
111+
case GEQ -> comparison >= 0;
112+
case LT -> comparison < 0;
113+
case LEQ -> comparison <= 0;
114+
default -> throw new IllegalStateException("Unexpected value: " + operator);
115+
};
116+
}
117+
118+
private Instant asInstant(String isoString) {
119+
try {
120+
return Instant.parse(isoString);
121+
} catch (DateTimeParseException e) {
122+
return null;
123+
}
124+
}
125+
}

policy-extension/src/main/java/org/eclipse/edc/extension/possiblepolicy/PossiblePolicyExtension.java

+22
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515

1616
package org.eclipse.edc.extension.possiblepolicy;
1717

18+
import org.eclipse.edc.connector.core.policy.ContractExpiryCheckFunction;
1819
import org.eclipse.edc.policy.engine.spi.PolicyEngine;
1920
import org.eclipse.edc.policy.engine.spi.RuleBindingRegistry;
2021
import org.eclipse.edc.policy.model.Permission;
@@ -23,6 +24,8 @@
2324
import org.eclipse.edc.runtime.metamodel.annotation.Inject;
2425
import org.eclipse.edc.spi.system.ServiceExtension;
2526
import org.eclipse.edc.spi.system.ServiceExtensionContext;
27+
28+
import static org.eclipse.edc.connector.contract.spi.validation.ContractValidationService.TRANSFER_SCOPE;
2629
import static org.eclipse.edc.policy.model.OdrlNamespace.ODRL_SCHEMA;
2730

2831
import java.util.Map;
@@ -55,16 +58,35 @@ public String name() {
5558
public void initialize(ServiceExtensionContext context) {
5659
var monitor = context.getMonitor();
5760

61+
// register use action for both negotiation and transfer scope
5862
ruleBindingRegistry.bind("use", NEGOTIATION_SCOPE);
5963
ruleBindingRegistry.bind("USE", NEGOTIATION_SCOPE);
6064
ruleBindingRegistry.bind(ODRL_SCHEMA + "use", NEGOTIATION_SCOPE);
65+
ruleBindingRegistry.bind("use", TRANSFER_SCOPE);
66+
ruleBindingRegistry.bind("USE", TRANSFER_SCOPE);
67+
ruleBindingRegistry.bind(ODRL_SCHEMA + "use", TRANSFER_SCOPE);
6168

69+
// iterate over claim constraint key map and register functions for negotiation and transfer scope
6270
for (Map.Entry<String, String> entry : CONSTRAINT_KEY_MAP.entrySet()) {
6371
ruleBindingRegistry.bind(entry.getKey(), NEGOTIATION_SCOPE);
6472
policyEngine.registerFunction(NEGOTIATION_SCOPE, Permission.class, entry.getKey(),
6573
new ClientClaimConstraintFunction<>(monitor, entry.getValue(), VERBOSE));
6674
policyEngine.registerFunction(NEGOTIATION_SCOPE, Prohibition.class, entry.getKey(),
6775
new ClientClaimConstraintFunction<>(monitor, entry.getValue(), VERBOSE));
76+
77+
ruleBindingRegistry.bind(entry.getKey(), TRANSFER_SCOPE);
78+
policyEngine.registerFunction(TRANSFER_SCOPE, Permission.class, entry.getKey(),
79+
new ClientClaimConstraintFunction<>(monitor, entry.getValue(), VERBOSE));
80+
policyEngine.registerFunction(TRANSFER_SCOPE, Prohibition.class, entry.getKey(),
81+
new ClientClaimConstraintFunction<>(monitor, entry.getValue(), VERBOSE));
6882
}
83+
84+
// also register reimplementation of standard edc contract expiry check function for negotiation scope
85+
ruleBindingRegistry.bind(ContractExpiryCheckFunction.CONTRACT_EXPIRY_EVALUATION_KEY, NEGOTIATION_SCOPE);
86+
policyEngine.registerFunction(NEGOTIATION_SCOPE, Permission.class, ContractExpiryCheckFunction.CONTRACT_EXPIRY_EVALUATION_KEY,
87+
new NegotiationContractExpiryCheckFunction<>(monitor));
88+
policyEngine.registerFunction(NEGOTIATION_SCOPE, Prohibition.class, ContractExpiryCheckFunction.CONTRACT_EXPIRY_EVALUATION_KEY,
89+
new NegotiationContractExpiryCheckFunction<>(monitor));
90+
// standard edc contract expiry check is already registered for transfer scope in contract-core extension
6991
}
7092
}

0 commit comments

Comments
 (0)