-
Notifications
You must be signed in to change notification settings - Fork 283
Description
Bug Report
Hey everyone
Describe the Bug
If a policy constraint's left operand is bound to a scope, but no evaluation function for that scope is registered, a set of default cases applies depending on the constraint operator (ref). As of right now, only the operators EQ, IN and NEQ are being handled. If the policy constraint contains any other operator, the function returns null, which propagates a Null Pointer Exception (NPE).
Expected Behavior
If a constraint is being evaluated but no evaluation function for it was found, the evaluation should fail.
Note that the policydefinitions API tries to enforce this by searching for an evaluation function when a PolicyDefinition is being registered, but one can be created for a different scope. For example, a policy constraint left operand can be bound to the CATALOG_SCOPE, but it's function bound to the TRANSFER_SCOPE.
Observed Behavior
NPE propagated on Policy evaluation.
For example, when requesting a Catalog, the NPE is propagated on the Provider Connector and the Consumer Connector receives a 500.
Steps to Reproduce
Steps to reproduce the behavior:
- Create an extension that registers the following:
// Action needs to be bound so that the permission in not filtered out (ScopeFilter, L76-L78)
ruleBindingRegistry.bind("http://www.w3.org/ns/odrl/2/use", CATALOG_SCOPE);
// Bind a left operand to CATALOG_SCOPE
ruleBindingRegistry.bind("https://w3id.org/edc/v0.0.1/ns/leftOp", CATALOG_SCOPE);
// Bind an evaluation function to another scope (e.g. TRANSFER_SCOPE). Needed to be able to create the PolicyDefinition
policyEngine.registerScope(TRANSFER_SCOPE, TransferProcessPolicyContext.class);
policyEngine.registerFunction(
TransferProcessPolicyContext.class,
Permission.class,
"https://w3id.org/edc/v0.0.1/ns/leftOp",
(operator, rightValue, rule, ctx) -> true);- Start a Connector pair, Consumer and Provider
- Create any dummy
Asset - Create a
PolicyDefinitionas follows:
{
"@context": {
"@vocab": "https://w3id.org/edc/v0.0.1/ns/"
},
"@id": "test-policy",
"policy": {
"@context": "http://www.w3.org/ns/odrl.jsonld",
"@type": "Set",
"permission": [
{
"action": "odrl:use", // use bound action
"constraint": {
"leftOperand": "leftOp", // use bound operand
"operator": "isAnyOf", // use an unhandled operator (e.g. isAnyOf)
"rightOperand": "rightOp"
}
}
]
}
}- Create a
ContractDefinitionwith the created policy definition as the access policy and usage policy - Send a Catalog Request
- Verify receives 500 error from Provider
Context Information
- Tested in version 0.14.0, but I believe the Policy Engine / Evaluator has not changed since
Detailed Description
500 error:
NPE logs:
SEVERE 2025-12-22T17:42:03.084785254 JerseyExtension: Unexpected exception caught
java.lang.NullPointerException: Cannot invoke "java.lang.Boolean.booleanValue()" because "result" is null
at org.eclipse.edc.policy.evaluator.PolicyEvaluator.visitRule(PolicyEvaluator.java:240)
at org.eclipse.edc.policy.evaluator.PolicyEvaluator.visitPermission(PolicyEvaluator.java:99)
at org.eclipse.edc.policy.evaluator.PolicyEvaluator.visitPermission(PolicyEvaluator.java:48)
at org.eclipse.edc.policy.model.Permission.accept(Permission.java:42)
at org.eclipse.edc.policy.evaluator.PolicyEvaluator.lambda$visitPolicy$0(PolicyEvaluator.java:75)
at java.base/java.util.ArrayList.forEach(Unknown Source)
at org.eclipse.edc.policy.evaluator.PolicyEvaluator.visitPolicy(PolicyEvaluator.java:75)
at org.eclipse.edc.policy.evaluator.PolicyEvaluator.visitPolicy(PolicyEvaluator.java:48)
at org.eclipse.edc.policy.model.Policy.accept(Policy.java:94)
at org.eclipse.edc.policy.evaluator.PolicyEvaluator.evaluate(PolicyEvaluator.java:67)
at org.eclipse.edc.policy.engine.PolicyEngineImpl.evaluate(PolicyEngineImpl.java:140)
at org.eclipse.edc.connector.controlplane.catalog.ContractDefinitionResolverImpl.lambda$resolveFor$2(ContractDefinitionResolverImpl.java:62)
at java.base/java.util.Optional.map(Unknown Source)
at org.eclipse.edc.connector.controlplane.catalog.ContractDefinitionResolverImpl.lambda$resolveFor$3(ContractDefinitionResolverImpl.java:62)
at java.base/java.util.stream.ReferencePipeline$2$1.accept(Unknown Source)
at java.base/java.util.stream.SliceOps$1$1.accept(Unknown Source)
at java.base/java.util.stream.ReferencePipeline$2$1.accept(Unknown Source)
at java.base/java.util.concurrent.ConcurrentHashMap$ValueSpliterator.tryAdvance(Unknown Source)
at java.base/java.util.stream.ReferencePipeline.forEachWithCancel(Unknown Source)
at java.base/java.util.stream.AbstractPipeline.copyIntoWithCancel(Unknown Source)
at java.base/java.util.stream.AbstractPipeline.copyInto(Unknown Source)
at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(Unknown Source)
at java.base/java.util.stream.AbstractPipeline.evaluate(Unknown Source)
at java.base/java.util.stream.AbstractPipeline.evaluateToArrayNode(Unknown Source)
at java.base/java.util.stream.ReferencePipeline.toArray(Unknown Source)
at java.base/java.util.stream.ReferencePipeline.toArray(Unknown Source)
at java.base/java.util.stream.ReferencePipeline.toList(Unknown Source)
at org.eclipse.edc.connector.controlplane.catalog.ContractDefinitionResolverImpl.resolveFor(ContractDefinitionResolverImpl.java:67)
at org.eclipse.edc.connector.controlplane.catalog.DatasetResolverImpl.query(DatasetResolverImpl.java:67)
at org.eclipse.edc.connector.controlplane.services.catalog.CatalogProtocolServiceImpl.lambda$getCatalog$0(CatalogProtocolServiceImpl.java:62)
at org.eclipse.edc.spi.result.AbstractResult.map(AbstractResult.java:145)
at org.eclipse.edc.connector.controlplane.services.catalog.CatalogProtocolServiceImpl.lambda$getCatalog$1(CatalogProtocolServiceImpl.java:61)
at org.eclipse.edc.transaction.spi.NoopTransactionContext.execute(NoopTransactionContext.java:34)
at org.eclipse.edc.connector.controlplane.services.catalog.CatalogProtocolServiceImpl.getCatalog(CatalogProtocolServiceImpl.java:60)
at org.eclipse.edc.protocol.dsp.http.message.DspRequestHandlerImpl.createResource(DspRequestHandlerImpl.java:132)
at org.eclipse.edc.protocol.dsp.catalog.http.api.controller.BaseDspCatalogApiController.requestCatalog(BaseDspCatalogApiController.java:84)
Possible Implementation
I believe that the default cases that execute when no evaluation function is found were added in the very early stages of the Policy Engine, but now are not needed. A possible implementation would be to return false if an evaluation function was not found, and never return null.