From ad9f0e881c6904bbd2bc3b34de885b222749d455 Mon Sep 17 00:00:00 2001 From: Geoffroy Couprie Date: Sat, 15 Jun 2024 23:33:54 +0200 Subject: [PATCH] refactor the builder API to avoid symbol table manipulation (#103) * refactor the builder API to avoid symbol table manipulation the builder API was using symbol tables internally, which meant that datalog elements were converted too early, and that made the API too complicate. This aligns the builder implementation with the approach use in the rust implementation, where the builder types only contain other builder types, and the conversion with the symbol table is only done when calling the build() method. This also removes some functions from the public API that could be misused to get invalid symbol tables * Fix parser precedence The datalog parser had operator precedence issues that are now fixed by following closely the rust implementation --- .../org/biscuitsec/biscuit/datalog/Check.java | 41 ++-- .../org/biscuitsec/biscuit/datalog/Rule.java | 32 ++++ .../org/biscuitsec/biscuit/datalog/Scope.java | 18 ++ .../biscuit/datalog/SymbolTable.java | 28 +++ .../datalog/expressions/Expression.java | 27 ++- .../org/biscuitsec/biscuit/token/Biscuit.java | 47 ++--- .../org/biscuitsec/biscuit/token/Block.java | 47 +++++ .../biscuit/token/UnverifiedBiscuit.java | 10 +- .../biscuit/token/builder/Biscuit.java | 75 ++++---- .../biscuit/token/builder/Block.java | 132 +++++++------ .../builder/parser/ExpressionParser.java | 175 ++++++++++++++---- .../biscuit/token/builder/parser/Parser.java | 26 +-- .../biscuit/builder/BuilderTest.java | 6 +- .../biscuit/builder/parser/ParserTest.java | 46 +++-- .../biscuitsec/biscuit/token/BiscuitTest.java | 44 ++--- .../biscuitsec/biscuit/token/SamplesTest.java | 31 ++-- .../biscuit/token/UnverifiedBiscuitTest.java | 15 +- 17 files changed, 546 insertions(+), 254 deletions(-) diff --git a/src/main/java/org/biscuitsec/biscuit/datalog/Check.java b/src/main/java/org/biscuitsec/biscuit/datalog/Check.java index a3eb9745..467e61cc 100644 --- a/src/main/java/org/biscuitsec/biscuit/datalog/Check.java +++ b/src/main/java/org/biscuitsec/biscuit/datalog/Check.java @@ -35,21 +35,6 @@ public List queries() { return queries; } - @Override - public int hashCode() { - return Objects.hash(queries); - } - - @Override - public boolean equals(Object o) { - return super.equals(o); - } - - @Override - public String toString() { - return super.toString(); - } - public Schema.CheckV2 serialize() { Schema.CheckV2.Builder b = Schema.CheckV2.newBuilder(); @@ -95,4 +80,30 @@ static public Either deserializeV2(Schema.CheckV2 chec return Right(new Check(kind, queries)); } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + Check check = (Check) o; + + if (kind != check.kind) return false; + return Objects.equals(queries, check.queries); + } + + @Override + public int hashCode() { + int result = kind != null ? kind.hashCode() : 0; + result = 31 * result + (queries != null ? queries.hashCode() : 0); + return result; + } + + @Override + public String toString() { + return "Check{" + + "kind=" + kind + + ", queries=" + queries + + '}'; + } } diff --git a/src/main/java/org/biscuitsec/biscuit/datalog/Rule.java b/src/main/java/org/biscuitsec/biscuit/datalog/Rule.java index e30bb6dc..1f8f3c5d 100644 --- a/src/main/java/org/biscuitsec/biscuit/datalog/Rule.java +++ b/src/main/java/org/biscuitsec/biscuit/datalog/Rule.java @@ -243,4 +243,36 @@ static public Either deserializeV2(Schema.RuleV2 rule) return Right(new Rule(res.get(), body, expressions, scopes)); } } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + Rule rule = (Rule) o; + + if (!Objects.equals(head, rule.head)) return false; + if (!Objects.equals(body, rule.body)) return false; + if (!Objects.equals(expressions, rule.expressions)) return false; + return Objects.equals(scopes, rule.scopes); + } + + @Override + public int hashCode() { + int result = head != null ? head.hashCode() : 0; + result = 31 * result + (body != null ? body.hashCode() : 0); + result = 31 * result + (expressions != null ? expressions.hashCode() : 0); + result = 31 * result + (scopes != null ? scopes.hashCode() : 0); + return result; + } + + @Override + public String toString() { + return "Rule{" + + "head=" + head + + ", body=" + body + + ", expressions=" + expressions + + ", scopes=" + scopes + + '}'; + } } diff --git a/src/main/java/org/biscuitsec/biscuit/datalog/Scope.java b/src/main/java/org/biscuitsec/biscuit/datalog/Scope.java index ea374773..7aede2fc 100644 --- a/src/main/java/org/biscuitsec/biscuit/datalog/Scope.java +++ b/src/main/java/org/biscuitsec/biscuit/datalog/Scope.java @@ -83,4 +83,22 @@ public String toString() { ", publicKey=" + publicKey + '}'; } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + Scope scope = (Scope) o; + + if (publicKey != scope.publicKey) return false; + return kind == scope.kind; + } + + @Override + public int hashCode() { + int result = kind != null ? kind.hashCode() : 0; + result = 31 * result + (int) (publicKey ^ (publicKey >>> 32)); + return result; + } } diff --git a/src/main/java/org/biscuitsec/biscuit/datalog/SymbolTable.java b/src/main/java/org/biscuitsec/biscuit/datalog/SymbolTable.java index 0216b29d..3b99673a 100644 --- a/src/main/java/org/biscuitsec/biscuit/datalog/SymbolTable.java +++ b/src/main/java/org/biscuitsec/biscuit/datalog/SymbolTable.java @@ -280,4 +280,32 @@ public List getAllSymbols() { allSymbols.addAll(symbols); return allSymbols; } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + SymbolTable that = (SymbolTable) o; + + if (!dateTimeFormatter.equals(that.dateTimeFormatter)) return false; + if (!symbols.equals(that.symbols)) return false; + return publicKeys.equals(that.publicKeys); + } + + @Override + public int hashCode() { + int result = dateTimeFormatter.hashCode(); + result = 31 * result + symbols.hashCode(); + result = 31 * result + publicKeys.hashCode(); + return result; + } + + @Override + public String toString() { + return "SymbolTable{" + + "symbols=" + symbols + + ", publicKeys=" + publicKeys + + '}'; + } } diff --git a/src/main/java/org/biscuitsec/biscuit/datalog/expressions/Expression.java b/src/main/java/org/biscuitsec/biscuit/datalog/expressions/Expression.java index a66c3254..d691ac07 100644 --- a/src/main/java/org/biscuitsec/biscuit/datalog/expressions/Expression.java +++ b/src/main/java/org/biscuitsec/biscuit/datalog/expressions/Expression.java @@ -8,10 +8,7 @@ import io.vavr.control.Either; import io.vavr.control.Option; -import java.util.ArrayDeque; -import java.util.ArrayList; -import java.util.Deque; -import java.util.Map; +import java.util.*; import static io.vavr.API.Left; import static io.vavr.API.Right; @@ -78,4 +75,26 @@ static public Either deserializeV2(Schema.Express return Right(new Expression(ops)); } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + Expression that = (Expression) o; + + return Objects.equals(ops, that.ops); + } + + @Override + public int hashCode() { + return ops != null ? ops.hashCode() : 0; + } + + @Override + public String toString() { + return "Expression{" + + "ops=" + ops + + '}'; + } } diff --git a/src/main/java/org/biscuitsec/biscuit/token/Biscuit.java b/src/main/java/org/biscuitsec/biscuit/token/Biscuit.java index 372e0b4e..69fc45f0 100644 --- a/src/main/java/org/biscuitsec/biscuit/token/Biscuit.java +++ b/src/main/java/org/biscuitsec/biscuit/token/Biscuit.java @@ -29,7 +29,7 @@ public class Biscuit extends UnverifiedBiscuit { * @return */ public static org.biscuitsec.biscuit.token.builder.Biscuit builder(final KeyPair root) { - return new org.biscuitsec.biscuit.token.builder.Biscuit(new SecureRandom(), root, default_symbol_table()); + return new org.biscuitsec.biscuit.token.builder.Biscuit(new SecureRandom(), root); } /** @@ -42,7 +42,7 @@ public static org.biscuitsec.biscuit.token.builder.Biscuit builder(final KeyPair * @return */ public static org.biscuitsec.biscuit.token.builder.Biscuit builder(final SecureRandom rng, final KeyPair root) { - return new org.biscuitsec.biscuit.token.builder.Biscuit(rng, root, default_symbol_table()); + return new org.biscuitsec.biscuit.token.builder.Biscuit(rng, root); } /** @@ -50,26 +50,12 @@ public static org.biscuitsec.biscuit.token.builder.Biscuit builder(final SecureR * * @param rng random number generator * @param root root private key - * @param symbols symbol table * @return */ - public static org.biscuitsec.biscuit.token.builder.Biscuit builder(final SecureRandom rng, final KeyPair root, final Option root_key_id, SymbolTable symbols) { - return new org.biscuitsec.biscuit.token.builder.Biscuit(rng, root, root_key_id, symbols); + public static org.biscuitsec.biscuit.token.builder.Biscuit builder(final SecureRandom rng, final KeyPair root, final Option root_key_id) { + return new org.biscuitsec.biscuit.token.builder.Biscuit(rng, root, root_key_id); } - /** - * Creates a token builder - * - * @param rng random number generator - * @param root root private key - * @param symbols symbol table - * @return - */ - public static org.biscuitsec.biscuit.token.builder.Biscuit builder(final SecureRandom rng, final KeyPair root, SymbolTable symbols) { - return new org.biscuitsec.biscuit.token.builder.Biscuit(rng, root, symbols); - } - - /** * Creates a token * @@ -78,8 +64,8 @@ public static org.biscuitsec.biscuit.token.builder.Biscuit builder(final SecureR * @param authority authority block * @return Biscuit */ - static public Biscuit make(final SecureRandom rng, final KeyPair root, final SymbolTable symbols, final Block authority) throws Error.SymbolTableOverlap, Error.FormatError { - return Biscuit.make(rng, root, Option.none(), symbols, authority); + public static Biscuit make(final SecureRandom rng, final KeyPair root, final Block authority) throws Error.FormatError { + return Biscuit.make(rng, root, Option.none(), authority); } /** @@ -90,8 +76,8 @@ static public Biscuit make(final SecureRandom rng, final KeyPair root, final Sym * @param authority authority block * @return Biscuit */ - static public Biscuit make(final SecureRandom rng, final KeyPair root, final Integer root_key_id, final SymbolTable symbols, final Block authority) throws Error.SymbolTableOverlap, Error.FormatError { - return Biscuit.make(rng, root, Option.of(root_key_id), symbols, authority); + public static Biscuit make(final SecureRandom rng, final KeyPair root, final Integer root_key_id, final Block authority) throws Error.FormatError { + return Biscuit.make(rng, root, Option.of(root_key_id), authority); } /** @@ -102,12 +88,7 @@ static public Biscuit make(final SecureRandom rng, final KeyPair root, final Int * @param authority authority block * @return Biscuit */ - static private Biscuit make(final SecureRandom rng, final KeyPair root, final Option root_key_id, final SymbolTable symbols, final Block authority) throws Error.SymbolTableOverlap, Error.FormatError { - if (!Collections.disjoint(symbols.symbols, authority.symbols.symbols)) { - throw new Error.SymbolTableOverlap(); - } - - symbols.symbols.addAll(authority.symbols.symbols); + static private Biscuit make(final SecureRandom rng, final KeyPair root, final Option root_key_id, final Block authority) throws Error.FormatError { ArrayList blocks = new ArrayList<>(); KeyPair next = new KeyPair(rng); @@ -122,7 +103,7 @@ static private Biscuit make(final SecureRandom rng, final KeyPair root, final Op HashMap> publicKeyToBlockId = new HashMap<>(); Option c = Option.some(s); - return new Biscuit(authority, blocks, symbols, s, publicKeyToBlockId, revocation_ids, root_key_id); + return new Biscuit(authority, blocks, authority.symbols, s, publicKeyToBlockId, revocation_ids, root_key_id); } } @@ -325,7 +306,13 @@ public String serialize_b64url() throws Error.FormatError.SerializationError { public Biscuit attenuate(org.biscuitsec.biscuit.token.builder.Block block) throws Error { SecureRandom rng = new SecureRandom(); KeyPair keypair = new KeyPair(rng); - return attenuate(rng, keypair, block.build()); + SymbolTable builderSymbols = new SymbolTable(this.symbols); + return attenuate(rng, keypair, block.build(builderSymbols)); + } + + public Biscuit attenuate(final SecureRandom rng, final KeyPair keypair,org.biscuitsec.biscuit.token.builder.Block block) throws Error { + SymbolTable builderSymbols = new SymbolTable(this.symbols); + return attenuate(rng, keypair, block.build(builderSymbols)); } /** diff --git a/src/main/java/org/biscuitsec/biscuit/token/Block.java b/src/main/java/org/biscuitsec/biscuit/token/Block.java index 22d14a64..9e8fed83 100644 --- a/src/main/java/org/biscuitsec/biscuit/token/Block.java +++ b/src/main/java/org/biscuitsec/biscuit/token/Block.java @@ -313,5 +313,52 @@ public Either to_bytes() { return Left(new Error.FormatError.SerializationError(e.toString())); } } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + Block block = (Block) o; + + if (version != block.version) return false; + if (!Objects.equals(symbols, block.symbols)) return false; + if (!Objects.equals(context, block.context)) return false; + if (!Objects.equals(facts, block.facts)) return false; + if (!Objects.equals(rules, block.rules)) return false; + if (!Objects.equals(checks, block.checks)) return false; + if (!Objects.equals(scopes, block.scopes)) return false; + if (!Objects.equals(publicKeys, block.publicKeys)) return false; + return Objects.equals(externalKey, block.externalKey); + } + + @Override + public int hashCode() { + int result = symbols != null ? symbols.hashCode() : 0; + result = 31 * result + (context != null ? context.hashCode() : 0); + result = 31 * result + (facts != null ? facts.hashCode() : 0); + result = 31 * result + (rules != null ? rules.hashCode() : 0); + result = 31 * result + (checks != null ? checks.hashCode() : 0); + result = 31 * result + (scopes != null ? scopes.hashCode() : 0); + result = 31 * result + (publicKeys != null ? publicKeys.hashCode() : 0); + result = 31 * result + (externalKey != null ? externalKey.hashCode() : 0); + result = 31 * result + (int) (version ^ (version >>> 32)); + return result; + } + + @Override + public String toString() { + return "Block{" + + "symbols=" + symbols + + ", context='" + context + '\'' + + ", facts=" + facts + + ", rules=" + rules + + ", checks=" + checks + + ", scopes=" + scopes + + ", publicKeys=" + publicKeys + + ", externalKey=" + externalKey + + ", version=" + version + + '}'; + } } diff --git a/src/main/java/org/biscuitsec/biscuit/token/UnverifiedBiscuit.java b/src/main/java/org/biscuitsec/biscuit/token/UnverifiedBiscuit.java index 302fd6d4..c008aa83 100644 --- a/src/main/java/org/biscuitsec/biscuit/token/UnverifiedBiscuit.java +++ b/src/main/java/org/biscuitsec/biscuit/token/UnverifiedBiscuit.java @@ -129,7 +129,7 @@ public String serialize_b64url() throws Error.FormatError.SerializationError { * @return */ public org.biscuitsec.biscuit.token.builder.Block create_block() { - return new org.biscuitsec.biscuit.token.builder.Block(1 + this.blocks.size(), new SymbolTable(this.symbols)); + return new org.biscuitsec.biscuit.token.builder.Block(1 + this.blocks.size()); } /** @@ -141,7 +141,13 @@ public org.biscuitsec.biscuit.token.builder.Block create_block() { public UnverifiedBiscuit attenuate(org.biscuitsec.biscuit.token.builder.Block block) throws NoSuchAlgorithmException, SignatureException, InvalidKeyException, Error { SecureRandom rng = new SecureRandom(); KeyPair keypair = new KeyPair(rng); - return attenuate(rng, keypair, block.build()); + SymbolTable builderSymbols = new SymbolTable(this.symbols); + return attenuate(rng, keypair, block.build(builderSymbols)); + } + + public UnverifiedBiscuit attenuate(final SecureRandom rng, final KeyPair keypair, org.biscuitsec.biscuit.token.builder.Block block) throws Error { + SymbolTable builderSymbols = new SymbolTable(this.symbols); + return attenuate(rng, keypair, block.build(builderSymbols)); } /** diff --git a/src/main/java/org/biscuitsec/biscuit/token/builder/Biscuit.java b/src/main/java/org/biscuitsec/biscuit/token/builder/Biscuit.java index d9a55f1b..db21a76b 100644 --- a/src/main/java/org/biscuitsec/biscuit/token/builder/Biscuit.java +++ b/src/main/java/org/biscuitsec/biscuit/token/builder/Biscuit.java @@ -2,16 +2,13 @@ import org.biscuitsec.biscuit.crypto.KeyPair; import org.biscuitsec.biscuit.crypto.PublicKey; -import org.biscuitsec.biscuit.datalog.*; -import org.biscuitsec.biscuit.datalog.Check; -import org.biscuitsec.biscuit.datalog.Fact; -import org.biscuitsec.biscuit.datalog.Rule; +import org.biscuitsec.biscuit.datalog.SchemaVersion; +import org.biscuitsec.biscuit.datalog.SymbolTable; import org.biscuitsec.biscuit.error.Error; import org.biscuitsec.biscuit.token.Block; import io.vavr.Tuple2; import io.vavr.control.Either; import io.vavr.control.Option; -import org.biscuitsec.biscuit.datalog.Scope; import org.biscuitsec.biscuit.token.builder.parser.Parser; import java.security.SecureRandom; @@ -19,12 +16,11 @@ import java.util.Arrays; import java.util.List; +import static org.biscuitsec.biscuit.token.UnverifiedBiscuit.default_symbol_table; + public class Biscuit { SecureRandom rng; KeyPair root; - int symbol_start; - int publicKeyStart; - SymbolTable symbols; String context; List facts; List rules; @@ -32,12 +28,9 @@ public class Biscuit { List scopes; Option root_key_id; - public Biscuit(final SecureRandom rng, final KeyPair root, SymbolTable base_symbols) { + public Biscuit(final SecureRandom rng, final KeyPair root) { this.rng = rng; this.root = root; - this.symbol_start = base_symbols.currentOffset(); - this.publicKeyStart = base_symbols.currentPublicKeyOffset(); - this.symbols = new SymbolTable(base_symbols); this.context = ""; this.facts = new ArrayList<>(); this.rules = new ArrayList<>(); @@ -46,11 +39,9 @@ public Biscuit(final SecureRandom rng, final KeyPair root, SymbolTable base_symb this.root_key_id = Option.none(); } - public Biscuit(final SecureRandom rng, final KeyPair root, Option root_key_id, SymbolTable base_symbols) { + public Biscuit(final SecureRandom rng, final KeyPair root, Option root_key_id) { this.rng = rng; this.root = root; - this.symbol_start = base_symbols.symbols.size(); - this.symbols = new SymbolTable(base_symbols); this.context = ""; this.facts = new ArrayList<>(); this.rules = new ArrayList<>(); @@ -61,7 +52,7 @@ public Biscuit(final SecureRandom rng, final KeyPair root, Option root_ public Biscuit add_authority_fact(org.biscuitsec.biscuit.token.builder.Fact f) throws Error.Language { f.validate(); - this.facts.add(f.convert(this.symbols)); + this.facts.add(f); return this; } @@ -79,7 +70,7 @@ public Biscuit add_authority_fact(String s) throws Error.Parser, Error.Language } public Biscuit add_authority_rule(org.biscuitsec.biscuit.token.builder.Rule rule) { - this.rules.add(rule.convert(this.symbols)); + this.rules.add(rule); return this; } @@ -97,7 +88,7 @@ public Biscuit add_authority_rule(String s) throws Error.Parser { } public Biscuit add_authority_check(org.biscuitsec.biscuit.token.builder.Check c) { - this.checks.add(c.convert(this.symbols)); + this.checks.add(c); return this; } @@ -120,7 +111,7 @@ public Biscuit set_context(String context) { } public Biscuit add_scope(org.biscuitsec.biscuit.token.builder.Scope scope) { - this.scopes.add(scope.convert(this.symbols)); + this.scopes.add(scope); return this; } @@ -129,31 +120,49 @@ public void set_root_key_id(Integer i) { } public org.biscuitsec.biscuit.token.Biscuit build() throws Error { - SymbolTable base_symbols = new SymbolTable(); - SymbolTable symbols = new SymbolTable(); + return build(default_symbol_table()); + } - for (int i = 0; i < this.symbol_start; i++) { - base_symbols.add(this.symbols.symbols.get(i)); + private org.biscuitsec.biscuit.token.Biscuit build(SymbolTable symbols) throws Error { + int symbol_start = symbols.currentOffset(); + int publicKeyStart = symbols.currentPublicKeyOffset(); + + List facts = new ArrayList<>(); + for(Fact f: this.facts) { + facts.add(f.convert(symbols)); + } + List rules = new ArrayList<>(); + for(Rule r: this.rules) { + rules.add(r.convert(symbols)); } + List checks = new ArrayList<>(); + for(Check c: this.checks) { + checks.add(c.convert(symbols)); + } + List scopes = new ArrayList<>(); + for(Scope s: this.scopes) { + scopes.add(s.convert(symbols)); + } + SchemaVersion schemaVersion = new SchemaVersion(facts, rules, checks, scopes); - for (int i = this.symbol_start; i < this.symbols.symbols.size(); i++) { - symbols.add(this.symbols.symbols.get(i)); + SymbolTable block_symbols = new SymbolTable(); + + for (int i = symbol_start; i < symbols.symbols.size(); i++) { + block_symbols.add(symbols.symbols.get(i)); } List publicKeys = new ArrayList<>(); - for (int i = this.publicKeyStart; i < this.symbols.currentPublicKeyOffset(); i++) { - publicKeys.add(this.symbols.publicKeys().get(i)); + for (int i = publicKeyStart; i < symbols.currentPublicKeyOffset(); i++) { + publicKeys.add(symbols.publicKeys().get(i)); } - SchemaVersion schemaVersion = new SchemaVersion(this.facts, this.rules, this.checks, this.scopes); - - Block authority_block = new Block(symbols, context, this.facts, this.rules, - this.checks, scopes, publicKeys, Option.none(), schemaVersion.version()); + Block authority_block = new Block(block_symbols, context, facts, rules, + checks, scopes, publicKeys, Option.none(), schemaVersion.version()); if (this.root_key_id.isDefined()) { - return org.biscuitsec.biscuit.token.Biscuit.make(this.rng, this.root, this.root_key_id.get(), base_symbols, authority_block); + return org.biscuitsec.biscuit.token.Biscuit.make(this.rng, this.root, this.root_key_id.get(), authority_block); } else { - return org.biscuitsec.biscuit.token.Biscuit.make(this.rng, this.root, base_symbols, authority_block); + return org.biscuitsec.biscuit.token.Biscuit.make(this.rng, this.root, authority_block); } } diff --git a/src/main/java/org/biscuitsec/biscuit/token/builder/Block.java b/src/main/java/org/biscuitsec/biscuit/token/builder/Block.java index c4591e03..dde4a247 100644 --- a/src/main/java/org/biscuitsec/biscuit/token/builder/Block.java +++ b/src/main/java/org/biscuitsec/biscuit/token/builder/Block.java @@ -2,80 +2,40 @@ import org.biscuitsec.biscuit.crypto.PublicKey; -import org.biscuitsec.biscuit.datalog.*; -import org.biscuitsec.biscuit.datalog.Check; -import org.biscuitsec.biscuit.datalog.Fact; -import org.biscuitsec.biscuit.datalog.Rule; +import org.biscuitsec.biscuit.datalog.SymbolTable; import org.biscuitsec.biscuit.error.Error; +import org.biscuitsec.biscuit.datalog.SchemaVersion; import io.vavr.Tuple2; import io.vavr.control.Either; import io.vavr.control.Option; -import org.biscuitsec.biscuit.datalog.Scope; import org.biscuitsec.biscuit.token.builder.parser.Parser; import static org.biscuitsec.biscuit.datalog.Check.Kind.One; +import static org.biscuitsec.biscuit.token.UnverifiedBiscuit.default_symbol_table; import static org.biscuitsec.biscuit.token.builder.Utils.*; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Date; -import java.util.List; +import java.util.*; public class Block { long index; - int symbol_start; - int publicKeyStart; - SymbolTable symbols; String context; List facts; List rules; List checks; List scopes; - List publicKeys; - Option externalKey; - public Block(long index, SymbolTable base_symbols) { + public Block(long index) { this.index = index; - this.symbol_start = base_symbols.currentOffset(); - this.publicKeyStart = base_symbols.currentPublicKeyOffset(); - this.symbols = new SymbolTable(base_symbols); this.context = ""; this.facts = new ArrayList<>(); this.rules = new ArrayList<>(); this.checks = new ArrayList<>(); this.scopes = new ArrayList<>(); - this.publicKeys = new ArrayList<>(); - this.externalKey = Option.none(); - } - - public Block setExternalKey(Option externalKey) { - this.externalKey = externalKey; - return this; - } - - public Block addPublicKey(PublicKey publicKey) { - this.publicKeys.add(publicKey); - return this; - } - - public Block addPublicKeys(List publicKeys) { - this.publicKeys.addAll(publicKeys); - return this; - } - - public Block setPublicKeys(List publicKeys) { - this.publicKeys = publicKeys; - return this; - } - - public Block addSymbol(String symbol) { - this.symbols.add(symbol); - return this; } public Block add_fact(org.biscuitsec.biscuit.token.builder.Fact f) { - this.facts.add(f.convert(this.symbols)); + this.facts.add(f); return this; } @@ -93,7 +53,7 @@ public Block add_fact(String s) throws Error.Parser { } public Block add_rule(org.biscuitsec.biscuit.token.builder.Rule rule) { - this.rules.add(rule.convert(this.symbols)); + this.rules.add(rule); return this; } @@ -111,7 +71,7 @@ public Block add_rule(String s) throws Error.Parser { } public Block add_check(org.biscuitsec.biscuit.token.builder.Check check) { - this.checks.add(check.convert(this.symbols)); + this.checks.add(check); return this; } @@ -129,7 +89,7 @@ public Block add_check(String s) throws Error.Parser { } public Block add_scope(org.biscuitsec.biscuit.token.builder.Scope scope) { - this.scopes.add(scope.convert(this.symbols)); + this.scopes.add(scope); return this; } @@ -139,21 +99,79 @@ public Block set_context(String context) { } public org.biscuitsec.biscuit.token.Block build() { - SymbolTable symbols = new SymbolTable(); + return build(default_symbol_table(), Option.none()); + } - for (int i = this.symbol_start; i < this.symbols.symbols.size(); i++) { - symbols.add(this.symbols.symbols.get(i)); + public org.biscuitsec.biscuit.token.Block build(final Option externalKey) { + return build(default_symbol_table(), externalKey); + } + + public org.biscuitsec.biscuit.token.Block build(SymbolTable symbols) { + return build(symbols, Option.none()); + } + + public org.biscuitsec.biscuit.token.Block build(SymbolTable symbols, final Option externalKey) { + int symbol_start = symbols.currentOffset(); + int publicKeyStart = symbols.currentPublicKeyOffset(); + + List facts = new ArrayList<>(); + for(Fact f: this.facts) { + facts.add(f.convert(symbols)); + } + List rules = new ArrayList<>(); + for(Rule r: this.rules) { + rules.add(r.convert(symbols)); + } + List checks = new ArrayList<>(); + for(Check c: this.checks) { + checks.add(c.convert(symbols)); + } + List scopes = new ArrayList<>(); + for(Scope s: this.scopes) { + scopes.add(s.convert(symbols)); + } + SchemaVersion schemaVersion = new SchemaVersion(facts, rules, checks, scopes); + + SymbolTable block_symbols = new SymbolTable(); + + for (int i = symbol_start; i < symbols.symbols.size(); i++) { + block_symbols.add(symbols.symbols.get(i)); } + List publicKeys = new ArrayList<>(); - for (int i = this.publicKeyStart; i < this.symbols.currentPublicKeyOffset(); i++) { - publicKeys.add(this.symbols.publicKeys().get(i)); + for (int i = publicKeyStart; i < symbols.currentPublicKeyOffset(); i++) { + publicKeys.add(symbols.publicKeys().get(i)); } - SchemaVersion schemaVersion = new SchemaVersion(this.facts, this.rules, this.checks, this.scopes); + return new org.biscuitsec.biscuit.token.Block(block_symbols, this.context, facts, rules, checks, + scopes, publicKeys, externalKey, schemaVersion.version()); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + Block block = (Block) o; + + if (index != block.index) return false; + if (!Objects.equals(context, block.context)) return false; + if (!Objects.equals(facts, block.facts)) return false; + if (!Objects.equals(rules, block.rules)) return false; + if (!Objects.equals(checks, block.checks)) return false; + return Objects.equals(scopes, block.scopes); + } - return new org.biscuitsec.biscuit.token.Block(symbols, this.context, this.facts, this.rules, this.checks, - this.scopes, publicKeys, this.externalKey, schemaVersion.version()); + @Override + public int hashCode() { + int result = (int) (index ^ (index >>> 32)); + result = 31 * result + (context != null ? context.hashCode() : 0); + result = 31 * result + (facts != null ? facts.hashCode() : 0); + result = 31 * result + (rules != null ? rules.hashCode() : 0); + result = 31 * result + (checks != null ? checks.hashCode() : 0); + result = 31 * result + (scopes != null ? scopes.hashCode() : 0); + return result; } public Block check_right(String right) { diff --git a/src/main/java/org/biscuitsec/biscuit/token/builder/parser/ExpressionParser.java b/src/main/java/org/biscuitsec/biscuit/token/builder/parser/ExpressionParser.java index 86664e7b..cf76c1e8 100644 --- a/src/main/java/org/biscuitsec/biscuit/token/builder/parser/ExpressionParser.java +++ b/src/main/java/org/biscuitsec/biscuit/token/builder/parser/ExpressionParser.java @@ -13,6 +13,19 @@ public static Either> parse(String s) { return expr(space(s)); } + // Top-lever parser for an expression. Expression parsers are layered in + // order to support operator precedence (see https://en.wikipedia.org/wiki/Operator-precedence_parser). + // + // See https://github.com/biscuit-auth/biscuit/blob/master/SPECIFICATIONS.md#grammar + // for the precedence order of operators in biscuit datalog. + // + // The operators with the lowest precedence are parsed at the outer level, + // and their operands delegate to parsers that progressively handle more + // tightly binding operators. + // + // This level handles the last operator in the precedence list: `||` + // `||` is left associative, so multiple `||` expressions can be combined: + // `a || b || c <=> (a || b) || c` public static Either> expr(String s) { Either> res1 = expr1(s); if (res1.isLeft()) { @@ -54,6 +67,9 @@ public static Either> expr(String s) { return Either.right(new Tuple2<>(s, e)); } + /// This level handles `&&` + /// `&&` is left associative, so multiple `&&` expressions can be combined: + /// `a && b && c <=> (a && b) && c` public static Either> expr1(String s) { Either> res1 = expr2(s); if (res1.isLeft()) { @@ -95,6 +111,9 @@ public static Either> expr1(String s) { return Either.right(new Tuple2<>(s, e)); } + /// This level handles comparison operators (`==`, `>`, `>=`, `<`, `<=`). + /// Those operators are _not_ associative and require explicit grouping + /// with parentheses. public static Either> expr2(String s) { Either> res1 = expr3(s); if (res1.isLeft()) { @@ -105,13 +124,53 @@ public static Either> expr2(String s) { s = t1._1; Expression e = t1._2; + s = space(s); + + Either> res2 = binary_op2(s); + if (res2.isLeft()) { + return Either.right(t1); + + } + Tuple2 t2 = res2.get(); + s = t2._1; + Expression.Op op = t2._2; + + s = space(s); + + Either> res3 = expr3(s); + if (res3.isLeft()) { + return Either.left(res3.getLeft()); + } + Tuple2 t3 = res3.get(); + + s = t3._1; + Expression e2 = t3._2; + + e = new Expression.Binary(op, e, e2); + + return Either.right(new Tuple2<>(s, e)); + } + + /// This level handles `|`. + /// It is left associative, so multiple expressions can be combined: + /// `a | b | c <=> (a | b) | c` + public static Either> expr3(String s) { + Either> res1 = expr4(s); + if (res1.isLeft()) { + return Either.left(res1.getLeft()); + } + Tuple2 t1 = res1.get(); + + s = t1._1; + Expression e = t1._2; + while(true) { s = space(s); if(s.length() == 0) { break; } - Either> res2 = binary_op2(s); + Either> res2 = binary_op3(s); if (res2.isLeft()) { break; } @@ -121,7 +180,7 @@ public static Either> expr2(String s) { s = space(s); - Either> res3 = expr3(s); + Either> res3 = expr4(s); if (res3.isLeft()) { return Either.left(res3.getLeft()); } @@ -136,8 +195,11 @@ public static Either> expr2(String s) { return Either.right(new Tuple2<>(s, e)); } - public static Either> expr3(String s) { - Either> res1 = expr4(s); + /// This level handles `^`. + /// It is left associative, so multiple expressions can be combined: + /// `a ^ b ^ c <=> (a ^ b) ^ c` + public static Either> expr4(String s) { + Either> res1 = expr5(s); if (res1.isLeft()) { return Either.left(res1.getLeft()); } @@ -152,7 +214,7 @@ public static Either> expr3(String s) { break; } - Either> res2 = binary_op3(s); + Either> res2 = binary_op4(s); if (res2.isLeft()) { break; } @@ -162,7 +224,7 @@ public static Either> expr3(String s) { s = space(s); - Either> res3 = expr4(s); + Either> res3 = expr5(s); if (res3.isLeft()) { return Either.left(res3.getLeft()); } @@ -177,8 +239,11 @@ public static Either> expr3(String s) { return Either.right(new Tuple2<>(s, e)); } - public static Either> expr4(String s) { - Either> res1 = expr5(s); + /// This level handles `&`. + /// It is left associative, so multiple expressions can be combined: + /// `a & b & c <=> (a & b) & c` + public static Either> expr5(String s) { + Either> res1 = expr6(s); if (res1.isLeft()) { return Either.left(res1.getLeft()); } @@ -193,7 +258,7 @@ public static Either> expr4(String s) { break; } - Either> res2 = binary_op4(s); + Either> res2 = binary_op5(s); if (res2.isLeft()) { break; } @@ -203,7 +268,7 @@ public static Either> expr4(String s) { s = space(s); - Either> res3 = expr5(s); + Either> res3 = expr6(s); if (res3.isLeft()) { return Either.left(res3.getLeft()); } @@ -218,8 +283,11 @@ public static Either> expr4(String s) { return Either.right(new Tuple2<>(s, e)); } - public static Either> expr5(String s) { - Either> res1 = expr6(s); + /// This level handles `+` and `-`. + /// They are left associative, so multiple expressions can be combined: + /// `a + b - c <=> (a + b) - c` + public static Either> expr6(String s) { + Either> res1 = expr7(s); if (res1.isLeft()) { return Either.left(res1.getLeft()); } @@ -234,7 +302,7 @@ public static Either> expr5(String s) { break; } - Either> res2 = binary_op5(s); + Either> res2 = binary_op6(s); if (res2.isLeft()) { break; } @@ -244,7 +312,7 @@ public static Either> expr5(String s) { s = space(s); - Either> res3 = expr6(s); + Either> res3 = expr7(s); if (res3.isLeft()) { return Either.left(res3.getLeft()); } @@ -259,8 +327,11 @@ public static Either> expr5(String s) { return Either.right(new Tuple2<>(s, e)); } - public static Either> expr6(String s) { - Either> res1 = expr7(s); + /// This level handles `*` and `/`. + /// They are left associative, so multiple expressions can be combined: + /// `a * b / c <=> (a * b) / c` + public static Either> expr7(String s) { + Either> res1 = expr8(s); if (res1.isLeft()) { return Either.left(res1.getLeft()); } @@ -275,7 +346,7 @@ public static Either> expr6(String s) { break; } - Either> res2 = binary_op6(s); + Either> res2 = binary_op7(s); if (res2.isLeft()) { break; } @@ -285,7 +356,7 @@ public static Either> expr6(String s) { s = space(s); - Either> res3 = expr7(s); + Either> res3 = expr8(s); if (res3.isLeft()) { return Either.left(res3.getLeft()); } @@ -300,7 +371,30 @@ public static Either> expr6(String s) { return Either.right(new Tuple2<>(s, e)); } - public static Either> expr7(String s) { + /// This level handles `!` (prefix negation) + public static Either> expr8(String s) { + + s = space(s); + + if(s.startsWith("!")) { + s = space(s.substring(1)); + + Either> res = expr9(s); + if (res.isLeft()) { + return Either.left(res.getLeft()); + } + + Tuple2 t = res.get(); + return Either.right(new Tuple2<>(t._1, new Expression.Unary(Expression.Op.Negate, t._2))); + } else { + return expr9(s); + } + } + + /// This level handles methods. Methods can take either zero or one + /// argument in addition to the expression they are called on. + /// The name of the method decides its arity. + public static Either> expr9(String s) { Either> res1 = expr_term(s); if (res1.isLeft()) { return Either.left(res1.getLeft()); @@ -321,7 +415,7 @@ public static Either> expr7(String s) { } s = s.substring(1); - Either> res2 = binary_op7(s); + Either> res2 = binary_op8(s); if (!res2.isLeft()) { Tuple2 t2 = res2.get(); s = space(t2._1); @@ -333,7 +427,7 @@ public static Either> expr7(String s) { s = space(s.substring(1)); - Either> res3 = expr_term(s); + Either> res3 = expr(s); if (res3.isLeft()) { return Either.left(res3.getLeft()); } @@ -360,7 +454,7 @@ public static Either> expr7(String s) { } public static Either> expr_term(String s) { - Either> res1 = unary(s); + Either> res1 = unary_parens(s); if (res1.isRight()) { return res1; } @@ -450,9 +544,6 @@ public static Either> unary_parens(String s) { } public static Either> binary_op0(String s) { - if(s.startsWith("&&")) { - return Either.right(new Tuple2<>(s.substring(2), Expression.Op.And)); - } if(s.startsWith("||")) { return Either.right(new Tuple2<>(s.substring(2), Expression.Op.Or)); } @@ -461,6 +552,14 @@ public static Either> binary_op0(String s) } public static Either> binary_op1(String s) { + if(s.startsWith("&&")) { + return Either.right(new Tuple2<>(s.substring(2), Expression.Op.And)); + } + + return Either.left(new Error(s, "unrecognized op")); + } + + public static Either> binary_op2(String s) { if(s.startsWith("<=")) { return Either.right(new Tuple2<>(s.substring(2), Expression.Op.LessOrEqual)); } @@ -483,17 +582,6 @@ public static Either> binary_op1(String s) return Either.left(new Error(s, "unrecognized op")); } - public static Either> binary_op2(String s) { - - if(s.startsWith("+")) { - return Either.right(new Tuple2<>(s.substring(1), Expression.Op.Add)); - } - if(s.startsWith("-")) { - return Either.right(new Tuple2<>(s.substring(1), Expression.Op.Sub)); - } - - return Either.left(new Error(s, "unrecognized op")); - } public static Either> binary_op3(String s) { if(s.startsWith("^")) { @@ -520,6 +608,19 @@ public static Either> binary_op5(String s) } public static Either> binary_op6(String s) { + + if(s.startsWith("+")) { + return Either.right(new Tuple2<>(s.substring(1), Expression.Op.Add)); + } + if(s.startsWith("-")) { + return Either.right(new Tuple2<>(s.substring(1), Expression.Op.Sub)); + } + + return Either.left(new Error(s, "unrecognized op")); + } + + + public static Either> binary_op7(String s) { if(s.startsWith("*")) { return Either.right(new Tuple2<>(s.substring(1), Expression.Op.Mul)); } @@ -530,7 +631,7 @@ public static Either> binary_op6(String s) return Either.left(new Error(s, "unrecognized op")); } - public static Either> binary_op7(String s) { + public static Either> binary_op8(String s) { if(s.startsWith("intersection")) { return Either.right(new Tuple2<>(s.substring(12), Expression.Op.Intersection)); } diff --git a/src/main/java/org/biscuitsec/biscuit/token/builder/parser/Parser.java b/src/main/java/org/biscuitsec/biscuit/token/builder/parser/Parser.java index 26e45b15..57a7dee2 100644 --- a/src/main/java/org/biscuitsec/biscuit/token/builder/parser/Parser.java +++ b/src/main/java/org/biscuitsec/biscuit/token/builder/parser/Parser.java @@ -16,10 +16,6 @@ import java.util.function.Function; public class Parser { - public static Either>, Block> datalog(long index, SymbolTable baseSymbols, String s) { - return datalog(index, baseSymbols, null, s); - } - /** * Takes a datalog string with \n as datalog line separator. It tries to parse * each line using fact, rule, check and scope sequentially. @@ -28,23 +24,17 @@ public static Either>, Block> datalog(long index, Symbo * else it returns a Map[lineNumber, List[Error]] * * @param index block index - * @param baseSymbols symbols table - * @param blockSymbols block's custom symbols table (added to baseSymbols) * @param s datalog string to parse * @return Either>, Block> */ - public static Either>, Block> datalog(long index, SymbolTable baseSymbols, SymbolTable blockSymbols, String s) { - Block blockBuilder = new Block(index, baseSymbols); + public static Either>, Block> datalog(long index, String s) { + Block blockBuilder = new Block(index); // empty block code if (s.isEmpty()) { return Either.right(blockBuilder); } - if (blockSymbols != null) { - blockSymbols.symbols.forEach(blockBuilder::addSymbol); - } - Map> errors = new HashMap<>(); s = removeCommentsAndWhitespaces(s); @@ -69,31 +59,31 @@ public static Either>, Block> datalog(long index, Symbo }); if (!parsed) { - parsed = scope(code).fold(e -> { + parsed = fact(code).fold(e -> { lineErrors.add(e); return false; }, r -> { - blockBuilder.add_scope(r._2); + blockBuilder.add_fact(r._2); return true; }); } if (!parsed) { - parsed = fact(code).fold(e -> { + parsed = check(code).fold(e -> { lineErrors.add(e); return false; }, r -> { - blockBuilder.add_fact(r._2); + blockBuilder.add_check(r._2); return true; }); } if (!parsed) { - parsed = check(code).fold(e -> { + parsed = scope(code).fold(e -> { lineErrors.add(e); return false; }, r -> { - blockBuilder.add_check(r._2); + blockBuilder.add_scope(r._2); return true; }); } diff --git a/src/test/java/org/biscuitsec/biscuit/builder/BuilderTest.java b/src/test/java/org/biscuitsec/biscuit/builder/BuilderTest.java index ed0b590e..b981c58f 100644 --- a/src/test/java/org/biscuitsec/biscuit/builder/BuilderTest.java +++ b/src/test/java/org/biscuitsec/biscuit/builder/BuilderTest.java @@ -28,7 +28,7 @@ public void testBuild() throws Error.Language, Error.SymbolTableOverlap, Error.F KeyPair root = new KeyPair(rng); SymbolTable symbols = Biscuit.default_symbol_table(); - Block authority_builder = new Block(0, symbols); + Block authority_builder = new Block(0); authority_builder.add_fact(Utils.fact("revocation_id", Arrays.asList(Utils.date(Date.from(Instant.now()))))); authority_builder.add_fact(Utils.fact("right", Arrays.asList(Utils.s("admin")))); authority_builder.add_rule(Utils.constrained_rule("right", @@ -57,7 +57,9 @@ public void testBuild() throws Error.Language, Error.SymbolTableOverlap, Error.F ))))) ) )); - Biscuit rootBiscuit = Biscuit.make(rng, root, symbols, authority_builder.build()); + + org.biscuitsec.biscuit.token.Block authority = authority_builder.build(symbols); + Biscuit rootBiscuit = Biscuit.make(rng, root, authority); System.out.println(rootBiscuit.print()); diff --git a/src/test/java/org/biscuitsec/biscuit/builder/parser/ParserTest.java b/src/test/java/org/biscuitsec/biscuit/builder/parser/ParserTest.java index 9ec36a1f..4c53b52b 100644 --- a/src/test/java/org/biscuitsec/biscuit/builder/parser/ParserTest.java +++ b/src/test/java/org/biscuitsec/biscuit/builder/parser/ParserTest.java @@ -170,6 +170,29 @@ void expressionIntersectionAndContainsAndLengthEqualsTest() { ))), res); } + @Test + void testNegatePrecedence() { + Either> res = + Parser.check("check if !false && true"); + assertEquals(Either.right(new Tuple2<>("", + Utils.check( + Utils.constrained_rule("query", + new ArrayList<>(), + new ArrayList<>(), + List.of( + new Expression.Binary( + Expression.Op.And, + new Expression.Unary( + Expression.Op.Negate, + new Expression.Value(new Term.Bool(false)) + ), + new Expression.Value(new Term.Bool(true)) + ) + ) + )))), + res); + } + @Test void ruleWithFreeExpressionVariables() { Either> res = @@ -401,7 +424,6 @@ void testParens() throws org.biscuitsec.biscuit.error.Error.Execution { @Test void testDatalogSucceeds() throws org.biscuitsec.biscuit.error.Error.Parser { - SymbolTable symbols = Biscuit.default_symbol_table(); String l1 = "fact1(1, 2)"; String l2 = "fact2(\"2\")"; @@ -409,17 +431,17 @@ void testDatalogSucceeds() throws org.biscuitsec.biscuit.error.Error.Parser { String l4 = "check if rule1(2)"; String toParse = String.join(";", Arrays.asList(l1, l2, l3, l4)); - Either>, Block> output = Parser.datalog(1, symbols, toParse); + Either>, Block> output = Parser.datalog(1, toParse); assertTrue(output.isRight()); - Block validBlock = new Block(1, symbols); + Block validBlock = new Block(1); validBlock.add_fact(l1); validBlock.add_fact(l2); validBlock.add_rule(l3); validBlock.add_check(l4); output.forEach(block -> - assertArrayEquals(block.build().to_bytes().get(), validBlock.build().to_bytes().get()) + assertEquals(block, validBlock) ); } @@ -430,14 +452,14 @@ void testDatalogSucceedsArrays() throws org.biscuitsec.biscuit.error.Error.Parse String l1 = "check if [2, 3].union([2])"; String toParse = String.join(";", List.of(l1)); - Either>, Block> output = Parser.datalog(1, symbols, toParse); + Either>, Block> output = Parser.datalog(1, toParse); assertTrue(output.isRight()); - Block validBlock = new Block(1, symbols); + Block validBlock = new Block(1); validBlock.add_check(l1); output.forEach(block -> - assertArrayEquals(block.build().to_bytes().get(), validBlock.build().to_bytes().get()) + assertEquals(block, validBlock) ); } @@ -448,14 +470,14 @@ void testDatalogSucceedsArraysContains() throws org.biscuitsec.biscuit.error.Err String l1 = "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)); - Either>, Block> output = Parser.datalog(1, symbols, toParse); + Either>, Block> output = Parser.datalog(1, toParse); assertTrue(output.isRight()); - Block validBlock = new Block(1, symbols); + Block validBlock = new Block(1); validBlock.add_check(l1); output.forEach(block -> - assertArrayEquals(block.build().to_bytes().get(), validBlock.build().to_bytes().get()) + assertEquals(block, validBlock) ); } @@ -467,7 +489,7 @@ void testDatalogFailed() { String l2 = "check fact(1)"; // typo missing "if" String toParse = String.join(";", Arrays.asList(l1, l2)); - Either>, Block> output = Parser.datalog(1, symbols, toParse); + Either>, Block> output = Parser.datalog(1, toParse); assertTrue(output.isLeft()); } @@ -486,7 +508,7 @@ void testDatalogRemoveComment() throws org.biscuitsec.biscuit.error.Error.Parser String l8 = "comment */"; String toParse = String.join("", Arrays.asList(l0, l1, l2, l3, l4, l5, l6, l7, l8)); - Either>, Block> output = Parser.datalog(1, symbols, toParse); + Either>, Block> output = Parser.datalog(1, toParse); assertTrue(output.isRight()); } } \ No newline at end of file diff --git a/src/test/java/org/biscuitsec/biscuit/token/BiscuitTest.java b/src/test/java/org/biscuitsec/biscuit/token/BiscuitTest.java index 60575130..17ac83db 100644 --- a/src/test/java/org/biscuitsec/biscuit/token/BiscuitTest.java +++ b/src/test/java/org/biscuitsec/biscuit/token/BiscuitTest.java @@ -38,14 +38,13 @@ public void testBasic() throws NoSuchAlgorithmException, SignatureException, Inv KeyPair root = new KeyPair(rng); - SymbolTable symbols = Biscuit.default_symbol_table(); - Block authority_builder = new Block(0, symbols); + Block authority_builder = new Block(0); authority_builder.add_fact(fact("right", Arrays.asList(s("file1"), s("read")))); authority_builder.add_fact(fact("right", Arrays.asList(s("file2"), s("read")))); authority_builder.add_fact(fact("right", Arrays.asList(s("file1"), s("write")))); - Biscuit b = Biscuit.make(rng, root, Biscuit.default_symbol_table(), authority_builder.build()); + Biscuit b = Biscuit.make(rng, root, authority_builder.build()); System.out.println(b.print()); @@ -78,7 +77,7 @@ public void testBasic() throws NoSuchAlgorithmException, SignatureException, Inv ) ))); - Biscuit b2 = deser.attenuate(rng, keypair2, builder.build()); + Biscuit b2 = deser.attenuate(rng, keypair2, builder); System.out.println(b2.print()); @@ -109,7 +108,7 @@ public void testBasic() throws NoSuchAlgorithmException, SignatureException, Inv ) ))); - Biscuit b3 = deser2.attenuate(rng, keypair3, builder3.build()); + Biscuit b3 = deser2.attenuate(rng, keypair3, builder3); System.out.println(b3.print()); @@ -182,7 +181,7 @@ public void testFolders() throws NoSuchAlgorithmException, SignatureException, I block2.check_right("read"); KeyPair keypair2 = new KeyPair(rng); - Biscuit b2 = b.attenuate(rng, keypair2, block2.build()); + Biscuit b2 = b.attenuate(rng, keypair2, block2); Authorizer v1 = b2.authorizer(); v1.add_fact("resource(\"/folder1/file1\")"); @@ -227,12 +226,11 @@ public void testMultipleAttenuation() throws NoSuchAlgorithmException, Signature SecureRandom rng = new SecureRandom(); KeyPair root = new KeyPair(rng); - SymbolTable symbols = Biscuit.default_symbol_table(); - Block authority_builder = new Block(0, symbols); + Block authority_builder = new Block(0); Date date = Date.from(Instant.now()); authority_builder.add_fact(fact("revocation_id", Arrays.asList(date(date)))); - Biscuit biscuit = Biscuit.make(rng, root, Biscuit.default_symbol_table(), authority_builder.build()); + Biscuit biscuit = Biscuit.make(rng, root, authority_builder.build()); Block builder = biscuit.create_block(); builder.add_fact(fact( @@ -240,12 +238,12 @@ public void testMultipleAttenuation() throws NoSuchAlgorithmException, Signature Arrays.asList(s("topic"), s("tenant"), s("namespace"), s("topic"), s("produce")) )); - String attenuatedB64 = biscuit.attenuate(rng, new KeyPair(rng), builder.build()).serialize_b64url(); + String attenuatedB64 = biscuit.attenuate(rng, new KeyPair(rng), builder).serialize_b64url(); System.out.println("attenuated: " + attenuatedB64); Biscuit.from_b64url(attenuatedB64, root.public_key()); - String attenuated2B64 = biscuit.attenuate(rng, new KeyPair(rng), builder.build()).serialize_b64url(); + String attenuated2B64 = biscuit.attenuate(rng, new KeyPair(rng), builder).serialize_b64url(); System.out.println("attenuated2: " + attenuated2B64); Biscuit.from_b64url(attenuated2B64, root.public_key()); @@ -278,7 +276,7 @@ public void testReset() throws NoSuchAlgorithmException, SignatureException, Inv block2.check_right("read"); KeyPair keypair2 = new KeyPair(rng); - Biscuit b2 = b.attenuate(rng, keypair2, block2.build()); + Biscuit b2 = b.attenuate(rng, keypair2, block2); Authorizer v1 = b2.authorizer(); v1.allow(); @@ -347,7 +345,7 @@ public void testEmptyAuthorizer() throws NoSuchAlgorithmException, SignatureExce block2.check_right("read"); KeyPair keypair2 = new KeyPair(rng); - Biscuit b2 = b.attenuate(rng, keypair2, block2.build()); + Biscuit b2 = b.attenuate(rng, keypair2, block2); Authorizer v1 = new Authorizer(); v1.allow(); @@ -371,13 +369,12 @@ public void testBasicWithNamespaces() throws NoSuchAlgorithmException, Signature KeyPair root = new KeyPair(rng); - SymbolTable symbols = Biscuit.default_symbol_table(); - Block authority_builder = new Block(0, symbols); + Block authority_builder = new Block(0); authority_builder.add_fact(fact("namespace:right", Arrays.asList(s("file1"), s("read")))); authority_builder.add_fact(fact("namespace:right", Arrays.asList(s("file1"), s("write")))); authority_builder.add_fact(fact("namespace:right", Arrays.asList(s("file2"), s("read")))); - Biscuit b = Biscuit.make(rng, root, Biscuit.default_symbol_table(), authority_builder.build()); + Biscuit b = Biscuit.make(rng, root, authority_builder.build()); System.out.println(b.print()); @@ -410,7 +407,7 @@ public void testBasicWithNamespaces() throws NoSuchAlgorithmException, Signature ) ))); - Biscuit b2 = deser.attenuate(rng, keypair2, builder.build()); + Biscuit b2 = deser.attenuate(rng, keypair2, builder); System.out.println(b2.print()); @@ -441,7 +438,7 @@ public void testBasicWithNamespaces() throws NoSuchAlgorithmException, Signature ) ))); - Biscuit b3 = deser2.attenuate(rng, keypair3, builder3.build()); + Biscuit b3 = deser2.attenuate(rng, keypair3, builder3); System.out.println(b3.print()); @@ -497,7 +494,7 @@ public void testBasicWithNamespacesWithAddAuthorityFact() throws NoSuchAlgorithm KeyPair root = new KeyPair(rng); SymbolTable symbols = Biscuit.default_symbol_table(); - org.biscuitsec.biscuit.token.builder.Biscuit o = new org.biscuitsec.biscuit.token.builder.Biscuit(rng, root, symbols); + org.biscuitsec.biscuit.token.builder.Biscuit o = new org.biscuitsec.biscuit.token.builder.Biscuit(rng, root); o.add_authority_fact("namespace:right(\"file1\",\"read\")"); o.add_authority_fact("namespace:right(\"file1\",\"write\")"); o.add_authority_fact("namespace:right(\"file2\",\"read\")"); @@ -534,7 +531,7 @@ public void testBasicWithNamespacesWithAddAuthorityFact() throws NoSuchAlgorithm ) ))); - Biscuit b2 = deser.attenuate(rng, keypair2, builder.build()); + Biscuit b2 = deser.attenuate(rng, keypair2, builder); System.out.println(b2.print()); @@ -565,7 +562,7 @@ public void testBasicWithNamespacesWithAddAuthorityFact() throws NoSuchAlgorithm ) ))); - Biscuit b3 = deser2.attenuate(rng, keypair3, builder3.build()); + Biscuit b3 = deser2.attenuate(rng, keypair3, builder3); System.out.println(b3.print()); @@ -619,14 +616,13 @@ public void testRootKeyId() throws NoSuchAlgorithmException, SignatureException, KeyPair root = new KeyPair(rng); - SymbolTable symbols = Biscuit.default_symbol_table(); - Block authority_builder = new Block(0, symbols); + Block authority_builder = new Block(0); authority_builder.add_fact(fact("right", Arrays.asList(s("file1"), s("read")))); authority_builder.add_fact(fact("right", Arrays.asList(s("file2"), s("read")))); authority_builder.add_fact(fact("right", Arrays.asList(s("file1"), s("write")))); - Biscuit b = Biscuit.make(rng, root, 1, Biscuit.default_symbol_table(), authority_builder.build()); + Biscuit b = Biscuit.make(rng, root, 1, authority_builder.build()); System.out.println(b.print()); diff --git a/src/test/java/org/biscuitsec/biscuit/token/SamplesTest.java b/src/test/java/org/biscuitsec/biscuit/token/SamplesTest.java index 9e13dbed..9147d5a9 100644 --- a/src/test/java/org/biscuitsec/biscuit/token/SamplesTest.java +++ b/src/test/java/org/biscuitsec/biscuit/token/SamplesTest.java @@ -65,30 +65,35 @@ void compareBlock(SymbolTable baseSymbols, long sampleBlockIndex, Block sampleBl Option sampleExternalKey = sampleBlock.getExternalKey(); List samplePublicKeys = sampleBlock.getPublicKeys(); String sampleDatalog = sampleBlock.getCode().replace("\"","\\\""); - SymbolTable sampleSymbols = new SymbolTable(sampleBlock.symbols); - Either>, org.biscuitsec.biscuit.token.builder.Block> outputSample = Parser.datalog(sampleBlockIndex, baseSymbols, sampleDatalog); - assertTrue(outputSample.isRight()); + Either>, org.biscuitsec.biscuit.token.builder.Block> outputSample = Parser.datalog(sampleBlockIndex, sampleDatalog); - if (!block.publicKeys.isEmpty()) { - outputSample.get().addPublicKeys(samplePublicKeys); + // the invalid block rule with unbound variable cannot be parsed + if(outputSample.isLeft()) { + return; } + SymbolTable sampleSymbols; if (!block.externalKey.isDefined()) { - sampleSymbols.symbols.forEach(baseSymbols::add); + sampleSymbols = new SymbolTable(baseSymbols); } else { - SymbolTable freshSymbols = new SymbolTable(); - sampleSymbols.symbols.forEach(freshSymbols::add); - outputSample.get().setExternalKey(sampleExternalKey); + sampleSymbols = new SymbolTable(); } - org.biscuitsec.biscuit.token.Block generatedSampleBlock = outputSample.get().build(); + org.biscuitsec.biscuit.token.Block generatedSampleBlock = outputSample.get().build(sampleSymbols); System.out.println(generatedSampleBlock.symbols.symbols); System.out.println(block.symbols.symbols); + System.out.println(sampleSymbols.symbols); + if(!block.externalKey.isDefined()) { + generatedSampleBlock.symbols.symbols.forEach(baseSymbols::add); + } + System.out.println(baseSymbols.symbols); - System.out.println(outputSample.get().build().print(sampleSymbols)); - System.out.println(block.print(sampleSymbols)); - assertArrayEquals(outputSample.get().build().to_bytes().get(), block.to_bytes().get()); + System.out.println(generatedSampleBlock.print(baseSymbols)); + System.out.println(block.print(baseSymbols)); + assertEquals(generatedSampleBlock.print(baseSymbols), block.print(baseSymbols)); + assertEquals(generatedSampleBlock, block); + assertArrayEquals(generatedSampleBlock.to_bytes().get(), block.to_bytes().get()); } DynamicTest process_testcase(final TestCase testCase, final PublicKey publicKey, final KeyPair privateKey) { diff --git a/src/test/java/org/biscuitsec/biscuit/token/UnverifiedBiscuitTest.java b/src/test/java/org/biscuitsec/biscuit/token/UnverifiedBiscuitTest.java index 555b25df..4f2fff79 100644 --- a/src/test/java/org/biscuitsec/biscuit/token/UnverifiedBiscuitTest.java +++ b/src/test/java/org/biscuitsec/biscuit/token/UnverifiedBiscuitTest.java @@ -32,13 +32,14 @@ public void testBasic() throws Error, NoSuchAlgorithmException, SignatureExcepti KeyPair keypair0 = new KeyPair(rng); - SymbolTable symbols = Biscuit.default_symbol_table(); - org.biscuitsec.biscuit.token.builder.Block block0 = new org.biscuitsec.biscuit.token.builder.Block(0, symbols); - block0.add_fact(Utils.fact("right", List.of(Utils.s("file1"), Utils.s("read")))); - block0.add_fact(Utils.fact("right", List.of(Utils.s("file2"), Utils.s("read")))); - block0.add_fact(Utils.fact("right", List.of(Utils.s("file1"), Utils.s("write")))); + // org.biscuitsec.biscuit.token.builder.Block block0 = new org.biscuitsec.biscuit.token.builder.Block(0); + org.biscuitsec.biscuit.token.builder.Biscuit block0 = Biscuit.builder(rng, keypair0); + block0.add_authority_fact(Utils.fact("right", List.of(Utils.s("file1"), Utils.s("read")))); + block0.add_authority_fact(Utils.fact("right", List.of(Utils.s("file2"), Utils.s("read")))); + block0.add_authority_fact(Utils.fact("right", List.of(Utils.s("file1"), Utils.s("write")))); - Biscuit biscuit0 = Biscuit.make(rng, keypair0, Biscuit.default_symbol_table(), block0.build()); + + Biscuit biscuit0 = block0.build(); System.out.println(biscuit0.print()); System.out.println("serializing the first token"); @@ -98,7 +99,7 @@ public void testBasic() throws Error, NoSuchAlgorithmException, SignatureExcepti ) ))); - UnverifiedBiscuit unverifiedBiscuit2 = unverifiedBiscuit1.attenuate(rng, keypair2, block2.build()); + UnverifiedBiscuit unverifiedBiscuit2 = unverifiedBiscuit1.attenuate(rng, keypair2, block2); System.out.println(unverifiedBiscuit2.print());