Background: I am trying to integrate CEL Policy (Java) as a policy evaluator for the Transaction Tokens implementation in Keycloak. The example below is taken from the Transaction Tokens draft:
"tctx": {
"action": "BUY",
"ticker": "MSFT",
"quantity": 100,
"customer_type": {
"geo": "US",
"level": "VIP"
}
}
The contents of the tctx claim can be arbitrary (customer/application specific) and won't be defined by any schema. Therefore, we cannot rely on the Proto schema and the generated class hierarchy - only JSON parsing at runtime. My first attempt was to use Jackson:
CelCompiler CEL_COMPILER = CelCompilerFactory.standardCelCompilerBuilder()
.addVar("tctx", MapType.create(SimpleType.STRING, SimpleType.ANY))
.build();
var json = """
{
"action": "BUY",
"ticker": "MSFT",
"quantity": 100,
"customer_type": {
"geo": "US",
"level": "FOO"
}
}
""";
Map<String, Object> tctx = new ObjectMapper().readValue(json, new TypeReference<>() {});
CelAbstractSyntaxTree ast = CEL_COMPILER.compile("tctx.quantity > 100").getAst();
CelRuntime.Program program = CEL_RUNTIME.createProgram(ast);
var result = program.eval(Map.of(
"tctx", tctx
));
Though CEL was able to understand the data structure, it failed while evaluating tctx.quantity > 100:
Exception in thread "main" dev.cel.runtime.CelEvaluationException: evaluation error at <input>:14: No matching overload for function '_>_'. Overload candidates: greater_int64
Then it turned out I could not perform any arithmetic with that field at all. After some debugging, I have found the root cause - the Jackson parser converts JSON numbers into Java Integers, and there are no int32 overloads in CEL. This can be boiled down to the following:
CelCompiler CEL_COMPILER = CelCompilerFactory.standardCelCompilerBuilder()
.addVar("x", SimpleType.INT)
.addVar("a32", ListType.create(SimpleType.INT))
.addVar("a64", ListType.create(SimpleType.INT))
.build();
CelAbstractSyntaxTree ast = CEL_COMPILER.compile("a32[0] > 0").getAst(); // -> No matching overload for function '_>_'. Overload candidates: greater_int64
// CelAbstractSyntaxTree ast = CEL_COMPILER.compile("a64[0] > 0").getAst(); // -> true
// CelAbstractSyntaxTree ast = CEL_COMPILER.compile("x > 0").getAst(); // -> true
CelRuntime.Program program = CEL_RUNTIME.createProgram(ast);
var result = program.eval(Map.of(
"a32", List.of(1, 2, 3),
"a64", List.of(1L, 2L, 3L),
"x", 42
));
Interestingly, an Integer value for the variable x will be automatically cast to Long, but nested Integers (members of a32) won't. Nested Longs also work fine (as in a64).
Then, I have decided to switch to Protobuf Value for my JSON, instead of Jackson:
Value.Builder builder = Value.newBuilder();
JsonFormat.parser().merge(json, builder);
var tctx = builder.build();
This gave a different error:
Exception in thread "main" dev.cel.runtime.CelEvaluationException: evaluation error: No matching overload for function '_>=_'. Overload candidates: greater_equals_int64
Turns out that Protobuf converts all JSON numbers to Doubles, but the right-hand operand in tctx.quantity > 100 is a Long - and again, there's neither automatic casting nor overloads for double vs. int operations. Even a simplest expression like 1.0 > 0 won't work:
dev.cel.common.CelValidationException: ERROR: <input>:1:5: found no matching overload for '_>_' applied to '(double, int)' (candidates: (bool, bool),(int, int),(uint, uint),(double, double),(string, string),(bytes, bytes),(google.protobuf.Timestamp, google.protobuf.Timestamp),(google.protobuf.Duration, google.protobuf.Duration))
| 1.0 > 0
While I can mention this double vs. int restriction in the docs, the behavior seems counter-intuitive to me as I haven't seen it in any modern languages; also, this is not observed in CEL-Go. It would be nice if we could make integer and real arithmetic in CEL more seamless and straightforward.
Apart from the above - thanks for great software 👏 Looking forward to having CEL in Keycloak in general, not limited to the Transaction Tokens use case.
Background: I am trying to integrate CEL Policy (Java) as a policy evaluator for the Transaction Tokens implementation in Keycloak. The example below is taken from the Transaction Tokens draft:
The contents of the
tctxclaim can be arbitrary (customer/application specific) and won't be defined by any schema. Therefore, we cannot rely on the Proto schema and the generated class hierarchy - only JSON parsing at runtime. My first attempt was to use Jackson:Though CEL was able to understand the data structure, it failed while evaluating
tctx.quantity > 100:Then it turned out I could not perform any arithmetic with that field at all. After some debugging, I have found the root cause - the Jackson parser converts JSON numbers into Java
Integers, and there are noint32overloads in CEL. This can be boiled down to the following:Interestingly, an
Integervalue for the variablexwill be automatically cast toLong, but nestedIntegers (members ofa32) won't. NestedLongs also work fine (as ina64).Then, I have decided to switch to Protobuf Value for my JSON, instead of Jackson:
This gave a different error:
Turns out that Protobuf converts all JSON numbers to
Doubles, but the right-hand operand intctx.quantity > 100is aLong- and again, there's neither automatic casting nor overloads for double vs. int operations. Even a simplest expression like1.0 > 0won't work:While I can mention this double vs. int restriction in the docs, the behavior seems counter-intuitive to me as I haven't seen it in any modern languages; also, this is not observed in CEL-Go. It would be nice if we could make integer and real arithmetic in CEL more seamless and straightforward.
Apart from the above - thanks for great software 👏 Looking forward to having CEL in Keycloak in general, not limited to the Transaction Tokens use case.