diff --git a/src/main/java/org/eclipse/biscuit/datalog/Check.java b/src/main/java/org/eclipse/biscuit/datalog/Check.java index 52e81c26..9094c1da 100644 --- a/src/main/java/org/eclipse/biscuit/datalog/Check.java +++ b/src/main/java/org/eclipse/biscuit/datalog/Check.java @@ -6,6 +6,7 @@ package org.eclipse.biscuit.datalog; import static biscuit.format.schema.Schema.CheckV2.Kind.All; +import static biscuit.format.schema.Schema.CheckV2.Kind.Reject; import biscuit.format.schema.Schema; import java.util.ArrayList; @@ -17,7 +18,8 @@ public final class Check { public enum Kind { ONE, - ALL + ALL, + REJECT } private static final int HASH_CODE_SEED = 31; @@ -47,6 +49,9 @@ public Schema.CheckV2 serialize() { case ALL: b.setKind(All); break; + case REJECT: + b.setKind(Reject); + break; default: } @@ -68,6 +73,9 @@ public static Result deserializeV2(Schema.CheckV2 chec case All: kind = Kind.ALL; break; + case Reject: + kind = Kind.REJECT; + break; default: kind = Kind.ONE; break; diff --git a/src/main/java/org/eclipse/biscuit/datalog/MapKey.java b/src/main/java/org/eclipse/biscuit/datalog/MapKey.java new file mode 100644 index 00000000..c9008b0e --- /dev/null +++ b/src/main/java/org/eclipse/biscuit/datalog/MapKey.java @@ -0,0 +1,21 @@ +package org.eclipse.biscuit.datalog; + +import biscuit.format.schema.Schema; +import org.eclipse.biscuit.error.Error; +import org.eclipse.biscuit.error.Result; + +public abstract class MapKey extends Term { + public abstract Schema.MapKey serializeMapKey(); + + public abstract org.eclipse.biscuit.token.builder.MapKey toMapKey(SymbolTable symbolTable); + + public static Result deserializeMapKeyEnum(Schema.MapKey mapKey) { + if (mapKey.hasInteger()) { + return Term.Integer.deserializeMapKey(mapKey); + } else if (mapKey.hasString()) { + return Term.Str.deserializeMapKey(mapKey); + } else { + return Result.err(new Error.FormatError.DeserializationError("invalid MapKey kind")); + } + } +} diff --git a/src/main/java/org/eclipse/biscuit/datalog/Predicate.java b/src/main/java/org/eclipse/biscuit/datalog/Predicate.java index 6673f7f7..c3a1a6f3 100644 --- a/src/main/java/org/eclipse/biscuit/datalog/Predicate.java +++ b/src/main/java/org/eclipse/biscuit/datalog/Predicate.java @@ -90,7 +90,7 @@ public Schema.PredicateV2 serialize() { Schema.PredicateV2.Builder builder = Schema.PredicateV2.newBuilder().setName(this.name); for (int i = 0; i < this.terms.size(); i++) { - builder.addTerms(this.terms.get(i).serialize()); + builder.addTerms(this.terms.get(i).serializeTerm()); } return builder.build(); diff --git a/src/main/java/org/eclipse/biscuit/datalog/SchemaVersion.java b/src/main/java/org/eclipse/biscuit/datalog/SchemaVersion.java index 1bb21e9f..a6794eab 100644 --- a/src/main/java/org/eclipse/biscuit/datalog/SchemaVersion.java +++ b/src/main/java/org/eclipse/biscuit/datalog/SchemaVersion.java @@ -6,6 +6,8 @@ package org.eclipse.biscuit.datalog; import java.util.List; +import java.util.Optional; +import org.eclipse.biscuit.crypto.PublicKey; import org.eclipse.biscuit.datalog.expressions.Expression; import org.eclipse.biscuit.datalog.expressions.Op; import org.eclipse.biscuit.error.Error; @@ -13,80 +15,111 @@ import org.eclipse.biscuit.token.format.SerializedBiscuit; public final class SchemaVersion { - private boolean containsScopes; - private boolean containsCheckAll; - private boolean containsV4; + public static int version( + List facts, + List rules, + List checks, + List scopes, + Optional externalKey) { + if (containsV6(facts, rules, checks)) { + return SerializedBiscuit.DATALOG_3_3; + } + if (containsV5(externalKey)) { + return SerializedBiscuit.DATALOG_3_2; + } + if (containsV4(rules, checks, scopes)) { + return SerializedBiscuit.DATALOG_3_1; + } + return SerializedBiscuit.MIN_SCHEMA_VERSION; + } - public SchemaVersion(List facts, List rules, List checks, List scopes) { - containsScopes = !scopes.isEmpty(); + public static Result checkCompatibility( + int version, + List facts, + List rules, + List checks, + List scopes, + Optional externalKey) { + if (version < SerializedBiscuit.DATALOG_3_1 && containsV4(rules, checks, scopes)) { + return Result.err( + new Error.FormatError.DeserializationError( + "v" + version + " blocks must not have v4 features")); + } + if (version < SerializedBiscuit.DATALOG_3_2 && containsV5(externalKey)) { + return Result.err( + new Error.FormatError.DeserializationError( + "v" + version + " blocks must not have v5 features")); + } + if (version < SerializedBiscuit.DATALOG_3_3 && containsV6(facts, rules, checks)) { + return Result.err( + new Error.FormatError.DeserializationError( + "v" + version + " blocks must not have v6 features")); + } + return Result.ok(null); + } - if (!containsScopes) { - for (Rule r : rules) { - if (!r.scopes().isEmpty()) { - containsScopes = true; - break; - } - } + private static boolean containsV4(List rules, List checks, List scopes) { + if (!scopes.isEmpty()) { + return true; } - if (!containsScopes) { - for (Check check : checks) { - for (Rule query : check.queries()) { - if (!query.scopes().isEmpty()) { - containsScopes = true; - break; - } - } + for (Rule rule : rules) { + if (!rule.scopes().isEmpty()) { + return true; + } + if (containsV4Ops(rule.expressions())) { + return true; } } - containsCheckAll = false; for (Check check : checks) { if (check.kind() == Check.Kind.ALL) { - containsCheckAll = true; - break; + return true; } - } - - containsV4 = false; - for (Check check : checks) { for (Rule query : check.queries()) { + if (!query.scopes().isEmpty()) { + return true; + } if (containsV4Ops(query.expressions())) { - containsV4 = true; - break; + return true; } } } + + return false; } - public int version() { - if (containsScopes || containsV4 || containsCheckAll) { - return 4; - } else { - return SerializedBiscuit.MIN_SCHEMA_VERSION; - } + private static boolean containsV5(Optional externalKey) { + return externalKey.isPresent(); } - public Result checkCompatibility(int version) { - if (version < 4) { - if (containsScopes) { - return Result.err( - new Error.FormatError.DeserializationError("v3 blocks must not have scopes")); + private static boolean containsV6(List facts, List rules, List checks) { + for (Fact fact : facts) { + if (containsV6Terms(fact.predicate().terms())) { + return true; } - if (containsV4) { - return Result.err( - new Error.FormatError.DeserializationError( - "v3 blocks must not have v4 operators (bitwise operators or !=")); + } + + for (Rule rule : rules) { + if (containsV6Ops(rule.expressions())) { + return true; } - if (containsCheckAll) { - return Result.err( - new Error.FormatError.DeserializationError("v3 blocks must not use check all")); + } + + for (Check check : checks) { + if (check.kind() == Check.Kind.REJECT) { + return true; + } + for (Rule query : check.queries()) { + if (containsV6Ops(query.expressions())) { + return true; + } } } - return Result.ok(null); + return false; } - public static boolean containsV4Ops(List expressions) { + private static boolean containsV4Ops(List expressions) { for (Expression e : expressions) { for (Op op : e.getOps()) { if (op instanceof Op.Binary) { @@ -104,4 +137,55 @@ public static boolean containsV4Ops(List expressions) { } return false; } + + private static boolean containsV6Ops(List expressions) { + for (Expression e : expressions) { + for (Op op : e.getOps()) { + if (op instanceof Op.Unary) { + Op.Unary b = (Op.Unary) op; + switch (b.getOp()) { + case TypeOf: + return true; + default: + } + } else if (op instanceof Op.Binary) { + Op.Binary b = (Op.Binary) op; + switch (b.getOp()) { + case HeterogeneousEqual: + case HeterogeneousNotEqual: + case LazyAnd: + case LazyOr: + case Get: + case Any: + case All: + case TryOr: + return true; + default: + } + } else if (op instanceof Op.Closure) { + return true; + } else if (op instanceof Term.Null) { + return true; + } else if (op instanceof Term.Array) { + return true; + } else if (op instanceof Term.Map) { + return true; + } + } + } + return false; + } + + private static boolean containsV6Terms(List terms) { + for (Term term : terms) { + if (term instanceof Term.Null) { + return true; + } else if (term instanceof Term.Array) { + return true; + } else if (term instanceof Term.Map) { + return true; + } + } + return false; + } } diff --git a/src/main/java/org/eclipse/biscuit/datalog/SymbolTable.java b/src/main/java/org/eclipse/biscuit/datalog/SymbolTable.java index 19c2f3c6..d95b49b3 100644 --- a/src/main/java/org/eclipse/biscuit/datalog/SymbolTable.java +++ b/src/main/java/org/eclipse/biscuit/datalog/SymbolTable.java @@ -12,6 +12,7 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.Map; import java.util.Optional; import java.util.stream.Collectors; import org.eclipse.biscuit.crypto.PublicKey; @@ -228,7 +229,26 @@ public String formatTerm(final Term i) { final List values = ((Term.Set) i) .value().stream().map((v) -> this.formatTerm(v)).collect(Collectors.toList()); + if (values.isEmpty()) { + return "{,}"; + } else { + Collections.sort(values); + return "{" + String.join(", ", values) + "}"; + } + } else if (i instanceof Term.Null) { + return "null"; + } else if (i instanceof Term.Array) { + final List values = + ((Term.Array) i) + .value().stream().map((v) -> this.formatTerm(v)).collect(Collectors.toList()); return "[" + String.join(", ", values) + "]"; + } else if (i instanceof Term.Map) { + ArrayList entries = new ArrayList(); + for (Map.Entry entry : ((Term.Map) i).value().entrySet()) { + entries.add(formatTerm(entry.getKey()) + ": " + formatTerm(entry.getValue())); + } + Collections.sort(entries); + return "{" + String.join(", ", entries) + "}"; } else { return "???"; } @@ -247,6 +267,9 @@ public String formatCheck(final Check c) { case ALL: prefix = "check all "; break; + case REJECT: + prefix = "reject if "; + break; default: prefix = "check if "; break; diff --git a/src/main/java/org/eclipse/biscuit/datalog/Term.java b/src/main/java/org/eclipse/biscuit/datalog/Term.java index 7a761031..2d7dfe80 100644 --- a/src/main/java/org/eclipse/biscuit/datalog/Term.java +++ b/src/main/java/org/eclipse/biscuit/datalog/Term.java @@ -8,15 +8,57 @@ import biscuit.format.schema.Schema; import com.google.protobuf.ByteString; import java.io.Serializable; +import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; +import java.util.Deque; +import java.util.HashMap; import java.util.HashSet; +import java.util.List; +import org.eclipse.biscuit.datalog.expressions.Op; import org.eclipse.biscuit.error.Error; import org.eclipse.biscuit.error.Result; -public abstract class Term implements Serializable { +public abstract class Term extends Op implements Serializable { + public abstract boolean match(Term other); - public abstract Schema.TermV2 serialize(); + public abstract Schema.TermV2 serializeTerm(); + + public abstract String typeOf(); + + public abstract org.eclipse.biscuit.token.builder.Term toTerm(SymbolTable symbolTable); + + @Override + public Schema.Op serialize() { + return Schema.Op.newBuilder().setValue(this.serializeTerm()).build(); + } + + @Override + public void evaluate( + Deque stack, + java.util.Map variables, + TemporarySymbolTable temporarySymbolTable) + throws Error.Execution { + if (this instanceof Term.Variable) { + Term.Variable var = (Term.Variable) this; + Term valueVar = variables.get(var.value()); + if (valueVar != null) { + stack.push(valueVar); + } else { + throw new Error.Execution("cannot find a variable for index " + this); + } + } else { + stack.push(this); + } + } + + @Override + public String print(Deque stack, SymbolTable symbols) { + String s = symbols.formatTerm(this); + stack.push(s); + return s; + } public static Result deserializeEnumV2(Schema.TermV2 term) { if (term.hasDate()) { @@ -33,13 +75,72 @@ public static Result deserializeEnumV2(Schema.TermV2 te return Bool.deserializeV2(term); } else if (term.hasSet()) { return Set.deserializeV2(term); + } else if (term.hasNull()) { + return Null.deserializeV2(term); + } else if (term.hasArray()) { + return Array.deserializeV2(term); + } else if (term.hasMap()) { + return Map.deserializeV2(term); } else { return Result.err( new Error.FormatError.DeserializationError("invalid Term kind: term.getKind()")); } } - public abstract org.eclipse.biscuit.token.builder.Term toTerm(SymbolTable symbolTable); + public static final class Null extends Term implements Serializable { + public boolean match(final Term other) { + if (other instanceof Variable) { + return true; + } else { + return this.equals(other); + } + } + + public Null() {} + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + return true; + } + + @Override + public int hashCode() { + return 0; + } + + @Override + public String toString() { + return "null"; + } + + public Schema.TermV2 serializeTerm() { + return Schema.TermV2.newBuilder().setNull(Schema.Empty.newBuilder().build()).build(); + } + + public String typeOf() { + return "null"; + } + + public static Result deserializeV2(Schema.TermV2 term) { + if (!term.hasNull()) { + return Result.err( + new Error.FormatError.DeserializationError("invalid Term kind, expected null")); + } else { + return Result.ok(new Null()); + } + } + + public org.eclipse.biscuit.token.builder.Term toTerm(SymbolTable symbolTable) { + return new org.eclipse.biscuit.token.builder.Term.Null(); + } + } public static final class Date extends Term implements Serializable { private final long value; @@ -84,10 +185,14 @@ public String toString() { return "@" + this.value; } - public Schema.TermV2 serialize() { + public Schema.TermV2 serializeTerm() { return Schema.TermV2.newBuilder().setDate(this.value).build(); } + public String typeOf() { + return "date"; + } + public static Result deserializeV2(Schema.TermV2 term) { if (!term.hasDate()) { return Result.err( @@ -102,7 +207,7 @@ public org.eclipse.biscuit.token.builder.Term toTerm(SymbolTable symbolTable) { } } - public static final class Integer extends Term implements Serializable { + public static final class Integer extends MapKey implements Serializable { private final long value; public long value() { @@ -147,10 +252,18 @@ public String toString() { return "" + this.value; } - public Schema.TermV2 serialize() { + public Schema.TermV2 serializeTerm() { return Schema.TermV2.newBuilder().setInteger(this.value).build(); } + public String typeOf() { + return "integer"; + } + + public Schema.MapKey serializeMapKey() { + return Schema.MapKey.newBuilder().setInteger(this.value).build(); + } + public static Result deserializeV2(Schema.TermV2 term) { if (!term.hasInteger()) { return Result.err( @@ -160,9 +273,22 @@ public static Result deserializeV2(Schema.TermV2 term) } } + public static Result deserializeMapKey(Schema.MapKey term) { + if (!term.hasInteger()) { + return Result.err( + new Error.FormatError.DeserializationError("invalid Term kind, expected integer")); + } else { + return Result.ok(new Integer(term.getInteger())); + } + } + public org.eclipse.biscuit.token.builder.Term toTerm(SymbolTable symbolTable) { return new org.eclipse.biscuit.token.builder.Term.Integer(this.value); } + + public org.eclipse.biscuit.token.builder.MapKey toMapKey(SymbolTable symbolTable) { + return new org.eclipse.biscuit.token.builder.Term.Integer(this.value); + } } public static final class Bytes extends Term implements Serializable { @@ -210,10 +336,14 @@ public String toString() { return this.value.toString(); } - public Schema.TermV2 serialize() { + public Schema.TermV2 serializeTerm() { return Schema.TermV2.newBuilder().setBytes(ByteString.copyFrom(this.value)).build(); } + public String typeOf() { + return "bytes"; + } + public static Result deserializeV2(Schema.TermV2 term) { if (!term.hasBytes()) { return Result.err( @@ -228,7 +358,7 @@ public org.eclipse.biscuit.token.builder.Term toTerm(SymbolTable symbolTable) { } } - public static final class Str extends Term implements Serializable { + public static final class Str extends MapKey implements Serializable { private final long value; public long value() { @@ -268,10 +398,18 @@ public int hashCode() { return (int) (value ^ (value >>> 32)); } - public Schema.TermV2 serialize() { + public Schema.TermV2 serializeTerm() { return Schema.TermV2.newBuilder().setString(this.value).build(); } + public String typeOf() { + return "string"; + } + + public Schema.MapKey serializeMapKey() { + return Schema.MapKey.newBuilder().setString(this.value).build(); + } + public static Result deserializeV2(Schema.TermV2 term) { if (!term.hasString()) { return Result.err( @@ -281,10 +419,24 @@ public static Result deserializeV2(Schema.TermV2 term) } } + public static Result deserializeMapKey(Schema.MapKey term) { + if (!term.hasString()) { + return Result.err( + new Error.FormatError.DeserializationError("invalid Term kind, expected string")); + } else { + return Result.ok(new Str(term.getString())); + } + } + public org.eclipse.biscuit.token.builder.Term toTerm(SymbolTable symbolTable) { return new org.eclipse.biscuit.token.builder.Term.Str( symbolTable.formatSymbol((int) this.value)); } + + public org.eclipse.biscuit.token.builder.MapKey toMapKey(SymbolTable symbolTable) { + return new org.eclipse.biscuit.token.builder.Term.Str( + symbolTable.formatSymbol((int) this.value)); + } } public static final class Variable extends Term implements Serializable { @@ -326,10 +478,14 @@ public String toString() { return this.value + "?"; } - public Schema.TermV2 serialize() { + public Schema.TermV2 serializeTerm() { return Schema.TermV2.newBuilder().setVariable((int) this.value).build(); } + public String typeOf() { + throw new InternalError(); + } + public static Result deserializeV2(Schema.TermV2 term) { if (!term.hasVariable()) { return Result.err( @@ -390,10 +546,14 @@ public String toString() { return "" + this.value; } - public Schema.TermV2 serialize() { + public Schema.TermV2 serializeTerm() { return Schema.TermV2.newBuilder().setBool(this.value).build(); } + public String typeOf() { + return "bool"; + } + public static Result deserializeV2(Schema.TermV2 term) { if (!term.hasBool()) { return Result.err( @@ -408,6 +568,215 @@ public org.eclipse.biscuit.token.builder.Term toTerm(SymbolTable symbolTable) { } } + public static final class Array extends Term implements Serializable { + private final List value; + + public List value() { + return Collections.unmodifiableList(this.value); + } + + public boolean match(final Term other) { + if (other instanceof Variable) { + return true; + } + if (other instanceof Array) { + return this.value.equals(((Array) other).value); + } + return false; + } + + public Array(final List value) { + this.value = value; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + Array array = (Array) o; + + return value.equals(array.value); + } + + @Override + public int hashCode() { + return value.hashCode(); + } + + @Override + public String toString() { + return "" + value; + } + + public Schema.TermV2 serializeTerm() { + Schema.Array.Builder s = Schema.Array.newBuilder(); + + for (Term l : this.value) { + s.addArray(l.serializeTerm()); + } + + return Schema.TermV2.newBuilder().setArray(s).build(); + } + + public String typeOf() { + return "array"; + } + + public static Result deserializeV2(Schema.TermV2 term) { + if (!term.hasArray()) { + return Result.err( + new Error.FormatError.DeserializationError("invalid Term kind, expected array")); + } else { + java.util.List values = new ArrayList<>(); + Schema.Array s = term.getArray(); + + for (Schema.TermV2 l : s.getArrayList()) { + var res = Term.deserializeEnumV2(l); + if (res.isErr()) { + return Result.err(res.getErr()); + } else { + Term value = res.getOk(); + + if (value instanceof Variable) { + return Result.err( + new Error.FormatError.DeserializationError("arrays cannot contain variables")); + } + + values.add(value); + } + } + + if (values.isEmpty()) { + return Result.err(new Error.FormatError.DeserializationError("invalid Array value")); + } else { + return Result.ok(new Array(values)); + } + } + } + + public org.eclipse.biscuit.token.builder.Term toTerm(SymbolTable symbolTable) { + ArrayList s = new ArrayList<>(); + + for (Term i : this.value) { + s.add(i.toTerm(symbolTable)); + } + + return new org.eclipse.biscuit.token.builder.Term.Array(s); + } + } + + public static final class Map extends Term implements Serializable { + private final HashMap value; + + public HashMap value() { + return this.value; + } + + public boolean match(final Term other) { + if (other instanceof Variable) { + return true; + } + if (other instanceof Map) { + return this.value.equals(((Map) other).value); + } + return false; + } + + public Map(final HashMap value) { + this.value = value; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + Map array = (Map) o; + + return value.equals(array.value); + } + + @Override + public int hashCode() { + return value.hashCode(); + } + + @Override + public String toString() { + return "" + value; + } + + public Schema.TermV2 serializeTerm() { + Schema.Map.Builder s = Schema.Map.newBuilder(); + + for (java.util.Map.Entry i : this.value.entrySet()) { + Schema.MapEntry.Builder m = Schema.MapEntry.newBuilder(); + m.setKey(i.getKey().serializeMapKey()); + m.setValue(i.getValue().serializeTerm()); + s.addEntries(m); + } + + return Schema.TermV2.newBuilder().setMap(s).build(); + } + + public String typeOf() { + return "map"; + } + + public static Result deserializeV2(Schema.TermV2 term) { + if (!term.hasMap()) { + return Result.err( + new Error.FormatError.DeserializationError("invalid Term kind, expected map")); + } + java.util.HashMap values = new HashMap(); + Schema.Map s = term.getMap(); + + for (Schema.MapEntry l : s.getEntriesList()) { + var resKey = MapKey.deserializeMapKeyEnum(l.getKey()); + if (resKey.isErr()) { + return Result.err(resKey.getErr()); + } + var resValue = Term.deserializeEnumV2(l.getValue()); + if (resValue.isErr()) { + return Result.err(resValue.getErr()); + } + MapKey key = resKey.getOk(); + Term value = resValue.getOk(); + if (value instanceof Variable) { + return Result.err( + new Error.FormatError.DeserializationError("maps cannot contain variables")); + } + values.put(key, value); + } + + if (values.isEmpty()) { + return Result.err(new Error.FormatError.DeserializationError("invalid map value")); + } else { + return Result.ok(new Map(values)); + } + } + + public org.eclipse.biscuit.token.builder.Term toTerm(SymbolTable symbolTable) { + HashMap s = + new HashMap<>(); + + for (java.util.Map.Entry i : this.value.entrySet()) { + s.put(i.getKey().toMapKey(symbolTable), i.getValue().toTerm(symbolTable)); + } + + return new org.eclipse.biscuit.token.builder.Term.Map(s); + } + } + public static final class Set extends Term implements Serializable { private final HashSet value; @@ -450,19 +819,35 @@ public int hashCode() { @Override public String toString() { - return "" + value; + if (value.size() == 0) { + return "{,}"; + } + String s = "{"; + int count = 0; + for (Term elem : value) { + s += elem; + count += 1; + if (count < value.size()) { + s += ", "; + } + } + return s + "}"; } - public Schema.TermV2 serialize() { + public Schema.TermV2 serializeTerm() { Schema.TermSet.Builder s = Schema.TermSet.newBuilder(); for (Term l : this.value) { - s.addSet(l.serialize()); + s.addSet(l.serializeTerm()); } return Schema.TermV2.newBuilder().setSet(s).build(); } + public String typeOf() { + return "set"; + } + public static Result deserializeV2(Schema.TermV2 term) { if (!term.hasSet()) { return Result.err( @@ -487,11 +872,7 @@ public static Result deserializeV2(Schema.TermV2 term) } } - if (values.isEmpty()) { - return Result.err(new Error.FormatError.DeserializationError("invalid Set value")); - } else { - return Result.ok(new Set(values)); - } + return Result.ok(new Set(values)); } } diff --git a/src/main/java/org/eclipse/biscuit/datalog/expressions/Expression.java b/src/main/java/org/eclipse/biscuit/datalog/expressions/Expression.java index c3e26fd1..e6917f0e 100644 --- a/src/main/java/org/eclipse/biscuit/datalog/expressions/Expression.java +++ b/src/main/java/org/eclipse/biscuit/datalog/expressions/Expression.java @@ -32,12 +32,17 @@ public ArrayList getOps() { // FIXME: should return a Result public Term evaluate(Map variables, TemporarySymbolTable temporarySymbolTable) throws Error.Execution { - Deque stack = new ArrayDeque(16); // Default value + Deque stack = new ArrayDeque(16); // Default value for (Op op : ops) { op.evaluate(stack, variables, temporarySymbolTable); } if (stack.size() == 1) { - return stack.pop(); + Op op = stack.pop(); + if (op instanceof Term) { + return (Term) op; + } else { + throw new Error.Execution(this, "expression evaluated to closure"); + } } else { throw new Error.Execution(this, "execution"); } diff --git a/src/main/java/org/eclipse/biscuit/datalog/expressions/Op.java b/src/main/java/org/eclipse/biscuit/datalog/expressions/Op.java index df6ab8d9..51272beb 100644 --- a/src/main/java/org/eclipse/biscuit/datalog/expressions/Op.java +++ b/src/main/java/org/eclipse/biscuit/datalog/expressions/Op.java @@ -9,21 +9,26 @@ import com.google.re2j.Matcher; import com.google.re2j.Pattern; import java.io.UnsupportedEncodingException; -import java.util.Arrays; +import java.util.ArrayDeque; +import java.util.ArrayList; import java.util.Deque; +import java.util.HashMap; import java.util.HashSet; +import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Set; +import org.eclipse.biscuit.datalog.MapKey; import org.eclipse.biscuit.datalog.SymbolTable; import org.eclipse.biscuit.datalog.TemporarySymbolTable; import org.eclipse.biscuit.datalog.Term; import org.eclipse.biscuit.error.Error; import org.eclipse.biscuit.error.Result; +import org.eclipse.biscuit.token.builder.Expression; public abstract class Op { public abstract void evaluate( - Deque stack, Map variables, TemporarySymbolTable temporarySymbolTable) + Deque stack, Map variables, TemporarySymbolTable temporarySymbolTable) throws Error.Execution; public abstract String print(Deque stack, SymbolTable symbols); @@ -33,89 +38,23 @@ public abstract void evaluate( public static Result deserializeV2(Schema.Op op) { if (op.hasValue()) { var res = Term.deserializeEnumV2(op.getValue()); - return res.isOk() ? Result.ok(new Op.Value(res.getOk())) : Result.err(res.getErr()); + return res.isOk() ? Result.ok(res.getOk()) : Result.err(res.getErr()); } else if (op.hasUnary()) { return Op.Unary.deserializeV2(op.getUnary()); } else if (op.hasBinary()) { return Op.Binary.deserializeV1(op.getBinary()); + } else if (op.hasClosure()) { + return Op.Closure.deserializeV1(op.getClosure()); } else { return Result.err(new Error.FormatError.DeserializationError("invalid unary operation")); } } - public static final class Value extends Op { - private final Term value; - - public Value(Term value) { - this.value = value; - } - - public Term getValue() { - return value; - } - - @Override - public void evaluate( - Deque stack, Map variables, TemporarySymbolTable temporarySymbolTable) - throws Error.Execution { - if (value instanceof Term.Variable) { - Term.Variable var = (Term.Variable) value; - Term valueVar = variables.get(var.value()); - if (valueVar != null) { - stack.push(valueVar); - } else { - throw new Error.Execution("cannot find a variable for index " + value); - } - } else { - stack.push(value); - } - } - - @Override - public String print(Deque stack, SymbolTable symbolTable) { - String s = symbolTable.formatTerm(value); - stack.push(s); - return s; - } - - @Override - public Schema.Op serialize() { - Schema.Op.Builder b = Schema.Op.newBuilder(); - - b.setValue(this.value.serialize()); - - return b.build(); - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - - Value value1 = (Value) o; - - return value.equals(value1.value); - } - - @Override - public int hashCode() { - return value.hashCode(); - } - - @Override - public String toString() { - return "Value(" + value + ')'; - } - } - public enum UnaryOp { Negate, Parens, Length, + TypeOf, } public static final class Unary extends Op { @@ -131,9 +70,9 @@ public UnaryOp getOp() { @Override public void evaluate( - Deque stack, Map variables, TemporarySymbolTable temporarySymbolTable) + Deque stack, Map variables, TemporarySymbolTable temporarySymbolTable) throws Error.Execution { - Term value = stack.pop(); + Op value = stack.pop(); switch (this.op) { case Negate: if (value instanceof Term.Bool) { @@ -162,12 +101,20 @@ public void evaluate( stack.push(new Term.Integer(((Term.Bytes) value).value().length)); } else if (value instanceof Term.Set) { stack.push(new Term.Integer(((Term.Set) value).value().size())); + } else if (value instanceof Term.Array) { + stack.push(new Term.Integer(((Term.Array) value).value().size())); + } else if (value instanceof Term.Map) { + stack.push(new Term.Integer(((Term.Map) value).value().size())); } else { throw new Error.Execution("invalid type for length op"); } break; + case TypeOf: + Term term = (Term) value; + stack.push(new Term.Str(temporarySymbolTable.insert(term.typeOf()))); + break; default: - throw new Error.Execution("invalid type for length op"); + throw new Error.Execution("invalid type for op " + this.op); } } @@ -188,6 +135,10 @@ public String print(Deque stack, SymbolTable symbolTable) { s = prec + ".length()"; stack.push(s); break; + case TypeOf: + s = prec + ".type()"; + stack.push(s); + break; default: } return s; @@ -209,6 +160,9 @@ public Schema.Op serialize() { case Length: b1.setKind(Schema.OpUnary.Kind.Length); break; + case TypeOf: + b1.setKind(Schema.OpUnary.Kind.TypeOf); + break; default: } @@ -225,6 +179,8 @@ public static Result deserializeV2(Schema.OpUnary op) { return Result.ok(new Op.Unary(UnaryOp.Parens)); case Length: return Result.ok(new Op.Unary(UnaryOp.Length)); + case TypeOf: + return Result.ok(new Op.Unary(UnaryOp.TypeOf)); default: } @@ -263,6 +219,8 @@ public enum BinaryOp { GreaterOrEqual, Equal, NotEqual, + HeterogeneousEqual, + HeterogeneousNotEqual, Contains, Prefix, Suffix, @@ -273,11 +231,17 @@ public enum BinaryOp { Div, And, Or, + LazyAnd, + LazyOr, Intersection, Union, BitwiseAnd, BitwiseOr, BitwiseXor, + Get, + Any, + All, + TryOr, } public static final class Binary extends Op { @@ -293,10 +257,10 @@ public BinaryOp getOp() { @Override public void evaluate( - Deque stack, Map variables, TemporarySymbolTable temporarySymbolTable) + Deque stack, Map variables, TemporarySymbolTable temporarySymbolTable) throws Error.Execution { - Term right = stack.pop(); - Term left = stack.pop(); + Op right = stack.pop(); + Op left = stack.pop(); switch (this.op) { case LessThan: @@ -336,55 +300,41 @@ public void evaluate( } break; case Equal: - if (right instanceof Term.Bool && left instanceof Term.Bool) { - stack.push(new Term.Bool(((Term.Bool) left).value() == ((Term.Bool) right).value())); - } - if (right instanceof Term.Integer && left instanceof Term.Integer) { - stack.push( - new Term.Bool(((Term.Integer) left).value() == ((Term.Integer) right).value())); - } - if (right instanceof Term.Str && left instanceof Term.Str) { - stack.push(new Term.Bool(((Term.Str) left).value() == ((Term.Str) right).value())); - } - if (right instanceof Term.Bytes && left instanceof Term.Bytes) { - stack.push( - new Term.Bool( - Arrays.equals(((Term.Bytes) left).value(), (((Term.Bytes) right).value())))); - } - if (right instanceof Term.Date && left instanceof Term.Date) { - stack.push(new Term.Bool(((Term.Date) left).value() == ((Term.Date) right).value())); - } - if (right instanceof Term.Set && left instanceof Term.Set) { - Set leftSet = ((Term.Set) left).value(); - Set rightSet = ((Term.Set) right).value(); - stack.push( - new Term.Bool(leftSet.size() == rightSet.size() && leftSet.containsAll(rightSet))); + if (left instanceof Term && right instanceof Term) { + if (left.getClass() == right.getClass()) { + stack.push(new Term.Bool(left.equals(right))); + } else { + throw new Error.Execution( + Error.Execution.Kind.InvalidType, "cannot compare disparate types"); + } + } else { + throw new Error.Execution(Error.Execution.Kind.InvalidType, "cannot compare closures"); } break; case NotEqual: - if (right instanceof Term.Bool && left instanceof Term.Bool) { - stack.push(new Term.Bool(((Term.Bool) left).value() != ((Term.Bool) right).value())); - } - if (right instanceof Term.Integer && left instanceof Term.Integer) { - stack.push( - new Term.Bool(((Term.Integer) left).value() != ((Term.Integer) right).value())); - } - if (right instanceof Term.Str && left instanceof Term.Str) { - stack.push(new Term.Bool(((Term.Str) left).value() != ((Term.Str) right).value())); - } - if (right instanceof Term.Bytes && left instanceof Term.Bytes) { - stack.push( - new Term.Bool( - !Arrays.equals(((Term.Bytes) left).value(), (((Term.Bytes) right).value())))); + if (left instanceof Term && right instanceof Term) { + if (left.getClass() == right.getClass()) { + stack.push(new Term.Bool(!left.equals(right))); + } else { + throw new Error.Execution( + Error.Execution.Kind.InvalidType, "cannot compare disparate types"); + } + } else { + throw new Error.Execution(Error.Execution.Kind.InvalidType, "cannot compare closures"); } - if (right instanceof Term.Date && left instanceof Term.Date) { - stack.push(new Term.Bool(((Term.Date) left).value() != ((Term.Date) right).value())); + break; + case HeterogeneousEqual: + if (left instanceof Term && right instanceof Term) { + stack.push(new Term.Bool(left.equals(right))); + } else { + throw new Error.Execution(Error.Execution.Kind.InvalidType, "cannot compare closures"); } - if (right instanceof Term.Set && left instanceof Term.Set) { - Set leftSet = ((Term.Set) left).value(); - Set rightSet = ((Term.Set) right).value(); - stack.push( - new Term.Bool(leftSet.size() != rightSet.size() || !leftSet.containsAll(rightSet))); + break; + case HeterogeneousNotEqual: + if (left instanceof Term && right instanceof Term) { + stack.push(new Term.Bool(!left.equals(right))); + } else { + throw new Error.Execution(Error.Execution.Kind.InvalidType, "cannot compare closures"); } break; case Contains: @@ -419,6 +369,19 @@ public void evaluate( stack.push(new Term.Bool(leftS.get().contains(rightS.get()))); } + if (left instanceof Term.Array) { + List array = ((Term.Array) left).value(); + stack.push(new Term.Bool(array.contains(right))); + } + if (left instanceof Term.Map) { + HashMap map = ((Term.Map) left).value(); + if (right instanceof MapKey) { + MapKey key = (MapKey) right; + stack.push(new Term.Bool(map.containsKey(key))); + } else { + stack.push(new Term.Bool(false)); + } + } break; case Prefix: if (right instanceof Term.Str && left instanceof Term.Str) { @@ -437,6 +400,15 @@ public void evaluate( stack.push(new Term.Bool(leftS.get().startsWith(rightS.get()))); } + if (left instanceof Term.Array && right instanceof Term.Array) { + List leftArray = ((Term.Array) left).value(); + List rightArray = ((Term.Array) right).value(); + if (leftArray.size() < rightArray.size()) { + stack.push(new Term.Bool(false)); + } else { + stack.push(new Term.Bool(leftArray.subList(0, rightArray.size()).equals(rightArray))); + } + } break; case Suffix: if (right instanceof Term.Str && left instanceof Term.Str) { @@ -454,6 +426,19 @@ public void evaluate( } stack.push(new Term.Bool(leftS.get().endsWith(rightS.get()))); } + if (left instanceof Term.Array && right instanceof Term.Array) { + List leftArray = ((Term.Array) left).value(); + List rightArray = ((Term.Array) right).value(); + if (leftArray.size() < rightArray.size()) { + stack.push(new Term.Bool(false)); + } else { + stack.push( + new Term.Bool( + leftArray + .subList(leftArray.size() - rightArray.size(), leftArray.size()) + .equals(rightArray))); + } + } break; case Regex: if (right instanceof Term.Str && left instanceof Term.Str) { @@ -548,6 +533,32 @@ public void evaluate( stack.push(new Term.Bool(((Term.Bool) left).value() || ((Term.Bool) right).value())); } break; + case LazyAnd: + if (left instanceof Term.Bool && right instanceof Closure) { + if (((Term.Bool) left).value()) { + Closure closure = (Closure) right; + Term result = closure.call(variables, temporarySymbolTable); + if (result instanceof Term.Bool) { + stack.push((Term.Bool) result); + } + } else { + stack.push(new Term.Bool(false)); + } + } + break; + case LazyOr: + if (left instanceof Term.Bool && right instanceof Closure) { + if (((Term.Bool) left).value()) { + stack.push(new Term.Bool(true)); + } else { + Closure closure = (Closure) right; + Term result = closure.call(variables, temporarySymbolTable); + if (result instanceof Term.Bool) { + stack.push((Term.Bool) result); + } + } + } + break; case Intersection: if (right instanceof Term.Set && left instanceof Term.Set) { HashSet intersec = new HashSet(); @@ -592,6 +603,143 @@ public void evaluate( stack.push(new Term.Integer(r ^ l)); } break; + case Get: + if (right instanceof Term.Integer && left instanceof Term.Array) { + int index = (int) ((Term.Integer) right).value(); + List array = ((Term.Array) left).value(); + if (index >= array.size() || index < 0) { + stack.push(new Term.Null()); + } else { + Term element = array.get(index); + if (element != null) { + stack.push(element); + } else { + stack.push(new Term.Null()); + } + } + } + if (right instanceof MapKey && left instanceof Term.Map) { + MapKey key = (MapKey) right; + HashMap map = ((Term.Map) left).value(); + Term value = map.get(key); + if (value != null) { + stack.push(value); + } else { + stack.push(new Term.Null()); + } + } + break; + case Any: + if (right instanceof Closure) { + Closure closure = (Closure) right; + boolean result = false; + if (left instanceof Term.Array) { + List array = ((Term.Array) left).value(); + for (Term elem : array) { + Term returnValue = closure.call(elem, variables, temporarySymbolTable); + if (!(returnValue instanceof Term.Bool)) { + throw new Error.Execution("any op did not evaluate to a boolean"); + } + result = ((Term.Bool) returnValue).value(); + if (result) { + break; + } + } + } else if (left instanceof Term.Set) { + HashSet set = ((Term.Set) left).value(); + for (Term elem : set) { + Term returnValue = closure.call(elem, variables, temporarySymbolTable); + if (!(returnValue instanceof Term.Bool)) { + throw new Error.Execution("any op did not evaluate to a boolean"); + } + result = ((Term.Bool) returnValue).value(); + if (result) { + break; + } + } + } else if (left instanceof Term.Map) { + HashMap map = ((Term.Map) left).value(); + for (Map.Entry entry : map.entrySet()) { + List params = new ArrayList<>(List.of(entry.getKey(), entry.getValue())); + Term returnValue = + closure.call(new Term.Array(params), variables, temporarySymbolTable); + if (!(returnValue instanceof Term.Bool)) { + throw new Error.Execution("any op did not evaluate to a boolean"); + } + result = ((Term.Bool) returnValue).value(); + if (result) { + break; + } + } + } else { + throw new Error.Execution("left operand of any op is not a collection"); + } + stack.push(new Term.Bool(result)); + } else { + throw new Error.Execution("right operand of any op is not a closure"); + } + break; + case All: + if (right instanceof Closure) { + Closure closure = (Closure) right; + boolean result = true; + if (left instanceof Term.Array) { + List array = ((Term.Array) left).value(); + for (Term elem : array) { + Term returnValue = closure.call(elem, variables, temporarySymbolTable); + if (!(returnValue instanceof Term.Bool)) { + throw new Error.Execution("all op did not evaluate to a boolean"); + } + result = ((Term.Bool) returnValue).value(); + if (!result) { + break; + } + } + } else if (left instanceof Term.Set) { + HashSet set = ((Term.Set) left).value(); + for (Term elem : set) { + Term returnValue = closure.call(elem, variables, temporarySymbolTable); + if (!(returnValue instanceof Term.Bool)) { + throw new Error.Execution("all op did not evaluate to a boolean"); + } + result = ((Term.Bool) returnValue).value(); + if (!result) { + break; + } + } + } else if (left instanceof Term.Map) { + HashMap map = ((Term.Map) left).value(); + for (Map.Entry entry : map.entrySet()) { + ArrayList params = new ArrayList<>(List.of(entry.getKey(), entry.getValue())); + Term returnValue = + closure.call(new Term.Array(params), variables, temporarySymbolTable); + if (!(returnValue instanceof Term.Bool)) { + throw new Error.Execution("all op did not evaluate to a boolean"); + } + result = ((Term.Bool) returnValue).value(); + if (!result) { + break; + } + } + } else { + throw new Error.Execution("left operand of all op is not a collection"); + } + stack.push(new Term.Bool(result)); + } else { + throw new Error.Execution("right operand of all op is not a closure"); + } + break; + case TryOr: + if (left instanceof Closure) { + Closure closure = (Closure) left; + try { + Term leftValue = closure.call(variables, temporarySymbolTable); + stack.push(leftValue); + } catch (Error e) { + stack.push(right); + } + } + break; default: throw new Error.Execution("binary exec error for op" + this); } @@ -619,14 +767,22 @@ public String print(Deque stack, SymbolTable symbolTable) { s = left + " >= " + right; stack.push(s); break; - case Equal: + case HeterogeneousEqual: s = left + " == " + right; stack.push(s); break; - case NotEqual: + case HeterogeneousNotEqual: s = left + " != " + right; stack.push(s); break; + case Equal: + s = left + " === " + right; + stack.push(s); + break; + case NotEqual: + s = left + " !== " + right; + stack.push(s); + break; case Contains: s = left + ".contains(" + right + ")"; stack.push(s); @@ -667,6 +823,14 @@ public String print(Deque stack, SymbolTable symbolTable) { s = left + " || " + right; stack.push(s); break; + case LazyAnd: + s = left + " && " + right; + stack.push(s); + break; + case LazyOr: + s = left + " || " + right; + stack.push(s); + break; case Intersection: s = left + ".intersection(" + right + ")"; stack.push(s); @@ -687,6 +851,22 @@ public String print(Deque stack, SymbolTable symbolTable) { s = left + " ^ " + right; stack.push(s); break; + case Get: + s = left + ".get(" + right + ")"; + stack.push(s); + break; + case Any: + s = left + ".any(" + right + ")"; + stack.push(s); + break; + case All: + s = left + ".any(" + right + ")"; + stack.push(s); + break; + case TryOr: + s = left + ".try_or(" + right + ")"; + stack.push(s); + break; default: } @@ -718,6 +898,12 @@ public Schema.Op serialize() { case NotEqual: b1.setKind(Schema.OpBinary.Kind.NotEqual); break; + case HeterogeneousEqual: + b1.setKind(Schema.OpBinary.Kind.HeterogeneousEqual); + break; + case HeterogeneousNotEqual: + b1.setKind(Schema.OpBinary.Kind.HeterogeneousNotEqual); + break; case Contains: b1.setKind(Schema.OpBinary.Kind.Contains); break; @@ -748,6 +934,12 @@ public Schema.Op serialize() { case Or: b1.setKind(Schema.OpBinary.Kind.Or); break; + case LazyAnd: + b1.setKind(Schema.OpBinary.Kind.LazyAnd); + break; + case LazyOr: + b1.setKind(Schema.OpBinary.Kind.LazyOr); + break; case Intersection: b1.setKind(Schema.OpBinary.Kind.Intersection); break; @@ -763,6 +955,18 @@ public Schema.Op serialize() { case BitwiseXor: b1.setKind(Schema.OpBinary.Kind.BitwiseXor); break; + case Get: + b1.setKind(Schema.OpBinary.Kind.Get); + break; + case Any: + b1.setKind(Schema.OpBinary.Kind.Any); + break; + case All: + b1.setKind(Schema.OpBinary.Kind.All); + break; + case TryOr: + b1.setKind(Schema.OpBinary.Kind.Try); + break; default: } @@ -785,6 +989,10 @@ public static Result deserializeV1(Schema.OpBinary op) { return Result.ok(new Op.Binary(BinaryOp.Equal)); case NotEqual: return Result.ok(new Op.Binary(BinaryOp.NotEqual)); + case HeterogeneousEqual: + return Result.ok(new Op.Binary(BinaryOp.HeterogeneousEqual)); + case HeterogeneousNotEqual: + return Result.ok(new Op.Binary(BinaryOp.HeterogeneousNotEqual)); case Contains: return Result.ok(new Op.Binary(BinaryOp.Contains)); case Prefix: @@ -805,6 +1013,10 @@ public static Result deserializeV1(Schema.OpBinary op) { return Result.ok(new Op.Binary(BinaryOp.And)); case Or: return Result.ok(new Op.Binary(BinaryOp.Or)); + case LazyAnd: + return Result.ok(new Op.Binary(BinaryOp.LazyAnd)); + case LazyOr: + return Result.ok(new Op.Binary(BinaryOp.LazyOr)); case Intersection: return Result.ok(new Op.Binary(BinaryOp.Intersection)); case Union: @@ -815,6 +1027,14 @@ public static Result deserializeV1(Schema.OpBinary op) { return Result.ok(new Op.Binary(BinaryOp.BitwiseOr)); case BitwiseXor: return Result.ok(new Op.Binary(BinaryOp.BitwiseXor)); + case Get: + return Result.ok(new Op.Binary(BinaryOp.Get)); + case Any: + return Result.ok(new Op.Binary(BinaryOp.Any)); + case All: + return Result.ok(new Op.Binary(BinaryOp.All)); + case Try: + return Result.ok(new Op.Binary(BinaryOp.TryOr)); default: return Result.err( new Error.FormatError.DeserializationError( @@ -846,4 +1066,130 @@ public int hashCode() { return op.hashCode(); } } + + public static final class Closure extends Op { + private final ArrayList params; + private final ArrayList ops; + + public Closure(ArrayList params, ArrayList ops) { + this.params = params; + this.ops = ops; + } + + public void evaluate( + Deque stack, Map variables, TemporarySymbolTable temporarySymbolTable) + throws Error.Execution { + stack.push(this); + } + + int arity() { + return params.size(); + } + + Term call(Map variables, TemporarySymbolTable temporarySymbolTable) + throws Error.Execution { + if (arity() != 0) { + throw new Error.Execution("called closure with arity " + arity() + "with no arguments"); + } + Deque stack = new ArrayDeque(16); // Default value + for (Op op : ops) { + op.evaluate(stack, variables, temporarySymbolTable); + } + if (stack.size() != 1) { + throw new Error.Execution("invalid closure expression"); + } + return (Term) stack.pop(); + } + + Term call(Term arg, Map variables, TemporarySymbolTable temporarySymbolTable) + throws Error.Execution { + if (arity() != 1) { + throw new Error.Execution("called closure with arity " + arity() + "with 1 argument"); + } + if (variables.putIfAbsent(params.get(0), arg) != null) { + throw new Error.Execution( + Error.Execution.Kind.ShadowedVariable, "closure parameter shadows variable"); + } + Deque stack = new ArrayDeque(16); // Default value + for (Op op : ops) { + op.evaluate(stack, variables, temporarySymbolTable); + } + variables.remove(params.get(0)); + if (stack.size() != 1) { + throw new Error.Execution("invalid closure expression"); + } + return (Term) stack.pop(); + } + + public String print(Deque stack, SymbolTable symbols) { + String paramNames = null; + for (Long param : params) { + String paramName = symbols.getSymbol(param.intValue()).get(); + if (paramNames == null) { + paramNames = "$" + paramName; + } else { + paramNames = paramNames + ", $" + paramName; + } + } + + String s; + Deque bodyStack = new ArrayDeque<>(); + for (Op op : ops) { + op.print(bodyStack, symbols); + } + if (paramNames == null) { + s = bodyStack.remove(); + } else { + s = paramNames + " -> " + bodyStack.remove(); + } + stack.push(s); + return s; + } + + public Schema.Op serialize() { + Schema.Op.Builder b = Schema.Op.newBuilder(); + Schema.OpClosure.Builder b1 = Schema.OpClosure.newBuilder(); + + for (Long param : params) { + b1.addParams(param.intValue()); + } + + for (Op op : this.ops) { + b1.addOps(op.serialize()); + } + + b.setClosure(b1.build()); + + return b.build(); + } + + public static Result deserializeV1(Schema.OpClosure closure) { + ArrayList params = new ArrayList<>(); + ArrayList ops = new ArrayList<>(); + + for (long param : closure.getParamsList()) { + params.add(new Long(param)); + } + + for (Schema.Op op : closure.getOpsList()) { + var res = Op.deserializeV2(op); + if (res.isErr()) { + return Result.err(res.getErr()); + } else { + ops.add(res.getOk()); + } + } + + return Result.ok(new Op.Closure(params, ops)); + } + + public Expression toExpression(SymbolTable symbols) { + ArrayList paramNames = new ArrayList<>(); + for (Long param : params) { + paramNames.add(symbols.getSymbol(param.intValue()).get()); + } + Expression body = Expression.convertFrom(ops, symbols); + return new Expression.Closure(paramNames, body); + } + } } diff --git a/src/main/java/org/eclipse/biscuit/error/Error.java b/src/main/java/org/eclipse/biscuit/error/Error.java index 4c1cfbea..e71ddf90 100644 --- a/src/main/java/org/eclipse/biscuit/error/Error.java +++ b/src/main/java/org/eclipse/biscuit/error/Error.java @@ -705,7 +705,9 @@ public JsonNode toJson() { public static final class Execution extends Error { public enum Kind { Execution, - Overflow + Overflow, + ShadowedVariable, + InvalidType } Expression expr; @@ -765,6 +767,21 @@ public JsonNode toJson() { } } + public static final class Shadowing extends Error { + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + return o != null && getClass() == o.getClass(); + } + + @Override + public JsonNode toJson() { + return TextNode.valueOf("Shadowing"); + } + } + public static final class Parser extends Error { public final org.eclipse.biscuit.token.builder.parser.Error error; diff --git a/src/main/java/org/eclipse/biscuit/token/Authorizer.java b/src/main/java/org/eclipse/biscuit/token/Authorizer.java index 2e91dd1c..024afcd9 100644 --- a/src/main/java/org/eclipse/biscuit/token/Authorizer.java +++ b/src/main/java/org/eclipse/biscuit/token/Authorizer.java @@ -5,6 +5,8 @@ package org.eclipse.biscuit.token; +import static org.eclipse.biscuit.datalog.Check.Kind.REJECT; + import java.time.Instant; import java.util.ArrayList; import java.util.Date; @@ -32,7 +34,6 @@ import org.eclipse.biscuit.error.LogicError; import org.eclipse.biscuit.error.Result; import org.eclipse.biscuit.token.builder.Check; -import org.eclipse.biscuit.token.builder.Expression; import org.eclipse.biscuit.token.builder.Fact; import org.eclipse.biscuit.token.builder.Rule; import org.eclipse.biscuit.token.builder.Term; @@ -261,7 +262,7 @@ public Authorizer addRule(String s) throws Error.Parser { if (res.isErr()) { throw new Error.Parser(res.getErr()); } - return addRule(res.getOk()._2); + return addRule(res.getOk()._1); } public TrustedOrigins authorizerTrustedOrigins() { @@ -318,10 +319,7 @@ public Authorizer allow() { q.add( Utils.constrainedRule( - "allow", - new ArrayList<>(), - new ArrayList<>(), - List.of(new Expression.Value(new Term.Bool(true))))); + "allow", new ArrayList<>(), new ArrayList<>(), List.of(new Term.Bool(true)))); this.policies.add(new Policy(q, Policy.Kind.ALLOW)); return this; @@ -332,10 +330,7 @@ public Authorizer deny() { q.add( Utils.constrainedRule( - "deny", - new ArrayList<>(), - new ArrayList<>(), - List.of(new Expression.Value(new Term.Bool(true))))); + "deny", new ArrayList<>(), new ArrayList<>(), List.of(new Term.Bool(true)))); this.policies.add(new Policy(q, Policy.Kind.DENY)); return this; @@ -421,17 +416,19 @@ public Long authorize(RunLimits limits) throws Error { boolean successful = false; for (int j = 0; j < c.queries().size(); j++) { - boolean res = false; org.eclipse.biscuit.datalog.Rule query = c.queries().get(j); TrustedOrigins ruleTrustedOrigins = TrustedOrigins.fromScopes( query.scopes(), authorizerTrustedOrigins, Long.MAX_VALUE, this.publicKeyToBlockId); switch (c.kind()) { case ONE: - res = world.queryMatch(query, Long.MAX_VALUE, ruleTrustedOrigins, symbolTable); + successful = world.queryMatch(query, Long.MAX_VALUE, ruleTrustedOrigins, symbolTable); break; case ALL: - res = world.queryMatchAll(query, ruleTrustedOrigins, symbolTable); + successful = world.queryMatchAll(query, ruleTrustedOrigins, symbolTable); + break; + case REJECT: + successful = world.queryMatch(query, Long.MAX_VALUE, ruleTrustedOrigins, symbolTable); break; default: throw new RuntimeException("unmapped kind"); @@ -441,13 +438,12 @@ public Long authorize(RunLimits limits) throws Error { throw new Error.Timeout(); } - if (res) { - successful = true; + if (successful) { break; } } - if (!successful) { + if (successful == (c.kind() == REJECT)) { errors.add(new FailedCheck.FailedAuthorizer(i, symbolTable.formatCheck(c))); } } @@ -467,17 +463,19 @@ public Long authorize(RunLimits limits) throws Error { org.eclipse.biscuit.datalog.Check check = c.convert(symbolTable); for (int k = 0; k < check.queries().size(); k++) { - boolean res = false; org.eclipse.biscuit.datalog.Rule query = check.queries().get(k); TrustedOrigins ruleTrustedOrigins = TrustedOrigins.fromScopes( query.scopes(), authorityTrustedOrigins, 0, this.publicKeyToBlockId); switch (check.kind()) { case ONE: - res = world.queryMatch(query, (long) 0, ruleTrustedOrigins, symbolTable); + successful = world.queryMatch(query, (long) 0, ruleTrustedOrigins, symbolTable); break; case ALL: - res = world.queryMatchAll(query, ruleTrustedOrigins, symbolTable); + successful = world.queryMatchAll(query, ruleTrustedOrigins, symbolTable); + break; + case REJECT: + successful = world.queryMatch(query, (long) 0, ruleTrustedOrigins, symbolTable); break; default: throw new RuntimeException("unmapped kind"); @@ -487,13 +485,12 @@ public Long authorize(RunLimits limits) throws Error { throw new Error.Timeout(); } - if (res) { - successful = true; + if (successful) { break; } } - if (!successful) { + if (successful == (c.kind() == REJECT)) { errors.add(new FailedCheck.FailedBlock(0, j, symbolTable.formatCheck(check))); } } @@ -544,17 +541,19 @@ public Long authorize(RunLimits limits) throws Error { org.eclipse.biscuit.datalog.Check check = c.convert(symbolTable); for (int k = 0; k < check.queries().size(); k++) { - boolean res = false; org.eclipse.biscuit.datalog.Rule query = check.queries().get(k); TrustedOrigins ruleTrustedOrigins = TrustedOrigins.fromScopes( query.scopes(), blockTrustedOrigins, i + 1, this.publicKeyToBlockId); switch (check.kind()) { case ONE: - res = world.queryMatch(query, (long) i + 1, ruleTrustedOrigins, symbolTable); + successful = world.queryMatch(query, (long) i + 1, ruleTrustedOrigins, symbolTable); break; case ALL: - res = world.queryMatchAll(query, ruleTrustedOrigins, symbolTable); + successful = world.queryMatchAll(query, ruleTrustedOrigins, symbolTable); + break; + case REJECT: + successful = world.queryMatch(query, (long) i + 1, ruleTrustedOrigins, symbolTable); break; default: throw new RuntimeException("unmapped kind"); @@ -564,13 +563,12 @@ public Long authorize(RunLimits limits) throws Error { throw new Error.Timeout(); } - if (res) { - successful = true; + if (successful) { break; } } - if (!successful) { + if (successful == (check.kind() == REJECT)) { errors.add(new FailedCheck.FailedBlock(i + 1, j, symbolTable.formatCheck(check))); } } diff --git a/src/main/java/org/eclipse/biscuit/token/Block.java b/src/main/java/org/eclipse/biscuit/token/Block.java index 0b05af06..38d410d2 100644 --- a/src/main/java/org/eclipse/biscuit/token/Block.java +++ b/src/main/java/org/eclipse/biscuit/token/Block.java @@ -21,8 +21,6 @@ import org.eclipse.biscuit.datalog.SchemaVersion; import org.eclipse.biscuit.datalog.Scope; import org.eclipse.biscuit.datalog.SymbolTable; -import org.eclipse.biscuit.datalog.expressions.Expression; -import org.eclipse.biscuit.datalog.expressions.Op; import org.eclipse.biscuit.error.Error; import org.eclipse.biscuit.error.Result; import org.eclipse.biscuit.token.format.SerializedBiscuit; @@ -224,58 +222,10 @@ public Schema.Block serialize() { b.addPublicKeys(pk.serialize()); } - b.setVersion(getSchemaVersion()); + b.setVersion(SchemaVersion.version(facts, rules, checks, scopes, externalKey)); return b.build(); } - int getSchemaVersion() { - boolean containsScopes = !this.scopes.isEmpty(); - boolean containsCheckAll = false; - boolean containsV4 = false; - - for (Rule r : this.rules) { - containsScopes |= !r.scopes().isEmpty(); - for (Expression e : r.expressions()) { - containsV4 |= containsV4Op(e); - } - } - for (Check c : this.checks) { - containsCheckAll |= c.kind() == Check.Kind.ALL; - - for (Rule q : c.queries()) { - containsScopes |= !q.scopes().isEmpty(); - for (Expression e : q.expressions()) { - containsV4 |= containsV4Op(e); - } - } - } - - if (this.externalKey.isPresent()) { - return SerializedBiscuit.MAX_SCHEMA_VERSION; - - } else if (containsScopes || containsCheckAll || containsV4) { - return 4; - } else { - return SerializedBiscuit.MIN_SCHEMA_VERSION; - } - } - - boolean containsV4Op(Expression e) { - for (Op op : e.getOps()) { - if (op instanceof Op.Binary) { - Op.BinaryOp o = ((Op.Binary) op).getOp(); - if (o == Op.BinaryOp.BitwiseAnd - || o == Op.BinaryOp.BitwiseOr - || o == Op.BinaryOp.BitwiseXor - || o == Op.BinaryOp.NotEqual) { - return true; - } - } - } - - return false; - } - /** * Deserializes a block from its Protobuf representation * @@ -349,8 +299,7 @@ public static Result deserialize( } } - SchemaVersion schemaVersion = new SchemaVersion(facts, rules, checks, scopes); - var res = schemaVersion.checkCompatibility(version); + var res = SchemaVersion.checkCompatibility(version, facts, rules, checks, scopes, externalKey); if (res.isErr()) { Error.FormatError e = res.getErr(); return Result.err(e); diff --git a/src/main/java/org/eclipse/biscuit/token/builder/Biscuit.java b/src/main/java/org/eclipse/biscuit/token/builder/Biscuit.java index 1006dc70..962fd8dc 100644 --- a/src/main/java/org/eclipse/biscuit/token/builder/Biscuit.java +++ b/src/main/java/org/eclipse/biscuit/token/builder/Biscuit.java @@ -148,7 +148,7 @@ private org.eclipse.biscuit.token.Biscuit build(SymbolTable symbolTable) throws for (Scope s : this.scopes) { scopes.add(s.convert(symbolTable)); } - SchemaVersion schemaVersion = new SchemaVersion(facts, rules, checks, scopes); + int version = SchemaVersion.version(facts, rules, checks, scopes, Optional.empty()); SymbolTable blockSymbols = new SymbolTable(); @@ -171,7 +171,7 @@ private org.eclipse.biscuit.token.Biscuit build(SymbolTable symbolTable) throws scopes, publicKeys, Optional.empty(), - schemaVersion.version()); + version); if (this.rootKeyId.isPresent()) { return org.eclipse.biscuit.token.Biscuit.make( diff --git a/src/main/java/org/eclipse/biscuit/token/builder/Block.java b/src/main/java/org/eclipse/biscuit/token/builder/Block.java index dc7fce0d..540f18f3 100644 --- a/src/main/java/org/eclipse/biscuit/token/builder/Block.java +++ b/src/main/java/org/eclipse/biscuit/token/builder/Block.java @@ -128,7 +128,7 @@ public org.eclipse.biscuit.token.Block build( for (Scope s : this.scopes) { scopes.add(s.convert(symbolTable)); } - SchemaVersion schemaVersion = new SchemaVersion(facts, rules, checks, scopes); + int version = SchemaVersion.version(facts, rules, checks, scopes, externalKey); SymbolTable blockSymbols = new SymbolTable(); @@ -142,15 +142,7 @@ public org.eclipse.biscuit.token.Block build( } return new org.eclipse.biscuit.token.Block( - blockSymbols, - this.context, - facts, - rules, - checks, - scopes, - publicKeys, - externalKey, - schemaVersion.version()); + blockSymbols, this.context, facts, rules, checks, scopes, publicKeys, externalKey, version); } @Override @@ -211,10 +203,7 @@ public Block resourcePrefix(String prefix) { Arrays.asList(var("resource")), Arrays.asList(pred("resource", Arrays.asList(var("resource")))), Arrays.asList( - new Expression.Binary( - Expression.Op.Prefix, - new Expression.Value(var("resource")), - new Expression.Value(string(prefix)))))); + new Expression.Binary(Expression.OpCode.Prefix, var("resource"), string(prefix))))); return this.addCheck(new Check(ONE, queries)); } @@ -227,10 +216,7 @@ public Block resourceSuffix(String suffix) { Arrays.asList(var("resource")), Arrays.asList(pred("resource", Arrays.asList(var("resource")))), Arrays.asList( - new Expression.Binary( - Expression.Op.Suffix, - new Expression.Value(var("resource")), - new Expression.Value(string(suffix)))))); + new Expression.Binary(Expression.OpCode.Suffix, var("resource"), string(suffix))))); return this.addCheck(new Check(ONE, queries)); } @@ -243,10 +229,7 @@ public Block setExpirationDate(Date d) { Arrays.asList(var("date")), Arrays.asList(pred("time", Arrays.asList(var("date")))), Arrays.asList( - new Expression.Binary( - Expression.Op.LessOrEqual, - new Expression.Value(var("date")), - new Expression.Value(date(d)))))); + new Expression.Binary(Expression.OpCode.LessOrEqual, var("date"), date(d))))); return this.addCheck(new Check(ONE, queries)); } diff --git a/src/main/java/org/eclipse/biscuit/token/builder/Check.java b/src/main/java/org/eclipse/biscuit/token/builder/Check.java index 296c38d7..cf1fe0e5 100644 --- a/src/main/java/org/eclipse/biscuit/token/builder/Check.java +++ b/src/main/java/org/eclipse/biscuit/token/builder/Check.java @@ -5,23 +5,22 @@ package org.eclipse.biscuit.token.builder; -import static org.eclipse.biscuit.datalog.Check.Kind.ONE; - import java.util.ArrayList; import java.util.List; import java.util.stream.Collectors; +import org.eclipse.biscuit.datalog.Check.Kind; import org.eclipse.biscuit.datalog.SymbolTable; public final class Check { - private final org.eclipse.biscuit.datalog.Check.Kind kind; + private final Kind kind; private final List queries; - public Check(org.eclipse.biscuit.datalog.Check.Kind kind, List queries) { + public Check(Kind kind, List queries) { this.kind = kind; this.queries = queries; } - public Check(org.eclipse.biscuit.datalog.Check.Kind kind, Rule query) { + public Check(Kind kind, Rule query) { this.kind = kind; ArrayList r = new ArrayList<>(); @@ -48,15 +47,24 @@ public static Check convertFrom(org.eclipse.biscuit.datalog.Check r, SymbolTable return new Check(r.kind(), queries); } + public Kind kind() { + return kind; + } + @Override public String toString() { final List qs = queries.stream().map((q) -> q.bodyToString()).collect(Collectors.toList()); - if (kind == ONE) { - return "check if " + String.join(" or ", qs); - } else { - return "check all " + String.join(" or ", qs); + switch (kind) { + case ONE: + return "check if " + String.join(" or ", qs); + case ALL: + return "check all " + String.join(" or ", qs); + case REJECT: + return "reject if " + String.join(" or ", qs); + default: + return "check if " + String.join(" or ", qs); } } diff --git a/src/main/java/org/eclipse/biscuit/token/builder/Expression.java b/src/main/java/org/eclipse/biscuit/token/builder/Expression.java index 9fd98993..7ac58d27 100644 --- a/src/main/java/org/eclipse/biscuit/token/builder/Expression.java +++ b/src/main/java/org/eclipse/biscuit/token/builder/Expression.java @@ -11,112 +11,140 @@ import java.util.List; import java.util.Set; import org.eclipse.biscuit.datalog.SymbolTable; +import org.eclipse.biscuit.datalog.expressions.Op; +import org.eclipse.biscuit.error.Error; public abstract class Expression { - public final org.eclipse.biscuit.datalog.expressions.Expression convert(SymbolTable symbolTable) { - ArrayList ops = new ArrayList<>(); + public final org.eclipse.biscuit.datalog.expressions.Expression convertExpr( + SymbolTable symbolTable) { + ArrayList ops = new ArrayList<>(); this.toOpcodes(symbolTable, ops); return new org.eclipse.biscuit.datalog.expressions.Expression(ops); } - public static Expression convertFrom( - org.eclipse.biscuit.datalog.expressions.Expression e, SymbolTable symbolTable) { - ArrayList ops = new ArrayList<>(); + public static Expression convertFrom(ArrayList ops, SymbolTable symbolTable) { Deque stack = new ArrayDeque(16); - for (org.eclipse.biscuit.datalog.expressions.Op op : e.getOps()) { - if (op instanceof org.eclipse.biscuit.datalog.expressions.Op.Value) { - org.eclipse.biscuit.datalog.expressions.Op.Value v = - (org.eclipse.biscuit.datalog.expressions.Op.Value) op; - stack.push(new Expression.Value(Term.convertFrom(v.getValue(), symbolTable))); - } else if (op instanceof org.eclipse.biscuit.datalog.expressions.Op.Unary) { - org.eclipse.biscuit.datalog.expressions.Op.Unary v = - (org.eclipse.biscuit.datalog.expressions.Op.Unary) op; + for (Op op : ops) { + if (op instanceof org.eclipse.biscuit.datalog.Term) { + org.eclipse.biscuit.datalog.Term v = (org.eclipse.biscuit.datalog.Term) op; + stack.push(Term.convertFrom(v, symbolTable)); + } else if (op instanceof Op.Closure) { + Op.Closure closure = (Op.Closure) op; + stack.push(closure.toExpression(symbolTable)); + } else if (op instanceof Op.Unary) { + Op.Unary v = (Op.Unary) op; Expression e1 = stack.pop(); switch (v.getOp()) { case Length: - stack.push(new Expression.Unary(Op.Length, e1)); + stack.push(new Expression.Unary(OpCode.Length, e1)); break; case Negate: - stack.push(new Expression.Unary(Op.Negate, e1)); + stack.push(new Expression.Unary(OpCode.Negate, e1)); break; case Parens: - stack.push(new Expression.Unary(Op.Parens, e1)); + stack.push(new Expression.Unary(OpCode.Parens, e1)); + break; + case TypeOf: + stack.push(new Expression.Unary(OpCode.TypeOf, e1)); break; default: return null; } - } else if (op instanceof org.eclipse.biscuit.datalog.expressions.Op.Binary) { - org.eclipse.biscuit.datalog.expressions.Op.Binary v = - (org.eclipse.biscuit.datalog.expressions.Op.Binary) op; + } else if (op instanceof Op.Binary) { + Op.Binary v = (Op.Binary) op; Expression e2 = stack.pop(); Expression e1 = stack.pop(); switch (v.getOp()) { case LessThan: - stack.push(new Expression.Binary(Op.LessThan, e1, e2)); + stack.push(new Expression.Binary(OpCode.LessThan, e1, e2)); break; case GreaterThan: - stack.push(new Expression.Binary(Op.GreaterThan, e1, e2)); + stack.push(new Expression.Binary(OpCode.GreaterThan, e1, e2)); break; case LessOrEqual: - stack.push(new Expression.Binary(Op.LessOrEqual, e1, e2)); + stack.push(new Expression.Binary(OpCode.LessOrEqual, e1, e2)); break; case GreaterOrEqual: - stack.push(new Expression.Binary(Op.GreaterOrEqual, e1, e2)); + stack.push(new Expression.Binary(OpCode.GreaterOrEqual, e1, e2)); break; case Equal: - stack.push(new Expression.Binary(Op.Equal, e1, e2)); + stack.push(new Expression.Binary(OpCode.Equal, e1, e2)); break; case NotEqual: - stack.push(new Expression.Binary(Op.NotEqual, e1, e2)); + stack.push(new Expression.Binary(OpCode.NotEqual, e1, e2)); + break; + case HeterogeneousEqual: + stack.push(new Expression.Binary(OpCode.HeterogeneousEqual, e1, e2)); + break; + case HeterogeneousNotEqual: + stack.push(new Expression.Binary(OpCode.HeterogeneousNotEqual, e1, e2)); break; case Contains: - stack.push(new Expression.Binary(Op.Contains, e1, e2)); + stack.push(new Expression.Binary(OpCode.Contains, e1, e2)); break; case Prefix: - stack.push(new Expression.Binary(Op.Prefix, e1, e2)); + stack.push(new Expression.Binary(OpCode.Prefix, e1, e2)); break; case Suffix: - stack.push(new Expression.Binary(Op.Suffix, e1, e2)); + stack.push(new Expression.Binary(OpCode.Suffix, e1, e2)); break; case Regex: - stack.push(new Expression.Binary(Op.Regex, e1, e2)); + stack.push(new Expression.Binary(OpCode.Regex, e1, e2)); break; case Add: - stack.push(new Expression.Binary(Op.Add, e1, e2)); + stack.push(new Expression.Binary(OpCode.Add, e1, e2)); break; case Sub: - stack.push(new Expression.Binary(Op.Sub, e1, e2)); + stack.push(new Expression.Binary(OpCode.Sub, e1, e2)); break; case Mul: - stack.push(new Expression.Binary(Op.Mul, e1, e2)); + stack.push(new Expression.Binary(OpCode.Mul, e1, e2)); break; case Div: - stack.push(new Expression.Binary(Op.Div, e1, e2)); + stack.push(new Expression.Binary(OpCode.Div, e1, e2)); break; case And: - stack.push(new Expression.Binary(Op.And, e1, e2)); + stack.push(new Expression.Binary(OpCode.And, e1, e2)); break; case Or: - stack.push(new Expression.Binary(Op.Or, e1, e2)); + stack.push(new Expression.Binary(OpCode.Or, e1, e2)); + break; + case LazyAnd: + stack.push(new Expression.Binary(OpCode.LazyAnd, e1, e2)); + break; + case LazyOr: + stack.push(new Expression.Binary(OpCode.LazyOr, e1, e2)); break; case Intersection: - stack.push(new Expression.Binary(Op.Intersection, e1, e2)); + stack.push(new Expression.Binary(OpCode.Intersection, e1, e2)); break; case Union: - stack.push(new Expression.Binary(Op.Union, e1, e2)); + stack.push(new Expression.Binary(OpCode.Union, e1, e2)); break; case BitwiseAnd: - stack.push(new Expression.Binary(Op.BitwiseAnd, e1, e2)); + stack.push(new Expression.Binary(OpCode.BitwiseAnd, e1, e2)); break; case BitwiseOr: - stack.push(new Expression.Binary(Op.BitwiseOr, e1, e2)); + stack.push(new Expression.Binary(OpCode.BitwiseOr, e1, e2)); break; case BitwiseXor: - stack.push(new Expression.Binary(Op.BitwiseXor, e1, e2)); + stack.push(new Expression.Binary(OpCode.BitwiseXor, e1, e2)); + break; + case Get: + stack.push(new Expression.Binary(OpCode.Get, e1, e2)); + break; + case Any: + stack.push(new Expression.Binary(OpCode.Any, e1, e2)); + break; + case All: + stack.push(new Expression.Binary(OpCode.All, e1, e2)); + break; + case TryOr: + stack.push(new Expression.Binary(OpCode.TryOr, e1, e2)); break; default: return null; @@ -127,12 +155,11 @@ public static Expression convertFrom( return stack.pop(); } - public abstract void toOpcodes( - SymbolTable symbolTable, List ops); + public abstract void toOpcodes(SymbolTable symbolTable, List ops); - public abstract void gatherVariables(Set variables); + public abstract void gatherVariables(Set variables) throws Error.Shadowing; - public enum Op { + public enum OpCode { Negate, Parens, LessThan, @@ -141,6 +168,8 @@ public enum Op { GreaterOrEqual, Equal, NotEqual, + HeterogeneousEqual, + HeterogeneousNotEqual, Contains, Prefix, Suffix, @@ -151,93 +180,52 @@ public enum Op { Div, And, Or, + LazyAnd, + LazyOr, Length, Intersection, Union, BitwiseAnd, BitwiseOr, - BitwiseXor - } - - public static final class Value extends Expression { - public final Term value; - - public Value(Term value) { - this.value = value; - } - - public void toOpcodes( - SymbolTable symbolTable, List ops) { - ops.add( - new org.eclipse.biscuit.datalog.expressions.Op.Value(this.value.convert(symbolTable))); - } - - public void gatherVariables(Set variables) { - if (this.value instanceof Term.Variable) { - variables.add(((Term.Variable) this.value).value); - } - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - - Value value1 = (Value) o; - - return value != null ? value.equals(value1.value) : value1.value == null; - } - - @Override - public int hashCode() { - return value != null ? value.hashCode() : 0; - } - - @Override - public String toString() { - return value.toString(); - } + BitwiseXor, + TypeOf, + Get, + Any, + All, + TryOr, } public static final class Unary extends Expression { - private final Op op; + private final OpCode op; private final Expression arg1; - public Unary(Op op, Expression arg1) { + public Unary(OpCode op, Expression arg1) { this.op = op; this.arg1 = arg1; } - public void toOpcodes( - SymbolTable symbolTable, List ops) { + public void toOpcodes(SymbolTable symbolTable, List ops) { this.arg1.toOpcodes(symbolTable, ops); switch (this.op) { case Negate: - ops.add( - new org.eclipse.biscuit.datalog.expressions.Op.Unary( - org.eclipse.biscuit.datalog.expressions.Op.UnaryOp.Negate)); + ops.add(new Op.Unary(Op.UnaryOp.Negate)); break; case Parens: - ops.add( - new org.eclipse.biscuit.datalog.expressions.Op.Unary( - org.eclipse.biscuit.datalog.expressions.Op.UnaryOp.Parens)); + ops.add(new Op.Unary(Op.UnaryOp.Parens)); break; case Length: - ops.add( - new org.eclipse.biscuit.datalog.expressions.Op.Unary( - org.eclipse.biscuit.datalog.expressions.Op.UnaryOp.Length)); + ops.add(new Op.Unary(Op.UnaryOp.Length)); + break; + case TypeOf: + ops.add(new Op.Unary(Op.UnaryOp.TypeOf)); break; default: - throw new RuntimeException("unmapped ops"); + throw new RuntimeException("unmapped ops: " + this.op); } } - public void gatherVariables(Set variables) { + public void gatherVariables(Set variables) throws Error.Shadowing { this.arg1.gatherVariables(variables); } @@ -274,6 +262,8 @@ public String toString() { return "(" + arg1 + ")"; case Length: return arg1.toString() + ".length()"; + case TypeOf: + return arg1.toString() + ".type()"; default: return ""; } @@ -281,133 +271,114 @@ public String toString() { } public static final class Binary extends Expression { - private final Op op; + private final OpCode op; private final Expression arg1; private final Expression arg2; - public Binary(Op op, Expression arg1, Expression arg2) { + public Binary(OpCode op, Expression arg1, Expression arg2) { this.op = op; this.arg1 = arg1; this.arg2 = arg2; } - public void toOpcodes( - SymbolTable symbolTable, List ops) { + public void toOpcodes(SymbolTable symbolTable, List ops) { this.arg1.toOpcodes(symbolTable, ops); this.arg2.toOpcodes(symbolTable, ops); switch (this.op) { case LessThan: - ops.add( - new org.eclipse.biscuit.datalog.expressions.Op.Binary( - org.eclipse.biscuit.datalog.expressions.Op.BinaryOp.LessThan)); + ops.add(new Op.Binary(Op.BinaryOp.LessThan)); break; case GreaterThan: - ops.add( - new org.eclipse.biscuit.datalog.expressions.Op.Binary( - org.eclipse.biscuit.datalog.expressions.Op.BinaryOp.GreaterThan)); + ops.add(new Op.Binary(Op.BinaryOp.GreaterThan)); break; case LessOrEqual: - ops.add( - new org.eclipse.biscuit.datalog.expressions.Op.Binary( - org.eclipse.biscuit.datalog.expressions.Op.BinaryOp.LessOrEqual)); + ops.add(new Op.Binary(Op.BinaryOp.LessOrEqual)); break; case GreaterOrEqual: - ops.add( - new org.eclipse.biscuit.datalog.expressions.Op.Binary( - org.eclipse.biscuit.datalog.expressions.Op.BinaryOp.GreaterOrEqual)); + ops.add(new Op.Binary(Op.BinaryOp.GreaterOrEqual)); break; case Equal: - ops.add( - new org.eclipse.biscuit.datalog.expressions.Op.Binary( - org.eclipse.biscuit.datalog.expressions.Op.BinaryOp.Equal)); + ops.add(new Op.Binary(Op.BinaryOp.Equal)); break; case NotEqual: - ops.add( - new org.eclipse.biscuit.datalog.expressions.Op.Binary( - org.eclipse.biscuit.datalog.expressions.Op.BinaryOp.NotEqual)); + ops.add(new Op.Binary(Op.BinaryOp.NotEqual)); + break; + case HeterogeneousEqual: + ops.add(new Op.Binary(Op.BinaryOp.HeterogeneousEqual)); + break; + case HeterogeneousNotEqual: + ops.add(new Op.Binary(Op.BinaryOp.HeterogeneousNotEqual)); break; case Contains: - ops.add( - new org.eclipse.biscuit.datalog.expressions.Op.Binary( - org.eclipse.biscuit.datalog.expressions.Op.BinaryOp.Contains)); + ops.add(new Op.Binary(Op.BinaryOp.Contains)); break; case Prefix: - ops.add( - new org.eclipse.biscuit.datalog.expressions.Op.Binary( - org.eclipse.biscuit.datalog.expressions.Op.BinaryOp.Prefix)); + ops.add(new Op.Binary(Op.BinaryOp.Prefix)); break; case Suffix: - ops.add( - new org.eclipse.biscuit.datalog.expressions.Op.Binary( - org.eclipse.biscuit.datalog.expressions.Op.BinaryOp.Suffix)); + ops.add(new Op.Binary(Op.BinaryOp.Suffix)); break; case Regex: - ops.add( - new org.eclipse.biscuit.datalog.expressions.Op.Binary( - org.eclipse.biscuit.datalog.expressions.Op.BinaryOp.Regex)); + ops.add(new Op.Binary(Op.BinaryOp.Regex)); break; case Add: - ops.add( - new org.eclipse.biscuit.datalog.expressions.Op.Binary( - org.eclipse.biscuit.datalog.expressions.Op.BinaryOp.Add)); + ops.add(new Op.Binary(Op.BinaryOp.Add)); break; case Sub: - ops.add( - new org.eclipse.biscuit.datalog.expressions.Op.Binary( - org.eclipse.biscuit.datalog.expressions.Op.BinaryOp.Sub)); + ops.add(new Op.Binary(Op.BinaryOp.Sub)); break; case Mul: - ops.add( - new org.eclipse.biscuit.datalog.expressions.Op.Binary( - org.eclipse.biscuit.datalog.expressions.Op.BinaryOp.Mul)); + ops.add(new Op.Binary(Op.BinaryOp.Mul)); break; case Div: - ops.add( - new org.eclipse.biscuit.datalog.expressions.Op.Binary( - org.eclipse.biscuit.datalog.expressions.Op.BinaryOp.Div)); + ops.add(new Op.Binary(Op.BinaryOp.Div)); break; case And: - ops.add( - new org.eclipse.biscuit.datalog.expressions.Op.Binary( - org.eclipse.biscuit.datalog.expressions.Op.BinaryOp.And)); + ops.add(new Op.Binary(Op.BinaryOp.And)); break; case Or: - ops.add( - new org.eclipse.biscuit.datalog.expressions.Op.Binary( - org.eclipse.biscuit.datalog.expressions.Op.BinaryOp.Or)); + ops.add(new Op.Binary(Op.BinaryOp.Or)); + break; + case LazyAnd: + ops.add(new Op.Binary(Op.BinaryOp.LazyAnd)); + break; + case LazyOr: + ops.add(new Op.Binary(Op.BinaryOp.LazyOr)); break; case Intersection: - ops.add( - new org.eclipse.biscuit.datalog.expressions.Op.Binary( - org.eclipse.biscuit.datalog.expressions.Op.BinaryOp.Intersection)); + ops.add(new Op.Binary(Op.BinaryOp.Intersection)); break; case Union: - ops.add( - new org.eclipse.biscuit.datalog.expressions.Op.Binary( - org.eclipse.biscuit.datalog.expressions.Op.BinaryOp.Union)); + ops.add(new Op.Binary(Op.BinaryOp.Union)); break; case BitwiseAnd: - ops.add( - new org.eclipse.biscuit.datalog.expressions.Op.Binary( - org.eclipse.biscuit.datalog.expressions.Op.BinaryOp.BitwiseAnd)); + ops.add(new Op.Binary(Op.BinaryOp.BitwiseAnd)); break; case BitwiseOr: - ops.add( - new org.eclipse.biscuit.datalog.expressions.Op.Binary( - org.eclipse.biscuit.datalog.expressions.Op.BinaryOp.BitwiseOr)); + ops.add(new Op.Binary(Op.BinaryOp.BitwiseOr)); break; case BitwiseXor: - ops.add( - new org.eclipse.biscuit.datalog.expressions.Op.Binary( - org.eclipse.biscuit.datalog.expressions.Op.BinaryOp.BitwiseXor)); + ops.add(new Op.Binary(Op.BinaryOp.BitwiseXor)); + break; + case Get: + ops.add(new Op.Binary(Op.BinaryOp.Get)); + break; + case Any: + ops.add(new Op.Binary(Op.BinaryOp.Any)); + break; + case All: + ops.add(new Op.Binary(Op.BinaryOp.All)); + break; + case TryOr: + ops.add(new Op.Binary(Op.BinaryOp.TryOr)); break; default: - throw new RuntimeException("unmapped ops"); + throw new RuntimeException("unmapped ops: " + this.op); } } - public void gatherVariables(Set variables) { + public void gatherVariables(Set variables) throws Error.Shadowing { this.arg1.gatherVariables(variables); this.arg2.gatherVariables(variables); } @@ -452,8 +423,12 @@ public String toString() { case GreaterOrEqual: return arg1.toString() + " >= " + arg2.toString(); case Equal: - return arg1.toString() + " == " + arg2.toString(); + return arg1.toString() + " === " + arg2.toString(); case NotEqual: + return arg1.toString() + " !== " + arg2.toString(); + case HeterogeneousEqual: + return arg1.toString() + " == " + arg2.toString(); + case HeterogeneousNotEqual: return arg1.toString() + " != " + arg2.toString(); case Contains: return arg1.toString() + ".contains(" + arg2.toString() + ")"; @@ -475,6 +450,10 @@ public String toString() { return arg1.toString() + " && " + arg2.toString(); case Or: return arg1.toString() + " || " + arg2.toString(); + case LazyAnd: + return arg1.toString() + " && " + arg2.toString(); + case LazyOr: + return arg1.toString() + " || " + arg2.toString(); case Intersection: return arg1.toString() + ".intersection(" + arg2.toString() + ")"; case Union: @@ -485,9 +464,96 @@ public String toString() { return arg1.toString() + " | " + arg2.toString(); case BitwiseXor: return arg1.toString() + " ^ " + arg2.toString(); + case Get: + return arg1.toString() + ".get(" + arg2.toString() + ")"; + case Any: + return arg1.toString() + ".any(" + arg2.toString() + ")"; + case All: + return arg1.toString() + ".all(" + arg2.toString() + ")"; + case TryOr: + return arg1.toString() + ".try_or(" + arg2.toString() + ")"; default: return ""; } } } + + public static final class Closure extends Expression { + private ArrayList params; + private Expression body; + + public Closure(Expression body) { + this.params = new ArrayList<>(); + this.body = body; + } + + public Closure(ArrayList params, Expression body) { + this.params = params; + this.body = body; + } + + public void toOpcodes(SymbolTable symbolTable, List ops) { + ArrayList paramIndexes = new ArrayList(); + for (String param : params) { + long index = symbolTable.insert(param); + paramIndexes.add(new Long(index)); + } + ArrayList bodyOps = new ArrayList<>(); + body.toOpcodes(symbolTable, bodyOps); + ops.add(new Op.Closure(paramIndexes, bodyOps)); + } + + public void gatherVariables(Set variables) throws Error.Shadowing { + for (String param : params) { + if (variables.contains(param)) { + throw new Error.Shadowing(); + } + } + body.gatherVariables(variables); + for (String param : params) { + variables.remove(param); + } + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + Closure closure = (Closure) o; + + if (params.size() != closure.params.size()) { + return false; + } + + for (int i = 0; i < params.size(); i++) { + if (params.get(i) != closure.params.get(i)) { + return false; + } + } + + return body.equals(closure.body); + } + + @Override + public String toString() { + if (params.size() == 0) { + return body.toString(); + } + + String s = null; + for (String param : params) { + if (s == null) { + s = "$" + param; + } else { + s = s + ", $" + param; + } + } + return s + " -> " + body.toString(); + } + } } diff --git a/src/main/java/org/eclipse/biscuit/token/builder/MapKey.java b/src/main/java/org/eclipse/biscuit/token/builder/MapKey.java new file mode 100644 index 00000000..20079ce4 --- /dev/null +++ b/src/main/java/org/eclipse/biscuit/token/builder/MapKey.java @@ -0,0 +1,7 @@ +package org.eclipse.biscuit.token.builder; + +import org.eclipse.biscuit.datalog.SymbolTable; + +public abstract class MapKey extends Term { + public abstract org.eclipse.biscuit.datalog.MapKey convertMapKey(SymbolTable symbolTable); +} diff --git a/src/main/java/org/eclipse/biscuit/token/builder/Rule.java b/src/main/java/org/eclipse/biscuit/token/builder/Rule.java index 9dcdcfa4..73ce4501 100644 --- a/src/main/java/org/eclipse/biscuit/token/builder/Rule.java +++ b/src/main/java/org/eclipse/biscuit/token/builder/Rule.java @@ -45,10 +45,10 @@ public Rule( } } for (Expression e : expressions) { - if (e instanceof Expression.Value) { - Expression.Value ev = (Expression.Value) e; - if (ev.value instanceof Term.Variable) { - variables.put(((Term.Variable) ev.value).value, Optional.empty()); + if (e instanceof Term) { + Term term = (Term) e; + if (term instanceof Term.Variable) { + variables.put(((Term.Variable) term).value, Optional.empty()); } } } @@ -116,14 +116,14 @@ public void applyVariables() { this.expressions.stream() .flatMap( e -> { - if (e instanceof Expression.Value) { - Expression.Value ev = (Expression.Value) e; - if (ev.value instanceof Term.Variable) { + if (e instanceof Term) { + Term term = (Term) e; + if (term instanceof Term.Variable) { Optional t = laVariables.getOrDefault( - ((Term.Variable) ev.value).value, Optional.empty()); + ((Term.Variable) term).value, Optional.empty()); if (t.isPresent()) { - return Stream.of(new Expression.Value(t.get())); + return Stream.of(t.get()); } } } @@ -147,7 +147,11 @@ public Result validateVariables() { .collect(Collectors.toSet()); for (Expression e : this.expressions) { - e.gatherVariables(freeVariables); + try { + e.gatherVariables(freeVariables); + } catch (Error.Shadowing err) { + return Result.err("rule expression contains closure parameters which shadow variables"); + } } if (freeVariables.isEmpty()) { return Result.ok(this); @@ -182,7 +186,7 @@ public org.eclipse.biscuit.datalog.Rule convert(SymbolTable symbolTable) { } for (Expression e : r.expressions) { - expressions.add(e.convert(symbolTable)); + expressions.add(e.convertExpr(symbolTable)); } for (Scope s : r.scopes) { @@ -202,7 +206,7 @@ public static Rule convertFrom(org.eclipse.biscuit.datalog.Rule r, SymbolTable s } for (org.eclipse.biscuit.datalog.expressions.Expression e : r.expressions()) { - expressions.add(Expression.convertFrom(e, symbolTable)); + expressions.add(Expression.convertFrom(e.getOps(), symbolTable)); } for (org.eclipse.biscuit.datalog.Scope s : r.scopes()) { diff --git a/src/main/java/org/eclipse/biscuit/token/builder/Term.java b/src/main/java/org/eclipse/biscuit/token/builder/Term.java index c348047b..c5778e6b 100644 --- a/src/main/java/org/eclipse/biscuit/token/builder/Term.java +++ b/src/main/java/org/eclipse/biscuit/token/builder/Term.java @@ -10,18 +10,31 @@ import java.time.format.DateTimeFormatter; import java.util.Arrays; import java.util.Collections; +import java.util.HashMap; import java.util.HashSet; import java.util.Objects; +import java.util.stream.Collectors; import org.eclipse.biscuit.datalog.SymbolTable; +import org.eclipse.biscuit.datalog.expressions.Op; -public abstract class Term { +public abstract class Term extends Expression { public abstract org.eclipse.biscuit.datalog.Term convert(SymbolTable symbolTable); public static Term convertFrom(org.eclipse.biscuit.datalog.Term id, SymbolTable symbols) { return id.toTerm(symbols); } - public static final class Str extends Term { + public void toOpcodes(SymbolTable symbolTable, java.util.List ops) { + ops.add(this.convert(symbolTable)); + } + + public void gatherVariables(java.util.Set variables) { + if (this instanceof Term.Variable) { + variables.add(((Term.Variable) this).value); + } + } + + public static final class Str extends MapKey { final String value; public Str(String value) { @@ -33,6 +46,10 @@ public org.eclipse.biscuit.datalog.Term convert(SymbolTable symbolTable) { return new org.eclipse.biscuit.datalog.Term.Str(symbolTable.insert(this.value)); } + public org.eclipse.biscuit.datalog.MapKey convertMapKey(SymbolTable symbolTable) { + return new org.eclipse.biscuit.datalog.Term.Str(symbolTable.insert(this.value)); + } + public String getValue() { return value; } @@ -101,7 +118,7 @@ public int hashCode() { } } - public static final class Integer extends Term { + public static final class Integer extends MapKey { final long value; public Integer(long value) { @@ -117,6 +134,10 @@ public org.eclipse.biscuit.datalog.Term convert(SymbolTable symbolTable) { return new org.eclipse.biscuit.datalog.Term.Integer(this.value); } + public org.eclipse.biscuit.datalog.MapKey convertMapKey(SymbolTable symbolTable) { + return new org.eclipse.biscuit.datalog.Term.Integer(this.value); + } + @Override public String toString() { return String.valueOf(value); @@ -183,6 +204,36 @@ public int hashCode() { } } + public static final class Null extends Term { + public Null() {} + + public org.eclipse.biscuit.datalog.Term convert(SymbolTable symbolTable) { + return new org.eclipse.biscuit.datalog.Term.Null(); + } + + @Override + public String toString() { + return "null"; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + return true; + } + + @Override + public int hashCode() { + return 0; + } + } + public static final class Date extends Term { final long value; @@ -272,6 +323,99 @@ public int hashCode() { } } + public static final class Array extends Term { + final java.util.List value; + + public Array(java.util.List value) { + this.value = value; + } + + @Override + public org.eclipse.biscuit.datalog.Term convert(SymbolTable symbolTable) { + return new org.eclipse.biscuit.datalog.Term.Array( + value.stream().map(t -> t.convert(symbolTable)).collect(Collectors.toList())); + } + + public java.util.List getValue() { + return Collections.unmodifiableList(value); + } + + @Override + public String toString() { + return value.toString(); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + Array array = (Array) o; + + return Objects.equals(value, array.value); + } + + @Override + public int hashCode() { + return value != null ? value.hashCode() : 0; + } + } + + public static final class Map extends Term { + final HashMap value; + + public Map(HashMap value) { + this.value = value; + } + + @Override + public org.eclipse.biscuit.datalog.Term convert(SymbolTable symbolTable) { + HashMap s = + new HashMap<>(); + + for (java.util.Map.Entry i : this.value.entrySet()) { + s.put(i.getKey().convertMapKey(symbolTable), i.getValue().convert(symbolTable)); + } + + return new org.eclipse.biscuit.datalog.Term.Map(s); + } + + public java.util.Map getValue() { + return Collections.unmodifiableMap(value); + } + + @Override + public String toString() { + return value.entrySet().stream() + .map(entry -> entry.getKey() + ": " + entry.getValue()) + .sorted() + .collect(Collectors.joining(", ", "{", "}")); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + Map map = (Map) o; + + return Objects.equals(value, map.value); + } + + @Override + public int hashCode() { + return value != null ? value.hashCode() : 0; + } + } + public static final class Set extends Term { final java.util.Set value; @@ -296,7 +440,19 @@ public java.util.Set getValue() { @Override public String toString() { - return value.toString(); + if (value.size() == 0) { + return "{,}"; + } + String s = "{"; + int count = 0; + for (Term elem : value) { + s += elem; + count += 1; + if (count < value.size()) { + s += ", "; + } + } + return s + "}"; } @Override diff --git a/src/main/java/org/eclipse/biscuit/token/builder/parser/ExpressionParser.java b/src/main/java/org/eclipse/biscuit/token/builder/parser/ExpressionParser.java index be033cb3..ffa8b04a 100644 --- a/src/main/java/org/eclipse/biscuit/token/builder/parser/ExpressionParser.java +++ b/src/main/java/org/eclipse/biscuit/token/builder/parser/ExpressionParser.java @@ -7,7 +7,9 @@ import static org.eclipse.biscuit.token.builder.parser.Parser.space; import static org.eclipse.biscuit.token.builder.parser.Parser.term; +import static org.eclipse.biscuit.token.builder.parser.Parser.variable; +import java.util.ArrayList; import org.eclipse.biscuit.datalog.Pair; import org.eclipse.biscuit.error.Result; import org.eclipse.biscuit.token.builder.Expression; @@ -54,7 +56,7 @@ public static Result, Error> expr(String s) { if (res2.isErr()) { break; } - Pair t2 = res2.getOk(); + Pair t2 = res2.getOk(); s = t2._1; s = space(s); @@ -66,8 +68,8 @@ public static Result, Error> expr(String s) { Pair t3 = res3.getOk(); s = t3._1; - Expression e2 = t3._2; - Expression.Op op = t2._2; + Expression e2 = new Expression.Closure(t3._2); + Expression.OpCode op = t2._2; e = new Expression.Binary(op, e, e2); } @@ -97,7 +99,7 @@ public static Result, Error> expr1(String s) { if (res2.isErr()) { break; } - Pair t2 = res2.getOk(); + Pair t2 = res2.getOk(); s = t2._1; s = space(s); @@ -109,8 +111,8 @@ public static Result, Error> expr1(String s) { Pair t3 = res3.getOk(); s = t3._1; - Expression e2 = t3._2; - Expression.Op op = t2._2; + Expression e2 = new Expression.Closure(t3._2); + Expression.OpCode op = t2._2; e = new Expression.Binary(op, e, e2); } @@ -135,7 +137,7 @@ public static Result, Error> expr2(String s) { if (res2.isErr()) { return Result.ok(t1); } - Pair t2 = res2.getOk(); + Pair t2 = res2.getOk(); s = t2._1; s = space(s); @@ -148,7 +150,7 @@ public static Result, Error> expr2(String s) { s = t3._1; Expression e2 = t3._2; - Expression.Op op = t2._2; + Expression.OpCode op = t2._2; Expression e = t1._2; e = new Expression.Binary(op, e, e2); @@ -178,7 +180,7 @@ public static Result, Error> expr3(String s) { if (res2.isErr()) { break; } - Pair t2 = res2.getOk(); + Pair t2 = res2.getOk(); s = t2._1; s = space(s); @@ -192,7 +194,7 @@ public static Result, Error> expr3(String s) { s = t3._1; Expression e2 = t3._2; - Expression.Op op = t2._2; + Expression.OpCode op = t2._2; e = new Expression.Binary(op, e, e2); } @@ -222,7 +224,7 @@ public static Result, Error> expr4(String s) { if (res2.isErr()) { break; } - Pair t2 = res2.getOk(); + Pair t2 = res2.getOk(); s = t2._1; s = space(s); @@ -236,7 +238,7 @@ public static Result, Error> expr4(String s) { s = t3._1; Expression e2 = t3._2; - Expression.Op op = t2._2; + Expression.OpCode op = t2._2; e = new Expression.Binary(op, e, e2); } @@ -266,7 +268,7 @@ public static Result, Error> expr5(String s) { if (res2.isErr()) { break; } - Pair t2 = res2.getOk(); + Pair t2 = res2.getOk(); s = t2._1; s = space(s); @@ -280,7 +282,7 @@ public static Result, Error> expr5(String s) { s = t3._1; Expression e2 = t3._2; - Expression.Op op = t2._2; + Expression.OpCode op = t2._2; e = new Expression.Binary(op, e, e2); } @@ -310,7 +312,7 @@ public static Result, Error> expr6(String s) { if (res2.isErr()) { break; } - Pair t2 = res2.getOk(); + Pair t2 = res2.getOk(); s = t2._1; s = space(s); @@ -324,7 +326,7 @@ public static Result, Error> expr6(String s) { s = t3._1; Expression e2 = t3._2; - Expression.Op op = t2._2; + Expression.OpCode op = t2._2; e = new Expression.Binary(op, e, e2); } @@ -354,7 +356,7 @@ public static Result, Error> expr7(String s) { if (res2.isErr()) { break; } - Pair t2 = res2.getOk(); + Pair t2 = res2.getOk(); s = t2._1; s = space(s); @@ -368,7 +370,7 @@ public static Result, Error> expr7(String s) { s = t3._1; Expression e2 = t3._2; - Expression.Op op = t2._2; + Expression.OpCode op = t2._2; e = new Expression.Binary(op, e, e2); } @@ -389,7 +391,7 @@ public static Result, Error> expr8(String s) { } Pair t = res.getOk(); - return Result.ok(new Pair<>(t._1, new Expression.Unary(Expression.Op.Negate, t._2))); + return Result.ok(new Pair<>(t._1, new Expression.Unary(Expression.OpCode.Negate, t._2))); } else { return expr9(s); } @@ -421,7 +423,7 @@ public static Result, Error> expr9(String s) { s = s.substring(1); var res2 = binaryOp8(s); if (!res2.isErr()) { - Pair t2 = res2.getOk(); + Pair t2 = res2.getOk(); s = space(t2._1); if (!s.startsWith("(")) { @@ -430,7 +432,13 @@ public static Result, Error> expr9(String s) { s = space(s.substring(1)); - var res3 = expr(s); + Expression.OpCode op = t2._2; + Result, Error> res3; + if (op == Expression.OpCode.Any || op == Expression.OpCode.All) { + res3 = exprClosure(s); + } else { + res3 = expr(s); + } if (res3.isErr()) { return Result.err(res3.getErr()); } @@ -444,12 +452,18 @@ public static Result, Error> expr9(String s) { s = space(s.substring(1)); Expression e2 = t3._2; - Expression.Op op = t2._2; + if (op == Expression.OpCode.TryOr) { + e = new Expression.Closure(e); + } + e = new Expression.Binary(op, e, e2); } else { if (s.startsWith("length()")) { - e = new Expression.Unary(Expression.Op.Length, e); - s = s.substring(9); + e = new Expression.Unary(Expression.OpCode.Length, e); + s = s.substring(8); + } else if (s.startsWith("type()")) { + e = new Expression.Unary(Expression.OpCode.TypeOf, e); + s = s.substring(6); } } } @@ -457,6 +471,41 @@ public static Result, Error> expr9(String s) { return Result.ok(new Pair<>(s, e)); } + public static Result, Error> exprClosure(String s) { + ArrayList params = new ArrayList<>(); + + while (true) { + s = space(s); + var paramResult = variable(s); + if (paramResult.isErr()) { + return Result.err(paramResult.getErr()); + } + var paramPair = paramResult.getOk(); + s = space(paramPair._1); + params.add(paramPair._2.getValue()); + + if (!s.startsWith(",")) { + break; + } + s = s.substring(1); + } + + if (!s.startsWith("->")) { + return Result.err(new Error(s, "missing ->")); + } + s = space(s.substring(2)); + + var bodyResult = expr(s); + if (bodyResult.isErr()) { + return Result.err(bodyResult.getErr()); + } + Pair bodyPair = bodyResult.getOk(); + s = space(bodyPair._1); + Expression body = bodyPair._2; + + return Result.ok(new Pair<>(s, new Expression.Closure(params, body))); + } + public static Result, Error> exprTerm(String s) { var res1 = unaryParens(s); if (res1.isOk()) { @@ -468,7 +517,7 @@ public static Result, Error> exprTerm(String s) { return Result.err(res2.getErr()); } Pair t2 = res2.getOk(); - Expression e = new Expression.Value(t2._2); + Expression e = t2._2; return Result.ok(new Pair<>(t2._1, e)); } @@ -485,7 +534,7 @@ public static Result, Error> unary(String s) { } Pair t = res.getOk(); - return Result.ok(new Pair<>(t._1, new Expression.Unary(Expression.Op.Negate, t._2))); + return Result.ok(new Pair<>(t._1, new Expression.Unary(Expression.OpCode.Negate, t._2))); } if (s.startsWith("(")) { @@ -503,7 +552,7 @@ public static Result, Error> unary(String s) { if (res.isOk()) { Pair t = res.getOk(); s = space(t._1); - e = new Expression.Value(t._2); + e = t._2; } else { var res2 = unaryParens(s); if (res2.isErr()) { @@ -517,7 +566,10 @@ public static Result, Error> unary(String s) { if (s.startsWith(".length()")) { s = space(s.substring(9)); - return Result.ok(new Pair<>(s, new Expression.Unary(Expression.Op.Length, e))); + return Result.ok(new Pair<>(s, new Expression.Unary(Expression.OpCode.Length, e))); + } else if (s.startsWith(".type()")) { + s = s.substring(7); + return Result.ok(new Pair<>(s, new Expression.Unary(Expression.OpCode.TypeOf, e))); } else { return Result.err(new Error(s, "unexpected token")); } @@ -540,116 +592,133 @@ public static Result, Error> unaryParens(String s) { } s = space(s.substring(1)); - return Result.ok(new Pair<>(s, new Expression.Unary(Expression.Op.Parens, t._2))); + return Result.ok(new Pair<>(s, new Expression.Unary(Expression.OpCode.Parens, t._2))); } else { return Result.err(new Error(s, "missing (")); } } - public static Result, Error> binaryOp0(String s) { + public static Result, Error> binaryOp0(String s) { if (s.startsWith("||")) { - return Result.ok(new Pair<>(s.substring(2), Expression.Op.Or)); + return Result.ok(new Pair<>(s.substring(2), Expression.OpCode.LazyOr)); } return Result.err(new Error(s, "unrecognized op")); } - public static Result, Error> binaryOp1(String s) { + public static Result, Error> binaryOp1(String s) { if (s.startsWith("&&")) { - return Result.ok(new Pair<>(s.substring(2), Expression.Op.And)); + return Result.ok(new Pair<>(s.substring(2), Expression.OpCode.LazyAnd)); } return Result.err(new Error(s, "unrecognized op")); } - public static Result, Error> binaryOp2(String s) { + public static Result, Error> binaryOp2(String s) { if (s.startsWith("<=")) { - return Result.ok(new Pair<>(s.substring(2), Expression.Op.LessOrEqual)); + return Result.ok(new Pair<>(s.substring(2), Expression.OpCode.LessOrEqual)); } if (s.startsWith(">=")) { - return Result.ok(new Pair<>(s.substring(2), Expression.Op.GreaterOrEqual)); + return Result.ok(new Pair<>(s.substring(2), Expression.OpCode.GreaterOrEqual)); } if (s.startsWith("<")) { - return Result.ok(new Pair<>(s.substring(1), Expression.Op.LessThan)); + return Result.ok(new Pair<>(s.substring(1), Expression.OpCode.LessThan)); } if (s.startsWith(">")) { - return Result.ok(new Pair<>(s.substring(1), Expression.Op.GreaterThan)); + return Result.ok(new Pair<>(s.substring(1), Expression.OpCode.GreaterThan)); + } + if (s.startsWith("===")) { + return Result.ok(new Pair<>(s.substring(3), Expression.OpCode.Equal)); + } + if (s.startsWith("!==")) { + return Result.ok(new Pair<>(s.substring(3), Expression.OpCode.NotEqual)); } if (s.startsWith("==")) { - return Result.ok(new Pair<>(s.substring(2), Expression.Op.Equal)); + return Result.ok(new Pair<>(s.substring(2), Expression.OpCode.HeterogeneousEqual)); } if (s.startsWith("!=")) { - return Result.ok(new Pair<>(s.substring(2), Expression.Op.NotEqual)); + return Result.ok(new Pair<>(s.substring(2), Expression.OpCode.HeterogeneousNotEqual)); } return Result.err(new Error(s, "unrecognized op")); } - public static Result, Error> binaryOp3(String s) { + public static Result, Error> binaryOp3(String s) { if (s.startsWith("^")) { - return Result.ok(new Pair<>(s.substring(1), Expression.Op.BitwiseXor)); + return Result.ok(new Pair<>(s.substring(1), Expression.OpCode.BitwiseXor)); } return Result.err(new Error(s, "unrecognized op")); } - public static Result, Error> binaryOp4(String s) { + public static Result, Error> binaryOp4(String s) { if (s.startsWith("|") && !s.startsWith("||")) { - return Result.ok(new Pair<>(s.substring(1), Expression.Op.BitwiseOr)); + return Result.ok(new Pair<>(s.substring(1), Expression.OpCode.BitwiseOr)); } return Result.err(new Error(s, "unrecognized op")); } - public static Result, Error> binaryOp5(String s) { + public static Result, Error> binaryOp5(String s) { if (s.startsWith("&") && !s.startsWith("&&")) { - return Result.ok(new Pair<>(s.substring(1), Expression.Op.BitwiseAnd)); + return Result.ok(new Pair<>(s.substring(1), Expression.OpCode.BitwiseAnd)); } return Result.err(new Error(s, "unrecognized op")); } - public static Result, Error> binaryOp6(String s) { - + public static Result, Error> binaryOp6(String s) { if (s.startsWith("+")) { - return Result.ok(new Pair<>(s.substring(1), Expression.Op.Add)); + return Result.ok(new Pair<>(s.substring(1), Expression.OpCode.Add)); } if (s.startsWith("-")) { - return Result.ok(new Pair<>(s.substring(1), Expression.Op.Sub)); + return Result.ok(new Pair<>(s.substring(1), Expression.OpCode.Sub)); } return Result.err(new Error(s, "unrecognized op")); } - public static Result, Error> binaryOp7(String s) { + public static Result, Error> binaryOp7(String s) { if (s.startsWith("*")) { - return Result.ok(new Pair<>(s.substring(1), Expression.Op.Mul)); + return Result.ok(new Pair<>(s.substring(1), Expression.OpCode.Mul)); } if (s.startsWith("/")) { - return Result.ok(new Pair<>(s.substring(1), Expression.Op.Div)); + return Result.ok(new Pair<>(s.substring(1), Expression.OpCode.Div)); } return Result.err(new Error(s, "unrecognized op")); } - public static Result, Error> binaryOp8(String s) { + public static Result, Error> binaryOp8(String s) { if (s.startsWith("intersection")) { - return Result.ok(new Pair<>(s.substring(12), Expression.Op.Intersection)); + return Result.ok(new Pair<>(s.substring(12), Expression.OpCode.Intersection)); } if (s.startsWith("union")) { - return Result.ok(new Pair<>(s.substring(5), Expression.Op.Union)); + return Result.ok(new Pair<>(s.substring(5), Expression.OpCode.Union)); } if (s.startsWith("contains")) { - return Result.ok(new Pair<>(s.substring(8), Expression.Op.Contains)); + return Result.ok(new Pair<>(s.substring(8), Expression.OpCode.Contains)); } if (s.startsWith("starts_with")) { - return Result.ok(new Pair<>(s.substring(11), Expression.Op.Prefix)); + return Result.ok(new Pair<>(s.substring(11), Expression.OpCode.Prefix)); } if (s.startsWith("ends_with")) { - return Result.ok(new Pair<>(s.substring(9), Expression.Op.Suffix)); + return Result.ok(new Pair<>(s.substring(9), Expression.OpCode.Suffix)); } if (s.startsWith("matches")) { - return Result.ok(new Pair<>(s.substring(7), Expression.Op.Regex)); + return Result.ok(new Pair<>(s.substring(7), Expression.OpCode.Regex)); + } + if (s.startsWith("get")) { + return Result.ok(new Pair<>(s.substring(3), Expression.OpCode.Get)); + } + if (s.startsWith("any")) { + return Result.ok(new Pair<>(s.substring(3), Expression.OpCode.Any)); + } + if (s.startsWith("all")) { + return Result.ok(new Pair<>(s.substring(3), Expression.OpCode.All)); + } + if (s.startsWith("try_or")) { + return Result.ok(new Pair<>(s.substring(6), Expression.OpCode.TryOr)); } return Result.err(new Error(s, "unrecognized op")); diff --git a/src/main/java/org/eclipse/biscuit/token/builder/parser/Parser.java b/src/main/java/org/eclipse/biscuit/token/builder/parser/Parser.java index c2b976fd..f1b8992f 100644 --- a/src/main/java/org/eclipse/biscuit/token/builder/parser/Parser.java +++ b/src/main/java/org/eclipse/biscuit/token/builder/parser/Parser.java @@ -22,6 +22,7 @@ import org.eclipse.biscuit.token.builder.Check; import org.eclipse.biscuit.token.builder.Expression; import org.eclipse.biscuit.token.builder.Fact; +import org.eclipse.biscuit.token.builder.MapKey; import org.eclipse.biscuit.token.builder.Predicate; import org.eclipse.biscuit.token.builder.Rule; import org.eclipse.biscuit.token.builder.Scope; @@ -252,6 +253,9 @@ public static Result, Error> check(String s) { } else if (s.startsWith("check all")) { kind = org.eclipse.biscuit.datalog.Check.Kind.ALL; s = s.substring("check all".length()); + } else if (s.startsWith("reject if")) { + kind = org.eclipse.biscuit.datalog.Check.Kind.REJECT; + s = s.substring("reject if".length()); } else { return Result.err(new Error(s, "missing check prefix")); } @@ -309,13 +313,18 @@ public static Result>, Error> checkBody(String s) { var body = bodyRes.getOk(); s = body.head; - // FIXME: parse scopes - queries.add( + + Rule rule = new Rule( new Predicate("query", new ArrayList<>()), body.predicates, body.expressions, - body.scopes)); + body.scopes); + var valid = rule.validateVariables(); + if (valid.isErr()) { + return Result.err(new Error(s, valid.getErr())); + } + queries.add(valid.getOk()); int i = 0; while (true) { @@ -397,6 +406,10 @@ public static Result, Error> predicate(String s) { String name = tn._1; s = tn._2; + if (name.length() == 0) { + return Result.err(new Error(s, "no predicate name")); + } + s = space(s); if (s.length() == 0 || s.charAt(0) != '(') { return Result.err(new Error(s, "opening parens not found for predicate " + name)); @@ -589,12 +602,30 @@ public static Result, Error> term(String s) { return Result.ok(new Pair<>(t._1, t._2)); } + var res9 = array(s); + if (res9.isOk()) { + Pair t = res9.getOk(); + return Result.ok(new Pair<>(t._1, t._2)); + } + + var res10 = map(s); + if (res10.isOk()) { + Pair t = res10.getOk(); + return Result.ok(new Pair<>(t._1, t._2)); + } + var res7 = set(s); if (res7.isOk()) { Pair t = res7.getOk(); return Result.ok(new Pair<>(t._1, t._2)); } + var res11 = nullTerm(s); + if (res11.isOk()) { + Pair t = res11.getOk(); + return Result.ok(new Pair<>(t._1, t._2)); + } + var res6 = bool(s); if (res6.isOk()) { Pair t = res6.getOk(); @@ -633,12 +664,30 @@ public static Result, Error> factTerm(String s) { return Result.ok(new Pair<>(t._1, t._2)); } + var res9 = array(s); + if (res9.isOk()) { + Pair t = res9.getOk(); + return Result.ok(new Pair<>(t._1, t._2)); + } + + var res10 = map(s); + if (res10.isOk()) { + Pair t = res10.getOk(); + return Result.ok(new Pair<>(t._1, t._2)); + } + var res7 = set(s); if (res7.isOk()) { Pair t = res7.getOk(); return Result.ok(new Pair<>(t._1, t._2)); } + var res11 = nullTerm(s); + if (res11.isOk()) { + Pair t = res11.getOk(); + return Result.ok(new Pair<>(t._1, t._2)); + } + var res6 = bool(s); if (res6.isOk()) { Pair t = res6.getOk(); @@ -666,6 +715,22 @@ public static Result, Error> factTerm(String s) { return Result.err(new Error(s, "unrecognized value")); } + public static Result, Error> mapKey(String s) { + var res1 = string(s); + if (res1.isOk()) { + Pair t = res1.getOk(); + return Result.ok(new Pair<>(t._1, t._2)); + } + + var res2 = integer(s); + if (res2.isOk()) { + Pair t = res2.getOk(); + return Result.ok(new Pair<>(t._1, t._2)); + } + + return Result.err(new Error(s, "unrecognized value")); + } + public static Result, Error> string(String s) { if (s.charAt(0) != '"') { return Result.err(new Error(s, "not a string")); @@ -727,7 +792,8 @@ public static Result, Error> integer(String s) { } public static Result, Error> date(String s) { - Pair t = takewhile(s, (c) -> c != ' ' && c != ',' && c != ')' && c != ']'); + Pair t = + takewhile(s, (c) -> c != ' ' && c != ',' && c != ')' && c != '}' && c != ']'); try { OffsetDateTime d = OffsetDateTime.parse(t._1); @@ -750,6 +816,15 @@ public static Result, Error> variable(String s) { return Result.ok(new Pair(t._2, (Term.Variable) Utils.var(t._1))); } + public static Result, Error> nullTerm(String s) { + if (s.startsWith("null")) { + s = s.substring(4); + return Result.ok(new Pair<>(s, new Term.Null())); + } else { + return Result.err(new Error(s, "not a null")); + } + } + public static Result, Error> bool(String s) { boolean b; if (s.startsWith("true")) { @@ -765,13 +840,125 @@ public static Result, Error> bool(String s) { return Result.ok(new Pair<>(s, new Term.Bool(b))); } - public static Result, Error> set(String s) { + public static Result, Error> array(String s) { if (s.isEmpty() || s.charAt(0) != '[') { - return Result.err(new Error(s, "not a set")); + return Result.err(new Error(s, "not an array")); + } + s = s.substring(1); + + ArrayList terms = new ArrayList(); + while (true) { + + s = space(s); + + var res = factTerm(s); + if (res.isErr()) { + break; + } + + Pair t = res.getOk(); + + if (t._2 instanceof Term.Variable) { + return Result.err(new Error(s, "arrays cannot contain variables")); + } + + s = t._1; + terms.add(t._2); + + s = space(s); + + if (s.isEmpty() || s.charAt(0) != ',') { + break; + } else { + s = s.substring(1); + } + } + + s = space(s); + if (s.isEmpty() || s.charAt(0) != ']') { + return Result.err(new Error(s, "closing bracket not found")); } + String remaining = s.substring(1); + + return Result.ok(new Pair<>(remaining, new Term.Array(terms))); + } + + public static Result, Error> map(String s) { + if (s.isEmpty() || s.charAt(0) != '{') { + return Result.err(new Error(s, "not a map")); + } s = s.substring(1); + HashMap v = new HashMap(); + while (true) { + s = space(s); + + var resKey = mapKey(s); + if (resKey.isErr()) { + break; + } + + Pair t1 = resKey.getOk(); + s = space(t1._1); + MapKey key = t1._2; + + if (s.isEmpty() || s.charAt(0) != ':') { + return Result.err(new Error(s, "colon not found in map")); + } + + s = s.substring(1); + s = space(s); + + var resVal = factTerm(s); + if (resVal.isErr()) { + break; + } + + Pair t2 = resVal.getOk(); + s = space(t2._1); + + if (t2._2 instanceof Term.Variable) { + return Result.err(new Error(s, "maps cannot contain variables")); + } + Term value = t2._2; + v.put(key, value); + + if (s.isEmpty() || s.charAt(0) != ',') { + break; + } else { + s = s.substring(1); + } + } + + s = space(s); + if (s.isEmpty() || s.charAt(0) != '}') { + return Result.err(new Error(s, "closing brace not found")); + } + + String remaining = s.substring(1); + + return Result.ok(new Pair<>(remaining, new Term.Map(v))); + } + + public static Result, Error> set(String s) { + + if (s.isEmpty() || s.charAt(0) != '{') { + return Result.err(new Error(s, "not a set")); + } + + s = space(s.substring(1)); + + if (s.charAt(0) == ',') { + s = space(s.substring(1)); + if (s.charAt(0) == '}') { + s = s.substring(1); + return Result.ok(new Pair<>(s, new Term.Set(new HashSet()))); + } else { + return Result.err(new Error(s, "closing brace not found")); + } + } + HashSet terms = new HashSet(); while (true) { @@ -801,8 +988,8 @@ public static Result, Error> set(String s) { } s = space(s); - if (s.isEmpty() || s.charAt(0) != ']') { - return Result.err(new Error(s, "closing square bracket not found")); + if (s.isEmpty() || s.charAt(0) != '}') { + return Result.err(new Error(s, "closing brace not found")); } String remaining = s.substring(1); diff --git a/src/main/java/org/eclipse/biscuit/token/format/SerializedBiscuit.java b/src/main/java/org/eclipse/biscuit/token/format/SerializedBiscuit.java index f527f8f7..7bbc2179 100644 --- a/src/main/java/org/eclipse/biscuit/token/format/SerializedBiscuit.java +++ b/src/main/java/org/eclipse/biscuit/token/format/SerializedBiscuit.java @@ -37,7 +37,7 @@ public final class SerializedBiscuit { // minimum supported version of the serialization format public static final int MIN_SCHEMA_VERSION = 3; // maximum supported version of the serialization format - public static final int MAX_SCHEMA_VERSION = 5; + public static final int MAX_SCHEMA_VERSION = 6; // starting version for datalog 3.1 features (check all, bitwise operators, !=, …) public static final int DATALOG_3_1 = 4; // starting version for 3rd party blocks (datalog 3.2) diff --git a/src/test/java/org/eclipse/biscuit/builder/BuilderTest.java b/src/test/java/org/eclipse/biscuit/builder/BuilderTest.java index 36474870..953a3a39 100644 --- a/src/test/java/org/eclipse/biscuit/builder/BuilderTest.java +++ b/src/test/java/org/eclipse/biscuit/builder/BuilderTest.java @@ -58,15 +58,14 @@ public void testBuild() throws Error.Language, Error.SymbolTableOverlap, Error.F Utils.var("operation")))), Arrays.asList( new Expression.Binary( - Expression.Op.Contains, - new Expression.Value(Utils.var("operation")), - new Expression.Value( - new Term.Set( - new HashSet<>( - Arrays.asList( - Utils.str("create_topic"), - Utils.str("get_topic"), - Utils.str("get_topics"))))))))); + Expression.OpCode.Contains, + Utils.var("operation"), + new Term.Set( + new HashSet<>( + Arrays.asList( + Utils.str("create_topic"), + Utils.str("get_topic"), + Utils.str("get_topics")))))))); authorityBuilder.addRule( Utils.constrainedRule( "right", @@ -87,10 +86,9 @@ public void testBuild() throws Error.Language, Error.SymbolTableOverlap, Error.F Utils.var("operation")))), Arrays.asList( new Expression.Binary( - Expression.Op.Contains, - new Expression.Value(Utils.var("operation")), - new Expression.Value( - new Term.Set(new HashSet<>(Arrays.asList(Utils.str("lookup"))))))))); + Expression.OpCode.Contains, + Utils.var("operation"), + new Term.Set(new HashSet<>(Arrays.asList(Utils.str("lookup")))))))); org.eclipse.biscuit.token.Block authority = authorityBuilder.build(symbolTable); Biscuit rootBiscuit = Biscuit.make(rng, root, authority); @@ -120,11 +118,13 @@ public void testStringValueOfSetTerm() { String actual = new Term.Set(Set.of(new Term.Str("a"), new Term.Str("b"), new Term.Integer((3)))) .toString(); - assertTrue(actual.startsWith("["), "starts with ["); - assertTrue(actual.endsWith("]"), "ends with ]"); + assertTrue(actual.startsWith("{"), "starts with {"); + assertTrue(actual.endsWith("}"), "ends with }"); assertTrue(actual.contains("\"a\""), "contains a"); assertTrue(actual.contains("\"b\""), "contains b"); assertTrue(actual.contains("3"), "contains 3"); + String empty = new Term.Set(new java.util.HashSet()).toString(); + assertEquals("{,}", empty); } @Test diff --git a/src/test/java/org/eclipse/biscuit/builder/parser/ParserTest.java b/src/test/java/org/eclipse/biscuit/builder/parser/ParserTest.java index 35df0c29..aa90e997 100644 --- a/src/test/java/org/eclipse/biscuit/builder/parser/ParserTest.java +++ b/src/test/java/org/eclipse/biscuit/builder/parser/ParserTest.java @@ -127,9 +127,9 @@ void testRuleWithExpression() { Utils.pred("resource", List.of(Utils.string("file1")))), List.of( new Expression.Binary( - Expression.Op.LessOrEqual, - new Expression.Value(Utils.var("0")), - new Expression.Value(new Term.Date(1575452801))))))), + Expression.OpCode.LessOrEqual, + Utils.var("0"), + new Term.Date(1575452801)))))), res); } @@ -150,62 +150,55 @@ void testRuleWithExpressionOrdering() { Utils.pred("resource", List.of(Utils.string("file1")))), List.of( new Expression.Binary( - Expression.Op.LessOrEqual, - new Expression.Value(Utils.var("0")), - new Expression.Value(new Term.Date(1575452801))))))), + Expression.OpCode.LessOrEqual, + Utils.var("0"), + new Term.Date(1575452801)))))), res); } @Test void expressionIntersectionAndContainsTest() { - var res = Parser.expression("[1, 2, 3].intersection([1, 2]).contains(1)"); + var res = Parser.expression("{1, 2, 3}.intersection({1, 2}).contains(1)"); assertEquals( Result.ok( new Pair<>( "", new Expression.Binary( - Expression.Op.Contains, + Expression.OpCode.Contains, new Expression.Binary( - Expression.Op.Intersection, - new Expression.Value( - Utils.set( - new HashSet<>( - Arrays.asList( - Utils.integer(1), Utils.integer(2), Utils.integer(3))))), - new Expression.Value( - Utils.set( - new HashSet<>(Arrays.asList(Utils.integer(1), Utils.integer(2)))))), - new Expression.Value(Utils.integer(1))))), + Expression.OpCode.Intersection, + Utils.set( + new HashSet<>( + Arrays.asList( + Utils.integer(1), Utils.integer(2), Utils.integer(3)))), + Utils.set( + new HashSet<>(Arrays.asList(Utils.integer(1), Utils.integer(2))))), + Utils.integer(1)))), res); } @Test void expressionIntersectionAndContainsAndLengthEqualsTest() { - var res = Parser.expression("[1, 2, 3].intersection([1, 2]).length() == 2"); + var res = Parser.expression("{1, 2, 3}.intersection({1, 2}).length() === 2"); assertEquals( Result.ok( new Pair<>( "", new Expression.Binary( - Expression.Op.Equal, + Expression.OpCode.Equal, new Expression.Unary( - Expression.Op.Length, + Expression.OpCode.Length, new Expression.Binary( - Expression.Op.Intersection, - new Expression.Value( - Utils.set( - new HashSet<>( - Arrays.asList( - Utils.integer(1), - Utils.integer(2), - Utils.integer(3))))), - new Expression.Value( - Utils.set( - new HashSet<>( - Arrays.asList(Utils.integer(1), Utils.integer(2))))))), - new Expression.Value(Utils.integer(2))))), + Expression.OpCode.Intersection, + Utils.set( + new HashSet<>( + Arrays.asList( + Utils.integer(1), Utils.integer(2), Utils.integer(3)))), + Utils.set( + new HashSet<>(Arrays.asList(Utils.integer(1), Utils.integer(2)))))), + Utils.integer(2)))), res); } @@ -223,11 +216,10 @@ void testNegatePrecedence() { new ArrayList<>(), List.of( new Expression.Binary( - Expression.Op.And, + Expression.OpCode.LazyAnd, new Expression.Unary( - Expression.Op.Negate, - new Expression.Value(new Term.Bool(false))), - new Expression.Value(new Term.Bool(true)))))))), + Expression.OpCode.Negate, new Term.Bool(false)), + new Expression.Closure(new Term.Bool(true)))))))), res); } @@ -291,8 +283,7 @@ void testCheck() { void testExpression() { var res = Parser.expression(" -1 "); - assertEquals( - new Pair("", new Expression.Value(Utils.integer(-1))), res.getOk()); + assertEquals(new Pair("", Utils.integer(-1)), res.getOk()); var res2 = Parser.expression(" $0 <= 2019-12-04T09:46:41+00:00"); @@ -300,9 +291,7 @@ void testExpression() { new Pair( "", new Expression.Binary( - Expression.Op.LessOrEqual, - new Expression.Value(Utils.var("0")), - new Expression.Value(new Term.Date(1575452801)))), + Expression.OpCode.LessOrEqual, Utils.var("0"), new Term.Date(1575452801))), res2.getOk()); var res3 = Parser.expression(" 1 < $test + 2 "); @@ -312,24 +301,22 @@ void testExpression() { new Pair( "", new Expression.Binary( - Expression.Op.LessThan, - new Expression.Value(Utils.integer(1)), + Expression.OpCode.LessThan, + Utils.integer(1), new Expression.Binary( - Expression.Op.Add, - new Expression.Value(Utils.var("test")), - new Expression.Value(Utils.integer(2)))))), + Expression.OpCode.Add, Utils.var("test"), Utils.integer(2))))), res3); SymbolTable s3 = new SymbolTable(); long test = s3.insert("test"); assertEquals( Arrays.asList( - new Op.Value(new org.eclipse.biscuit.datalog.Term.Integer(1)), - new Op.Value(new org.eclipse.biscuit.datalog.Term.Variable(test)), - new Op.Value(new org.eclipse.biscuit.datalog.Term.Integer(2)), + new org.eclipse.biscuit.datalog.Term.Integer(1), + new org.eclipse.biscuit.datalog.Term.Variable(test), + new org.eclipse.biscuit.datalog.Term.Integer(2), new Op.Binary(Op.BinaryOp.Add), new Op.Binary(Op.BinaryOp.LessThan)), - res3.getOk()._2.convert(s3).getOps()); + res3.getOk()._2.convertExpr(s3).getOps()); var res4 = Parser.expression(" 2 < $test && $var2.starts_with(\"test\") && true "); @@ -338,21 +325,20 @@ void testExpression() { new Pair( "", new Expression.Binary( - Expression.Op.And, + Expression.OpCode.LazyAnd, new Expression.Binary( - Expression.Op.And, - new Expression.Binary( - Expression.Op.LessThan, - new Expression.Value(Utils.integer(2)), - new Expression.Value(Utils.var("test"))), + Expression.OpCode.LazyAnd, new Expression.Binary( - Expression.Op.Prefix, - new Expression.Value(Utils.var("var2")), - new Expression.Value(Utils.string("test")))), - new Expression.Value(new Term.Bool(true))))), + Expression.OpCode.LessThan, Utils.integer(2), Utils.var("test")), + new Expression.Closure( + new Expression.Binary( + Expression.OpCode.Prefix, + Utils.var("var2"), + Utils.string("test")))), + new Expression.Closure(new Term.Bool(true))))), res4); - var res5 = Parser.expression(" [ \"abc\", \"def\" ].contains($operation) "); + var res5 = Parser.expression(" { \"abc\", \"def\" }.contains($operation) "); HashSet s = new HashSet<>(); s.add(Utils.str("abc")); @@ -363,9 +349,7 @@ void testExpression() { new Pair( "", new Expression.Binary( - Expression.Op.Contains, - new Expression.Value(Utils.set(s)), - new Expression.Value(Utils.var("operation"))))), + Expression.OpCode.Contains, Utils.set(s), Utils.var("operation")))), res5); } @@ -378,24 +362,22 @@ void testParens() throws org.eclipse.biscuit.error.Error.Execution { new Pair( "", new Expression.Binary( - Expression.Op.Add, - new Expression.Value(Utils.integer(1)), + Expression.OpCode.Add, + Utils.integer(1), new Expression.Binary( - Expression.Op.Mul, - new Expression.Value(Utils.integer(2)), - new Expression.Value(Utils.integer(3)))))), + Expression.OpCode.Mul, Utils.integer(2), Utils.integer(3))))), res); Expression e = res.getOk()._2; SymbolTable s = new SymbolTable(); - org.eclipse.biscuit.datalog.expressions.Expression ex = e.convert(s); + org.eclipse.biscuit.datalog.expressions.Expression ex = e.convertExpr(s); assertEquals( Arrays.asList( - new Op.Value(new org.eclipse.biscuit.datalog.Term.Integer(1)), - new Op.Value(new org.eclipse.biscuit.datalog.Term.Integer(2)), - new Op.Value(new org.eclipse.biscuit.datalog.Term.Integer(3)), + new org.eclipse.biscuit.datalog.Term.Integer(1), + new org.eclipse.biscuit.datalog.Term.Integer(2), + new org.eclipse.biscuit.datalog.Term.Integer(3), new Op.Binary(Op.BinaryOp.Mul), new Op.Binary(Op.BinaryOp.Add)), ex.getOps()); @@ -412,28 +394,26 @@ void testParens() throws org.eclipse.biscuit.error.Error.Execution { new Pair( "", new Expression.Binary( - Expression.Op.Mul, + Expression.OpCode.Mul, new Expression.Unary( - Expression.Op.Parens, + Expression.OpCode.Parens, new Expression.Binary( - Expression.Op.Add, - new Expression.Value(Utils.integer(1)), - new Expression.Value(Utils.integer(2)))), - new Expression.Value(Utils.integer(3))))), + Expression.OpCode.Add, Utils.integer(1), Utils.integer(2))), + Utils.integer(3)))), res2); Expression e2 = res2.getOk()._2; SymbolTable s2 = new SymbolTable(); - org.eclipse.biscuit.datalog.expressions.Expression ex2 = e2.convert(s2); + org.eclipse.biscuit.datalog.expressions.Expression ex2 = e2.convertExpr(s2); assertEquals( Arrays.asList( - new Op.Value(new org.eclipse.biscuit.datalog.Term.Integer(1)), - new Op.Value(new org.eclipse.biscuit.datalog.Term.Integer(2)), + new org.eclipse.biscuit.datalog.Term.Integer(1), + new org.eclipse.biscuit.datalog.Term.Integer(2), new Op.Binary(Op.BinaryOp.Add), new Op.Unary(Op.UnaryOp.Parens), - new Op.Value(new org.eclipse.biscuit.datalog.Term.Integer(3)), + new org.eclipse.biscuit.datalog.Term.Integer(3), new Op.Binary(Op.BinaryOp.Mul)), ex2.getOps()); @@ -467,7 +447,7 @@ void testDatalogSucceeds() throws org.eclipse.biscuit.error.Error.Parser { @Test void testDatalogSucceedsArrays() throws org.eclipse.biscuit.error.Error.Parser { - String l1 = "check if [2, 3].union([2])"; + String l1 = "check if {2, 3}.union({2})"; String toParse = String.join(";", List.of(l1)); var output = Parser.datalog(1, toParse); @@ -482,7 +462,7 @@ void testDatalogSucceedsArrays() throws org.eclipse.biscuit.error.Error.Parser { @Test void testDatalogSucceedsArraysContains() throws org.eclipse.biscuit.error.Error.Parser { String l1 = - "check if [2019-12-04T09:46:41Z, 2020-12-04T09:46:41Z].contains(2020-12-04T09:46:41Z)"; + "check if {2019-12-04T09:46:41Z, 2020-12-04T09:46:41Z}.contains(2020-12-04T09:46:41Z)"; String toParse = String.join(";", List.of(l1)); var output = Parser.datalog(1, toParse); diff --git a/src/test/java/org/eclipse/biscuit/datalog/ExpressionTest.java b/src/test/java/org/eclipse/biscuit/datalog/ExpressionTest.java index 54d894f5..93b607f5 100644 --- a/src/test/java/org/eclipse/biscuit/datalog/ExpressionTest.java +++ b/src/test/java/org/eclipse/biscuit/datalog/ExpressionTest.java @@ -29,8 +29,8 @@ public void testNegate() throws Error.Execution { new Expression( new ArrayList( Arrays.asList( - new Op.Value(new Term.Integer(1)), - new Op.Value(new Term.Variable(SymbolTable.DEFAULT_SYMBOLS_OFFSET + 2)), + new Term.Integer(1), + new Term.Variable(SymbolTable.DEFAULT_SYMBOLS_OFFSET + 2), new Op.Binary(Op.BinaryOp.LessThan), new Op.Unary(Op.UnaryOp.Negate)))); @@ -53,8 +53,8 @@ public void testAddsStr() throws Error.Execution { new Expression( new ArrayList( Arrays.asList( - new Op.Value(new Term.Str(SymbolTable.DEFAULT_SYMBOLS_OFFSET)), - new Op.Value(new Term.Str(SymbolTable.DEFAULT_SYMBOLS_OFFSET + 1)), + new Term.Str(SymbolTable.DEFAULT_SYMBOLS_OFFSET), + new Term.Str(SymbolTable.DEFAULT_SYMBOLS_OFFSET + 1), new Op.Binary(Op.BinaryOp.Add)))); assertEquals("\"a\" + \"b\"", e.print(symbolTable).get()); @@ -74,8 +74,8 @@ public void testContainsStr() throws Error.Execution { new Expression( new ArrayList( Arrays.asList( - new Op.Value(new Term.Str(SymbolTable.DEFAULT_SYMBOLS_OFFSET)), - new Op.Value(new Term.Str(SymbolTable.DEFAULT_SYMBOLS_OFFSET + 1)), + new Term.Str(SymbolTable.DEFAULT_SYMBOLS_OFFSET), + new Term.Str(SymbolTable.DEFAULT_SYMBOLS_OFFSET + 1), new Op.Binary(Op.BinaryOp.Contains)))); assertEquals("\"ab\".contains(\"b\")", e.print(symbolTable).get()); @@ -94,8 +94,8 @@ public void testNegativeContainsStr() throws Error.Execution { new Expression( new ArrayList( Arrays.asList( - new Op.Value(new Term.Str(SymbolTable.DEFAULT_SYMBOLS_OFFSET)), - new Op.Value(new Term.Str(SymbolTable.DEFAULT_SYMBOLS_OFFSET + 1)), + new Term.Str(SymbolTable.DEFAULT_SYMBOLS_OFFSET), + new Term.Str(SymbolTable.DEFAULT_SYMBOLS_OFFSET + 1), new Op.Binary(Op.BinaryOp.Contains), new Op.Unary(Op.UnaryOp.Negate)))); @@ -113,22 +113,17 @@ public void testIntersectionAndContains() throws Error.Execution { new Expression( new ArrayList( Arrays.asList( - new Op.Value( - new Term.Set( - new HashSet<>( - Arrays.asList( - new Term.Integer(1), - new Term.Integer(2), - new Term.Integer(3))))), - new Op.Value( - new Term.Set( - new HashSet<>( - Arrays.asList(new Term.Integer(1), new Term.Integer(2))))), + new Term.Set( + new HashSet<>( + Arrays.asList( + new Term.Integer(1), new Term.Integer(2), new Term.Integer(3)))), + new Term.Set( + new HashSet<>(Arrays.asList(new Term.Integer(1), new Term.Integer(2)))), new Op.Binary(Op.BinaryOp.Intersection), - new Op.Value(new Term.Integer(1)), + new Term.Integer(1), new Op.Binary(Op.BinaryOp.Contains)))); - assertEquals("[1, 2, 3].intersection([1, 2]).contains(1)", e.print(symbolTable).get()); + assertEquals("{1, 2, 3}.intersection({1, 2}).contains(1)", e.print(symbolTable).get()); assertEquals( new Term.Bool(true), e.evaluate(new HashMap<>(), new TemporarySymbolTable(symbolTable))); diff --git a/src/test/java/org/eclipse/biscuit/datalog/WorldTest.java b/src/test/java/org/eclipse/biscuit/datalog/WorldTest.java index fee5b8d5..844a0bef 100644 --- a/src/test/java/org/eclipse/biscuit/datalog/WorldTest.java +++ b/src/test/java/org/eclipse/biscuit/datalog/WorldTest.java @@ -334,8 +334,8 @@ public void testNumbers() throws Error { new Expression( new ArrayList( Arrays.asList( - new Op.Value(new Term.Variable(syms.insert("id"))), - new Op.Value(new Term.Integer(1)), + new Term.Variable(syms.insert("id")), + new Term.Integer(1), new Op.Binary(Op.BinaryOp.LessThan)))))), (long) 0, new TrustedOrigins(0), @@ -375,8 +375,8 @@ private final FactSet testSuffix( new Expression( new ArrayList( Arrays.asList( - new Op.Value(new Term.Variable(syms.insert("domain"))), - new Op.Value(syms.add(suffix)), + new Term.Variable(syms.insert("domain")), + syms.add(suffix), new Op.Binary(Op.BinaryOp.Suffix)))))), (long) 0, new TrustedOrigins(0), @@ -492,14 +492,14 @@ public void testDate() throws Error { new Expression( new ArrayList( Arrays.asList( - new Op.Value(new Term.Variable(syms.insert("date"))), - new Op.Value(new Term.Date(t2_timestamp)), + new Term.Variable(syms.insert("date")), + new Term.Date(t2_timestamp), new Op.Binary(Op.BinaryOp.LessOrEqual)))), new Expression( new ArrayList( Arrays.asList( - new Op.Value(new Term.Variable(syms.insert("date"))), - new Op.Value(new Term.Date(0)), + new Term.Variable(syms.insert("date")), + new Term.Date(0), new Op.Binary(Op.BinaryOp.GreaterOrEqual)))))); System.out.println("testing r1: " + syms.formatRule(r1)); @@ -534,14 +534,14 @@ public void testDate() throws Error { new Expression( new ArrayList( Arrays.asList( - new Op.Value(new Term.Variable(syms.insert("date"))), - new Op.Value(new Term.Date(t2_timestamp)), + new Term.Variable(syms.insert("date")), + new Term.Date(t2_timestamp), new Op.Binary(Op.BinaryOp.GreaterOrEqual)))), new Expression( new ArrayList( Arrays.asList( - new Op.Value(new Term.Variable(syms.insert("date"))), - new Op.Value(new Term.Date(0)), + new Term.Variable(syms.insert("date")), + new Term.Date(0), new Op.Binary(Op.BinaryOp.GreaterOrEqual)))))); System.out.println("testing r2: " + syms.formatRule(r2)); @@ -597,12 +597,10 @@ public void testSet() throws Error { new Expression( new ArrayList( Arrays.asList( - new Op.Value( - new Term.Set( - new HashSet<>( - Arrays.asList( - new Term.Integer(0L), new Term.Integer(1L))))), - new Op.Value(new Term.Variable(syms.insert("int"))), + new Term.Set( + new HashSet<>( + Arrays.asList(new Term.Integer(0L), new Term.Integer(1L)))), + new Term.Variable(syms.insert("int")), new Op.Binary(Op.BinaryOp.Contains)))))); System.out.println("testing r1: " + syms.formatRule(r1)); FactSet res = w.queryRule(r1, (long) 0, new TrustedOrigins(0), syms); @@ -640,12 +638,10 @@ public void testSet() throws Error { new Expression( new ArrayList( Arrays.asList( - new Op.Value( - new Term.Set( - new HashSet<>( - Arrays.asList( - new Term.Str(abcSymId), new Term.Str(ghiSymId))))), - new Op.Value(new Term.Variable(syms.insert("sym"))), + new Term.Set( + new HashSet<>( + Arrays.asList(new Term.Str(abcSymId), new Term.Str(ghiSymId)))), + new Term.Variable(syms.insert("sym")), new Op.Binary(Op.BinaryOp.Contains), new Op.Unary(Op.UnaryOp.Negate)))))); @@ -685,11 +681,9 @@ public void testSet() throws Error { new Expression( new ArrayList( Arrays.asList( - new Op.Value( - new Term.Set( - new HashSet<>( - Arrays.asList(syms.add("test"), syms.add("aaa"))))), - new Op.Value(new Term.Variable(syms.insert("str"))), + new Term.Set( + new HashSet<>(Arrays.asList(syms.add("test"), syms.add("aaa")))), + new Term.Variable(syms.insert("str")), new Op.Binary(Op.BinaryOp.Contains)))))); System.out.println("testing r3: " + syms.formatRule(r3)); res = w.queryRule(r3, (long) 0, new TrustedOrigins(0), syms); diff --git a/src/test/java/org/eclipse/biscuit/token/AuthorizerTest.java b/src/test/java/org/eclipse/biscuit/token/AuthorizerTest.java index a5e4e1ff..e504dce5 100644 --- a/src/test/java/org/eclipse/biscuit/token/AuthorizerTest.java +++ b/src/test/java/org/eclipse/biscuit/token/AuthorizerTest.java @@ -20,7 +20,6 @@ import org.eclipse.biscuit.datalog.RunLimits; import org.eclipse.biscuit.error.Error; import org.eclipse.biscuit.error.Error.Parser; -import org.eclipse.biscuit.token.builder.Expression; import org.eclipse.biscuit.token.builder.Term; import org.junit.jupiter.api.Test; @@ -41,7 +40,7 @@ public void testAuthorizerPolicy() throws Parser { "deny", new ArrayList<>(), new ArrayList<>(), - Arrays.asList(new Expression.Value(new Term.Bool(true))))), + Arrays.asList(new Term.Bool(true)))), Policy.Kind.DENY)); assertEquals(2, policies.size()); @@ -59,7 +58,7 @@ public void testPuttingSomeFactsInBiscuitAndGettingThemBackOutAgain() throws Exc .addAuthorityFact("email(\"bob@example.com\")") .addAuthorityFact("id(123)") .addAuthorityFact("enabled(true)") - .addAuthorityFact("perms([1,2,3])") + .addAuthorityFact("perms({1,2,3})") .build(); Authorizer authorizer = @@ -91,7 +90,7 @@ public void testDatalogAuthorizer() throws Exception { .addAuthorityFact("email(\"bob@example.com\")") .addAuthorityFact("id(123)") .addAuthorityFact("enabled(true)") - .addAuthorityFact("perms([1,2,3])") + .addAuthorityFact("perms({1,2,3})") .build(); Authorizer authorizer = diff --git a/src/test/java/org/eclipse/biscuit/token/BiscuitTest.java b/src/test/java/org/eclipse/biscuit/token/BiscuitTest.java index 0bbe0e16..59ffb361 100644 --- a/src/test/java/org/eclipse/biscuit/token/BiscuitTest.java +++ b/src/test/java/org/eclipse/biscuit/token/BiscuitTest.java @@ -754,7 +754,7 @@ public void testCheckAll() Authorizer authorizer = biscuit.verify(root.getPublicKey()).authorizer(); authorizer.addFact("operation(\"read\")"); authorizer.addFact("operation(\"write\")"); - authorizer.addFact("allowed_operations([\"write\"])"); + authorizer.addFact("allowed_operations({\"write\"})"); authorizer.addPolicy("allow if true"); try { @@ -777,7 +777,7 @@ public void testCheckAll() Authorizer authorizer2 = biscuit.verify(root.getPublicKey()).authorizer(); authorizer2.addFact("operation(\"read\")"); authorizer2.addFact("operation(\"write\")"); - authorizer2.addFact("allowed_operations([\"read\", \"write\"])"); + authorizer2.addFact("allowed_operations({\"read\", \"write\"})"); authorizer2.addPolicy("allow if true"); authorizer2.authorize(new RunLimits(500, 100, Duration.ofMillis(500))); diff --git a/src/test/java/org/eclipse/biscuit/token/SamplesTest.java b/src/test/java/org/eclipse/biscuit/token/SamplesTest.java index 12b7457c..eed0df39 100644 --- a/src/test/java/org/eclipse/biscuit/token/SamplesTest.java +++ b/src/test/java/org/eclipse/biscuit/token/SamplesTest.java @@ -24,7 +24,6 @@ import java.security.SecureRandom; import java.time.Duration; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashMap; @@ -182,16 +181,12 @@ DynamicTest processTestcase( compareBlocks(privateKey, testCase.token, token); byte[] serBlockAuthority = token.authority.toBytes().getOk(); - System.out.println(Arrays.toString(serBlockAuthority)); - System.out.println( - Arrays.toString(token.serializedBiscuit.getAuthority().getBlock())); org.eclipse.biscuit.token.Block deserBlockAuthority = fromBytes(serBlockAuthority, token.authority.getExternalKey()).getOk(); + assertEquals(token.authority.getVersion(), deserBlockAuthority.getVersion()); assertEquals( token.authority.print(token.symbolTable), deserBlockAuthority.print(token.symbolTable)); - assert (Arrays.equals( - serBlockAuthority, token.serializedBiscuit.getAuthority().getBlock())); for (int i = 0; i < token.blocks.size() - 1; i++) { org.eclipse.biscuit.token.Block block = token.blocks.get(i); @@ -199,8 +194,8 @@ DynamicTest processTestcase( byte[] serBlock = block.toBytes().getOk(); org.eclipse.biscuit.token.Block deserBlock = fromBytes(serBlock, block.getExternalKey()).getOk(); + assertEquals(block.getVersion(), deserBlock.getVersion()); assertEquals(block.print(token.symbolTable), deserBlock.print(token.symbolTable)); - assert (Arrays.equals(serBlock, signedBlock.getBlock())); } List revocationIds = token.revocationIdentifiers(); @@ -217,7 +212,9 @@ DynamicTest processTestcase( for (String f : authorizerFacts) { f = f.trim(); if (!f.isEmpty()) { - if (f.startsWith("check if") || f.startsWith("check all")) { + if (f.startsWith("check if") + || f.startsWith("check all") + || f.startsWith("reject if")) { authorizer.addCheck(f); } else if (f.startsWith("allow if") || f.startsWith("deny if")) { authorizer.addPolicy(f); @@ -593,11 +590,13 @@ public CheckSet( ? origin.min(BigInteger.valueOf(Long.MAX_VALUE)).longValue() : Long.MAX_VALUE; this.checks = checks; + Collections.sort(this.checks); } public CheckSet(List checks) { this.origin = null; this.checks = checks; + Collections.sort(this.checks); } @Override diff --git a/src/test/resources/samples/README.md b/src/test/resources/samples/README.md index 4f9608ed..9e40b16e 100644 --- a/src/test/resources/samples/README.md +++ b/src/test/resources/samples/README.md @@ -132,6 +132,8 @@ symbols: ["file1", "file2"] public keys: [] +block version: 3 + ``` right("file1", "read"); right("file2", "read"); @@ -143,6 +145,8 @@ symbols: ["0"] public keys: [] +block version: 3 + ``` check if resource($0), operation("read"), right($0, "read"); ``` @@ -859,6 +863,8 @@ symbols: ["file1", "file2"] public keys: [] +block version: 3 + ``` right("file1", "read"); right("file2", "read"); @@ -869,9 +875,11 @@ symbols: ["valid_date", "0", "1"] public keys: [] +block version: 3 + ``` valid_date("file1") <- time($0), resource("file1"), $0 <= 2030-12-31T12:59:59Z; -valid_date($1) <- time($0), resource($1), $0 <= 1999-12-31T12:59:59Z, !["file1"].contains($1); +valid_date($1) <- time($0), resource($1), $0 <= 1999-12-31T12:59:59Z, !{"file1"}.contains($1); check if valid_date($0), resource($0); ``` @@ -893,7 +901,7 @@ authorizer world: ``` World { facts: [ - AuthorizerFactSet { + Facts { origin: { None, }, @@ -902,7 +910,7 @@ World { "time(2020-12-21T09:23:12Z)", ], }, - AuthorizerFactSet { + Facts { origin: { None, Some( @@ -913,7 +921,7 @@ World { "valid_date(\"file1\")", ], }, - AuthorizerFactSet { + Facts { origin: { Some( 0, @@ -926,18 +934,18 @@ World { }, ] rules: [ - AuthorizerRuleSet { + Rules { origin: Some( 1, ), rules: [ "valid_date(\"file1\") <- time($0), resource(\"file1\"), $0 <= 2030-12-31T12:59:59Z", - "valid_date($1) <- time($0), resource($1), $0 <= 1999-12-31T12:59:59Z, ![\"file1\"].contains($1)", + "valid_date($1) <- time($0), resource($1), $0 <= 1999-12-31T12:59:59Z, !{\"file1\"}.contains($1)", ], }, ] checks: [ - AuthorizerCheckSet { + Checks { origin: Some( 1, ), @@ -971,7 +979,7 @@ authorizer world: ``` World { facts: [ - AuthorizerFactSet { + Facts { origin: { None, }, @@ -980,7 +988,7 @@ World { "time(2020-12-21T09:23:12Z)", ], }, - AuthorizerFactSet { + Facts { origin: { Some( 0, @@ -993,18 +1001,18 @@ World { }, ] rules: [ - AuthorizerRuleSet { + Rules { origin: Some( 1, ), rules: [ "valid_date(\"file1\") <- time($0), resource(\"file1\"), $0 <= 2030-12-31T12:59:59Z", - "valid_date($1) <- time($0), resource($1), $0 <= 1999-12-31T12:59:59Z, ![\"file1\"].contains($1)", + "valid_date($1) <- time($0), resource($1), $0 <= 1999-12-31T12:59:59Z, !{\"file1\"}.contains($1)", ], }, ] checks: [ - AuthorizerCheckSet { + Checks { origin: Some( 1, ), @@ -1272,48 +1280,48 @@ symbols: ["hello world", "hello", "world", "aaabde", "a*c?.e", "abd", "aaa", "b" public keys: [] +block version: 3 + ``` check if true; check if !false; -check if !false && true; -check if false || true; -check if (true || false) && true; -check if true == true; -check if false == false; +check if true === true; +check if false === false; check if 1 < 2; check if 2 > 1; check if 1 <= 2; check if 1 <= 1; check if 2 >= 1; check if 2 >= 2; -check if 3 == 3; -check if 1 + 2 * 3 - 4 / 2 == 5; -check if "hello world".starts_with("hello") && "hello world".ends_with("world"); +check if 3 === 3; +check if 1 + 2 * 3 - 4 / 2 === 5; +check if "hello world".starts_with("hello"), "hello world".ends_with("world"); check if "aaabde".matches("a*c?.e"); check if "aaabde".contains("abd"); -check if "aaabde" == "aaa" + "b" + "de"; -check if "abcD12" == "abcD12"; -check if "abcD12".length() == 6; -check if "é".length() == 2; +check if "aaabde" === "aaa" + "b" + "de"; +check if "abcD12" === "abcD12"; +check if "abcD12".length() === 6; +check if "é".length() === 2; check if 2019-12-04T09:46:41Z < 2020-12-04T09:46:41Z; check if 2020-12-04T09:46:41Z > 2019-12-04T09:46:41Z; check if 2019-12-04T09:46:41Z <= 2020-12-04T09:46:41Z; check if 2020-12-04T09:46:41Z >= 2020-12-04T09:46:41Z; check if 2020-12-04T09:46:41Z >= 2019-12-04T09:46:41Z; check if 2020-12-04T09:46:41Z >= 2020-12-04T09:46:41Z; -check if 2020-12-04T09:46:41Z == 2020-12-04T09:46:41Z; -check if hex:12ab == hex:12ab; -check if [1, 2].contains(2); -check if [2019-12-04T09:46:41Z, 2020-12-04T09:46:41Z].contains(2020-12-04T09:46:41Z); -check if [false, true].contains(true); -check if ["abc", "def"].contains("abc"); -check if [hex:12ab, hex:34de].contains(hex:34de); -check if [1, 2].contains([2]); -check if [1, 2] == [1, 2]; -check if [1, 2].intersection([2, 3]) == [2]; -check if [1, 2].union([2, 3]) == [1, 2, 3]; -check if [1, 2, 3].intersection([1, 2]).contains(1); -check if [1, 2, 3].intersection([1, 2]).length() == 2; +check if 2020-12-04T09:46:41Z === 2020-12-04T09:46:41Z; +check if hex:12ab === hex:12ab; +check if {1, 2}.contains(2); +check if {2019-12-04T09:46:41Z, 2020-12-04T09:46:41Z}.contains(2020-12-04T09:46:41Z); +check if {false, true}.contains(true); +check if {"abc", "def"}.contains("abc"); +check if {hex:12ab, hex:34de}.contains(hex:34de); +check if {1, 2}.contains({2}); +check if {1, 2} === {1, 2}; +check if {1, 2}.intersection({2, 3}) === {2}; +check if {1, 2}.union({2, 3}) === {1, 2, 3}; +check if {1, 2, 3}.intersection({1, 2}).contains(1); +check if {1, 2, 3}.intersection({1, 2}).length() === 2; +check if {,}.length() === 0; ``` ### validation @@ -1324,7 +1332,7 @@ allow if true; ``` revocation ids: -- `3d5b23b502b3dd920bfb68b9039164d1563bb8927210166fa5c17f41b76b31bb957bc2ed3318452958f658baa2d398fe4cf25c58a27e6c8bc42c9702c8aa1b0c` +- `fa358e4e3bea896415b1859e6cd347e64e1918fb86e31ae3fe208628321576a47f7a269760357e291c827ec9cbe322074f6860a546207a64e133c83a214bb505` authorizer world: ``` @@ -1332,22 +1340,20 @@ World { facts: [] rules: [] checks: [ - AuthorizerCheckSet { + Checks { origin: Some( 0, ), checks: [ "check if !false", - "check if !false && true", - "check if \"aaabde\" == \"aaa\" + \"b\" + \"de\"", + "check if \"aaabde\" === \"aaa\" + \"b\" + \"de\"", "check if \"aaabde\".contains(\"abd\")", "check if \"aaabde\".matches(\"a*c?.e\")", - "check if \"abcD12\" == \"abcD12\"", - "check if \"abcD12\".length() == 6", - "check if \"hello world\".starts_with(\"hello\") && \"hello world\".ends_with(\"world\")", - "check if \"é\".length() == 2", - "check if (true || false) && true", - "check if 1 + 2 * 3 - 4 / 2 == 5", + "check if \"abcD12\" === \"abcD12\"", + "check if \"abcD12\".length() === 6", + "check if \"hello world\".starts_with(\"hello\"), \"hello world\".ends_with(\"world\")", + "check if \"é\".length() === 2", + "check if 1 + 2 * 3 - 4 / 2 === 5", "check if 1 < 2", "check if 1 <= 1", "check if 1 <= 2", @@ -1356,28 +1362,28 @@ World { "check if 2 >= 2", "check if 2019-12-04T09:46:41Z < 2020-12-04T09:46:41Z", "check if 2019-12-04T09:46:41Z <= 2020-12-04T09:46:41Z", - "check if 2020-12-04T09:46:41Z == 2020-12-04T09:46:41Z", + "check if 2020-12-04T09:46:41Z === 2020-12-04T09:46:41Z", "check if 2020-12-04T09:46:41Z > 2019-12-04T09:46:41Z", "check if 2020-12-04T09:46:41Z >= 2019-12-04T09:46:41Z", "check if 2020-12-04T09:46:41Z >= 2020-12-04T09:46:41Z", "check if 2020-12-04T09:46:41Z >= 2020-12-04T09:46:41Z", - "check if 3 == 3", - "check if [\"abc\", \"def\"].contains(\"abc\")", - "check if [1, 2, 3].intersection([1, 2]).contains(1)", - "check if [1, 2, 3].intersection([1, 2]).length() == 2", - "check if [1, 2] == [1, 2]", - "check if [1, 2].contains(2)", - "check if [1, 2].contains([2])", - "check if [1, 2].intersection([2, 3]) == [2]", - "check if [1, 2].union([2, 3]) == [1, 2, 3]", - "check if [2019-12-04T09:46:41Z, 2020-12-04T09:46:41Z].contains(2020-12-04T09:46:41Z)", - "check if [false, true].contains(true)", - "check if [hex:12ab, hex:34de].contains(hex:34de)", - "check if false == false", - "check if false || true", - "check if hex:12ab == hex:12ab", + "check if 3 === 3", + "check if false === false", + "check if hex:12ab === hex:12ab", "check if true", - "check if true == true", + "check if true === true", + "check if {\"abc\", \"def\"}.contains(\"abc\")", + "check if {,}.length() === 0", + "check if {1, 2, 3}.intersection({1, 2}).contains(1)", + "check if {1, 2, 3}.intersection({1, 2}).length() === 2", + "check if {1, 2} === {1, 2}", + "check if {1, 2}.contains(2)", + "check if {1, 2}.contains({2})", + "check if {1, 2}.intersection({2, 3}) === {2}", + "check if {1, 2}.union({2, 3}) === {1, 2, 3}", + "check if {2019-12-04T09:46:41Z, 2020-12-04T09:46:41Z}.contains(2020-12-04T09:46:41Z)", + "check if {false, true}.contains(true)", + "check if {hex:12ab, hex:34de}.contains(hex:34de)", ], }, ] @@ -1978,7 +1984,6 @@ result: `Ok(0)` ------------------------------ -## block rules: test025_check_all.bc ## block rules: test025_check_all.bc ### token @@ -1987,8 +1992,10 @@ symbols: ["allowed_operations", "A", "B", "op", "allowed"] public keys: [] +block version: 4 + ``` -allowed_operations(["A", "B"]); +allowed_operations({"A", "B"}); check all operation($op), allowed_operations($allowed), $allowed.contains($op); ``` @@ -2009,7 +2016,7 @@ authorizer world: ``` World { facts: [ - AuthorizerFactSet { + Facts { origin: { None, }, @@ -2018,20 +2025,20 @@ World { "operation(\"B\")", ], }, - AuthorizerFactSet { + Facts { origin: { Some( 0, ), }, facts: [ - "allowed_operations([\"A\", \"B\"])", + "allowed_operations({\"A\", \"B\"})", ], }, ] rules: [] checks: [ - AuthorizerCheckSet { + Checks { origin: Some( 0, ), @@ -2064,7 +2071,7 @@ authorizer world: ``` World { facts: [ - AuthorizerFactSet { + Facts { origin: { None, }, @@ -2073,20 +2080,63 @@ World { "operation(\"invalid\")", ], }, - AuthorizerFactSet { + Facts { + origin: { + Some( + 0, + ), + }, + facts: [ + "allowed_operations({\"A\", \"B\"})", + ], + }, +] + rules: [] + checks: [ + Checks { + origin: Some( + 0, + ), + checks: [ + "check all operation($op), allowed_operations($allowed), $allowed.contains($op)", + ], + }, +] + policies: [ + "allow if true", +] +} +``` + +result: `Err(FailedLogic(Unauthorized { policy: Allow(0), checks: [Block(FailedBlockCheck { block_id: 0, check_id: 0, rule: "check all operation($op), allowed_operations($allowed), $allowed.contains($op)" })] }))` +### validation for "no matches" + +authorizer code: +``` +allow if true; +``` + +revocation ids: +- `c456817012e1d523c6d145b6d6a3475d9f7dd4383c535454ff3f745ecf4234984ce09b9dec0551f3d783abe850f826ce43b12f1fd91999a4753a56ecf4c56d0d` + +authorizer world: +``` +World { + facts: [ + Facts { origin: { Some( 0, ), }, facts: [ - "allowed_operations([\"A\", \"B\"])", + "allowed_operations({\"A\", \"B\"})", ], }, ] rules: [] checks: [ - AuthorizerCheckSet { + Checks { origin: Some( 0, ), @@ -2345,81 +2395,108 @@ result: `Ok(3)` ------------------------------ -## ECDSA secp256r1 signatures: test036_secp256r1.bc +## integer wraparound: test027_integer_wraparound.bc ### token authority: -symbols: ["file1", "file2"] +symbols: [] public keys: [] -block version: 3 +block version: 4 ``` -right("file1", "read"); -right("file2", "read"); -right("file1", "write"); +check if 10000000000 * 10000000000 !== 0; +check if 9223372036854775807 + 1 !== 0; +check if -9223372036854775808 - 1 !== 0; ``` -1: -symbols: ["0"] +### validation + +authorizer code: +``` +allow if true; +``` + +revocation ids: +- `fb5e7ac2bb892f5cf2fb59677cfad1f96deabbc8e158e3fd1b5ee7c4b6949c999e2169187cbee53b943eebdadaaf68832747baa8cffa2ff9f78025a1f55f440c` + +authorizer world: +``` +World { + facts: [] + rules: [] + checks: [ + Checks { + origin: Some( + 0, + ), + checks: [ + "check if -9223372036854775808 - 1 !== 0", + "check if 10000000000 * 10000000000 !== 0", + "check if 9223372036854775807 + 1 !== 0", + ], + }, +] + policies: [ + "allow if true", +] +} +``` + +result: `Err(Execution(Overflow))` + + +------------------------------ + +## test expression syntax and all available operations (v4 blocks): test028_expressions_v4.bc +### token + +authority: +symbols: ["abcD12x", "abcD12"] public keys: [] -block version: 3 +block version: 4 ``` -check if resource($0), operation("read"), right($0, "read"); +check if true !== false; +check if 1 !== 3; +check if 1 | 2 ^ 3 === 0; +check if "abcD12x" !== "abcD12"; +check if 2022-12-04T09:46:41Z !== 2020-12-04T09:46:41Z; +check if hex:12abcd !== hex:12ab; +check if {1, 4} !== {1, 2}; ``` ### validation authorizer code: ``` -resource("file1"); -operation("read"); - allow if true; ``` revocation ids: -- `628b9a6d74cc80b3ece50befd1f5f0f025c0a35d51708b2e77c11aed5f968b93b4096c87ed8169605716de934e155443f140334d71708fcc4247e5a0a518b30d` -- `3046022100b60674854a12814cc36c8aab9600c1d9f9d3160e2334b72c0feede5a56213ea5022100a4f4bbf2dc33b309267af39fce76612017ddb6171e9cd2a3aa8a853f45f1675f` +- `9402c07923aa33bc911de80e61f388f5c4533e6b36e45317dc1db1e6bcc7664ed0c1c504d0ca8925208008961d95bbdbc36f6e3d91b3173369cc19ed625e9a0c` authorizer world: ``` World { - facts: [ - Facts { - origin: { - None, - }, - facts: [ - "operation(\"read\")", - "resource(\"file1\")", - ], - }, - Facts { - origin: { - Some( - 0, - ), - }, - facts: [ - "right(\"file1\", \"read\")", - "right(\"file1\", \"write\")", - "right(\"file2\", \"read\")", - ], - }, -] + facts: [] rules: [] checks: [ Checks { origin: Some( - 1, + 0, ), checks: [ - "check if resource($0), operation(\"read\"), right($0, \"read\")", + "check if \"abcD12x\" !== \"abcD12\"", + "check if 1 !== 3", + "check if 1 | 2 ^ 3 === 0", + "check if 2022-12-04T09:46:41Z !== 2020-12-04T09:46:41Z", + "check if hex:12abcd !== hex:12ab", + "check if true !== false", + "check if {1, 4} !== {1, 2}", ], }, ] @@ -2434,50 +2511,31 @@ result: `Ok(0)` ------------------------------ -## ECDSA secp256r1 signature on third-party block: test037_secp256r1_third_party.bc +## test reject if: test029_reject_if.bc ### token authority: -symbols: ["file1", "file2", "from_third"] - -public keys: ["secp256r1/025e918fd4463832aea2823dfd9716a36b4d9b1377bd53dd82ddf4c0bc75ed6bbf"] - -block version: 4 - -``` -right("file1", "read"); -right("file2", "read"); -right("file1", "write"); -check if from_third(true) trusting secp256r1/025e918fd4463832aea2823dfd9716a36b4d9b1377bd53dd82ddf4c0bc75ed6bbf; -``` - -1: -symbols: ["from_third", "0"] +symbols: ["test"] public keys: [] -external signature by: "secp256r1/025e918fd4463832aea2823dfd9716a36b4d9b1377bd53dd82ddf4c0bc75ed6bbf" - -block version: 5 +block version: 6 ``` -from_third(true); -check if resource($0), operation("read"), right($0, "read"); +reject if test($test), $test; ``` ### validation authorizer code: ``` -resource("file1"); -operation("read"); +test(false); allow if true; ``` revocation ids: -- `70f5402208516fd44cfc9df3dfcfc0a327ee9004f1801ed0a7abdcbbae923d566ddcd2d4a14f4622b35732c4e538af04075cc67ab0888fa2d8923cc668187f0f` -- `30450220793f95665d9af646339503a073670ea2c352459d2a2c2e14c57565f6c7eaf6bc022100cccadfc37e46755f52bb054ed206d7335067885df599a69431db40e33f33d4cf` +- `8d175329f7cf161f3cb5badc52f0e22e520956cdb565edbed963e9b047b20a314a7de1c9eba6b7bbf622636516ab3cc7f91572ae9461d3152825e0ece5127a0a` authorizer world: ``` @@ -2488,30 +2546,50 @@ World { None, }, facts: [ - "operation(\"read\")", - "resource(\"file1\")", + "test(false)", ], }, - Facts { - origin: { - Some( - 0, - ), - }, - facts: [ - "right(\"file1\", \"read\")", - "right(\"file1\", \"write\")", - "right(\"file2\", \"read\")", +] + rules: [] + checks: [ + Checks { + origin: Some( + 0, + ), + checks: [ + "reject if test($test), $test", ], }, +] + policies: [ + "allow if true", +] +} +``` + +result: `Ok(0)` +### validation for "rejection" + +authorizer code: +``` +test(true); + +allow if true; +``` + +revocation ids: +- `8d175329f7cf161f3cb5badc52f0e22e520956cdb565edbed963e9b047b20a314a7de1c9eba6b7bbf622636516ab3cc7f91572ae9461d3152825e0ece5127a0a` + +authorizer world: +``` +World { + facts: [ Facts { origin: { - Some( - 1, - ), + None, }, facts: [ - "from_third(true)", + "test(true)", ], }, ] @@ -2522,15 +2600,7 @@ World { 0, ), checks: [ - "check if from_third(true) trusting secp256r1/025e918fd4463832aea2823dfd9716a36b4d9b1377bd53dd82ddf4c0bc75ed6bbf", - ], - }, - Checks { - origin: Some( - 1, - ), - checks: [ - "check if resource($0), operation(\"read\"), right($0, \"read\")", + "reject if test($test), $test", ], }, ] @@ -2540,5 +2610,994 @@ World { } ``` -result: `Ok(0)` +result: `Err(FailedLogic(Unauthorized { policy: Allow(0), checks: [Block(FailedBlockCheck { block_id: 0, check_id: 0, rule: "reject if test($test), $test" })] }))` + + +------------------------------ + +## test null: test030_null.bc +### token + +authority: +symbols: ["fact", "value"] + +public keys: [] + +block version: 6 + +``` +check if fact(null, $value), $value == null; +reject if fact(null, $value), $value != null; +``` + +### validation + +authorizer code: +``` +fact(null, null); + +allow if true; +``` + +revocation ids: +- `fe50d65706a5945c76569d1ff2be8ece24276857631e96efa05959f73bb4ea8c772945738a01da77a1661aef2b8233b4f4e49ae220f2c81fd0b8da59c212750b` + +authorizer world: +``` +World { + facts: [ + Facts { + origin: { + None, + }, + facts: [ + "fact(null, null)", + ], + }, +] + rules: [] + checks: [ + Checks { + origin: Some( + 0, + ), + checks: [ + "check if fact(null, $value), $value == null", + "reject if fact(null, $value), $value != null", + ], + }, +] + policies: [ + "allow if true", +] +} +``` + +result: `Ok(0)` +### validation for "rejection1" + +authorizer code: +``` +fact(null, 1); + +allow if true; +``` + +revocation ids: +- `fe50d65706a5945c76569d1ff2be8ece24276857631e96efa05959f73bb4ea8c772945738a01da77a1661aef2b8233b4f4e49ae220f2c81fd0b8da59c212750b` + +authorizer world: +``` +World { + facts: [ + Facts { + origin: { + None, + }, + facts: [ + "fact(null, 1)", + ], + }, +] + rules: [] + checks: [ + Checks { + origin: Some( + 0, + ), + checks: [ + "check if fact(null, $value), $value == null", + "reject if fact(null, $value), $value != null", + ], + }, +] + policies: [ + "allow if true", +] +} +``` + +result: `Err(FailedLogic(Unauthorized { policy: Allow(0), checks: [Block(FailedBlockCheck { block_id: 0, check_id: 0, rule: "check if fact(null, $value), $value == null" }), Block(FailedBlockCheck { block_id: 0, check_id: 1, rule: "reject if fact(null, $value), $value != null" })] }))` +### validation for "rejection2" + +authorizer code: +``` +fact(null, true); + +allow if true; +``` + +revocation ids: +- `fe50d65706a5945c76569d1ff2be8ece24276857631e96efa05959f73bb4ea8c772945738a01da77a1661aef2b8233b4f4e49ae220f2c81fd0b8da59c212750b` + +authorizer world: +``` +World { + facts: [ + Facts { + origin: { + None, + }, + facts: [ + "fact(null, true)", + ], + }, +] + rules: [] + checks: [ + Checks { + origin: Some( + 0, + ), + checks: [ + "check if fact(null, $value), $value == null", + "reject if fact(null, $value), $value != null", + ], + }, +] + policies: [ + "allow if true", +] +} +``` + +result: `Err(FailedLogic(Unauthorized { policy: Allow(0), checks: [Block(FailedBlockCheck { block_id: 0, check_id: 0, rule: "check if fact(null, $value), $value == null" }), Block(FailedBlockCheck { block_id: 0, check_id: 1, rule: "reject if fact(null, $value), $value != null" })] }))` +### validation for "rejection3" + +authorizer code: +``` +fact(null, "abcd"); + +allow if true; +``` + +revocation ids: +- `fe50d65706a5945c76569d1ff2be8ece24276857631e96efa05959f73bb4ea8c772945738a01da77a1661aef2b8233b4f4e49ae220f2c81fd0b8da59c212750b` + +authorizer world: +``` +World { + facts: [ + Facts { + origin: { + None, + }, + facts: [ + "fact(null, \"abcd\")", + ], + }, +] + rules: [] + checks: [ + Checks { + origin: Some( + 0, + ), + checks: [ + "check if fact(null, $value), $value == null", + "reject if fact(null, $value), $value != null", + ], + }, +] + policies: [ + "allow if true", +] +} +``` + +result: `Err(FailedLogic(Unauthorized { policy: Allow(0), checks: [Block(FailedBlockCheck { block_id: 0, check_id: 0, rule: "check if fact(null, $value), $value == null" }), Block(FailedBlockCheck { block_id: 0, check_id: 1, rule: "reject if fact(null, $value), $value != null" })] }))` + + +------------------------------ + +## test heterogeneous equal: test031_heterogeneous_equal.bc +### token + +authority: +symbols: ["abcD12", "abcD12x", "fact", "value", "fact2"] + +public keys: [] + +block version: 6 + +``` +check if true == true; +check if false == false; +check if false != true; +check if 1 != true; +check if 1 == 1; +check if 1 != 3; +check if 1 != true; +check if "abcD12" == "abcD12"; +check if "abcD12x" != "abcD12"; +check if "abcD12x" != true; +check if 2022-12-04T09:46:41Z == 2022-12-04T09:46:41Z; +check if 2022-12-04T09:46:41Z != 2020-12-04T09:46:41Z; +check if 2022-12-04T09:46:41Z != true; +check if hex:12abcd == hex:12abcd; +check if hex:12abcd != hex:12ab; +check if hex:12abcd != true; +check if {1, 2} == {1, 2}; +check if {1, 4} != {1, 2}; +check if {1, 4} != true; +check if fact(1, $value), 1 == $value; +check if fact2(1, $value), 1 != $value; +``` + +### validation + +authorizer code: +``` +fact(1, 1); +fact2(1, 2); + +allow if true; +``` + +revocation ids: +- `be50b2040f4b5fe278b87815910d249eeb9ca5238cae4ea538e22afda11f576e868cbfe7e6b0a03b02ae0f22239ec908947d4bad5a878e4b9f7bd7de73e5c90a` + +authorizer world: +``` +World { + facts: [ + Facts { + origin: { + None, + }, + facts: [ + "fact(1, 1)", + "fact2(1, 2)", + ], + }, +] + rules: [] + checks: [ + Checks { + origin: Some( + 0, + ), + checks: [ + "check if \"abcD12\" == \"abcD12\"", + "check if \"abcD12x\" != \"abcD12\"", + "check if \"abcD12x\" != true", + "check if 1 != 3", + "check if 1 != true", + "check if 1 != true", + "check if 1 == 1", + "check if 2022-12-04T09:46:41Z != 2020-12-04T09:46:41Z", + "check if 2022-12-04T09:46:41Z != true", + "check if 2022-12-04T09:46:41Z == 2022-12-04T09:46:41Z", + "check if fact(1, $value), 1 == $value", + "check if fact2(1, $value), 1 != $value", + "check if false != true", + "check if false == false", + "check if hex:12abcd != hex:12ab", + "check if hex:12abcd != true", + "check if hex:12abcd == hex:12abcd", + "check if true == true", + "check if {1, 2} == {1, 2}", + "check if {1, 4} != true", + "check if {1, 4} != {1, 2}", + ], + }, +] + policies: [ + "allow if true", +] +} +``` + +result: `Ok(0)` +### validation for "evaluate to false" + +authorizer code: +``` +fact(1, 2); +fact2(1, 1); + +check if false != false; + +allow if true; +``` + +revocation ids: +- `be50b2040f4b5fe278b87815910d249eeb9ca5238cae4ea538e22afda11f576e868cbfe7e6b0a03b02ae0f22239ec908947d4bad5a878e4b9f7bd7de73e5c90a` + +authorizer world: +``` +World { + facts: [ + Facts { + origin: { + None, + }, + facts: [ + "fact(1, 2)", + "fact2(1, 1)", + ], + }, +] + rules: [] + checks: [ + Checks { + origin: Some( + 0, + ), + checks: [ + "check if \"abcD12\" == \"abcD12\"", + "check if \"abcD12x\" != \"abcD12\"", + "check if \"abcD12x\" != true", + "check if 1 != 3", + "check if 1 != true", + "check if 1 != true", + "check if 1 == 1", + "check if 2022-12-04T09:46:41Z != 2020-12-04T09:46:41Z", + "check if 2022-12-04T09:46:41Z != true", + "check if 2022-12-04T09:46:41Z == 2022-12-04T09:46:41Z", + "check if fact(1, $value), 1 == $value", + "check if fact2(1, $value), 1 != $value", + "check if false != true", + "check if false == false", + "check if hex:12abcd != hex:12ab", + "check if hex:12abcd != true", + "check if hex:12abcd == hex:12abcd", + "check if true == true", + "check if {1, 2} == {1, 2}", + "check if {1, 4} != true", + "check if {1, 4} != {1, 2}", + ], + }, + Checks { + origin: Some( + 18446744073709551615, + ), + checks: [ + "check if false != false", + ], + }, +] + policies: [ + "allow if true", +] +} +``` + +result: `Err(FailedLogic(Unauthorized { policy: Allow(0), checks: [Authorizer(FailedAuthorizerCheck { check_id: 0, rule: "check if false != false" }), Block(FailedBlockCheck { block_id: 0, check_id: 19, rule: "check if fact(1, $value), 1 == $value" }), Block(FailedBlockCheck { block_id: 0, check_id: 20, rule: "check if fact2(1, $value), 1 != $value" })] }))` + + +------------------------------ + +## test laziness and closures: test032_laziness_closures.bc +### token + +authority: +symbols: ["x", "p", "q"] + +public keys: [] + +block version: 6 + +``` +check if !false && true; +check if false || true; +check if (true || false) && true; +check if !(false && "x".intersection("x")); +check if true || "x".intersection("x"); +check if {1, 2, 3}.all($p -> $p > 0); +check if !{1, 2, 3}.all($p -> $p == 2); +check if {1, 2, 3}.any($p -> $p > 2); +check if !{1, 2, 3}.any($p -> $p > 3); +check if {1, 2, 3}.any($p -> $p > 1 && {3, 4, 5}.any($q -> $p == $q)); +``` + +### validation + +authorizer code: +``` +allow if true; +``` + +revocation ids: +- `2cd348b6df5f08b900903fd8d3fbea0bb89b665c331a2aa2131e0b8ecb38b3550275d4ccd8db35da6c4433eed1d456cfb761e3fcc7845894d891e986ca044b02` + +authorizer world: +``` +World { + facts: [] + rules: [] + checks: [ + Checks { + origin: Some( + 0, + ), + checks: [ + "check if !(false && \"x\".intersection(\"x\"))", + "check if !false && true", + "check if !{1, 2, 3}.all($p -> $p == 2)", + "check if !{1, 2, 3}.any($p -> $p > 3)", + "check if (true || false) && true", + "check if false || true", + "check if true || \"x\".intersection(\"x\")", + "check if {1, 2, 3}.all($p -> $p > 0)", + "check if {1, 2, 3}.any($p -> $p > 1 && {3, 4, 5}.any($q -> $p == $q))", + "check if {1, 2, 3}.any($p -> $p > 2)", + ], + }, +] + policies: [ + "allow if true", +] +} +``` + +result: `Ok(0)` +### validation for "shadowing" + +authorizer code: +``` +allow if {"true"}.any($p -> {"true"}.all($p -> $p)); +``` + +revocation ids: +- `2cd348b6df5f08b900903fd8d3fbea0bb89b665c331a2aa2131e0b8ecb38b3550275d4ccd8db35da6c4433eed1d456cfb761e3fcc7845894d891e986ca044b02` + +authorizer world: +``` +World { + facts: [] + rules: [] + checks: [ + Checks { + origin: Some( + 0, + ), + checks: [ + "check if !(false && \"x\".intersection(\"x\"))", + "check if !false && true", + "check if !{1, 2, 3}.all($p -> $p == 2)", + "check if !{1, 2, 3}.any($p -> $p > 3)", + "check if (true || false) && true", + "check if false || true", + "check if true || \"x\".intersection(\"x\")", + "check if {1, 2, 3}.all($p -> $p > 0)", + "check if {1, 2, 3}.any($p -> $p > 1 && {3, 4, 5}.any($q -> $p == $q))", + "check if {1, 2, 3}.any($p -> $p > 2)", + ], + }, +] + policies: [ + "allow if {\"true\"}.any($p -> {\"true\"}.all($p -> $p))", +] +} +``` + +result: `Err(Execution(ShadowedVariable))` + + +------------------------------ + +## test .type(): test033_typeof.bc +### token + +authority: +symbols: ["integer", "string", "test", "date", "bytes", "bool", "set", "null", "array", "map", "a", "t"] + +public keys: [] + +block version: 6 + +``` +integer(1); +string("test"); +date(2023-12-28T00:00:00Z); +bytes(hex:aa); +bool(true); +set({false, true}); +null(null); +array([1, 2, 3]); +map({"a": true}); +check if 1.type() == "integer"; +check if integer($t), $t.type() == "integer"; +check if "test".type() == "string"; +check if string($t), $t.type() == "string"; +check if (2023-12-28T00:00:00Z).type() == "date"; +check if date($t), $t.type() == "date"; +check if hex:aa.type() == "bytes"; +check if bytes($t), $t.type() == "bytes"; +check if true.type() == "bool"; +check if bool($t), $t.type() == "bool"; +check if {false, true}.type() == "set"; +check if set($t), $t.type() == "set"; +check if null.type() == "null"; +check if null($t), $t.type() == "null"; +check if array($t), $t.type() == "array"; +check if map($t), $t.type() == "map"; +``` + +### validation + +authorizer code: +``` +allow if true; +``` + +revocation ids: +- `e60875c6ef7917c227a5e4b2cabfe250a85fa0598eb3cf7987ded0da2b69a559a1665bd312aeecde78e76aeb28ea1c1a03ec9b7dec8aeb519e7867ef8ff9b402` + +authorizer world: +``` +World { + facts: [ + Facts { + origin: { + Some( + 0, + ), + }, + facts: [ + "array([1, 2, 3])", + "bool(true)", + "bytes(hex:aa)", + "date(2023-12-28T00:00:00Z)", + "integer(1)", + "map({\"a\": true})", + "null(null)", + "set({false, true})", + "string(\"test\")", + ], + }, +] + rules: [] + checks: [ + Checks { + origin: Some( + 0, + ), + checks: [ + "check if \"test\".type() == \"string\"", + "check if (2023-12-28T00:00:00Z).type() == \"date\"", + "check if 1.type() == \"integer\"", + "check if array($t), $t.type() == \"array\"", + "check if bool($t), $t.type() == \"bool\"", + "check if bytes($t), $t.type() == \"bytes\"", + "check if date($t), $t.type() == \"date\"", + "check if hex:aa.type() == \"bytes\"", + "check if integer($t), $t.type() == \"integer\"", + "check if map($t), $t.type() == \"map\"", + "check if null($t), $t.type() == \"null\"", + "check if null.type() == \"null\"", + "check if set($t), $t.type() == \"set\"", + "check if string($t), $t.type() == \"string\"", + "check if true.type() == \"bool\"", + "check if {false, true}.type() == \"set\"", + ], + }, +] + policies: [ + "allow if true", +] +} +``` + +result: `Ok(0)` + + +------------------------------ + +## test array and map operations: test034_array_map.bc +### token + +authority: +symbols: ["a", "b", "c", "p", "d", "A", "kv", "id", "roles"] + +public keys: [] + +block version: 6 + +``` +check if [1, 2, 1].length() == 3; +check if ["a", "b"] != true; +check if ["a", "b"] != [1, 2, 3]; +check if ["a", "b"] == ["a", "b"]; +check if ["a", "b"] === ["a", "b"]; +check if ["a", "b"] !== ["a", "c"]; +check if ["a", "b", "c"].contains("c"); +check if [1, 2, 3].starts_with([1, 2]); +check if [4, 5, 6].ends_with([6]); +check if [1, 2, "a"].get(2) == "a"; +check if [1, 2].get(3) == null; +check if [1, 2, 3].all($p -> $p > 0); +check if [1, 2, 3].any($p -> $p > 2); +check if {"a": 1, "b": 2, "c": 3, "d": 4}.length() == 4; +check if {1: "a", 2: "b"} != true; +check if {1: "a", 2: "b"} != {"a": 1, "b": 2}; +check if {1: "a", 2: "b"} == {1: "a", 2: "b"}; +check if {1: "a", 2: "b"} !== {"a": 1, "b": 2}; +check if {1: "a", 2: "b"} === {1: "a", 2: "b"}; +check if {"a": 1, "b": 2, "c": 3, "d": 4}.contains("d"); +check if {1: "A", "a": 1, "b": 2}.get("a") == 1; +check if {1: "A", "a": 1, "b": 2}.get(1) == "A"; +check if {1: "A", "a": 1, "b": 2}.get("c") == null; +check if {1: "A", "a": 1, "b": 2}.get(2) == null; +check if {"a": 1, "b": 2}.all($kv -> $kv.get(0) != "c" && $kv.get(1) < 3); +check if {1: "A", "a": 1, "b": 2}.any($kv -> $kv.get(0) == 1 && $kv.get(1) == "A"); +check if {"user": {"id": 1, "roles": ["admin"]}}.get("user").get("roles").contains("admin"); +``` + +### validation + +authorizer code: +``` +allow if true; +``` + +revocation ids: +- `b22238a06ca9c015d3c49d4ebaa7e8ab6e0d69119b3264033618e726d62fc6f4757a7bebc25f255444aba39994554a62a53ecc13b68802efab8da85ace62390d` + +authorizer world: +``` +World { + facts: [] + rules: [] + checks: [ + Checks { + origin: Some( + 0, + ), + checks: [ + "check if [\"a\", \"b\", \"c\"].contains(\"c\")", + "check if [\"a\", \"b\"] != [1, 2, 3]", + "check if [\"a\", \"b\"] != true", + "check if [\"a\", \"b\"] !== [\"a\", \"c\"]", + "check if [\"a\", \"b\"] == [\"a\", \"b\"]", + "check if [\"a\", \"b\"] === [\"a\", \"b\"]", + "check if [1, 2, \"a\"].get(2) == \"a\"", + "check if [1, 2, 1].length() == 3", + "check if [1, 2, 3].all($p -> $p > 0)", + "check if [1, 2, 3].any($p -> $p > 2)", + "check if [1, 2, 3].starts_with([1, 2])", + "check if [1, 2].get(3) == null", + "check if [4, 5, 6].ends_with([6])", + "check if {\"a\": 1, \"b\": 2, \"c\": 3, \"d\": 4}.contains(\"d\")", + "check if {\"a\": 1, \"b\": 2, \"c\": 3, \"d\": 4}.length() == 4", + "check if {\"a\": 1, \"b\": 2}.all($kv -> $kv.get(0) != \"c\" && $kv.get(1) < 3)", + "check if {\"user\": {\"id\": 1, \"roles\": [\"admin\"]}}.get(\"user\").get(\"roles\").contains(\"admin\")", + "check if {1: \"A\", \"a\": 1, \"b\": 2}.any($kv -> $kv.get(0) == 1 && $kv.get(1) == \"A\")", + "check if {1: \"A\", \"a\": 1, \"b\": 2}.get(\"a\") == 1", + "check if {1: \"A\", \"a\": 1, \"b\": 2}.get(\"c\") == null", + "check if {1: \"A\", \"a\": 1, \"b\": 2}.get(1) == \"A\"", + "check if {1: \"A\", \"a\": 1, \"b\": 2}.get(2) == null", + "check if {1: \"a\", 2: \"b\"} != true", + "check if {1: \"a\", 2: \"b\"} != {\"a\": 1, \"b\": 2}", + "check if {1: \"a\", 2: \"b\"} !== {\"a\": 1, \"b\": 2}", + "check if {1: \"a\", 2: \"b\"} == {1: \"a\", 2: \"b\"}", + "check if {1: \"a\", 2: \"b\"} === {1: \"a\", 2: \"b\"}", + ], + }, +] + policies: [ + "allow if true", +] +} +``` + +result: `Ok(0)` + +------------------------------ + +## ECDSA secp256r1 signatures: test036_secp256r1.bc +### token + +authority: +symbols: ["file1", "file2"] + +public keys: [] + +block version: 3 + +``` +right("file1", "read"); +right("file2", "read"); +right("file1", "write"); +``` + +1: +symbols: ["0"] + +public keys: [] + +block version: 3 + +``` +check if resource($0), operation("read"), right($0, "read"); +``` + +### validation + +authorizer code: +``` +resource("file1"); +operation("read"); + +allow if true; +``` + +revocation ids: +- `628b9a6d74cc80b3ece50befd1f5f0f025c0a35d51708b2e77c11aed5f968b93b4096c87ed8169605716de934e155443f140334d71708fcc4247e5a0a518b30d` +- `3046022100b60674854a12814cc36c8aab9600c1d9f9d3160e2334b72c0feede5a56213ea5022100a4f4bbf2dc33b309267af39fce76612017ddb6171e9cd2a3aa8a853f45f1675f` + +authorizer world: +``` +World { + facts: [ + Facts { + origin: { + None, + }, + facts: [ + "operation(\"read\")", + "resource(\"file1\")", + ], + }, + Facts { + origin: { + Some( + 0, + ), + }, + facts: [ + "right(\"file1\", \"read\")", + "right(\"file1\", \"write\")", + "right(\"file2\", \"read\")", + ], + }, +] + rules: [] + checks: [ + Checks { + origin: Some( + 1, + ), + checks: [ + "check if resource($0), operation(\"read\"), right($0, \"read\")", + ], + }, +] + policies: [ + "allow if true", +] +} +``` + +result: `Ok(0)` + + +------------------------------ + +## ECDSA secp256r1 signature on third-party block: test037_secp256r1_third_party.bc +### token + +authority: +symbols: ["file1", "file2", "from_third"] + +public keys: ["secp256r1/025e918fd4463832aea2823dfd9716a36b4d9b1377bd53dd82ddf4c0bc75ed6bbf"] + +block version: 4 + +``` +right("file1", "read"); +right("file2", "read"); +right("file1", "write"); +check if from_third(true) trusting secp256r1/025e918fd4463832aea2823dfd9716a36b4d9b1377bd53dd82ddf4c0bc75ed6bbf; +``` + +1: +symbols: ["from_third", "0"] + +public keys: [] + +external signature by: "secp256r1/025e918fd4463832aea2823dfd9716a36b4d9b1377bd53dd82ddf4c0bc75ed6bbf" + +block version: 5 + +``` +from_third(true); +check if resource($0), operation("read"), right($0, "read"); +``` + +### validation + +authorizer code: +``` +resource("file1"); +operation("read"); + +allow if true; +``` + +revocation ids: +- `70f5402208516fd44cfc9df3dfcfc0a327ee9004f1801ed0a7abdcbbae923d566ddcd2d4a14f4622b35732c4e538af04075cc67ab0888fa2d8923cc668187f0f` +- `30450220793f95665d9af646339503a073670ea2c352459d2a2c2e14c57565f6c7eaf6bc022100cccadfc37e46755f52bb054ed206d7335067885df599a69431db40e33f33d4cf` + +authorizer world: +``` +World { + facts: [ + Facts { + origin: { + None, + }, + facts: [ + "operation(\"read\")", + "resource(\"file1\")", + ], + }, + Facts { + origin: { + Some( + 0, + ), + }, + facts: [ + "right(\"file1\", \"read\")", + "right(\"file1\", \"write\")", + "right(\"file2\", \"read\")", + ], + }, + Facts { + origin: { + Some( + 1, + ), + }, + facts: [ + "from_third(true)", + ], + }, +] + rules: [] + checks: [ + Checks { + origin: Some( + 0, + ), + checks: [ + "check if from_third(true) trusting secp256r1/025e918fd4463832aea2823dfd9716a36b4d9b1377bd53dd82ddf4c0bc75ed6bbf", + ], + }, + Checks { + origin: Some( + 1, + ), + checks: [ + "check if resource($0), operation(\"read\"), right($0, \"read\")", + ], + }, +] + policies: [ + "allow if true", +] +} +``` + +result: `Ok(0)` + + +------------------------------ + +## test try operation: test038_try_op.bc +### token + +authority: +symbols: [] + +public keys: [] + +block version: 6 + +``` +check if (true === 12).try_or(true); +check if ((true === 12).try_or(true === 12)).try_or(true); +reject if (true == 12).try_or(true); +``` + +### validation + +authorizer code: +``` +allow if true; +``` + +revocation ids: +- `79674155cd5349604e89b00792aeaebfa0a512bd45edc289305ebec107f627d3d8c09847646a0d06c2390a4354771b2ebdc2cc66971f2d74ef744e4e81197600` + +authorizer world: +``` +World { + facts: [] + rules: [] + checks: [ + Checks { + origin: Some( + 0, + ), + checks: [ + "check if ((true === 12).try_or(true === 12)).try_or(true)", + "check if (true === 12).try_or(true)", + "reject if (true == 12).try_or(true)", + ], + }, +] + policies: [ + "allow if true", +] +} +``` + +result: `Ok(0)` +### validation for "right-hand side does not catch errors" + +authorizer code: +``` +check if true.try_or(true === 12); + +allow if true; +``` + +revocation ids: +- `79674155cd5349604e89b00792aeaebfa0a512bd45edc289305ebec107f627d3d8c09847646a0d06c2390a4354771b2ebdc2cc66971f2d74ef744e4e81197600` + +authorizer world: +``` +World { + facts: [] + rules: [] + checks: [ + Checks { + origin: Some( + 0, + ), + checks: [ + "check if ((true === 12).try_or(true === 12)).try_or(true)", + "check if (true === 12).try_or(true)", + "reject if (true == 12).try_or(true)", + ], + }, + Checks { + origin: Some( + 18446744073709551615, + ), + checks: [ + "check if true.try_or(true === 12)", + ], + }, +] + policies: [ + "allow if true", +] +} +``` + +result: `Err(Execution(InvalidType))` diff --git a/src/test/resources/samples/samples.json b/src/test/resources/samples/samples.json index 77720256..ab72835b 100644 --- a/src/test/resources/samples/samples.json +++ b/src/test/resources/samples/samples.json @@ -141,7 +141,8 @@ ], "public_keys": [], "external_key": null, - "code": "right(\"file1\", \"read\");\nright(\"file2\", \"read\");\nright(\"file1\", \"write\");\n" + "code": "right(\"file1\", \"read\");\nright(\"file2\", \"read\");\nright(\"file1\", \"write\");\n", + "version": 3 }, { "symbols": [ @@ -149,7 +150,8 @@ ], "public_keys": [], "external_key": null, - "code": "check if resource($0), operation(\"read\"), right($0, \"read\");\n" + "code": "check if resource($0), operation(\"read\"), right($0, \"read\");\n", + "version": 3 } ], "validations": { @@ -875,7 +877,8 @@ ], "public_keys": [], "external_key": null, - "code": "right(\"file1\", \"read\");\nright(\"file2\", \"read\");\n" + "code": "right(\"file1\", \"read\");\nright(\"file2\", \"read\");\n", + "version": 3 }, { "symbols": [ @@ -885,7 +888,8 @@ ], "public_keys": [], "external_key": null, - "code": "valid_date(\"file1\") <- time($0), resource(\"file1\"), $0 <= 2030-12-31T12:59:59Z;\nvalid_date($1) <- time($0), resource($1), $0 <= 1999-12-31T12:59:59Z, ![\"file1\"].contains($1);\ncheck if valid_date($0), resource($0);\n" + "code": "valid_date(\"file1\") <- time($0), resource(\"file1\"), $0 <= 2030-12-31T12:59:59Z;\nvalid_date($1) <- time($0), resource($1), $0 <= 1999-12-31T12:59:59Z, !{\"file1\"}.contains($1);\ncheck if valid_date($0), resource($0);\n", + "version": 3 } ], "validations": { @@ -925,7 +929,7 @@ "origin": 1, "rules": [ "valid_date(\"file1\") <- time($0), resource(\"file1\"), $0 <= 2030-12-31T12:59:59Z", - "valid_date($1) <- time($0), resource($1), $0 <= 1999-12-31T12:59:59Z, ![\"file1\"].contains($1)" + "valid_date($1) <- time($0), resource($1), $0 <= 1999-12-31T12:59:59Z, !{\"file1\"}.contains($1)" ] } ], @@ -977,7 +981,7 @@ "origin": 1, "rules": [ "valid_date(\"file1\") <- time($0), resource(\"file1\"), $0 <= 2030-12-31T12:59:59Z", - "valid_date($1) <- time($0), resource($1), $0 <= 1999-12-31T12:59:59Z, ![\"file1\"].contains($1)" + "valid_date($1) <- time($0), resource($1), $0 <= 1999-12-31T12:59:59Z, !{\"file1\"}.contains($1)" ] } ], @@ -1258,7 +1262,6 @@ "symbols": [ "hello world", "hello", - "hello", "world", "aaabde", "a*c?.e", @@ -1273,7 +1276,8 @@ ], "public_keys": [], "external_key": null, - "code": "check if true;\ncheck if !false;\ncheck if !false && true;\ncheck if false || true;\ncheck if (true || false) && true;\ncheck if true == true;\ncheck if false == false;\ncheck if 1 < 2;\ncheck if 2 > 1;\ncheck if 1 <= 2;\ncheck if 1 <= 1;\ncheck if 2 >= 1;\ncheck if 2 >= 2;\ncheck if 3 == 3;\ncheck if 1 + 2 * 3 - 4 / 2 == 5;\ncheck if \"hello world\".starts_with(\"hello\") && \"hello world\".ends_with(\"world\");\ncheck if \"aaabde\".matches(\"a*c?.e\");\ncheck if \"aaabde\".contains(\"abd\");\ncheck if \"aaabde\" == \"aaa\" + \"b\" + \"de\";\ncheck if \"abcD12\" == \"abcD12\";\ncheck if \"abcD12\".length() == 6;\ncheck if \"é\".length() == 2;\ncheck if 2019-12-04T09:46:41Z < 2020-12-04T09:46:41Z;\ncheck if 2020-12-04T09:46:41Z > 2019-12-04T09:46:41Z;\ncheck if 2019-12-04T09:46:41Z <= 2020-12-04T09:46:41Z;\ncheck if 2020-12-04T09:46:41Z >= 2020-12-04T09:46:41Z;\ncheck if 2020-12-04T09:46:41Z >= 2019-12-04T09:46:41Z;\ncheck if 2020-12-04T09:46:41Z >= 2020-12-04T09:46:41Z;\ncheck if 2020-12-04T09:46:41Z == 2020-12-04T09:46:41Z;\ncheck if hex:12ab == hex:12ab;\ncheck if [1, 2].contains(2);\ncheck if [2019-12-04T09:46:41Z, 2020-12-04T09:46:41Z].contains(2020-12-04T09:46:41Z);\ncheck if [false, true].contains(true);\ncheck if [\"abc\", \"def\"].contains(\"abc\");\ncheck if [hex:12ab, hex:34de].contains(hex:34de);\ncheck if [1, 2].contains([2]);\ncheck if [1, 2] == [1, 2];\ncheck if [1, 2].intersection([2, 3]) == [2];\ncheck if [1, 2].union([2, 3]) == [1, 2, 3];\ncheck if [1, 2, 3].intersection([1, 2]).contains(1);\ncheck if [1, 2, 3].intersection([1, 2]).length() == 2;\n" + "code": "check if true;\ncheck if !false;\ncheck if true === true;\ncheck if false === false;\ncheck if 1 < 2;\ncheck if 2 > 1;\ncheck if 1 <= 2;\ncheck if 1 <= 1;\ncheck if 2 >= 1;\ncheck if 2 >= 2;\ncheck if 3 === 3;\ncheck if 1 + 2 * 3 - 4 / 2 === 5;\ncheck if \"hello world\".starts_with(\"hello\"), \"hello world\".ends_with(\"world\");\ncheck if \"aaabde\".matches(\"a*c?.e\");\ncheck if \"aaabde\".contains(\"abd\");\ncheck if \"aaabde\" === \"aaa\" + \"b\" + \"de\";\ncheck if \"abcD12\" === \"abcD12\";\ncheck if \"abcD12\".length() === 6;\ncheck if \"é\".length() === 2;\ncheck if 2019-12-04T09:46:41Z < 2020-12-04T09:46:41Z;\ncheck if 2020-12-04T09:46:41Z > 2019-12-04T09:46:41Z;\ncheck if 2019-12-04T09:46:41Z <= 2020-12-04T09:46:41Z;\ncheck if 2020-12-04T09:46:41Z >= 2020-12-04T09:46:41Z;\ncheck if 2020-12-04T09:46:41Z >= 2019-12-04T09:46:41Z;\ncheck if 2020-12-04T09:46:41Z >= 2020-12-04T09:46:41Z;\ncheck if 2020-12-04T09:46:41Z === 2020-12-04T09:46:41Z;\ncheck if hex:12ab === hex:12ab;\ncheck if {1, 2}.contains(2);\ncheck if {2019-12-04T09:46:41Z, 2020-12-04T09:46:41Z}.contains(2020-12-04T09:46:41Z);\ncheck if {false, true}.contains(true);\ncheck if {\"abc\", \"def\"}.contains(\"abc\");\ncheck if {hex:12ab, hex:34de}.contains(hex:34de);\ncheck if {1, 2}.contains({2});\ncheck if {1, 2} === {1, 2};\ncheck if {1, 2}.intersection({2, 3}) === {2};\ncheck if {1, 2}.union({2, 3}) === {1, 2, 3};\ncheck if {1, 2, 3}.intersection({1, 2}).contains(1);\ncheck if {1, 2, 3}.intersection({1, 2}).length() === 2;\ncheck if {,}.length() === 0;\n", + "version": 3 } ], "validations": { @@ -1286,16 +1290,14 @@ "origin": 0, "checks": [ "check if !false", - "check if !false && true", - "check if \"aaabde\" == \"aaa\" + \"b\" + \"de\"", + "check if \"aaabde\" === \"aaa\" + \"b\" + \"de\"", "check if \"aaabde\".contains(\"abd\")", "check if \"aaabde\".matches(\"a*c?.e\")", - "check if \"abcD12\" == \"abcD12\"", - "check if \"abcD12\".length() == 6", - "check if \"hello world\".starts_with(\"hello\") && \"hello world\".ends_with(\"world\")", - "check if \"é\".length() == 2", - "check if (true || false) && true", - "check if 1 + 2 * 3 - 4 / 2 == 5", + "check if \"abcD12\" === \"abcD12\"", + "check if \"abcD12\".length() === 6", + "check if \"hello world\".starts_with(\"hello\"), \"hello world\".ends_with(\"world\")", + "check if \"é\".length() === 2", + "check if 1 + 2 * 3 - 4 / 2 === 5", "check if 1 < 2", "check if 1 <= 1", "check if 1 <= 2", @@ -1304,28 +1306,28 @@ "check if 2 >= 2", "check if 2019-12-04T09:46:41Z < 2020-12-04T09:46:41Z", "check if 2019-12-04T09:46:41Z <= 2020-12-04T09:46:41Z", - "check if 2020-12-04T09:46:41Z == 2020-12-04T09:46:41Z", + "check if 2020-12-04T09:46:41Z === 2020-12-04T09:46:41Z", "check if 2020-12-04T09:46:41Z > 2019-12-04T09:46:41Z", "check if 2020-12-04T09:46:41Z >= 2019-12-04T09:46:41Z", "check if 2020-12-04T09:46:41Z >= 2020-12-04T09:46:41Z", "check if 2020-12-04T09:46:41Z >= 2020-12-04T09:46:41Z", - "check if 3 == 3", - "check if [\"abc\", \"def\"].contains(\"abc\")", - "check if [1, 2, 3].intersection([1, 2]).contains(1)", - "check if [1, 2, 3].intersection([1, 2]).length() == 2", - "check if [1, 2] == [1, 2]", - "check if [1, 2].contains(2)", - "check if [1, 2].contains([2])", - "check if [1, 2].intersection([2, 3]) == [2]", - "check if [1, 2].union([2, 3]) == [1, 2, 3]", - "check if [2019-12-04T09:46:41Z, 2020-12-04T09:46:41Z].contains(2020-12-04T09:46:41Z)", - "check if [false, true].contains(true)", - "check if [hex:12ab, hex:34de].contains(hex:34de)", - "check if false == false", - "check if false || true", - "check if hex:12ab == hex:12ab", + "check if 3 === 3", + "check if false === false", + "check if hex:12ab === hex:12ab", "check if true", - "check if true == true" + "check if true === true", + "check if {\"abc\", \"def\"}.contains(\"abc\")", + "check if {,}.length() === 0", + "check if {1, 2, 3}.intersection({1, 2}).contains(1)", + "check if {1, 2, 3}.intersection({1, 2}).length() === 2", + "check if {1, 2} === {1, 2}", + "check if {1, 2}.contains(2)", + "check if {1, 2}.contains({2})", + "check if {1, 2}.intersection({2, 3}) === {2}", + "check if {1, 2}.union({2, 3}) === {1, 2, 3}", + "check if {2019-12-04T09:46:41Z, 2020-12-04T09:46:41Z}.contains(2020-12-04T09:46:41Z)", + "check if {false, true}.contains(true)", + "check if {hex:12ab, hex:34de}.contains(hex:34de)" ] } ], @@ -1338,7 +1340,7 @@ }, "authorizer_code": "allow if true;\n", "revocation_ids": [ - "3d5b23b502b3dd920bfb68b9039164d1563bb8927210166fa5c17f41b76b31bb957bc2ed3318452958f658baa2d398fe4cf25c58a27e6c8bc42c9702c8aa1b0c" + "fa358e4e3bea896415b1859e6cd347e64e1918fb86e31ae3fe208628321576a47f7a269760357e291c827ec9cbe322074f6860a546207a64e133c83a214bb505" ] } } @@ -1858,7 +1860,8 @@ ], "public_keys": [], "external_key": null, - "code": "allowed_operations([\"A\", \"B\"]);\ncheck all operation($op), allowed_operations($allowed), $allowed.contains($op);\n" + "code": "allowed_operations({\"A\", \"B\"});\ncheck all operation($op), allowed_operations($allowed), $allowed.contains($op);\n", + "version": 4 } ], "validations": { @@ -1879,7 +1882,7 @@ 0 ], "facts": [ - "allowed_operations([\"A\", \"B\"])" + "allowed_operations({\"A\", \"B\"})" ] } ], @@ -1921,7 +1924,7 @@ 0 ], "facts": [ - "allowed_operations([\"A\", \"B\"])" + "allowed_operations({\"A\", \"B\"})" ] } ], @@ -1962,6 +1965,56 @@ "revocation_ids": [ "c456817012e1d523c6d145b6d6a3475d9f7dd4383c535454ff3f745ecf4234984ce09b9dec0551f3d783abe850f826ce43b12f1fd91999a4753a56ecf4c56d0d" ] + }, + "no matches": { + "world": { + "facts": [ + { + "origin": [ + 0 + ], + "facts": [ + "allowed_operations({\"A\", \"B\"})" + ] + } + ], + "rules": [], + "checks": [ + { + "origin": 0, + "checks": [ + "check all operation($op), allowed_operations($allowed), $allowed.contains($op)" + ] + } + ], + "policies": [ + "allow if true" + ] + }, + "result": { + "Err": { + "FailedLogic": { + "Unauthorized": { + "policy": { + "Allow": 0 + }, + "checks": [ + { + "Block": { + "block_id": 0, + "check_id": 0, + "rule": "check all operation($op), allowed_operations($allowed), $allowed.contains($op)" + } + } + ] + } + } + } + }, + "authorizer_code": "allow if true;\n", + "revocation_ids": [ + "c456817012e1d523c6d145b6d6a3475d9f7dd4383c535454ff3f745ecf4234984ce09b9dec0551f3d783abe850f826ce43b12f1fd91999a4753a56ecf4c56d0d" + ] } } }, @@ -2145,27 +2198,108 @@ } }, { - "title": "ECDSA secp256r1 signatures", - "filename": "test036_secp256r1.bc", + "title": "integer wraparound", + "filename": "test027_integer_wraparound.bc", + "token": [ + { + "symbols": [], + "public_keys": [], + "external_key": null, + "code": "check if 10000000000 * 10000000000 !== 0;\ncheck if 9223372036854775807 + 1 !== 0;\ncheck if -9223372036854775808 - 1 !== 0;\n", + "version": 4 + } + ], + "validations": { + "": { + "world": { + "facts": [], + "rules": [], + "checks": [ + { + "origin": 0, + "checks": [ + "check if -9223372036854775808 - 1 !== 0", + "check if 10000000000 * 10000000000 !== 0", + "check if 9223372036854775807 + 1 !== 0" + ] + } + ], + "policies": [ + "allow if true" + ] + }, + "result": { + "Err": { + "Execution": "Overflow" + } + }, + "authorizer_code": "allow if true;\n", + "revocation_ids": [ + "fb5e7ac2bb892f5cf2fb59677cfad1f96deabbc8e158e3fd1b5ee7c4b6949c999e2169187cbee53b943eebdadaaf68832747baa8cffa2ff9f78025a1f55f440c" + ] + } + } + }, + { + "title": "test expression syntax and all available operations (v4 blocks)", + "filename": "test028_expressions_v4.bc", "token": [ { "symbols": [ - "file1", - "file2" + "abcD12x", + "abcD12" ], "public_keys": [], "external_key": null, - "code": "right(\"file1\", \"read\");\nright(\"file2\", \"read\");\nright(\"file1\", \"write\");\n", - "version": 3 - }, + "code": "check if true !== false;\ncheck if 1 !== 3;\ncheck if 1 | 2 ^ 3 === 0;\ncheck if \"abcD12x\" !== \"abcD12\";\ncheck if 2022-12-04T09:46:41Z !== 2020-12-04T09:46:41Z;\ncheck if hex:12abcd !== hex:12ab;\ncheck if {1, 4} !== {1, 2};\n", + "version": 4 + } + ], + "validations": { + "": { + "world": { + "facts": [], + "rules": [], + "checks": [ + { + "origin": 0, + "checks": [ + "check if \"abcD12x\" !== \"abcD12\"", + "check if 1 !== 3", + "check if 1 | 2 ^ 3 === 0", + "check if 2022-12-04T09:46:41Z !== 2020-12-04T09:46:41Z", + "check if hex:12abcd !== hex:12ab", + "check if true !== false", + "check if {1, 4} !== {1, 2}" + ] + } + ], + "policies": [ + "allow if true" + ] + }, + "result": { + "Ok": 0 + }, + "authorizer_code": "allow if true;\n", + "revocation_ids": [ + "9402c07923aa33bc911de80e61f388f5c4533e6b36e45317dc1db1e6bcc7664ed0c1c504d0ca8925208008961d95bbdbc36f6e3d91b3173369cc19ed625e9a0c" + ] + } + } + }, + { + "title": "test reject if", + "filename": "test029_reject_if.bc", + "token": [ { "symbols": [ - "0" + "test" ], "public_keys": [], "external_key": null, - "code": "check if resource($0), operation(\"read\"), right($0, \"read\");\n", - "version": 3 + "code": "reject if test($test), $test;\n", + "version": 6 } ], "validations": { @@ -2177,27 +2311,49 @@ null ], "facts": [ - "operation(\"read\")", - "resource(\"file1\")" + "test(false)" ] - }, + } + ], + "rules": [], + "checks": [ + { + "origin": 0, + "checks": [ + "reject if test($test), $test" + ] + } + ], + "policies": [ + "allow if true" + ] + }, + "result": { + "Ok": 0 + }, + "authorizer_code": "test(false);\n\nallow if true;\n", + "revocation_ids": [ + "8d175329f7cf161f3cb5badc52f0e22e520956cdb565edbed963e9b047b20a314a7de1c9eba6b7bbf622636516ab3cc7f91572ae9461d3152825e0ece5127a0a" + ] + }, + "rejection": { + "world": { + "facts": [ { "origin": [ - 0 + null ], "facts": [ - "right(\"file1\", \"read\")", - "right(\"file1\", \"write\")", - "right(\"file2\", \"read\")" + "test(true)" ] } ], "rules": [], "checks": [ { - "origin": 1, + "origin": 0, "checks": [ - "check if resource($0), operation(\"read\"), right($0, \"read\")" + "reject if test($test), $test" ] } ], @@ -2206,42 +2362,45 @@ ] }, "result": { - "Ok": 0 + "Err": { + "FailedLogic": { + "Unauthorized": { + "policy": { + "Allow": 0 + }, + "checks": [ + { + "Block": { + "block_id": 0, + "check_id": 0, + "rule": "reject if test($test), $test" + } + } + ] + } + } + } }, - "authorizer_code": "resource(\"file1\");\noperation(\"read\");\n\nallow if true;\n", + "authorizer_code": "test(true);\n\nallow if true;\n", "revocation_ids": [ - "628b9a6d74cc80b3ece50befd1f5f0f025c0a35d51708b2e77c11aed5f968b93b4096c87ed8169605716de934e155443f140334d71708fcc4247e5a0a518b30d", - "3046022100b60674854a12814cc36c8aab9600c1d9f9d3160e2334b72c0feede5a56213ea5022100a4f4bbf2dc33b309267af39fce76612017ddb6171e9cd2a3aa8a853f45f1675f" + "8d175329f7cf161f3cb5badc52f0e22e520956cdb565edbed963e9b047b20a314a7de1c9eba6b7bbf622636516ab3cc7f91572ae9461d3152825e0ece5127a0a" ] } } }, { - "title": "ECDSA secp256r1 signature on third-party block", - "filename": "test037_secp256r1_third_party.bc", + "title": "test null", + "filename": "test030_null.bc", "token": [ { "symbols": [ - "file1", - "file2", - "from_third" - ], - "public_keys": [ - "secp256r1/025e918fd4463832aea2823dfd9716a36b4d9b1377bd53dd82ddf4c0bc75ed6bbf" - ], - "external_key": null, - "code": "right(\"file1\", \"read\");\nright(\"file2\", \"read\");\nright(\"file1\", \"write\");\ncheck if from_third(true) trusting secp256r1/025e918fd4463832aea2823dfd9716a36b4d9b1377bd53dd82ddf4c0bc75ed6bbf;\n", - "version": 4 - }, - { - "symbols": [ - "from_third", - "0" + "fact", + "value" ], "public_keys": [], - "external_key": "secp256r1/025e918fd4463832aea2823dfd9716a36b4d9b1377bd53dd82ddf4c0bc75ed6bbf", - "code": "from_third(true);\ncheck if resource($0), operation(\"read\"), right($0, \"read\");\n", - "version": 5 + "external_key": null, + "code": "check if fact(null, $value), $value == null;\nreject if fact(null, $value), $value != null;\n", + "version": 6 } ], "validations": { @@ -2253,26 +2412,41 @@ null ], "facts": [ - "operation(\"read\")", - "resource(\"file1\")" + "fact(null, null)" ] - }, + } + ], + "rules": [], + "checks": [ { - "origin": [ - 0 - ], - "facts": [ - "right(\"file1\", \"read\")", - "right(\"file1\", \"write\")", - "right(\"file2\", \"read\")" + "origin": 0, + "checks": [ + "check if fact(null, $value), $value == null", + "reject if fact(null, $value), $value != null" ] - }, + } + ], + "policies": [ + "allow if true" + ] + }, + "result": { + "Ok": 0 + }, + "authorizer_code": "fact(null, null);\n\nallow if true;\n", + "revocation_ids": [ + "fe50d65706a5945c76569d1ff2be8ece24276857631e96efa05959f73bb4ea8c772945738a01da77a1661aef2b8233b4f4e49ae220f2c81fd0b8da59c212750b" + ] + }, + "rejection1": { + "world": { + "facts": [ { "origin": [ - 1 + null ], "facts": [ - "from_third(true)" + "fact(null, 1)" ] } ], @@ -2281,13 +2455,8 @@ { "origin": 0, "checks": [ - "check if from_third(true) trusting secp256r1/025e918fd4463832aea2823dfd9716a36b4d9b1377bd53dd82ddf4c0bc75ed6bbf" - ] - }, - { - "origin": 1, - "checks": [ - "check if resource($0), operation(\"read\"), right($0, \"read\")" + "check if fact(null, $value), $value == null", + "reject if fact(null, $value), $value != null" ] } ], @@ -2296,8 +2465,720 @@ ] }, "result": { - "Ok": 0 - }, + "Err": { + "FailedLogic": { + "Unauthorized": { + "policy": { + "Allow": 0 + }, + "checks": [ + { + "Block": { + "block_id": 0, + "check_id": 0, + "rule": "check if fact(null, $value), $value == null" + } + }, + { + "Block": { + "block_id": 0, + "check_id": 1, + "rule": "reject if fact(null, $value), $value != null" + } + } + ] + } + } + } + }, + "authorizer_code": "fact(null, 1);\n\nallow if true;\n", + "revocation_ids": [ + "fe50d65706a5945c76569d1ff2be8ece24276857631e96efa05959f73bb4ea8c772945738a01da77a1661aef2b8233b4f4e49ae220f2c81fd0b8da59c212750b" + ] + }, + "rejection2": { + "world": { + "facts": [ + { + "origin": [ + null + ], + "facts": [ + "fact(null, true)" + ] + } + ], + "rules": [], + "checks": [ + { + "origin": 0, + "checks": [ + "check if fact(null, $value), $value == null", + "reject if fact(null, $value), $value != null" + ] + } + ], + "policies": [ + "allow if true" + ] + }, + "result": { + "Err": { + "FailedLogic": { + "Unauthorized": { + "policy": { + "Allow": 0 + }, + "checks": [ + { + "Block": { + "block_id": 0, + "check_id": 0, + "rule": "check if fact(null, $value), $value == null" + } + }, + { + "Block": { + "block_id": 0, + "check_id": 1, + "rule": "reject if fact(null, $value), $value != null" + } + } + ] + } + } + } + }, + "authorizer_code": "fact(null, true);\n\nallow if true;\n", + "revocation_ids": [ + "fe50d65706a5945c76569d1ff2be8ece24276857631e96efa05959f73bb4ea8c772945738a01da77a1661aef2b8233b4f4e49ae220f2c81fd0b8da59c212750b" + ] + }, + "rejection3": { + "world": { + "facts": [ + { + "origin": [ + null + ], + "facts": [ + "fact(null, \"abcd\")" + ] + } + ], + "rules": [], + "checks": [ + { + "origin": 0, + "checks": [ + "check if fact(null, $value), $value == null", + "reject if fact(null, $value), $value != null" + ] + } + ], + "policies": [ + "allow if true" + ] + }, + "result": { + "Err": { + "FailedLogic": { + "Unauthorized": { + "policy": { + "Allow": 0 + }, + "checks": [ + { + "Block": { + "block_id": 0, + "check_id": 0, + "rule": "check if fact(null, $value), $value == null" + } + }, + { + "Block": { + "block_id": 0, + "check_id": 1, + "rule": "reject if fact(null, $value), $value != null" + } + } + ] + } + } + } + }, + "authorizer_code": "fact(null, \"abcd\");\n\nallow if true;\n", + "revocation_ids": [ + "fe50d65706a5945c76569d1ff2be8ece24276857631e96efa05959f73bb4ea8c772945738a01da77a1661aef2b8233b4f4e49ae220f2c81fd0b8da59c212750b" + ] + } + } + }, + { + "title": "test heterogeneous equal", + "filename": "test031_heterogeneous_equal.bc", + "token": [ + { + "symbols": [ + "abcD12", + "abcD12x", + "fact", + "value", + "fact2" + ], + "public_keys": [], + "external_key": null, + "code": "check if true == true;\ncheck if false == false;\ncheck if false != true;\ncheck if 1 != true;\ncheck if 1 == 1;\ncheck if 1 != 3;\ncheck if 1 != true;\ncheck if \"abcD12\" == \"abcD12\";\ncheck if \"abcD12x\" != \"abcD12\";\ncheck if \"abcD12x\" != true;\ncheck if 2022-12-04T09:46:41Z == 2022-12-04T09:46:41Z;\ncheck if 2022-12-04T09:46:41Z != 2020-12-04T09:46:41Z;\ncheck if 2022-12-04T09:46:41Z != true;\ncheck if hex:12abcd == hex:12abcd;\ncheck if hex:12abcd != hex:12ab;\ncheck if hex:12abcd != true;\ncheck if {1, 2} == {1, 2};\ncheck if {1, 4} != {1, 2};\ncheck if {1, 4} != true;\ncheck if fact(1, $value), 1 == $value;\ncheck if fact2(1, $value), 1 != $value;\n", + "version": 6 + } + ], + "validations": { + "": { + "world": { + "facts": [ + { + "origin": [ + null + ], + "facts": [ + "fact(1, 1)", + "fact2(1, 2)" + ] + } + ], + "rules": [], + "checks": [ + { + "origin": 0, + "checks": [ + "check if \"abcD12\" == \"abcD12\"", + "check if \"abcD12x\" != \"abcD12\"", + "check if \"abcD12x\" != true", + "check if 1 != 3", + "check if 1 != true", + "check if 1 != true", + "check if 1 == 1", + "check if 2022-12-04T09:46:41Z != 2020-12-04T09:46:41Z", + "check if 2022-12-04T09:46:41Z != true", + "check if 2022-12-04T09:46:41Z == 2022-12-04T09:46:41Z", + "check if fact(1, $value), 1 == $value", + "check if fact2(1, $value), 1 != $value", + "check if false != true", + "check if false == false", + "check if hex:12abcd != hex:12ab", + "check if hex:12abcd != true", + "check if hex:12abcd == hex:12abcd", + "check if true == true", + "check if {1, 2} == {1, 2}", + "check if {1, 4} != true", + "check if {1, 4} != {1, 2}" + ] + } + ], + "policies": [ + "allow if true" + ] + }, + "result": { + "Ok": 0 + }, + "authorizer_code": "fact(1, 1);\nfact2(1, 2);\n\nallow if true;\n", + "revocation_ids": [ + "be50b2040f4b5fe278b87815910d249eeb9ca5238cae4ea538e22afda11f576e868cbfe7e6b0a03b02ae0f22239ec908947d4bad5a878e4b9f7bd7de73e5c90a" + ] + }, + "evaluate to false": { + "world": { + "facts": [ + { + "origin": [ + null + ], + "facts": [ + "fact(1, 2)", + "fact2(1, 1)" + ] + } + ], + "rules": [], + "checks": [ + { + "origin": 0, + "checks": [ + "check if \"abcD12\" == \"abcD12\"", + "check if \"abcD12x\" != \"abcD12\"", + "check if \"abcD12x\" != true", + "check if 1 != 3", + "check if 1 != true", + "check if 1 != true", + "check if 1 == 1", + "check if 2022-12-04T09:46:41Z != 2020-12-04T09:46:41Z", + "check if 2022-12-04T09:46:41Z != true", + "check if 2022-12-04T09:46:41Z == 2022-12-04T09:46:41Z", + "check if fact(1, $value), 1 == $value", + "check if fact2(1, $value), 1 != $value", + "check if false != true", + "check if false == false", + "check if hex:12abcd != hex:12ab", + "check if hex:12abcd != true", + "check if hex:12abcd == hex:12abcd", + "check if true == true", + "check if {1, 2} == {1, 2}", + "check if {1, 4} != true", + "check if {1, 4} != {1, 2}" + ] + }, + { + "origin": 18446744073709551615, + "checks": [ + "check if false != false" + ] + } + ], + "policies": [ + "allow if true" + ] + }, + "result": { + "Err": { + "FailedLogic": { + "Unauthorized": { + "policy": { + "Allow": 0 + }, + "checks": [ + { + "Authorizer": { + "check_id": 0, + "rule": "check if false != false" + } + }, + { + "Block": { + "block_id": 0, + "check_id": 19, + "rule": "check if fact(1, $value), 1 == $value" + } + }, + { + "Block": { + "block_id": 0, + "check_id": 20, + "rule": "check if fact2(1, $value), 1 != $value" + } + } + ] + } + } + } + }, + "authorizer_code": "fact(1, 2);\nfact2(1, 1);\n\ncheck if false != false;\n\nallow if true;\n", + "revocation_ids": [ + "be50b2040f4b5fe278b87815910d249eeb9ca5238cae4ea538e22afda11f576e868cbfe7e6b0a03b02ae0f22239ec908947d4bad5a878e4b9f7bd7de73e5c90a" + ] + } + } + }, + { + "title": "test laziness and closures", + "filename": "test032_laziness_closures.bc", + "token": [ + { + "symbols": [ + "x", + "p", + "q" + ], + "public_keys": [], + "external_key": null, + "code": "check if !false && true;\ncheck if false || true;\ncheck if (true || false) && true;\ncheck if !(false && \"x\".intersection(\"x\"));\ncheck if true || \"x\".intersection(\"x\");\ncheck if {1, 2, 3}.all($p -> $p > 0);\ncheck if !{1, 2, 3}.all($p -> $p == 2);\ncheck if {1, 2, 3}.any($p -> $p > 2);\ncheck if !{1, 2, 3}.any($p -> $p > 3);\ncheck if {1, 2, 3}.any($p -> $p > 1 && {3, 4, 5}.any($q -> $p == $q));\n", + "version": 6 + } + ], + "validations": { + "": { + "world": { + "facts": [], + "rules": [], + "checks": [ + { + "origin": 0, + "checks": [ + "check if !(false && \"x\".intersection(\"x\"))", + "check if !false && true", + "check if !{1, 2, 3}.all($p -> $p == 2)", + "check if !{1, 2, 3}.any($p -> $p > 3)", + "check if (true || false) && true", + "check if false || true", + "check if true || \"x\".intersection(\"x\")", + "check if {1, 2, 3}.all($p -> $p > 0)", + "check if {1, 2, 3}.any($p -> $p > 1 && {3, 4, 5}.any($q -> $p == $q))", + "check if {1, 2, 3}.any($p -> $p > 2)" + ] + } + ], + "policies": [ + "allow if true" + ] + }, + "result": { + "Ok": 0 + }, + "authorizer_code": "allow if true;\n", + "revocation_ids": [ + "2cd348b6df5f08b900903fd8d3fbea0bb89b665c331a2aa2131e0b8ecb38b3550275d4ccd8db35da6c4433eed1d456cfb761e3fcc7845894d891e986ca044b02" + ] + }, + "shadowing": { + "world": { + "facts": [], + "rules": [], + "checks": [ + { + "origin": 0, + "checks": [ + "check if !(false && \"x\".intersection(\"x\"))", + "check if !false && true", + "check if !{1, 2, 3}.all($p -> $p == 2)", + "check if !{1, 2, 3}.any($p -> $p > 3)", + "check if (true || false) && true", + "check if false || true", + "check if true || \"x\".intersection(\"x\")", + "check if {1, 2, 3}.all($p -> $p > 0)", + "check if {1, 2, 3}.any($p -> $p > 1 && {3, 4, 5}.any($q -> $p == $q))", + "check if {1, 2, 3}.any($p -> $p > 2)" + ] + } + ], + "policies": [ + "allow if {\"true\"}.any($p -> {\"true\"}.all($p -> $p))" + ] + }, + "result": { + "Err": { + "Execution": "ShadowedVariable" + } + }, + "authorizer_code": "allow if {\"true\"}.any($p -> {\"true\"}.all($p -> $p));\n", + "revocation_ids": [ + "2cd348b6df5f08b900903fd8d3fbea0bb89b665c331a2aa2131e0b8ecb38b3550275d4ccd8db35da6c4433eed1d456cfb761e3fcc7845894d891e986ca044b02" + ] + } + } + }, + { + "title": "test .type()", + "filename": "test033_typeof.bc", + "token": [ + { + "symbols": [ + "integer", + "string", + "test", + "date", + "bytes", + "bool", + "set", + "null", + "array", + "map", + "a", + "t" + ], + "public_keys": [], + "external_key": null, + "code": "integer(1);\nstring(\"test\");\ndate(2023-12-28T00:00:00Z);\nbytes(hex:aa);\nbool(true);\nset({false, true});\nnull(null);\narray([1, 2, 3]);\nmap({\"a\": true});\ncheck if 1.type() == \"integer\";\ncheck if integer($t), $t.type() == \"integer\";\ncheck if \"test\".type() == \"string\";\ncheck if string($t), $t.type() == \"string\";\ncheck if (2023-12-28T00:00:00Z).type() == \"date\";\ncheck if date($t), $t.type() == \"date\";\ncheck if hex:aa.type() == \"bytes\";\ncheck if bytes($t), $t.type() == \"bytes\";\ncheck if true.type() == \"bool\";\ncheck if bool($t), $t.type() == \"bool\";\ncheck if {false, true}.type() == \"set\";\ncheck if set($t), $t.type() == \"set\";\ncheck if null.type() == \"null\";\ncheck if null($t), $t.type() == \"null\";\ncheck if array($t), $t.type() == \"array\";\ncheck if map($t), $t.type() == \"map\";\n", + "version": 6 + } + ], + "validations": { + "": { + "world": { + "facts": [ + { + "origin": [ + 0 + ], + "facts": [ + "array([1, 2, 3])", + "bool(true)", + "bytes(hex:aa)", + "date(2023-12-28T00:00:00Z)", + "integer(1)", + "map({\"a\": true})", + "null(null)", + "set({false, true})", + "string(\"test\")" + ] + } + ], + "rules": [], + "checks": [ + { + "origin": 0, + "checks": [ + "check if \"test\".type() == \"string\"", + "check if (2023-12-28T00:00:00Z).type() == \"date\"", + "check if 1.type() == \"integer\"", + "check if array($t), $t.type() == \"array\"", + "check if bool($t), $t.type() == \"bool\"", + "check if bytes($t), $t.type() == \"bytes\"", + "check if date($t), $t.type() == \"date\"", + "check if hex:aa.type() == \"bytes\"", + "check if integer($t), $t.type() == \"integer\"", + "check if map($t), $t.type() == \"map\"", + "check if null($t), $t.type() == \"null\"", + "check if null.type() == \"null\"", + "check if set($t), $t.type() == \"set\"", + "check if string($t), $t.type() == \"string\"", + "check if true.type() == \"bool\"", + "check if {false, true}.type() == \"set\"" + ] + } + ], + "policies": [ + "allow if true" + ] + }, + "result": { + "Ok": 0 + }, + "authorizer_code": "allow if true;\n", + "revocation_ids": [ + "e60875c6ef7917c227a5e4b2cabfe250a85fa0598eb3cf7987ded0da2b69a559a1665bd312aeecde78e76aeb28ea1c1a03ec9b7dec8aeb519e7867ef8ff9b402" + ] + } + } + }, + { + "title": "test array and map operations", + "filename": "test034_array_map.bc", + "token": [ + { + "symbols": [ + "a", + "b", + "c", + "p", + "d", + "A", + "kv", + "id", + "roles" + ], + "public_keys": [], + "external_key": null, + "code": "check if [1, 2, 1].length() == 3;\ncheck if [\"a\", \"b\"] != true;\ncheck if [\"a\", \"b\"] != [1, 2, 3];\ncheck if [\"a\", \"b\"] == [\"a\", \"b\"];\ncheck if [\"a\", \"b\"] === [\"a\", \"b\"];\ncheck if [\"a\", \"b\"] !== [\"a\", \"c\"];\ncheck if [\"a\", \"b\", \"c\"].contains(\"c\");\ncheck if [1, 2, 3].starts_with([1, 2]);\ncheck if [4, 5, 6].ends_with([6]);\ncheck if [1, 2, \"a\"].get(2) == \"a\";\ncheck if [1, 2].get(3) == null;\ncheck if [1, 2, 3].all($p -> $p > 0);\ncheck if [1, 2, 3].any($p -> $p > 2);\ncheck if {\"a\": 1, \"b\": 2, \"c\": 3, \"d\": 4}.length() == 4;\ncheck if {1: \"a\", 2: \"b\"} != true;\ncheck if {1: \"a\", 2: \"b\"} != {\"a\": 1, \"b\": 2};\ncheck if {1: \"a\", 2: \"b\"} == {1: \"a\", 2: \"b\"};\ncheck if {1: \"a\", 2: \"b\"} !== {\"a\": 1, \"b\": 2};\ncheck if {1: \"a\", 2: \"b\"} === {1: \"a\", 2: \"b\"};\ncheck if {\"a\": 1, \"b\": 2, \"c\": 3, \"d\": 4}.contains(\"d\");\ncheck if {1: \"A\", \"a\": 1, \"b\": 2}.get(\"a\") == 1;\ncheck if {1: \"A\", \"a\": 1, \"b\": 2}.get(1) == \"A\";\ncheck if {1: \"A\", \"a\": 1, \"b\": 2}.get(\"c\") == null;\ncheck if {1: \"A\", \"a\": 1, \"b\": 2}.get(2) == null;\ncheck if {\"a\": 1, \"b\": 2}.all($kv -> $kv.get(0) != \"c\" && $kv.get(1) < 3);\ncheck if {1: \"A\", \"a\": 1, \"b\": 2}.any($kv -> $kv.get(0) == 1 && $kv.get(1) == \"A\");\ncheck if {\"user\": {\"id\": 1, \"roles\": [\"admin\"]}}.get(\"user\").get(\"roles\").contains(\"admin\");\n", + "version": 6 + } + ], + "validations": { + "": { + "world": { + "facts": [], + "rules": [], + "checks": [ + { + "origin": 0, + "checks": [ + "check if [\"a\", \"b\", \"c\"].contains(\"c\")", + "check if [\"a\", \"b\"] != [1, 2, 3]", + "check if [\"a\", \"b\"] != true", + "check if [\"a\", \"b\"] !== [\"a\", \"c\"]", + "check if [\"a\", \"b\"] == [\"a\", \"b\"]", + "check if [\"a\", \"b\"] === [\"a\", \"b\"]", + "check if [1, 2, \"a\"].get(2) == \"a\"", + "check if [1, 2, 1].length() == 3", + "check if [1, 2, 3].all($p -> $p > 0)", + "check if [1, 2, 3].any($p -> $p > 2)", + "check if [1, 2, 3].starts_with([1, 2])", + "check if [1, 2].get(3) == null", + "check if [4, 5, 6].ends_with([6])", + "check if {\"a\": 1, \"b\": 2, \"c\": 3, \"d\": 4}.contains(\"d\")", + "check if {\"a\": 1, \"b\": 2, \"c\": 3, \"d\": 4}.length() == 4", + "check if {\"a\": 1, \"b\": 2}.all($kv -> $kv.get(0) != \"c\" && $kv.get(1) < 3)", + "check if {\"user\": {\"id\": 1, \"roles\": [\"admin\"]}}.get(\"user\").get(\"roles\").contains(\"admin\")", + "check if {\"a\": 1, \"b\": 2, 1: \"A\"}.any($kv -> $kv.get(0) == 1 && $kv.get(1) == \"A\")", + "check if {\"a\": 1, \"b\": 2, 1: \"A\"}.get(\"a\") == 1", + "check if {\"a\": 1, \"b\": 2, 1: \"A\"}.get(\"c\") == null", + "check if {\"a\": 1, \"b\": 2, 1: \"A\"}.get(1) == \"A\"", + "check if {\"a\": 1, \"b\": 2, 1: \"A\"}.get(2) == null", + "check if {1: \"a\", 2: \"b\"} != true", + "check if {1: \"a\", 2: \"b\"} != {\"a\": 1, \"b\": 2}", + "check if {1: \"a\", 2: \"b\"} !== {\"a\": 1, \"b\": 2}", + "check if {1: \"a\", 2: \"b\"} == {1: \"a\", 2: \"b\"}", + "check if {1: \"a\", 2: \"b\"} === {1: \"a\", 2: \"b\"}" + ] + } + ], + "policies": [ + "allow if true" + ] + }, + "result": { + "Ok": 0 + }, + "authorizer_code": "allow if true;\n", + "revocation_ids": [ + "b22238a06ca9c015d3c49d4ebaa7e8ab6e0d69119b3264033618e726d62fc6f4757a7bebc25f255444aba39994554a62a53ecc13b68802efab8da85ace62390d" + ] + } + } + }, + { + "title": "ECDSA secp256r1 signatures", + "filename": "test036_secp256r1.bc", + "token": [ + { + "symbols": [ + "file1", + "file2" + ], + "public_keys": [], + "external_key": null, + "code": "right(\"file1\", \"read\");\nright(\"file2\", \"read\");\nright(\"file1\", \"write\");\n", + "version": 3 + }, + { + "symbols": [ + "0" + ], + "public_keys": [], + "external_key": null, + "code": "check if resource($0), operation(\"read\"), right($0, \"read\");\n", + "version": 3 + } + ], + "validations": { + "": { + "world": { + "facts": [ + { + "origin": [ + null + ], + "facts": [ + "operation(\"read\")", + "resource(\"file1\")" + ] + }, + { + "origin": [ + 0 + ], + "facts": [ + "right(\"file1\", \"read\")", + "right(\"file1\", \"write\")", + "right(\"file2\", \"read\")" + ] + } + ], + "rules": [], + "checks": [ + { + "origin": 1, + "checks": [ + "check if resource($0), operation(\"read\"), right($0, \"read\")" + ] + } + ], + "policies": [ + "allow if true" + ] + }, + "result": { + "Ok": 0 + }, + "authorizer_code": "resource(\"file1\");\noperation(\"read\");\n\nallow if true;\n", + "revocation_ids": [ + "628b9a6d74cc80b3ece50befd1f5f0f025c0a35d51708b2e77c11aed5f968b93b4096c87ed8169605716de934e155443f140334d71708fcc4247e5a0a518b30d", + "3046022100b60674854a12814cc36c8aab9600c1d9f9d3160e2334b72c0feede5a56213ea5022100a4f4bbf2dc33b309267af39fce76612017ddb6171e9cd2a3aa8a853f45f1675f" + ] + } + } + }, + { + "title": "ECDSA secp256r1 signature on third-party block", + "filename": "test037_secp256r1_third_party.bc", + "token": [ + { + "symbols": [ + "file1", + "file2", + "from_third" + ], + "public_keys": [ + "secp256r1/025e918fd4463832aea2823dfd9716a36b4d9b1377bd53dd82ddf4c0bc75ed6bbf" + ], + "external_key": null, + "code": "right(\"file1\", \"read\");\nright(\"file2\", \"read\");\nright(\"file1\", \"write\");\ncheck if from_third(true) trusting secp256r1/025e918fd4463832aea2823dfd9716a36b4d9b1377bd53dd82ddf4c0bc75ed6bbf;\n", + "version": 4 + }, + { + "symbols": [ + "from_third", + "0" + ], + "public_keys": [], + "external_key": "secp256r1/025e918fd4463832aea2823dfd9716a36b4d9b1377bd53dd82ddf4c0bc75ed6bbf", + "code": "from_third(true);\ncheck if resource($0), operation(\"read\"), right($0, \"read\");\n", + "version": 5 + } + ], + "validations": { + "": { + "world": { + "facts": [ + { + "origin": [ + null + ], + "facts": [ + "operation(\"read\")", + "resource(\"file1\")" + ] + }, + { + "origin": [ + 0 + ], + "facts": [ + "right(\"file1\", \"read\")", + "right(\"file1\", \"write\")", + "right(\"file2\", \"read\")" + ] + }, + { + "origin": [ + 1 + ], + "facts": [ + "from_third(true)" + ] + } + ], + "rules": [], + "checks": [ + { + "origin": 0, + "checks": [ + "check if from_third(true) trusting secp256r1/025e918fd4463832aea2823dfd9716a36b4d9b1377bd53dd82ddf4c0bc75ed6bbf" + ] + }, + { + "origin": 1, + "checks": [ + "check if resource($0), operation(\"read\"), right($0, \"read\")" + ] + } + ], + "policies": [ + "allow if true" + ] + }, + "result": { + "Ok": 0 + }, "authorizer_code": "resource(\"file1\");\noperation(\"read\");\n\nallow if true;\n", "revocation_ids": [ "70f5402208516fd44cfc9df3dfcfc0a327ee9004f1801ed0a7abdcbbae923d566ddcd2d4a14f4622b35732c4e538af04075cc67ab0888fa2d8923cc668187f0f", @@ -2305,6 +3186,81 @@ ] } } + }, + { + "title": "test try operation", + "filename": "test038_try_op.bc", + "token": [ + { + "symbols": [], + "public_keys": [], + "external_key": null, + "code": "check if (true === 12).try_or(true);\ncheck if ((true === 12).try_or(true === 12)).try_or(true);\nreject if (true == 12).try_or(true);\n", + "version": 6 + } + ], + "validations": { + "": { + "world": { + "facts": [], + "rules": [], + "checks": [ + { + "origin": 0, + "checks": [ + "check if ((true === 12).try_or(true === 12)).try_or(true)", + "check if (true === 12).try_or(true)", + "reject if (true == 12).try_or(true)" + ] + } + ], + "policies": [ + "allow if true" + ] + }, + "result": { + "Ok": 0 + }, + "authorizer_code": "allow if true;\n", + "revocation_ids": [ + "79674155cd5349604e89b00792aeaebfa0a512bd45edc289305ebec107f627d3d8c09847646a0d06c2390a4354771b2ebdc2cc66971f2d74ef744e4e81197600" + ] + }, + "right-hand side does not catch errors": { + "world": { + "facts": [], + "rules": [], + "checks": [ + { + "origin": 0, + "checks": [ + "check if ((true === 12).try_or(true === 12)).try_or(true)", + "check if (true === 12).try_or(true)", + "reject if (true == 12).try_or(true)" + ] + }, + { + "origin": 18446744073709551615, + "checks": [ + "check if true.try_or(true === 12)" + ] + } + ], + "policies": [ + "allow if true" + ] + }, + "result": { + "Err": { + "Execution": "InvalidType" + } + }, + "authorizer_code": "check if true.try_or(true === 12);\n\nallow if true;\n", + "revocation_ids": [ + "79674155cd5349604e89b00792aeaebfa0a512bd45edc289305ebec107f627d3d8c09847646a0d06c2390a4354771b2ebdc2cc66971f2d74ef744e4e81197600" + ] + } + } } ] } diff --git a/src/test/resources/samples/test017_expressions.bc b/src/test/resources/samples/test017_expressions.bc index 1f3234c0..5b28d360 100644 Binary files a/src/test/resources/samples/test017_expressions.bc and b/src/test/resources/samples/test017_expressions.bc differ diff --git a/src/test/resources/samples/test028_expressions_v4.bc b/src/test/resources/samples/test028_expressions_v4.bc index c34d7a10..7d94e82d 100644 Binary files a/src/test/resources/samples/test028_expressions_v4.bc and b/src/test/resources/samples/test028_expressions_v4.bc differ diff --git a/src/test/resources/samples/test029_reject_if.bc b/src/test/resources/samples/test029_reject_if.bc new file mode 100644 index 00000000..86ef918b Binary files /dev/null and b/src/test/resources/samples/test029_reject_if.bc differ diff --git a/src/test/resources/samples/test030_null.bc b/src/test/resources/samples/test030_null.bc new file mode 100644 index 00000000..2868715d Binary files /dev/null and b/src/test/resources/samples/test030_null.bc differ diff --git a/src/test/resources/samples/test031_heterogeneous_equal.bc b/src/test/resources/samples/test031_heterogeneous_equal.bc new file mode 100644 index 00000000..2f6cb41f Binary files /dev/null and b/src/test/resources/samples/test031_heterogeneous_equal.bc differ diff --git a/src/test/resources/samples/test032_laziness_closures.bc b/src/test/resources/samples/test032_laziness_closures.bc new file mode 100644 index 00000000..9a98b876 Binary files /dev/null and b/src/test/resources/samples/test032_laziness_closures.bc differ diff --git a/src/test/resources/samples/test033_typeof.bc b/src/test/resources/samples/test033_typeof.bc new file mode 100644 index 00000000..517ae4e7 Binary files /dev/null and b/src/test/resources/samples/test033_typeof.bc differ diff --git a/src/test/resources/samples/test034_array_map.bc b/src/test/resources/samples/test034_array_map.bc new file mode 100644 index 00000000..9deb150a Binary files /dev/null and b/src/test/resources/samples/test034_array_map.bc differ diff --git a/src/test/resources/samples/test038_try_op.bc b/src/test/resources/samples/test038_try_op.bc new file mode 100644 index 00000000..4155d44f Binary files /dev/null and b/src/test/resources/samples/test038_try_op.bc differ