From 007f03b18c4fa3e24b6fb2494e11e7af5205dea3 Mon Sep 17 00:00:00 2001 From: Geoffroy Couprie Date: Sun, 31 Dec 2023 17:49:11 +0100 Subject: [PATCH 1/6] scope serialization and public key interning --- .../com/clevercloud/biscuit/datalog/Rule.java | 26 +++++- .../biscuit/datalog/SchemaVersion.java | 33 +++++--- .../clevercloud/biscuit/datalog/Scope.java | 79 ++++++++++++++++++ .../biscuit/datalog/SymbolTable.java | 23 +++++ .../com/clevercloud/biscuit/token/Block.java | 25 +++++- .../biscuit/token/builder/Biscuit.java | 14 +++- .../biscuit/token/builder/Block.java | 12 ++- .../biscuit/token/builder/Rule.java | 24 +++++- .../biscuit/token/builder/Scope.java | 83 +++++++++++++++++++ .../biscuit/token/builder/Utils.java | 4 +- 10 files changed, 296 insertions(+), 27 deletions(-) create mode 100644 src/main/java/com/clevercloud/biscuit/datalog/Scope.java create mode 100644 src/main/java/com/clevercloud/biscuit/token/builder/Scope.java diff --git a/src/main/java/com/clevercloud/biscuit/datalog/Rule.java b/src/main/java/com/clevercloud/biscuit/datalog/Rule.java index 1d208055..7fc45a2b 100644 --- a/src/main/java/com/clevercloud/biscuit/datalog/Rule.java +++ b/src/main/java/com/clevercloud/biscuit/datalog/Rule.java @@ -17,6 +17,7 @@ public final class Rule implements Serializable { private final Predicate head; private final List body; private final List expressions; + private final List scopes; public final Predicate head() { return this.head; @@ -30,6 +31,10 @@ public final List expressions() { return this.expressions; } + public List scopes() { + return scopes; + } + public void apply(final Set facts, final Set new_facts, SymbolTable symbols) { final Set variables_set = new HashSet<>(); for (final Predicate pred : this.body) { @@ -150,10 +155,12 @@ public boolean check_match_all(final Set facts, SymbolTable symbols) { } } - public Rule(final Predicate head, final List body, final List expressions) { + public Rule(final Predicate head, final List body, final List expressions, + final List scopes) { this.head = head; this.body = body; this.expressions = expressions; + this.scopes = scopes; } public Schema.RuleV2 serialize() { @@ -168,6 +175,10 @@ public Schema.RuleV2 serialize() { b.addExpressions(this.expressions.get(i).serialize()); } + for (Scope scope: this.scopes) { + b.addScope(scope.serialize()); + } + return b.build(); } @@ -194,12 +205,23 @@ static public Either deserializeV2(Schema.RuleV2 rule) } } + ArrayList scopes = new ArrayList<>(); + for (Schema.Scope scope: rule.getScopeList()) { + Either res = Scope.deserialize(scope); + if(res.isLeft()) { + Error.FormatError e = res.getLeft(); + return Left(e); + } else { + scopes.add(res.get()); + } + } + Either res = Predicate.deserializeV2(rule.getHead()); if(res.isLeft()) { Error.FormatError e = res.getLeft(); return Left(e); } else { - return Right(new Rule(res.get(), body, expressions)); + return Right(new Rule(res.get(), body, expressions, scopes)); } } } diff --git a/src/main/java/com/clevercloud/biscuit/datalog/SchemaVersion.java b/src/main/java/com/clevercloud/biscuit/datalog/SchemaVersion.java index a92c5702..6c185231 100644 --- a/src/main/java/com/clevercloud/biscuit/datalog/SchemaVersion.java +++ b/src/main/java/com/clevercloud/biscuit/datalog/SchemaVersion.java @@ -2,10 +2,7 @@ import com.clevercloud.biscuit.datalog.expressions.Expression; import com.clevercloud.biscuit.datalog.expressions.Op; -import com.clevercloud.biscuit.datalog.expressions.Op.BinaryOp; import com.clevercloud.biscuit.error.Error; -import com.clevercloud.biscuit.token.Block; -import com.clevercloud.biscuit.token.format.SerializedBiscuit; import io.vavr.control.Either; import java.util.List; @@ -20,16 +17,28 @@ public class SchemaVersion { private boolean containsCheckAll; private boolean containsV4; - public SchemaVersion(List facts, List rules, List checks) { + public SchemaVersion(List facts, List rules, List checks, List scopes) { // TODO - containsScopes = false; - /* - let contains_scopes = !scopes.is_empty() - || rules.iter().any(|r: &Rule| !r.scopes.is_empty()) - || checks - .iter() - .any(|c: &Check| c.queries.iter().any(|q| !q.scopes.is_empty())); - */ + containsScopes = !scopes.isEmpty(); + + if(!containsScopes) { + for(Rule r: rules) { + if (!r.scopes().isEmpty()) { + containsScopes = true; + break; + } + } + } + if(!containsScopes) { + for(Check check: checks) { + for(Rule query: check.queries()) { + if(!query.scopes().isEmpty()) { + containsScopes = true; + break; + } + } + } + } containsCheckAll = false; for(Check check: checks) { diff --git a/src/main/java/com/clevercloud/biscuit/datalog/Scope.java b/src/main/java/com/clevercloud/biscuit/datalog/Scope.java new file mode 100644 index 00000000..9ca131cc --- /dev/null +++ b/src/main/java/com/clevercloud/biscuit/datalog/Scope.java @@ -0,0 +1,79 @@ +package com.clevercloud.biscuit.datalog; + +import biscuit.format.schema.Schema; +import com.clevercloud.biscuit.datalog.expressions.Expression; +import com.clevercloud.biscuit.error.Error; +import io.vavr.control.Either; + +import java.util.ArrayList; + +import static io.vavr.API.Left; +import static io.vavr.API.Right; + +public class Scope { + public enum Kind { + Authority, + Previous, + PublicKey + } + + Kind kind; + long publicKey; + + private Scope(Kind kind, long publicKey) { + this.kind = kind; + this.publicKey = publicKey; + } + public static Scope authority() { + return new Scope(Kind.Authority, 0); + } + + public static Scope previous() { + return new Scope(Kind.Previous, 0); + } + + public static Scope publicKey(long publicKey) { + return new Scope(Kind.PublicKey, publicKey); + } + + public Kind kind() { + return kind; + } + + public long publicKey() { + return publicKey; + } + + public Schema.Scope serialize() { + Schema.Scope.Builder b = Schema.Scope.newBuilder(); + + switch(this.kind) { + case Authority: + b.setScopeType(Schema.Scope.ScopeType.Authority); + break; + case Previous: + b.setScopeType(Schema.Scope.ScopeType.Previous); + break; + case PublicKey: + b.setPublicKey(this.publicKey); + } + + return b.build(); + } + + static public Either deserialize(Schema.Scope scope) { + if (scope.hasPublicKey()) { + long publicKey = scope.getPublicKey(); + return Right(Scope.publicKey(publicKey)); + } + if(scope.hasScopeType()) { + switch(scope.getScopeType()) { + case Authority: + return Right(Scope.authority()); + case Previous: + return Right(Scope.previous()); + } + } + return Left(new Error.FormatError.DeserializationError("invalid Scope")); + } +} diff --git a/src/main/java/com/clevercloud/biscuit/datalog/SymbolTable.java b/src/main/java/com/clevercloud/biscuit/datalog/SymbolTable.java index 0b945efa..2eda1e79 100644 --- a/src/main/java/com/clevercloud/biscuit/datalog/SymbolTable.java +++ b/src/main/java/com/clevercloud/biscuit/datalog/SymbolTable.java @@ -1,5 +1,6 @@ package com.clevercloud.biscuit.datalog; +import com.clevercloud.biscuit.crypto.PublicKey; import com.clevercloud.biscuit.crypto.TokenSignature; import com.clevercloud.biscuit.datalog.expressions.Expression; import com.clevercloud.biscuit.token.builder.Utils; @@ -60,6 +61,7 @@ private String fromEpochIsoDate(long epochSec) { "query" ); public final List symbols; + public final List publicKeys; public long insert(final String symbol) { int index = this.defaultSymbols.indexOf(symbol); @@ -76,6 +78,16 @@ public long insert(final String symbol) { } } + public long insert(final PublicKey publicKey) { + int index = this.publicKeys.indexOf(publicKey); + if (index == -1) { + this.publicKeys.add(publicKey); + return this.publicKeys.size() - 1; + } else { + return index; + } + } + public Term add(final String symbol) { return new Term.Str(this.insert(symbol)); } @@ -106,6 +118,14 @@ public Option get_s(int i) { } } + public Option get_pk(int i) { + if (i >= 0 && i < this.publicKeys.size()) { + return Option.some(this.publicKeys.get(i)); + } else { + return Option.none(); + } + } + public String print_id(final Term value) { String _s = ""; if (value instanceof Term.Bool) { @@ -224,11 +244,14 @@ public String print_symbol(int i) { public SymbolTable() { this.symbols = new ArrayList<>(); + this.publicKeys = new ArrayList<>(); } public SymbolTable(SymbolTable s) { this.symbols = new ArrayList<>(); symbols.addAll(s.symbols); + this.publicKeys = new ArrayList<>(); + publicKeys.addAll(s.publicKeys); } public List getAllSymbols() { diff --git a/src/main/java/com/clevercloud/biscuit/token/Block.java b/src/main/java/com/clevercloud/biscuit/token/Block.java index edfd1f15..b2ab3453 100644 --- a/src/main/java/com/clevercloud/biscuit/token/Block.java +++ b/src/main/java/com/clevercloud/biscuit/token/Block.java @@ -28,6 +28,7 @@ public class Block { final List facts; final List rules; final List checks; + final List scopes; final long version; /** @@ -41,6 +42,7 @@ public Block(SymbolTable base_symbols) { this.facts = new ArrayList<>(); this.rules = new ArrayList<>(); this.checks = new ArrayList<>(); + this.scopes = new ArrayList<>(); this.version = SerializedBiscuit.MAX_SCHEMA_VERSION; } @@ -51,12 +53,14 @@ public Block(SymbolTable base_symbols) { * @param facts * @param checks */ - public Block(SymbolTable base_symbols, String context, List facts, List rules, List checks, int version) { + public Block(SymbolTable base_symbols, String context, List facts, List rules, List checks, + List scopes, int version) { this.symbols = base_symbols; this.context = context; this.facts = facts; this.rules = rules; this.checks = checks; + this.scopes = scopes; this.version = version; } @@ -122,6 +126,10 @@ public Schema.Block serialize() { b.addChecksV2(this.checks.get(i).serialize()); } + for (Scope scope: this.scopes) { + b.addScope(scope.serialize()); + } + b.setVersion(SerializedBiscuit.MAX_SCHEMA_VERSION); return b.build(); } @@ -179,14 +187,25 @@ static public Either deserialize(Schema.Block b) { } } - SchemaVersion schemaVersion = new SchemaVersion(facts, rules, checks); + ArrayList scopes = new ArrayList<>(); + for (Schema.Scope scope: b.getScopeList()) { + Either res = Scope.deserialize(scope); + if(res.isLeft()) { + Error.FormatError e = res.getLeft(); + return Left(e); + } else { + scopes.add(res.get()); + } + } + + SchemaVersion schemaVersion = new SchemaVersion(facts, rules, checks, scopes); Either res = schemaVersion.checkCompatibility(version); if (res.isLeft()) { Error.FormatError e = res.getLeft(); return Left(e); } - return Right(new Block(symbols, b.getContext(), facts, rules, checks, version)); + return Right(new Block(symbols, b.getContext(), facts, rules, checks, scopes, version)); } /** diff --git a/src/main/java/com/clevercloud/biscuit/token/builder/Biscuit.java b/src/main/java/com/clevercloud/biscuit/token/builder/Biscuit.java index 67e81f75..36db1a6e 100644 --- a/src/main/java/com/clevercloud/biscuit/token/builder/Biscuit.java +++ b/src/main/java/com/clevercloud/biscuit/token/builder/Biscuit.java @@ -5,6 +5,7 @@ import com.clevercloud.biscuit.datalog.Check; import com.clevercloud.biscuit.datalog.Fact; import com.clevercloud.biscuit.datalog.Rule; +import com.clevercloud.biscuit.datalog.Scope; import com.clevercloud.biscuit.error.Error; import com.clevercloud.biscuit.token.Block; import io.vavr.Tuple2; @@ -28,6 +29,7 @@ public class Biscuit { List facts; List rules; List checks; + List scopes; Option root_key_id; public Biscuit(final SecureRandom rng, final KeyPair root, SymbolTable base_symbols) { @@ -39,6 +41,7 @@ public Biscuit(final SecureRandom rng, final KeyPair root, SymbolTable base_symb this.facts = new ArrayList<>(); this.rules = new ArrayList<>(); this.checks = new ArrayList<>(); + this.scopes = new ArrayList<>(); this.root_key_id = Option.none(); } @@ -51,6 +54,7 @@ public Biscuit(final SecureRandom rng, final KeyPair root, Option root_ this.facts = new ArrayList<>(); this.rules = new ArrayList<>(); this.checks = new ArrayList<>(); + this.scopes = new ArrayList<>(); this.root_key_id = root_key_id; } @@ -116,6 +120,11 @@ public Biscuit set_context(String context) { return this; } + public Biscuit add_scope(com.clevercloud.biscuit.token.builder.Scope scope) { + this.scopes.add(scope.convert(this.symbols)); + return this; + } + public void set_root_key_id(Integer i) { this.root_key_id = Option.some(i); } @@ -131,10 +140,11 @@ public com.clevercloud.biscuit.token.Biscuit build() throws Error { for (int i = this.symbol_start; i < this.symbols.symbols.size(); i++) { symbols.add(this.symbols.symbols.get(i)); } - SchemaVersion schemaVersion = new SchemaVersion(this.facts, this.rules, this.checks); + + SchemaVersion schemaVersion = new SchemaVersion(this.facts, this.rules, this.checks, this.scopes); Block authority_block = new com.clevercloud.biscuit.token.Block(symbols, context, this.facts, this.rules, - this.checks, schemaVersion.version()); + this.checks, scopes, schemaVersion.version()); if (this.root_key_id.isDefined()) { return make(this.rng, this.root, this.root_key_id.get(), base_symbols, authority_block); diff --git a/src/main/java/com/clevercloud/biscuit/token/builder/Block.java b/src/main/java/com/clevercloud/biscuit/token/builder/Block.java index 67751931..80d16a6c 100644 --- a/src/main/java/com/clevercloud/biscuit/token/builder/Block.java +++ b/src/main/java/com/clevercloud/biscuit/token/builder/Block.java @@ -4,6 +4,7 @@ import com.clevercloud.biscuit.datalog.*; import com.clevercloud.biscuit.datalog.Check; import com.clevercloud.biscuit.datalog.Fact; +import com.clevercloud.biscuit.datalog.Scope; import com.clevercloud.biscuit.datalog.Rule; import com.clevercloud.biscuit.error.Error; import io.vavr.Tuple2; @@ -26,6 +27,7 @@ public class Block { List facts; List rules; List checks; + List scopes; public Block(long index, SymbolTable base_symbols) { this.index = index; @@ -35,6 +37,7 @@ public Block(long index, SymbolTable base_symbols) { this.facts = new ArrayList<>(); this.rules = new ArrayList<>(); this.checks = new ArrayList<>(); + this.scopes = new ArrayList<>(); } public Block add_fact(com.clevercloud.biscuit.token.builder.Fact f) { @@ -91,6 +94,11 @@ public Block add_check(String s) throws Error.Parser { return add_check(t._2); } + public Block add_scope(com.clevercloud.biscuit.token.builder.Scope scope) { + this.scopes.add(scope.convert(this.symbols)); + return this; + } + public Block set_context(String context) { this.context = context; return this; @@ -103,10 +111,10 @@ public com.clevercloud.biscuit.token.Block build() { symbols.add(this.symbols.symbols.get(i)); } - SchemaVersion schemaVersion = new SchemaVersion(this.facts, this.rules, this.checks); + SchemaVersion schemaVersion = new SchemaVersion(this.facts, this.rules, this.checks, this.scopes); return new com.clevercloud.biscuit.token.Block(symbols, this.context, this.facts, this.rules, this.checks, - schemaVersion.version()); + this.scopes, schemaVersion.version()); } public Block check_right(String right) { diff --git a/src/main/java/com/clevercloud/biscuit/token/builder/Rule.java b/src/main/java/com/clevercloud/biscuit/token/builder/Rule.java index 006521e8..69548c40 100644 --- a/src/main/java/com/clevercloud/biscuit/token/builder/Rule.java +++ b/src/main/java/com/clevercloud/biscuit/token/builder/Rule.java @@ -15,12 +15,14 @@ public class Rule implements Cloneable { List body; List expressions; Option>> variables; + List scopes; - public Rule(Predicate head, List body, List expressions) { + public Rule(Predicate head, List body, List expressions, List scopes) { Map> variables = new HashMap<>(); this.head = head; this.body = body; this.expressions = expressions; + this.scopes = scopes; for (Term t : head.terms) { if (t instanceof Term.Variable) { variables.put(((Term.Variable) t).value, Option.none()); @@ -50,7 +52,9 @@ public Rule clone() { body.addAll(this.body); List expressions = new ArrayList<>(); expressions.addAll(this.expressions); - return new Rule(head, body, expressions); + List scopes = new ArrayList<>(); + scopes.addAll(this.scopes); + return new Rule(head, body, expressions, scopes); } public void set(String name, Term term) throws Error.Language { @@ -127,6 +131,8 @@ public com.clevercloud.biscuit.datalog.Rule convert(SymbolTable symbols) { com.clevercloud.biscuit.datalog.Predicate head = r.head.convert(symbols); ArrayList body = new ArrayList<>(); ArrayList expressions = new ArrayList<>(); + ArrayList scopes = new ArrayList<>(); + for (Predicate p : r.body) { body.add(p.convert(symbols)); @@ -136,7 +142,11 @@ public com.clevercloud.biscuit.datalog.Rule convert(SymbolTable symbols) { expressions.add(e.convert(symbols)); } - return new com.clevercloud.biscuit.datalog.Rule(head, body, expressions); + for (Scope s : r.scopes) { + scopes.add(s.convert(symbols)); + } + + return new com.clevercloud.biscuit.datalog.Rule(head, body, expressions, scopes); } public static Rule convert_from(com.clevercloud.biscuit.datalog.Rule r, SymbolTable symbols) { @@ -144,6 +154,8 @@ public static Rule convert_from(com.clevercloud.biscuit.datalog.Rule r, SymbolTa ArrayList body = new ArrayList<>(); ArrayList expressions = new ArrayList<>(); + ArrayList scopes = new ArrayList<>(); + for (com.clevercloud.biscuit.datalog.Predicate p : r.body()) { body.add(Predicate.convert_from(p, symbols)); @@ -153,7 +165,11 @@ public static Rule convert_from(com.clevercloud.biscuit.datalog.Rule r, SymbolTa expressions.add(Expression.convert_from(e, symbols)); } - return new Rule(head, body, expressions); + for (com.clevercloud.biscuit.datalog.Scope s : r.scopes()) { + scopes.add(Scope.convert_from(s, symbols)); + } + + return new Rule(head, body, expressions, scopes); } @Override diff --git a/src/main/java/com/clevercloud/biscuit/token/builder/Scope.java b/src/main/java/com/clevercloud/biscuit/token/builder/Scope.java new file mode 100644 index 00000000..cd7bf05d --- /dev/null +++ b/src/main/java/com/clevercloud/biscuit/token/builder/Scope.java @@ -0,0 +1,83 @@ +package com.clevercloud.biscuit.token.builder; + +import biscuit.format.schema.Schema; +import com.clevercloud.biscuit.crypto.PublicKey; +import com.clevercloud.biscuit.datalog.SymbolTable; +import com.clevercloud.biscuit.error.Error; +import io.vavr.control.Either; + + +import java.util.ArrayList; + +import static io.vavr.API.Left; +import static io.vavr.API.Right; + +public class Scope { + enum Kind { + Authority, + Previous, + PublicKey, + Parameter, + } + + Kind kind; + PublicKey publicKey; + String parameter; + + private Scope(Kind kind) { + this.kind = kind; + this.publicKey = null; + this.parameter = new String(); + } + + private Scope(Kind kind, PublicKey publicKey) { + this.kind = kind; + this.publicKey = publicKey; + this.parameter = new String(); + } + private Scope(Kind kind, String parameter) { + this.kind = kind; + this.publicKey = null; + this.parameter = parameter; + } + public static Scope authority() { + return new Scope(Kind.Authority); + } + + public static Scope previous() { + return new Scope(Kind.Previous); + } + + public static Scope publicKey(PublicKey publicKey) { + return new Scope(Kind.PublicKey, publicKey); + } + + public com.clevercloud.biscuit.datalog.Scope convert(SymbolTable symbols) { + switch (this.kind) { + case Authority: + return com.clevercloud.biscuit.datalog.Scope.authority(); + case Previous: + return com.clevercloud.biscuit.datalog.Scope.previous(); + case Parameter: + throw new Exception("Remaining parameter: "+this.parameter); + case PublicKey: + return com.clevercloud.biscuit.datalog.Scope.publicKey(symbols.insert(this.publicKey)); + } + } + + public static Scope convert_from(com.clevercloud.biscuit.datalog.Scope scope, SymbolTable symbols) { + switch(scope.kind()) { + case Authority: + return new Scope(Kind.Authority); + case Previous: + return new Scope(Kind.Previous); + case PublicKey: + //FIXME error management should bubble up here + return new Scope(Kind.PublicKey, symbols.get_pk((int) scope.publicKey()).get()); + } + + //FIXME error management should bubble up here + throw new Exception("panic"); + + } +} diff --git a/src/main/java/com/clevercloud/biscuit/token/builder/Utils.java b/src/main/java/com/clevercloud/biscuit/token/builder/Utils.java index 35406c33..dab198b0 100644 --- a/src/main/java/com/clevercloud/biscuit/token/builder/Utils.java +++ b/src/main/java/com/clevercloud/biscuit/token/builder/Utils.java @@ -20,13 +20,13 @@ public static com.clevercloud.biscuit.token.builder.Predicate pred(String name, public static com.clevercloud.biscuit.token.builder.Rule rule(String head_name, List head_ids, List predicates) { - return new com.clevercloud.biscuit.token.builder.Rule(pred(head_name, head_ids), predicates, new ArrayList<>()); + return new com.clevercloud.biscuit.token.builder.Rule(pred(head_name, head_ids), predicates, new ArrayList<>(), new ArrayList<>()); } public static com.clevercloud.biscuit.token.builder.Rule constrained_rule(String head_name, List head_ids, List predicates, List expressions) { - return new com.clevercloud.biscuit.token.builder.Rule(pred(head_name, head_ids), predicates, expressions); + return new com.clevercloud.biscuit.token.builder.Rule(pred(head_name, head_ids), predicates, expressions, new ArrayList<>()); } public static Check check(com.clevercloud.biscuit.token.builder.Rule rule) { From 4747eb9ad16d67be999f4f4a8f0b4ce362482fed Mon Sep 17 00:00:00 2001 From: Geoffroy Couprie Date: Mon, 1 Jan 2024 14:34:56 +0100 Subject: [PATCH 2/6] parse and display scopes --- .../clevercloud/biscuit/crypto/PublicKey.java | 5 + .../com/clevercloud/biscuit/datalog/Rule.java | 9 +- .../biscuit/token/builder/Check.java | 5 + .../biscuit/token/builder/Rule.java | 13 ++ .../biscuit/token/builder/Scope.java | 49 ++++- .../biscuit/token/builder/Utils.java | 3 +- .../biscuit/token/builder/parser/Parser.java | 173 +++++++++++++++--- .../biscuit/builder/parser/ParserTest.java | 25 ++- 8 files changed, 252 insertions(+), 30 deletions(-) diff --git a/src/main/java/com/clevercloud/biscuit/crypto/PublicKey.java b/src/main/java/com/clevercloud/biscuit/crypto/PublicKey.java index 602f7622..e6e13505 100644 --- a/src/main/java/com/clevercloud/biscuit/crypto/PublicKey.java +++ b/src/main/java/com/clevercloud/biscuit/crypto/PublicKey.java @@ -55,4 +55,9 @@ public boolean equals(Object o) { public int hashCode() { return key.hashCode(); } + + @Override + public String toString() { + return "ed25519/" + toHex(); + } } diff --git a/src/main/java/com/clevercloud/biscuit/datalog/Rule.java b/src/main/java/com/clevercloud/biscuit/datalog/Rule.java index 7fc45a2b..7aadb3ab 100644 --- a/src/main/java/com/clevercloud/biscuit/datalog/Rule.java +++ b/src/main/java/com/clevercloud/biscuit/datalog/Rule.java @@ -155,7 +155,14 @@ public boolean check_match_all(final Set facts, SymbolTable symbols) { } } - public Rule(final Predicate head, final List body, final List expressions, + public Rule(final Predicate head, final List body, final List expressions) { + this.head = head; + this.body = body; + this.expressions = expressions; + this.scopes = new ArrayList<>(); + } + + public Rule(final Predicate head, final List body, final List expressions, final List scopes) { this.head = head; this.body = body; diff --git a/src/main/java/com/clevercloud/biscuit/token/builder/Check.java b/src/main/java/com/clevercloud/biscuit/token/builder/Check.java index c78c38d7..a31be3f8 100644 --- a/src/main/java/com/clevercloud/biscuit/token/builder/Check.java +++ b/src/main/java/com/clevercloud/biscuit/token/builder/Check.java @@ -55,6 +55,11 @@ public String toString() { res += ", "+ String.join(", ", e); } + if(!q.scopes.isEmpty()) { + final List e = q.scopes.stream().map((scope) -> scope.toString()).collect(Collectors.toList()); + res += " trusting " + String.join(", ", e); + } + return res; }).collect(Collectors.toList()); diff --git a/src/main/java/com/clevercloud/biscuit/token/builder/Rule.java b/src/main/java/com/clevercloud/biscuit/token/builder/Rule.java index 69548c40..19c7deca 100644 --- a/src/main/java/com/clevercloud/biscuit/token/builder/Rule.java +++ b/src/main/java/com/clevercloud/biscuit/token/builder/Rule.java @@ -109,9 +109,14 @@ public Either validate_variables() { return Stream.of(((Term.Variable) t).value); } else return Stream.empty(); }).collect(Collectors.toSet()); + for(Expression e: this.expressions) { e.gatherVariables(free_variables); } + if (free_variables.isEmpty()) { + return Either.right(this); + } + for (Predicate p : this.body) { for (Term term : p.terms) { if (term instanceof Term.Variable) { @@ -122,6 +127,7 @@ public Either validate_variables() { } } } + return Either.left("rule head or expressions contains variables that are not used in predicates of the rule's body: " + free_variables.toString()); } @@ -181,6 +187,7 @@ public boolean equals(Object o) { if (head != null ? !head.equals(rule.head) : rule.head != null) return false; if (body != null ? !body.equals(rule.body) : rule.body != null) return false; + if (scopes != null ? !scopes.equals(rule.scopes) : rule.scopes != null) return false; return expressions != null ? expressions.equals(rule.expressions) : rule.expressions == null; } @@ -189,6 +196,7 @@ 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; } @@ -204,6 +212,11 @@ public String toString() { res += ", " + String.join(", ", e); } + if(!r.scopes.isEmpty()) { + final List e = r.scopes.stream().map((scope) -> scope.toString()).collect(Collectors.toList()); + res += " trusting " + String.join(", ", e); + } + return res; } } diff --git a/src/main/java/com/clevercloud/biscuit/token/builder/Scope.java b/src/main/java/com/clevercloud/biscuit/token/builder/Scope.java index cd7bf05d..bec24afc 100644 --- a/src/main/java/com/clevercloud/biscuit/token/builder/Scope.java +++ b/src/main/java/com/clevercloud/biscuit/token/builder/Scope.java @@ -8,6 +8,7 @@ import java.util.ArrayList; +import java.util.Objects; import static io.vavr.API.Left; import static io.vavr.API.Right; @@ -52,6 +53,10 @@ public static Scope publicKey(PublicKey publicKey) { return new Scope(Kind.PublicKey, publicKey); } + public static Scope parameter(String parameter) { + return new Scope(Kind.Parameter, parameter); + } + public com.clevercloud.biscuit.datalog.Scope convert(SymbolTable symbols) { switch (this.kind) { case Authority: @@ -59,10 +64,14 @@ public com.clevercloud.biscuit.datalog.Scope convert(SymbolTable symbols) { case Previous: return com.clevercloud.biscuit.datalog.Scope.previous(); case Parameter: - throw new Exception("Remaining parameter: "+this.parameter); + //FIXME + return null; + //throw new Exception("Remaining parameter: "+this.parameter); case PublicKey: return com.clevercloud.biscuit.datalog.Scope.publicKey(symbols.insert(this.publicKey)); } + //FIXME + return null; } public static Scope convert_from(com.clevercloud.biscuit.datalog.Scope scope, SymbolTable symbols) { @@ -77,7 +86,43 @@ public static Scope convert_from(com.clevercloud.biscuit.datalog.Scope scope, Sy } //FIXME error management should bubble up here - throw new Exception("panic"); + //throw new Exception("panic"); + return null; + + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + Scope scope = (Scope) o; + + if (kind != scope.kind) return false; + if (!Objects.equals(publicKey, scope.publicKey)) return false; + return Objects.equals(parameter, scope.parameter); + } + @Override + public int hashCode() { + int result = kind.hashCode(); + result = 31 * result + (publicKey != null ? publicKey.hashCode() : 0); + result = 31 * result + (parameter != null ? parameter.hashCode() : 0); + return result; + } + + @Override + public String toString() { + switch (this.kind) { + case Authority: + return "authority"; + case Previous: + return "previous"; + case Parameter: + return "{"+this.parameter+"}"; + case PublicKey: + return this.publicKey.toString(); + } + return null; } } diff --git a/src/main/java/com/clevercloud/biscuit/token/builder/Utils.java b/src/main/java/com/clevercloud/biscuit/token/builder/Utils.java index dab198b0..3d04e6ac 100644 --- a/src/main/java/com/clevercloud/biscuit/token/builder/Utils.java +++ b/src/main/java/com/clevercloud/biscuit/token/builder/Utils.java @@ -57,7 +57,7 @@ public static Term set(HashSet s) { return new Term.Set(s); } - private static final char[] HEX_ARRAY = "0123456789ABCDEF".toCharArray(); + public static final char[] HEX_ARRAY = "0123456789ABCDEF".toCharArray(); public static String byteArrayToHexString(byte[] bytes) { char[] hexChars = new char[bytes.length * 2]; for (int j = 0; j < bytes.length; j++) { @@ -69,6 +69,7 @@ public static String byteArrayToHexString(byte[] bytes) { } public static byte[] hexStringToByteArray(String hex) { + hex = hex.toUpperCase(); int l = hex.length(); byte[] data = new byte[l/2]; for (int i = 0; i < l; i += 2) { diff --git a/src/main/java/com/clevercloud/biscuit/token/builder/parser/Parser.java b/src/main/java/com/clevercloud/biscuit/token/builder/parser/Parser.java index a0a837ed..badda0f6 100644 --- a/src/main/java/com/clevercloud/biscuit/token/builder/parser/Parser.java +++ b/src/main/java/com/clevercloud/biscuit/token/builder/parser/Parser.java @@ -1,9 +1,12 @@ package com.clevercloud.biscuit.token.builder.parser; +import biscuit.format.schema.Schema; +import com.clevercloud.biscuit.crypto.PublicKey; import com.clevercloud.biscuit.token.Policy; import com.clevercloud.biscuit.token.builder.*; import io.vavr.Tuple2; import io.vavr.Tuple3; +import io.vavr.Tuple4; import io.vavr.control.Either; import java.time.OffsetDateTime; @@ -15,8 +18,7 @@ import static com.clevercloud.biscuit.datalog.Check.Kind.All; import static com.clevercloud.biscuit.datalog.Check.Kind.One; -import static com.clevercloud.biscuit.token.builder.Utils.s; -import static com.clevercloud.biscuit.token.builder.Utils.var; +import static com.clevercloud.biscuit.token.builder.Utils.*; public class Parser { public static Either> fact(String s) { @@ -52,18 +54,18 @@ public static Either> rule(String s) { List predicates = new ArrayList(); s = s.substring(2); - Either, List>> bodyRes = rule_body(s); + Either, List, List>> bodyRes = rule_body(s); if (bodyRes.isLeft()) { return Either.left(bodyRes.getLeft()); } - Tuple3, List> body = bodyRes.get(); + Tuple4, List, List> body = bodyRes.get(); if (!body._1.isEmpty()) { return Either.left(new Error(s, "the string was not entirely parsed, remaining: " + body._1)); } - Rule rule = new Rule(head, body._2, body._3); + Rule rule = new Rule(head, body._2, body._3, body._4); Either valid = rule.validate_variables(); if (valid.isLeft()) { return Either.left(new Error(s, valid.getLeft())); @@ -131,15 +133,16 @@ public static Either> policy(String s) { public static Either>> check_body(String s) { List queries = new ArrayList<>(); - Either, List>> bodyRes = rule_body(s); + Either, List, List>> bodyRes = rule_body(s); if (bodyRes.isLeft()) { return Either.left(bodyRes.getLeft()); } - Tuple3, List> body = bodyRes.get(); + Tuple4, List, List> body = bodyRes.get(); s = body._1; - queries.add(new Rule(new Predicate("query", new ArrayList<>()), body._2, body._3)); + //FIXME: parse scopes + queries.add(new Rule(new Predicate("query", new ArrayList<>()), body._2, body._3, body._4)); int i = 0; while (true) { @@ -154,21 +157,21 @@ public static Either>> check_body(String s) { } s = s.substring(2); - Either, List>> bodyRes2 = rule_body(s); + Either, List, List>> bodyRes2 = rule_body(s); if (bodyRes2.isLeft()) { return Either.left(bodyRes2.getLeft()); } - Tuple3, List> body2 = bodyRes2.get(); + Tuple4, List, List> body2 = bodyRes2.get(); s = body2._1; - queries.add(new Rule(new Predicate("query", new ArrayList<>()), body2._2, body2._3)); + queries.add(new Rule(new Predicate("query", new ArrayList<>()), body2._2, body2._3, body2._4)); } return Either.right(new Tuple2<>(s, queries)); } - public static Either, List>> rule_body(String s) { + public static Either, List, List>> rule_body(String s) { List predicates = new ArrayList(); List expressions = new ArrayList<>(); @@ -200,9 +203,15 @@ public static Either, List>> r } } - //FIXME: handle constraints + Either>> res = scopes(s); + if(res.isLeft()) { + return Either.right(new Tuple4<>(s, predicates, expressions, new ArrayList<>())); + } else { + Tuple2> t = res.get(); + return Either.right(new Tuple4<>(t._1, predicates, expressions, t._2)); + + } - return Either.right(new Tuple3<>(s, predicates, expressions)); } public static Either> predicate(String s) { @@ -248,6 +257,82 @@ public static Either> predicate(String s) { return Either.right(new Tuple2(remaining, new Predicate(name, terms))); } + public static Either>> scopes(String s) { + if (!s.startsWith("trusting")) { + return Either.left(new Error(s, "missing scopes prefix")); + } + s = s.substring("trusting".length()); + s = space(s); + + List scopes = new ArrayList(); + + while (true) { + s = space(s); + + Either> res = scope(s); + if (res.isLeft()) { + break; + } + + Tuple2 t = res.get(); + s = t._1; + scopes.add(t._2); + + s = space(s); + + if (s.length() == 0 || s.charAt(0) != ',') { + break; + } else { + s = s.substring(1); + } + } + + return Either.right(new Tuple2(s, scopes)); + } + + public static Either> scope(String s) { + if (s.startsWith("authority")) { + s = s.substring("authority".length()); + return Either.right(new Tuple2(s, Scope.authority())); + } + + if (s.startsWith("previous")) { + s = s.substring("previous".length()); + return Either.right(new Tuple2(s, Scope.previous())); + } + + if (0 < s.length() && s.charAt(0) == '{') { + String remaining = s.substring(1); + Either> res = name(remaining); + if (res.isLeft()) { + return Either.left(new Error(s, "unrecognized parameter")); + } + Tuple2 t = res.get(); + if (0 < s.length() && s.charAt(0) == '}') { + return Either.right(new Tuple2(t._1, Scope.parameter(t._2))); + } else { + return Either.left(new Error(s, "unrecognized parameter end")); + } + } + + Either> res2 = publicKey(s); + if (res2.isLeft()) { + return Either.left(new Error(s, "unrecognized public key")); + } + Tuple2 t = res2.get(); + return Either.right(new Tuple2(t._1, Scope.publicKey(t._2))); + } + + public static Either> publicKey(String s) { + if (!s.startsWith("ed25519/")) { + return Either.left(new Error(s, "unrecognized public key prefix")); + } + + s = s.substring("ed25519/".length()); + Tuple2 t = hex(s); + return Either.right(new Tuple2(t._1, new PublicKey(Schema.PublicKey.Algorithm.Ed25519, t._2))); + } + public static Either> fact_predicate(String s) { Tuple2 tn = take_while(s, (c) -> Character.isAlphabetic(c) || Character.isDigit(c) || c == '_' || c == ':'); String name = tn._1; @@ -304,37 +389,43 @@ public static Either> term(String s) { Either> res5 = variable(s); if (res5.isRight()) { Tuple2 t = res5.get(); - return Either.right(new Tuple2(t._1, t._2)); + return Either.right(new Tuple2(t._1, t._2)); } Either> res2 = string(s); if (res2.isRight()) { Tuple2 t = res2.get(); - return Either.right(new Tuple2(t._1, t._2)); + return Either.right(new Tuple2(t._1, t._2)); } Either> res7 = set(s); if (res7.isRight()) { Tuple2 t = res7.get(); - return Either.right(new Tuple2(t._1, t._2)); + return Either.right(new Tuple2(t._1, t._2)); } Either> res6 = bool(s); if (res6.isRight()) { Tuple2 t = res6.get(); - return Either.right(new Tuple2<>(t._1, t._2)); + return Either.right(new Tuple2(t._1, t._2)); } Either> res4 = date(s); if (res4.isRight()) { Tuple2 t = res4.get(); - return Either.right(new Tuple2(t._1, t._2)); + return Either.right(new Tuple2(t._1, t._2)); } Either> res3 = integer(s); if (res3.isRight()) { Tuple2 t = res3.get(); - return Either.right(new Tuple2(t._1, t._2)); + return Either.right(new Tuple2(t._1, t._2)); + } + + Either> res8 = bytes(s); + if (res8.isRight()) { + Tuple2 t = res8.get(); + return Either.right(new Tuple2(t._1, t._2)); } return Either.left(new Error(s, "unrecognized value")); @@ -348,33 +439,38 @@ public static Either> fact_term(String s) { Either> res2 = string(s); if (res2.isRight()) { Tuple2 t = res2.get(); - return Either.right(new Tuple2(t._1, t._2)); + return Either.right(new Tuple2(t._1, t._2)); } Either> res7 = set(s); if (res7.isRight()) { Tuple2 t = res7.get(); - return Either.right(new Tuple2(t._1, t._2)); + return Either.right(new Tuple2(t._1, t._2)); } Either> res6 = bool(s); if (res6.isRight()) { Tuple2 t = res6.get(); - return Either.right(new Tuple2<>(t._1, t._2)); + return Either.right(new Tuple2(t._1, t._2)); } Either> res4 = date(s); if (res4.isRight()) { Tuple2 t = res4.get(); - return Either.right(new Tuple2(t._1, t._2)); + return Either.right(new Tuple2(t._1, t._2)); } Either> res3 = integer(s); if (res3.isRight()) { Tuple2 t = res3.get(); - return Either.right(new Tuple2(t._1, t._2)); + return Either.right(new Tuple2(t._1, t._2)); } + Either> res8 = bytes(s); + if (res8.isRight()) { + Tuple2 t = res8.get(); + return Either.right(new Tuple2(t._1, t._2)); + } return Either.left(new Error(s, "unrecognized value")); } @@ -522,6 +618,33 @@ public static Either> set(String s) { return Either.right(new Tuple2<>(remaining, new Term.Set(terms))); } + public static Either> bytes(String s) { + if (!s.startsWith("hex:")) { + return Either.left(new Error(s, "not a bytes array")); + } + s = s.substring(4); + Tuple2 t = hex(s); + return Either.right(new Tuple2<>(t._1, new Term.Bytes(t._2))); + } + + public static Tuple2 hex(String s) { + int index = 0; + for (int i = 0; i < s.length(); i++) { + char c = s.charAt(i); + if("0123456789ABCDEFabcdef".indexOf(c) == -1) { + break; + } + + index += 1; + } + + String hex = s.substring(0, index); + byte[] bytes = hexStringToByteArray(hex); + s = s.substring(index); + return new Tuple2<>(s,bytes); + + } + public static Either> expression(String s) { return ExpressionParser.parse(s); } diff --git a/src/test/java/com/clevercloud/biscuit/builder/parser/ParserTest.java b/src/test/java/com/clevercloud/biscuit/builder/parser/ParserTest.java index c132d24c..2eab7a35 100644 --- a/src/test/java/com/clevercloud/biscuit/builder/parser/ParserTest.java +++ b/src/test/java/com/clevercloud/biscuit/builder/parser/ParserTest.java @@ -1,5 +1,7 @@ package com.clevercloud.biscuit.builder.parser; +import biscuit.format.schema.Schema; +import com.clevercloud.biscuit.crypto.PublicKey; import com.clevercloud.biscuit.datalog.SymbolTable; import com.clevercloud.biscuit.datalog.expressions.Op; import com.clevercloud.biscuit.token.builder.*; @@ -130,7 +132,6 @@ void testRuleWithExpressionOrdering() { res); } - @Test void ruleWithFreeExpressionVariables() { Either> res = @@ -143,6 +144,28 @@ void ruleWithFreeExpressionVariables() { res); } + @Test + void testRuleWithScope() { + Either> res = + Parser.rule("valid_date(\"file1\") <- resource(\"file1\") trusting ed25519/6e9e6d5a75cf0c0e87ec1256b4dfed0ca3ba452912d213fcc70f8516583db9db, authority "); + assertEquals(Either.right(new Tuple2<>("", + new Rule( + new Predicate( + "valid_date", + List.of(string("file1") + )), + Arrays.asList( + pred("resource", List.of(string("file1"))) + ), + new ArrayList(), + Arrays.asList( + Scope.publicKey(new PublicKey(Schema.PublicKey.Algorithm.Ed25519, "6e9e6d5a75cf0c0e87ec1256b4dfed0ca3ba452912d213fcc70f8516583db9db")), + Scope.authority() + ) + ))), + res); + } + @Test void testCheck() { Either> res = From 7fc59c0f5e12edd15aec188935a1e84b95cba990 Mon Sep 17 00:00:00 2001 From: Geoffroy Couprie Date: Tue, 2 Jan 2024 16:16:03 +0100 Subject: [PATCH 3/6] integrate scopes in execution --- .../biscuit/datalog/AuthorizedWorld.java | 6 +- .../biscuit/datalog/Combinator.java | 201 ++++++++------ .../clevercloud/biscuit/datalog/FactSet.java | 109 ++++++++ .../biscuit/datalog/MatchedVariables.java | 7 +- .../clevercloud/biscuit/datalog/Origin.java | 67 +++++ .../com/clevercloud/biscuit/datalog/Rule.java | 194 +++++++------ .../clevercloud/biscuit/datalog/RuleSet.java | 45 +++ .../biscuit/datalog/SymbolTable.java | 8 +- .../biscuit/datalog/TemporarySymbolTable.java | 46 ++++ .../biscuit/datalog/TrustedOrigins.java | 80 ++++++ .../clevercloud/biscuit/datalog/World.java | 157 ++++++----- .../datalog/expressions/Expression.java | 11 +- .../biscuit/datalog/expressions/Op.java | 13 +- .../com/clevercloud/biscuit/error/Error.java | 30 ++ .../clevercloud/biscuit/token/Authorizer.java | 168 +++++++++--- .../biscuit/token/UnverifiedBiscuit.java | 113 -------- .../biscuit/builder/parser/ParserTest.java | 5 +- .../biscuit/datalog/ExpressionTest.java | 8 +- .../biscuit/datalog/WorldTest.java | 258 ++++++++++-------- .../biscuit/token/BiscuitTest.java | 81 +++--- .../biscuit/token/ExampleTest.java | 6 +- .../biscuit/token/SamplesJsonV2Test.java | 4 +- .../biscuit/token/SamplesV2Test.java | 21 +- .../biscuit/token/UnverifiedBiscuitTest.java | 31 +-- 24 files changed, 1066 insertions(+), 603 deletions(-) create mode 100644 src/main/java/com/clevercloud/biscuit/datalog/FactSet.java create mode 100644 src/main/java/com/clevercloud/biscuit/datalog/Origin.java create mode 100644 src/main/java/com/clevercloud/biscuit/datalog/RuleSet.java create mode 100644 src/main/java/com/clevercloud/biscuit/datalog/TemporarySymbolTable.java create mode 100644 src/main/java/com/clevercloud/biscuit/datalog/TrustedOrigins.java diff --git a/src/main/java/com/clevercloud/biscuit/datalog/AuthorizedWorld.java b/src/main/java/com/clevercloud/biscuit/datalog/AuthorizedWorld.java index b0b3266d..0058fb0a 100644 --- a/src/main/java/com/clevercloud/biscuit/datalog/AuthorizedWorld.java +++ b/src/main/java/com/clevercloud/biscuit/datalog/AuthorizedWorld.java @@ -4,11 +4,11 @@ public class AuthorizedWorld extends World { - public AuthorizedWorld(Set facts) { + public AuthorizedWorld(FactSet facts) { super(facts); } - public final Set queryAll(final Rule rule, SymbolTable symbols) { + /*public final Set queryAll(final Rule rule, SymbolTable symbols) { return this.query_rule(rule, symbols); - } + }*/ } diff --git a/src/main/java/com/clevercloud/biscuit/datalog/Combinator.java b/src/main/java/com/clevercloud/biscuit/datalog/Combinator.java index cc07de51..3f11261a 100644 --- a/src/main/java/com/clevercloud/biscuit/datalog/Combinator.java +++ b/src/main/java/com/clevercloud/biscuit/datalog/Combinator.java @@ -1,112 +1,153 @@ package com.clevercloud.biscuit.datalog; -import com.clevercloud.biscuit.datalog.expressions.Expression; +import io.vavr.Tuple2; import io.vavr.control.Option; import java.io.Serializable; import java.util.*; - -public final class Combinator implements Serializable { - private final MatchedVariables variables; - private final List next_predicates; - private final Set all_facts; - private final Predicate pred; - private final Iterator fit; - private Combinator current_it; +import java.util.function.Supplier; +import java.util.stream.Stream; + +public final class Combinator implements Serializable, Iterator>> { + private MatchedVariables variables; + private final Supplier>> allFacts; + private final List predicates; + private final Iterator> currentFacts; + private Combinator currentIt; private final SymbolTable symbols; - public Option next() { - while(true) { - if (this.current_it != null) { - Option next_vars_opt = this.current_it.next(); - // the iterator is empty, try with the next fact - if(next_vars_opt.isEmpty()) { - this.current_it = null; - continue; - } - return next_vars_opt; - } + private Origin currentOrigin; - // we iterate over the facts that match the current predicate - if (this.fit.hasNext()) { - final Fact current_fact = this.fit.next(); + private Option>> nextElement; - // create a new MatchedVariables in which we fix variables we could unify from our first predicate and the current fact - MatchedVariables vars = this.variables.clone(); - boolean match_ids = true; + @Override + public boolean hasNext() { + if(this.nextElement != null && this.nextElement.isDefined()) { + return true; + } + this.nextElement = getNext(); + return this.nextElement.isDefined(); + } + + @Override + public Tuple2> next() { + if(this.nextElement == null || !this.nextElement.isDefined()) { + this.nextElement = getNext(); + } + if(this.nextElement == null || !this.nextElement.isDefined()) { + throw new NoSuchElementException(); + } else { + Tuple2> t = this.nextElement.get(); + this.nextElement = Option.none(); + return t; + } + } - // we know the fact matches the predicate's format so they have the same number of terms - // fill the MatchedVariables before creating the next combinator - for (int i = 0; i < pred.terms().size(); ++i) { - final Term id = pred.terms().get(i); - if (id instanceof Term.Variable) { - final long key = ((Term.Variable) id).value(); - final Term value = current_fact.predicate().terms().get(i); + public Option>> getNext() { + if (this.predicates.isEmpty()) { + final Option> v_opt = this.variables.complete(); + if(v_opt.isEmpty()) { + return Option.none(); + } else { + Map variables = v_opt.get(); + // if there were no predicates, + // we should return a value, but only once. To prevent further + // successful calls, we create a set of variables that cannot + // possibly be completed, so the next call will fail + Set set = new HashSet(); + set.add((long)0); + + this.variables = new MatchedVariables(set); + return Option.some(new Tuple2(new Origin(), variables)); + } + } - if (!vars.insert(key, value)) { - match_ids = false; + while(true) { + if (this.currentIt == null) { + Predicate predicate = this.predicates.get(0); + + while (true) { + // we iterate over the facts that match the current predicate + if (this.currentFacts.hasNext()) { + final Tuple2 t = this.currentFacts.next(); + Origin currentOrigin = t._1; + Fact fact = t._2; + + // create a new MatchedVariables in which we fix variables we could unify from our first predicate and the current fact + MatchedVariables vars = this.variables.clone(); + boolean matchTerms = true; + + // we know the fact matches the predicate's format so they have the same number of terms + // fill the MatchedVariables before creating the next combinator + for (int i = 0; i < predicate.terms().size(); ++i) { + final Term term = predicate.terms().get(i); + if (term instanceof Term.Variable) { + final long key = ((Term.Variable) term).value(); + final Term value = fact.predicate().terms().get(i); + + if (!vars.insert(key, value)) { + matchTerms = false; + } + if (!matchTerms) { + break; + } + } } - if (!match_ids) { - break; + + // the fact did not match the predicate, try the next one + if (!matchTerms) { + continue; } - } - } - if (!match_ids) { - continue; - } + // there are no more predicates to check + if (this.predicates.size() == 1) { + final Option> v_opt = vars.complete(); + if (v_opt.isEmpty()) { + continue; + } else { + return Option.some(new Tuple2(currentOrigin, v_opt.get())); + } + } else { + this.currentOrigin = currentOrigin; + // we found a matching fact, we create a new combinator over the rest of the predicates + // no need to copy all the expressions at all levels + this.currentIt = new Combinator(vars, predicates.subList(1, predicates.size()), this.allFacts, this.symbols); + } + break; - // there are no more predicates to check - if (next_predicates.isEmpty()) { - final Option> v_opt = vars.complete(); - if(v_opt.isEmpty()) { - continue; } else { - return Option.some(vars); + return Option.none(); } - } else { - // we found a matching fact, we create a new combinator over the rest of the predicates - // no need to copy all of the expressions at all levels - this.current_it = new Combinator(vars, next_predicates, this.all_facts, this.symbols); } - } else { - break; } - } - - return Option.none(); - } - - public List> combine() { - final List> variables = new ArrayList<>(); - while(true) { - Option res = this.next(); - - if(res.isEmpty()) { - return variables; + if (this.currentIt == null) { + return Option.none(); } - Option> vars = res.get().complete(); - if(vars.isDefined()) { - variables.add(vars.get()); + Option>> opt = this.currentIt.getNext(); + + if (opt.isDefined()) { + Tuple2> t = opt.get(); + t._1.union(currentOrigin); + return Option.some(t); + } else { + currentOrigin = null; + currentIt = null; } } } + public Combinator(final MatchedVariables variables, final List predicates, - final Set all_facts, final SymbolTable symbols) { + Supplier>> all_facts, final SymbolTable symbols) { this.variables = variables; - this.all_facts = all_facts; - this.current_it = null; - this.pred = predicates.get(0); - this.fit = all_facts.stream().filter((fact) -> fact.match_predicate(predicates.get(0))).iterator(); + this.allFacts = all_facts; + this.currentIt = null; + this.predicates = predicates; + this.currentFacts = all_facts.get().filter((tuple) -> tuple._2.match_predicate(predicates.get(0))).iterator(); this.symbols = symbols; - - final List next_predicates = new ArrayList<>(); - for (int i = 1; i < predicates.size(); ++i) { - next_predicates.add(predicates.get(i)); - } - this.next_predicates = next_predicates; + this.currentOrigin = null; + this.nextElement = null; } } diff --git a/src/main/java/com/clevercloud/biscuit/datalog/FactSet.java b/src/main/java/com/clevercloud/biscuit/datalog/FactSet.java new file mode 100644 index 00000000..07e41e40 --- /dev/null +++ b/src/main/java/com/clevercloud/biscuit/datalog/FactSet.java @@ -0,0 +1,109 @@ +package com.clevercloud.biscuit.datalog; + +import io.vavr.Tuple2; + +import java.util.*; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +public class FactSet { + private final HashMap> facts; + + public FactSet() { + facts = new HashMap(); + } + + public FactSet(Origin o, HashSet factSet) { + facts = new HashMap(); + facts.put(o, factSet); + } + + public void add(Origin origin, Fact fact) { + if(!facts.containsKey(origin)) { + facts.put(origin, new HashSet()); + } + facts.get(origin).add(fact); + } + + public int size() { + int size = 0; + for(HashSet h: facts.values()) { + size += h.size(); + } + + return size; + } + + public FactSet clone() { + FactSet newFacts = new FactSet(); + + for(Map.Entry> entry: this.facts.entrySet()) { + HashSet h = new HashSet<>(); + h.addAll(entry.getValue()); + newFacts.facts.put(entry.getKey(), h); + } + + return newFacts; + } + + public void merge(FactSet other) { + for(Map.Entry> entry: other.facts.entrySet()) { + if(!facts.containsKey(entry.getKey())) { + facts.put(entry.getKey(), entry.getValue()); + } else { + facts.get(entry.getKey()).addAll(entry.getValue()); + } + } + } + public Stream iterator(TrustedOrigins blockIds) { + return facts.entrySet() + .stream() + .filter(entry -> { + Origin o = entry.getKey(); + return blockIds.contains(o); + }) + .flatMap(entry -> entry.getValue() + .stream() + .map(fact -> new Tuple2(entry.getKey(), fact))); + } + + public Stream iterator() { + return facts.entrySet() + .stream() + .flatMap(entry -> entry.getValue() + .stream() + .map(fact -> fact)); + } + + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + FactSet factSet = (FactSet) o; + + return facts.equals(factSet.facts); + } + + @Override + public int hashCode() { + return facts.hashCode(); + } + + @Override + public String toString() { + String res = "FactSet {"; + for(Map.Entry> entry: this.facts.entrySet()) { + res += "\n\t"+entry.getKey()+"["; + for(Fact fact: entry.getValue()) { + res += "\n\t\t"+fact; + } + res +="\n]"; + } + res += "\n}"; + + return res; + } +} + diff --git a/src/main/java/com/clevercloud/biscuit/datalog/MatchedVariables.java b/src/main/java/com/clevercloud/biscuit/datalog/MatchedVariables.java index 269aad81..9e90872e 100644 --- a/src/main/java/com/clevercloud/biscuit/datalog/MatchedVariables.java +++ b/src/main/java/com/clevercloud/biscuit/datalog/MatchedVariables.java @@ -23,6 +23,10 @@ public boolean insert(final long key, final Term value) { } } + public Optional get(final long key) { + return this.variables.get(key); + } + public boolean is_complete() { return this.variables.values().stream().allMatch((v) -> v.isPresent()); } @@ -61,8 +65,9 @@ public Option> check_expressions(List expressions, S if (vars.isDefined()) { Map variables = vars.get(); + for(Expression e: expressions) { - Option res = e.evaluate(variables, symbols); + Option res = e.evaluate(variables, new TemporarySymbolTable(symbols)); if(res.isEmpty()) { return Option.none(); diff --git a/src/main/java/com/clevercloud/biscuit/datalog/Origin.java b/src/main/java/com/clevercloud/biscuit/datalog/Origin.java new file mode 100644 index 00000000..c5279b9e --- /dev/null +++ b/src/main/java/com/clevercloud/biscuit/datalog/Origin.java @@ -0,0 +1,67 @@ +package com.clevercloud.biscuit.datalog; + +import java.util.*; + +public class Origin { + public HashSet inner; + + public Origin() { + inner = new HashSet<>(); + } + + private Origin(HashSet inner) { + this.inner = inner; + } + + public Origin(Long i) { + this.inner = new HashSet<>(); + this.inner.add(i); + } + + public Origin(int i) { + this.inner = new HashSet<>(); + this.inner.add((long)i); + } + + public static Origin authorizer() { + return new Origin(Long.MAX_VALUE); + } + public void add(int i) { + inner.add(new Long(i)); + } + public void add(long i) { + inner.add(new Long(i)); + } + + public void union(Origin other) { + this.inner.addAll(other.inner); + } + + public Origin clone() { + final HashSet newInner = new HashSet(); + newInner.addAll(this.inner); + return new Origin(newInner); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + Origin origin = (Origin) o; + + return Objects.equals(inner, origin.inner); + } + + @Override + public int hashCode() { + return inner != null ? inner.hashCode() : 0; + } + + @Override + public String toString() { + return "Origin{" + + "inner=" + inner + + '}'; + } +} diff --git a/src/main/java/com/clevercloud/biscuit/datalog/Rule.java b/src/main/java/com/clevercloud/biscuit/datalog/Rule.java index 7aadb3ab..ba367c66 100644 --- a/src/main/java/com/clevercloud/biscuit/datalog/Rule.java +++ b/src/main/java/com/clevercloud/biscuit/datalog/Rule.java @@ -3,12 +3,18 @@ import biscuit.format.schema.Schema; import com.clevercloud.biscuit.datalog.expressions.Expression; import com.clevercloud.biscuit.error.Error; +import com.clevercloud.biscuit.error.Error.InvalidType; +import io.vavr.Tuple2; +import io.vavr.Tuple3; import io.vavr.control.Either; import io.vavr.control.Option; import java.io.Serializable; import java.util.*; +import java.util.function.Supplier; import java.util.stream.Collectors; +import java.util.stream.Stream; +import java.util.stream.StreamSupport; import static io.vavr.API.Left; import static io.vavr.API.Right; @@ -35,124 +41,136 @@ public List scopes() { return scopes; } - public void apply(final Set facts, final Set new_facts, SymbolTable symbols) { - final Set variables_set = new HashSet<>(); - for (final Predicate pred : this.body) { - variables_set.addAll(pred.terms().stream().filter((id) -> id instanceof Term.Variable).map((id) -> ((Term.Variable) id).value()).collect(Collectors.toSet())); - } - final MatchedVariables variables = new MatchedVariables(variables_set); - if(this.body.isEmpty()) { - final Option> complete_vars_opt = variables.check_expressions(this.expressions, symbols); - if(complete_vars_opt.isDefined()) { - final Map h = complete_vars_opt.get(); - - final Predicate p = this.head.clone(); - final ListIterator idit = p.ids_iterator(); - while (idit.hasNext()) { - //FIXME: variables that appear in the head should appear in the body and constraints as well - final Term id = idit.next(); - if (id instanceof Term.Variable) { - final Term value = h.get(((Term.Variable) id).value()); - idit.set(value); + public Stream>> apply( + final Supplier>> factsSupplier, Long ruleOrigin, SymbolTable symbols) { + MatchedVariables variables = variablesSet(); + + Combinator combinator = new Combinator(variables, this.body, factsSupplier, symbols); + Spliterator>> splitItr = Spliterators + .spliteratorUnknownSize(combinator, Spliterator.ORDERED); + Stream>> stream = StreamSupport.stream(splitItr, false); + + //somehow we have inference errors when writing this as a lambda + return stream.map(t -> { + Origin origin = t._1; + Map generatedVariables = t._2; + TemporarySymbolTable temporarySymbols = new TemporarySymbolTable(symbols); + for (Expression e : this.expressions) { + Option res = e.evaluate(generatedVariables, temporarySymbols); + if (res.isDefined()) { + Term term = res.get(); + if (term instanceof Term.Bool) { + Term.Bool b = (Term.Bool) term; + if (!b.value()) { + return Either.right(new Tuple3(origin, generatedVariables, false)); + } + // continue evaluating if true + } else { + return Either.left(new InvalidType()); } } - - new_facts.add(new Fact(p)); } - } - - Combinator c = new Combinator(variables, this.body, facts, symbols); - while (true) { - final Option vars_opt = c.next(); - if(!vars_opt.isDefined()) { - break; + return Either.right(new Tuple3(origin, generatedVariables, true)); + /* + .filter((java.util.function.Predicate>) new java.util.function.Predicate, Boolean>>>() { + //somehow we have inference errors when writing this as a lambda + @Override + public boolean test(Either, Boolean>> res) { + return res.isRight() & res.get()._3.booleanValue(); } - MatchedVariables vars = vars_opt.get(); - final Option> complete_vars_opt = vars.check_expressions(this.expressions, symbols); - if(complete_vars_opt.isDefined()) { - final Map h = complete_vars_opt.get(); - final Predicate p = this.head.clone(); - final ListIterator idit = p.ids_iterator(); - boolean unbound_variable = false; - while (idit.hasNext()) { - final Term id = idit.next(); - if (id instanceof Term.Variable) { - final Term value = h.get(((Term.Variable) id).value()); - idit.set(value); - - // variables that appear in the head or expressions should appear in the body as well - if (value == null) { - unbound_variable = true; - } + })*/ + }).filter((java.util.function.Predicate>) + res -> res.isRight() & ((Tuple3, Boolean>)res.get())._3.booleanValue()).map(res -> { + Tuple3, Boolean> t = (Tuple3, Boolean>) res.get(); + Origin origin = t._1; + Map generatedVariables = t._2; + + Predicate p = this.head.clone(); + for (int index = 0; index < p.terms().size(); index++) { + if (p.terms().get(index) instanceof Term.Variable) { + Term.Variable var = (Term.Variable) p.terms().get(index); + if(!generatedVariables.containsKey(var.value())) { + //throw new Error("variables that appear in the head should appear in the body as well"); + return Either.left(new Error.InternalError()); } - } - - if (!unbound_variable) { - new_facts.add(new Fact(p)); + p.terms().set(index, generatedVariables.get(var.value())); } } - } + + origin.add(ruleOrigin); + return Either.right(new Tuple2(origin, new Fact(p))); + }); } - // do not produce new facts, only find one matching set of facts - public boolean find_match(final Set facts, SymbolTable symbols) { + private MatchedVariables variablesSet() { final Set variables_set = new HashSet<>(); + for (final Predicate pred : this.body) { variables_set.addAll(pred.terms().stream().filter((id) -> id instanceof Term.Variable).map((id) -> ((Term.Variable) id).value()).collect(Collectors.toSet())); } - final MatchedVariables variables = new MatchedVariables(variables_set); + return new MatchedVariables(variables_set); + } + + // do not produce new facts, only find one matching set of facts + public boolean find_match(final FactSet facts, Long origin, TrustedOrigins scope, SymbolTable symbols) throws Error { + MatchedVariables variables = variablesSet(); if(this.body.isEmpty()) { return variables.check_expressions(this.expressions, symbols).isDefined(); } - Combinator c = new Combinator(variables, this.body, facts, symbols); + Supplier>> factsSupplier = () -> facts.iterator(scope); + Stream>> stream = this.apply(factsSupplier, origin, symbols); - while(true) { - Option res = c.next(); - if (res.isDefined()) { - MatchedVariables vars = res.get(); - if (vars.check_expressions(this.expressions, symbols).isDefined()) { - return true; - } - } else { - return false; - } + Iterator>> it = stream.iterator(); + + if(!it.hasNext()) { + return false; + } + + Either> next = it.next(); + if(next.isRight()) { + return true; + } else { + throw next.getLeft(); } } // verifies that the expressions return true for every matching set of facts - public boolean check_match_all(final Set facts, SymbolTable symbols) { - final Set variables_set = new HashSet<>(); - for (final Predicate pred : this.body) { - variables_set.addAll(pred.terms().stream().filter((id) -> id instanceof Term.Variable).map((id) -> ((Term.Variable) id).value()).collect(Collectors.toSet())); - } - final MatchedVariables variables = new MatchedVariables(variables_set); + public boolean check_match_all(final FactSet facts, TrustedOrigins scope, SymbolTable symbols) throws InvalidType { + MatchedVariables variables = variablesSet(); if(this.body.isEmpty()) { return variables.check_expressions(this.expressions, symbols).isDefined(); } - Combinator c = new Combinator(variables, this.body, facts, symbols); - + Supplier>> factsSupplier = () -> facts.iterator(scope); + Combinator combinator = new Combinator(variables, this.body, factsSupplier, symbols); boolean found = false; - while(true) { - Option res = c.next(); - if (res.isDefined()) { - // we need at least one match - found = true; - - MatchedVariables vars = res.get(); - - // the expression must succeed for all the matching sets of facts - if (!vars.check_expressions(this.expressions, symbols).isDefined()) { - return false; - } - } else { - return found; - } - } + for (Combinator it = combinator; it.hasNext(); ) { + Tuple2> t = it.next(); + Map generatedVariables = t._2; + found = true; + + TemporarySymbolTable temporarySymbols = new TemporarySymbolTable(symbols); + for (Expression e : this.expressions) { + Option res = e.evaluate(generatedVariables, temporarySymbols); + if (res.isDefined()) { + Term term = res.get(); + if (term instanceof Term.Bool) { + Term.Bool b = (Term.Bool) term; + if (!b.value()) { + return false; + } + // continue evaluating if true + } else { + throw new InvalidType(); + } + } + } + } + return found; } public Rule(final Predicate head, final List body, final List expressions) { diff --git a/src/main/java/com/clevercloud/biscuit/datalog/RuleSet.java b/src/main/java/com/clevercloud/biscuit/datalog/RuleSet.java new file mode 100644 index 00000000..e4bd56f1 --- /dev/null +++ b/src/main/java/com/clevercloud/biscuit/datalog/RuleSet.java @@ -0,0 +1,45 @@ +package com.clevercloud.biscuit.datalog; + +import io.vavr.Tuple2; + +import java.util.*; +import java.util.stream.Stream; + +public class RuleSet { + public final HashMap>> rules; + + public RuleSet() { + rules = new HashMap(); + } + + public void add(Long origin, TrustedOrigins scope, Rule rule) { + if(!rules.containsKey(scope)) { + rules.put(scope, Arrays.asList(new Tuple2(origin, rule))); + } else { + rules.get(scope).add(new Tuple2(origin, rule)); + } + } + + public RuleSet clone() { + RuleSet newRules = new RuleSet(); + + for(Map.Entry>> entry: this.rules.entrySet()) { + List> l = new ArrayList<>(); + l.addAll(entry.getValue()); + newRules.rules.put(entry.getKey(), l); + } + + return newRules; + } + + public Stream iterator() { + return rules.entrySet() + .stream() + .flatMap(entry -> entry.getValue() + .stream() + .map(t -> t._2)); + } + public void clear() { + rules.clear(); + } +} diff --git a/src/main/java/com/clevercloud/biscuit/datalog/SymbolTable.java b/src/main/java/com/clevercloud/biscuit/datalog/SymbolTable.java index 2eda1e79..480c8d5d 100644 --- a/src/main/java/com/clevercloud/biscuit/datalog/SymbolTable.java +++ b/src/main/java/com/clevercloud/biscuit/datalog/SymbolTable.java @@ -78,6 +78,10 @@ public long insert(final String symbol) { } } + public int currentOffset() { + return this.symbols.size(); + } + public long insert(final PublicKey publicKey) { int index = this.publicKeys.indexOf(publicKey); if (index == -1) { @@ -225,8 +229,8 @@ public String print_check(final Check c) { } public String print_world(final World w) { - final List facts = w.facts().stream().map((f) -> this.print_fact(f)).collect(Collectors.toList()); - final List rules = w.rules().stream().map((r) -> this.print_rule(r)).collect(Collectors.toList()); + final List facts = w.facts().iterator().map((f) -> this.print_fact(f)).collect(Collectors.toList()); + final List rules = w.rules().iterator().map((r) -> this.print_rule(r)).collect(Collectors.toList()); StringBuilder b = new StringBuilder(); b.append("World {\n\tfacts: [\n\t\t"); diff --git a/src/main/java/com/clevercloud/biscuit/datalog/TemporarySymbolTable.java b/src/main/java/com/clevercloud/biscuit/datalog/TemporarySymbolTable.java new file mode 100644 index 00000000..d9055085 --- /dev/null +++ b/src/main/java/com/clevercloud/biscuit/datalog/TemporarySymbolTable.java @@ -0,0 +1,46 @@ +package com.clevercloud.biscuit.datalog; + +import io.vavr.control.Option; + +import java.util.ArrayList; +import java.util.List; + +import static com.clevercloud.biscuit.datalog.SymbolTable.DEFAULT_SYMBOLS_OFFSET; + +public class TemporarySymbolTable { + SymbolTable base; + int offset; + List symbols; + + public TemporarySymbolTable(SymbolTable base) { + this.offset = DEFAULT_SYMBOLS_OFFSET + base.currentOffset(); + this.base = base; + this.symbols = new ArrayList<>(); + } + + public Option get_s(int i) { + if(i >= this.offset) { + if(i - this.offset < this.symbols.size()) { + return Option.some(this.symbols.get(i - this.offset)); + } else { + return Option.none(); + } + } else { + return this.base.get_s(i); + } + } + + public long insert(final String symbol) { + Option opt = this.base.get(symbol); + if(opt.isDefined()) { + return opt.get().longValue(); + } + + int index = this.symbols.indexOf(symbol); + if(index != -1) { + return (long) (this.offset + index); + } + this.symbols.add(symbol); + return this.symbols.size() - 1 + this.offset; + } +} diff --git a/src/main/java/com/clevercloud/biscuit/datalog/TrustedOrigins.java b/src/main/java/com/clevercloud/biscuit/datalog/TrustedOrigins.java new file mode 100644 index 00000000..6597a24e --- /dev/null +++ b/src/main/java/com/clevercloud/biscuit/datalog/TrustedOrigins.java @@ -0,0 +1,80 @@ +package com.clevercloud.biscuit.datalog; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; + +public class TrustedOrigins { + private Origin inner; + + public TrustedOrigins(int ... origins) { + Origin origin = new Origin(); + for(int i: origins) { + origin.add(i); + } + inner = origin; + } + + private TrustedOrigins() { + inner = new Origin(); + } + + private TrustedOrigins(Origin inner) { + if(inner == null) { + throw new RuntimeException(); + } + this.inner = inner; + } + + public TrustedOrigins clone() { + return new TrustedOrigins(this.inner.clone()); + } + public static TrustedOrigins defaultOrigins() { + TrustedOrigins origins = new TrustedOrigins(); + origins.inner.add(0); + origins.inner.add(Long.MAX_VALUE); + return origins; + } + + public static TrustedOrigins fromScopes(List ruleScopes, + TrustedOrigins defaultOrigins, + long currentBlock, + HashMap> publicKeyToBlockId) { + if (ruleScopes.isEmpty()) { + TrustedOrigins origins = defaultOrigins.clone(); + origins.inner.add(currentBlock); + origins.inner.add(Long.MAX_VALUE); + return origins; + } + + TrustedOrigins origins = new TrustedOrigins(); + origins.inner.add(currentBlock); + origins.inner.add(Long.MAX_VALUE); + + for(Scope scope: ruleScopes) { + switch (scope.kind()) { + case Authority: + origins.inner.add(0); + break; + case Previous: + if(currentBlock != Long.MAX_VALUE) { + for(long i = 0; i < currentBlock+1; i++) { + origins.inner.add(i); + } + } + break; + case PublicKey: + List blockIds = publicKeyToBlockId.get(scope.publicKey()); + if(blockIds != null) { + origins.inner.inner.addAll(blockIds); + } + } + } + + return origins; + } + + public boolean contains(Origin factOrigin) { + return this.inner.inner.containsAll(factOrigin.inner); + } +} diff --git a/src/main/java/com/clevercloud/biscuit/datalog/World.java b/src/main/java/com/clevercloud/biscuit/datalog/World.java index 13dd23f1..357b6d85 100644 --- a/src/main/java/com/clevercloud/biscuit/datalog/World.java +++ b/src/main/java/com/clevercloud/biscuit/datalog/World.java @@ -1,56 +1,73 @@ package com.clevercloud.biscuit.datalog; +import com.clevercloud.biscuit.datalog.expressions.Expression; import com.clevercloud.biscuit.error.Error; +import io.vavr.Tuple2; +import io.vavr.control.Either; import java.io.Serializable; import java.time.Instant; -import java.util.ArrayList; -import java.util.HashSet; -import java.util.List; -import java.util.Set; +import java.util.*; +import java.util.function.Supplier; import java.util.stream.Collectors; +import java.util.stream.Stream; public class World implements Serializable { - private final Set facts; - private final List rules; + private final FactSet facts; + private final RuleSet rules; - public void add_fact(final Fact fact) { - this.facts.add(fact); + public void add_fact(final Origin origin, final Fact fact) { + this.facts.add(origin, fact); } - public void add_facts(final Set facts) { + /*public void add_facts(final Set facts) { this.facts.addAll(facts); - } + }*/ - public void add_rule(final Rule rule) { - this.rules.add(rule); + public void add_rule(Long origin, TrustedOrigins scope, Rule rule) { + this.rules.add(origin, scope, rule); } public void clearRules() { this.rules.clear(); } - public void run(final SymbolTable symbols) throws Error.TooManyFacts, Error.TooManyIterations, Error.Timeout { + public void run(final SymbolTable symbols) throws Error { this.run(new RunLimits(), symbols); } - public void run(RunLimits limits, final SymbolTable symbols) throws Error.TooManyFacts, Error.TooManyIterations, Error.Timeout { + public void run(RunLimits limits, final SymbolTable symbols) throws Error { int iterations = 0; Instant limit = Instant.now().plus(limits.maxTime); while(true) { - final Set new_facts = new HashSet<>(); - - for (final Rule rule : this.rules) { - rule.apply(this.facts, new_facts, symbols); - - if(Instant.now().compareTo(limit) >= 0) { - throw new Error.Timeout(); + final FactSet newFacts = new FactSet(); + + for(Map.Entry>> entry: this.rules.rules.entrySet()) { + for(Tuple2 t: entry.getValue()) { + Supplier>> factsSupplier = () -> this.facts.iterator(entry.getKey()); + + Stream>> stream = t._2.apply(factsSupplier, t._1, symbols); + for (Iterator>> it = stream.iterator(); it.hasNext(); ) { + Either> res = it.next(); + if(Instant.now().compareTo(limit) >= 0) { + throw new Error.Timeout(); + } + + if(res.isRight()) { + Tuple2 t2 = res.get(); + newFacts.add(t2._1, t2._2); + } else { + Error e = res.getLeft(); + throw e; + } + } } } final int len = this.facts.size(); - this.facts.addAll(new_facts); + this.facts.merge(newFacts); + if (this.facts.size() == len) { return ; } @@ -66,13 +83,13 @@ public void run(RunLimits limits, final SymbolTable symbols) throws Error.TooMan } } - public final Set facts() { + public final FactSet facts() { return this.facts; } - public List rules() { return this.rules; } + public RuleSet rules() { return this.rules; } - public final Set query(final Predicate pred) { + /*public final Set query(final Predicate pred) { return this.facts.stream().filter((f) -> { if (f.predicate().name() != pred.name()) { return false; @@ -86,78 +103,86 @@ public final Set query(final Predicate pred) { if (!fid.equals(pid)) { return false; } - /* FIXME: is it still necessary? - } else if (!(fid instanceof Term.Symbol && pid instanceof Term.Variable)) { - return false;*/ + // FIXME: is it still necessary? + //} else if (!(fid instanceof Term.Symbol && pid instanceof Term.Variable)) { + // return false; } } return true; }).collect(Collectors.toSet()); - } + }*/ + + public final FactSet query_rule(final Rule rule, Long origin, TrustedOrigins scope, SymbolTable symbols) throws Error { + final FactSet newFacts = new FactSet(); + + Supplier>> factsSupplier = () -> this.facts.iterator(scope); + + Stream>> stream = rule.apply(factsSupplier, origin, symbols); + for (Iterator>> it = stream.iterator(); it.hasNext(); ) { + Either> res = it.next(); + + if (res.isRight()) { + Tuple2 t2 = res.get(); + newFacts.add(t2._1, t2._2); + } else { + Error e = res.getLeft(); + throw e; + } + } - public final Set query_rule(final Rule rule, SymbolTable symbols) { - final Set new_facts = new HashSet<>(); - rule.apply(this.facts, new_facts, symbols); - return new_facts; + return newFacts; } - public final boolean query_match(final Rule rule, SymbolTable symbols) { - return rule.find_match(this.facts, symbols); + public final boolean query_match(final Rule rule, Long origin, TrustedOrigins scope, SymbolTable symbols) throws Error { + return rule.find_match(this.facts, origin, scope, symbols); } - public final boolean query_match_all(final Rule rule, SymbolTable symbols) { - return rule.check_match_all(this.facts, symbols); + public final boolean query_match_all(final Rule rule, TrustedOrigins scope, SymbolTable symbols) throws Error.InvalidType { + return rule.check_match_all(this.facts, scope, symbols); } public World() { - this.facts = new HashSet<>(); - this.rules = new ArrayList<>(); + this.facts = new FactSet(); + this.rules = new RuleSet(); } - public World(Set facts) { - this.facts = new HashSet<>(); - this.facts.addAll(facts); - this.rules = new ArrayList<>(); + public World(FactSet facts) { + this.facts = facts.clone(); + this.rules = new RuleSet(); } - public World(Set facts, List rules) { - this.facts = facts; - this.rules = rules; + public World(FactSet facts, RuleSet rules) { + this.facts = facts.clone(); + this.rules = rules.clone(); } - public World(Set facts, List rules, List checks) { + /*public World(Set facts, List rules, List checks) { this.facts = facts; this.rules = rules; - } + }*/ public World(World w) { - this.facts = new HashSet<>(); - for(Fact fact: w.facts) { - this.facts.add(fact); - } - - this.rules = new ArrayList<>(); - for(Rule rule: w.rules) { - this.rules.add(rule); - } - + this.facts = w.facts.clone(); + this.rules = w.rules.clone(); } public String print(SymbolTable symbol_table) { StringBuilder s = new StringBuilder(); s.append("World {\n\t\tfacts: ["); - for(Fact f: this.facts) { - s.append("\n\t\t\t"); - s.append(symbol_table.print_fact(f)); - } + for (Iterator it = this.facts.iterator().iterator(); it.hasNext(); ) { + Fact f = it.next(); + s.append("\n\t\t\t"); + s.append(symbol_table.print_fact(f)); + } s.append("\n\t\t]\n\t\trules: ["); - for(Rule r: this.rules) { - s.append("\n\t\t\t"); - s.append(symbol_table.print_rule(r)); - } + for (Iterator it = this.rules.iterator().iterator(); it.hasNext(); ) { + Rule r = it.next(); + s.append("\n\t\t\t"); + s.append(symbol_table.print_rule(r)); + } s.append("\n\t\t]\n\t}"); diff --git a/src/main/java/com/clevercloud/biscuit/datalog/expressions/Expression.java b/src/main/java/com/clevercloud/biscuit/datalog/expressions/Expression.java index f9d749ab..7a6098d3 100644 --- a/src/main/java/com/clevercloud/biscuit/datalog/expressions/Expression.java +++ b/src/main/java/com/clevercloud/biscuit/datalog/expressions/Expression.java @@ -1,6 +1,7 @@ package com.clevercloud.biscuit.datalog.expressions; import biscuit.format.schema.Schema; +import com.clevercloud.biscuit.datalog.TemporarySymbolTable; import com.clevercloud.biscuit.datalog.Term; import com.clevercloud.biscuit.datalog.SymbolTable; import com.clevercloud.biscuit.error.Error; @@ -26,15 +27,11 @@ public ArrayList getOps() { return ops; } - public Option evaluate(Map variables, SymbolTable symbols) { - /* - Create a SymbolTable from original one to keep previous SymbolTable state after a rule or check execution, - to avoid filling it up too much with concatenated strings (BinaryOp.Adds on String) - */ - SymbolTable tmpSymbols = new SymbolTable(symbols); + //FIXME: should return a Result + public Option evaluate(Map variables, TemporarySymbolTable symbols) { Deque stack = new ArrayDeque(16); //Default value for(Op op: ops){ - if(!op.evaluate(stack,variables, tmpSymbols)){ + if(!op.evaluate(stack,variables, symbols)){ return Option.none(); } } diff --git a/src/main/java/com/clevercloud/biscuit/datalog/expressions/Op.java b/src/main/java/com/clevercloud/biscuit/datalog/expressions/Op.java index 10abbbbe..de3077fb 100644 --- a/src/main/java/com/clevercloud/biscuit/datalog/expressions/Op.java +++ b/src/main/java/com/clevercloud/biscuit/datalog/expressions/Op.java @@ -1,6 +1,7 @@ package com.clevercloud.biscuit.datalog.expressions; import biscuit.format.schema.Schema; +import com.clevercloud.biscuit.datalog.TemporarySymbolTable; import com.clevercloud.biscuit.datalog.Term; import com.clevercloud.biscuit.datalog.SymbolTable; import com.clevercloud.biscuit.error.Error; @@ -15,7 +16,7 @@ import static io.vavr.API.Right; public abstract class Op { - public abstract boolean evaluate(Deque stack, Map variables, SymbolTable symbols); + public abstract boolean evaluate(Deque stack, Map variables, TemporarySymbolTable symbols); public abstract String print(Deque stack, SymbolTable symbols); @@ -45,7 +46,7 @@ public Term getValue() { } @Override - public boolean evaluate(Deque stack, Map variables, SymbolTable symbols) { + public boolean evaluate(Deque stack, Map variables, TemporarySymbolTable symbols) { if (value instanceof Term.Variable) { Term.Variable var = (Term.Variable) value; Term valueVar = variables.get(var.value()); @@ -117,7 +118,7 @@ public UnaryOp getOp() { } @Override - public boolean evaluate(Deque stack, Map variables, SymbolTable symbols) { + public boolean evaluate(Deque stack, Map variables, TemporarySymbolTable symbols) { Term value = stack.pop(); switch (this.op) { case Negate: @@ -260,7 +261,7 @@ public BinaryOp getOp() { } @Override - public boolean evaluate(Deque stack, Map variables, SymbolTable symbols) { + public boolean evaluate(Deque stack, Map variables, TemporarySymbolTable symbols) { Term right = stack.pop(); Term left = stack.pop(); @@ -432,8 +433,8 @@ public boolean evaluate(Deque stack, Map variables, SymbolTabl return false; } String concatenation = left_s.get() + right_s.get(); - symbols.add(concatenation); - stack.push(new Term.Str(symbols.get(concatenation).get())); + long index = symbols.insert(concatenation); + stack.push(new Term.Str(index)); return true; } break; diff --git a/src/main/java/com/clevercloud/biscuit/error/Error.java b/src/main/java/com/clevercloud/biscuit/error/Error.java index 0d35aeb8..7b2296fb 100644 --- a/src/main/java/com/clevercloud/biscuit/error/Error.java +++ b/src/main/java/com/clevercloud/biscuit/error/Error.java @@ -1,5 +1,6 @@ package com.clevercloud.biscuit.error; +import com.clevercloud.biscuit.datalog.expressions.Expression; import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.google.gson.JsonPrimitive; @@ -532,6 +533,35 @@ public JsonElement toJson(){ } } + public static class Execution extends Error { + Expression e; + + public Execution(Expression ex) { + e = ex; + } + @Override + public boolean equals(Object o) { + if (this == o) return true; + return o != null && getClass() == o.getClass(); + } + @Override + public JsonElement toJson(){ + return new JsonPrimitive("Execution"); + } + } + + public static class InvalidType extends Error { + @Override + public boolean equals(Object o) { + if (this == o) return true; + return o != null && getClass() == o.getClass(); + } + @Override + public JsonElement toJson(){ + return new JsonPrimitive("InvalidType"); + } + } + public static class Parser extends Error { final public com.clevercloud.biscuit.token.builder.parser.Error error; diff --git a/src/main/java/com/clevercloud/biscuit/token/Authorizer.java b/src/main/java/com/clevercloud/biscuit/token/Authorizer.java index 311d4f89..27bb5ce1 100644 --- a/src/main/java/com/clevercloud/biscuit/token/Authorizer.java +++ b/src/main/java/com/clevercloud/biscuit/token/Authorizer.java @@ -1,13 +1,15 @@ package com.clevercloud.biscuit.token; -import com.clevercloud.biscuit.datalog.AuthorizedWorld; -import com.clevercloud.biscuit.datalog.RunLimits; -import com.clevercloud.biscuit.datalog.SymbolTable; -import com.clevercloud.biscuit.datalog.World; +import com.clevercloud.biscuit.datalog.*; +import com.clevercloud.biscuit.datalog.Scope; import com.clevercloud.biscuit.error.Error; import com.clevercloud.biscuit.error.FailedCheck; import com.clevercloud.biscuit.error.LogicError; import com.clevercloud.biscuit.token.builder.*; +import com.clevercloud.biscuit.token.builder.Check; +import com.clevercloud.biscuit.token.builder.Fact; +import com.clevercloud.biscuit.token.builder.Rule; +import com.clevercloud.biscuit.token.builder.Term; import io.vavr.Tuple2; import io.vavr.control.Either; import io.vavr.control.Option; @@ -28,6 +30,8 @@ public class Authorizer { List checks; List> token_checks; List policies; + List scopes; + HashMap> publicKeyToBlockId; World world; SymbolTable symbols; @@ -37,7 +41,9 @@ private Authorizer(Biscuit token, World w) throws Error.FailedLogic { this.symbols = new SymbolTable(this.token.symbols); this.checks = new ArrayList<>(); this.policies = new ArrayList<>(); + this.scopes = new ArrayList<>(); this.token_checks = this.token.checks(); + this.publicKeyToBlockId = new HashMap<>(); update_on_token(); } @@ -53,6 +59,8 @@ public Authorizer() { this.checks = new ArrayList<>(); this.policies = new ArrayList<>(); this.token_checks = new ArrayList<>(); + this.scopes = new ArrayList<>(); + this.publicKeyToBlockId = new HashMap<>(); } private Authorizer(Biscuit token, List checks, List policies, @@ -63,6 +71,8 @@ private Authorizer(Biscuit token, List checks, List policies, this.token_checks = token_checks; this.world = world; this.symbols = symbols; + this.scopes = new ArrayList<>(); + this.publicKeyToBlockId = new HashMap<>(); } /** @@ -71,7 +81,6 @@ private Authorizer(Biscuit token, List checks, List policies, * also checks that the token is valid for this root public key * * @param token - * @param root * @return */ static public Authorizer make(Biscuit token) throws Error.FailedLogic { @@ -87,7 +96,7 @@ public void update_on_token() throws Error.FailedLogic { if (token != null) { for (com.clevercloud.biscuit.datalog.Fact fact : token.authority.facts) { com.clevercloud.biscuit.datalog.Fact converted_fact = Fact.convert_from(fact, token.symbols).convert(this.symbols); - world.add_fact(converted_fact); + world.add_fact(new Origin(0), converted_fact); } for (com.clevercloud.biscuit.datalog.Rule rule : token.authority.rules) { com.clevercloud.biscuit.token.builder.Rule _rule = Rule.convert_from(rule, token.symbols); @@ -112,7 +121,7 @@ public Authorizer add_token(Biscuit token) throws Error.FailedLogic { } public Authorizer add_fact(Fact fact) { - world.add_fact(fact.convert(symbols)); + world.add_fact(Origin.authorizer(), fact.convert(symbols)); return this; } @@ -130,10 +139,26 @@ public Authorizer add_fact(String s) throws Error.Parser { } public Authorizer add_rule(Rule rule) { - world.add_rule(rule.convert(symbols)); + com.clevercloud.biscuit.datalog.Rule r = rule.convert(symbols); + TrustedOrigins ruleTrustedOrigins = TrustedOrigins.fromScopes( + r.scopes(), + this.authorizerTrustedOrigins(), + Long.MAX_VALUE, + this.publicKeyToBlockId + ); + world.add_rule(Long.MAX_VALUE, ruleTrustedOrigins, r); return this; } + public TrustedOrigins authorizerTrustedOrigins() { + return TrustedOrigins.fromScopes( + this.scopes, + TrustedOrigins.defaultOrigins(), + Long.MAX_VALUE, + this.publicKeyToBlockId + ); + } + public Authorizer add_rule(String s) throws Error.Parser { Either> res = com.clevercloud.biscuit.token.builder.parser.Parser.rule(s); @@ -166,11 +191,11 @@ public Authorizer add_check(String s) throws Error.Parser { } public Authorizer set_time() throws Error.Language { - world.add_fact(fact("time", Arrays.asList(date(new Date()))).convert(symbols)); + world.add_fact(Origin.authorizer(), fact("time", Arrays.asList(date(new Date()))).convert(symbols)); return this; } - public List get_revocation_ids() throws Error.TooManyFacts, Error.TooManyIterations, Error.Timeout { + public List get_revocation_ids() throws Error { ArrayList ids = new ArrayList<>(); final Rule getRevocationIds = rule( @@ -237,11 +262,11 @@ public Authorizer add_policy(Policy p) { return this; } - public Set query(Rule query) throws Error.TooManyFacts, Error.TooManyIterations, Error.Timeout { + public Set query(Rule query) throws Error { return this.query(query, new RunLimits()); } - public Set query(String s) throws Error.Parser, Error.TooManyFacts, Error.TooManyIterations, Error.Timeout { + public Set query(String s) throws Error { Either> res = com.clevercloud.biscuit.token.builder.parser.Parser.rule(s); @@ -254,20 +279,41 @@ public Set query(String s) throws Error.Parser, Error.TooManyFacts, Error. return query(t._2); } - public Set query(Rule query, RunLimits limits) throws Error.TooManyFacts, Error.TooManyIterations, Error.Timeout { + public Set query(Rule query, RunLimits limits) throws Error { world.run(limits, symbols); - Set facts = world.query_rule(query.convert(symbols), symbols); + /* + let rule_trusted_origins = TrustedOrigins::from_scopes( + &rule.scopes, + &TrustedOrigins::default(), // for queries, we don't want to default on the authorizer trust + // queries are there to explore the final state of the world, + // whereas authorizer contents are there to authorize or not + // a token + usize::MAX, + &self.public_key_to_block_id, + ); + */ + com.clevercloud.biscuit.datalog.Rule rule = query.convert(symbols); + TrustedOrigins ruleTrustedorigins = TrustedOrigins.fromScopes( + rule.scopes(), + TrustedOrigins.defaultOrigins(), + Long.MAX_VALUE, + this.publicKeyToBlockId + ); + + FactSet facts = world.query_rule(rule, Long.MAX_VALUE, + ruleTrustedorigins, symbols); Set s = new HashSet<>(); - for (com.clevercloud.biscuit.datalog.Fact f : facts) { + for (Iterator it = facts.iterator().iterator(); it.hasNext(); ) { + com.clevercloud.biscuit.datalog.Fact f = (com.clevercloud.biscuit.datalog.Fact) it.next(); s.add(Fact.convert_from(f, symbols)); } return s; } - public Set query(String s, RunLimits limits) throws Error.Parser, Error.TooManyFacts, Error.TooManyIterations, Error.Timeout { + public Set query(String s, RunLimits limits) throws Error { Either> res = com.clevercloud.biscuit.token.builder.parser.Parser.rule(s); @@ -280,19 +326,22 @@ public Set query(String s, RunLimits limits) throws Error.Parser, Error.To return query(t._2, limits); } - public Tuple2 authorize() throws Error.Timeout, Error.FailedLogic, Error.TooManyFacts, Error.TooManyIterations { + public Long authorize() throws Error { return this.authorize(new RunLimits()); } - public Tuple2 authorize(RunLimits limits) throws Error.Timeout, Error.FailedLogic, Error.TooManyFacts, Error.TooManyIterations { + public Long authorize(RunLimits limits) throws Error { Instant timeLimit = Instant.now().plus(limits.maxTime); List errors = new LinkedList<>(); Option> policy_result = Option.none(); + Origin authorizerOrigin = Origin.authorizer(); + TrustedOrigins authorizerTrustedOrigins = this.authorizerTrustedOrigins(); + if (token != null) { for (com.clevercloud.biscuit.datalog.Fact fact : token.authority.facts) { com.clevercloud.biscuit.datalog.Fact converted_fact = Fact.convert_from(fact, token.symbols).convert(this.symbols); - world.add_fact(converted_fact); + world.add_fact(authorizerOrigin, converted_fact); } for (com.clevercloud.biscuit.datalog.Rule rule : token.authority.rules) { com.clevercloud.biscuit.token.builder.Rule _rule = Rule.convert_from(rule, token.symbols); @@ -306,8 +355,6 @@ public Tuple2 authorize(RunLimits limits) throws Error.Ti } world.run(limits, symbols); - world.clearRules(); - AuthorizedWorld authorizedWorld = new AuthorizedWorld(world.facts()); for (int i = 0; i < this.checks.size(); i++) { com.clevercloud.biscuit.datalog.Check c = this.checks.get(i).convert(symbols); @@ -315,12 +362,19 @@ public Tuple2 authorize(RunLimits limits) throws Error.Ti for (int j = 0; j < c.queries().size(); j++) { boolean res = false; + com.clevercloud.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.query_match(c.queries().get(j), symbols); + res = world.query_match(query, Long.MAX_VALUE, ruleTrustedOrigins, symbols); break; case All: - res = world.query_match_all(c.queries().get(j), symbols); + res = world.query_match_all(query, ruleTrustedOrigins, symbols); break; } @@ -341,6 +395,13 @@ public Tuple2 authorize(RunLimits limits) throws Error.Ti if (token != null) { + TrustedOrigins authorityTrustedOrigins = TrustedOrigins.fromScopes( + token.authority.scopes, + TrustedOrigins.defaultOrigins(), + 0, + this.publicKeyToBlockId + ); + for (int j = 0; j < token.authority.checks.size(); j++) { boolean successful = false; @@ -349,12 +410,19 @@ public Tuple2 authorize(RunLimits limits) throws Error.Ti for (int k = 0; k < check.queries().size(); k++) { boolean res = false; + com.clevercloud.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.query_match(check.queries().get(k), symbols); + res = world.query_match(query, (long)0, ruleTrustedOrigins, symbols); break; case All: - res = world.query_match_all(check.queries().get(k), symbols); + res = world.query_match_all(query, ruleTrustedOrigins, symbols); break; } @@ -379,8 +447,14 @@ public Tuple2 authorize(RunLimits limits) throws Error.Ti Policy policy = this.policies.get(i); for (int j = 0; j < policy.queries.size(); j++) { - Rule query = policy.queries.get(j); - boolean res = world.query_match(query.convert(symbols), symbols); + com.clevercloud.biscuit.datalog.Rule query = policy.queries.get(j).convert(symbols); + TrustedOrigins ruleTrustedOrigins = TrustedOrigins.fromScopes( + query.scopes(), + authorizerTrustedOrigins, + Long.MAX_VALUE, + this.publicKeyToBlockId + ); + boolean res = world.query_match(query, Long.MAX_VALUE, ruleTrustedOrigins, symbols); if (Instant.now().compareTo(timeLimit) >= 0) { throw new Error.Timeout(); @@ -400,12 +474,16 @@ public Tuple2 authorize(RunLimits limits) throws Error.Ti if (token != null) { for (int i = 0; i < token.blocks.size(); i++) { Block b = token.blocks.get(i); - - World blockWorld = new World(world); + TrustedOrigins blockTrustedOrigins = TrustedOrigins.fromScopes( + b.scopes, + TrustedOrigins.defaultOrigins(), + i+1, + this.publicKeyToBlockId + ); for (com.clevercloud.biscuit.datalog.Fact fact : b.facts) { com.clevercloud.biscuit.datalog.Fact converted_fact = Fact.convert_from(fact, token.symbols).convert(this.symbols); - blockWorld.add_fact(converted_fact); + world.add_fact(new Origin(i+1), converted_fact); } for (com.clevercloud.biscuit.datalog.Rule rule : b.rules) { @@ -416,12 +494,16 @@ public Tuple2 authorize(RunLimits limits) throws Error.Ti if(res.isLeft()){ throw new Error.FailedLogic(new LogicError.InvalidBlockRule(0, token.symbols.print_rule(converted_rule))); } - blockWorld.add_rule(converted_rule); + TrustedOrigins ruleTrustedOrigins = TrustedOrigins.fromScopes( + converted_rule.scopes(), + blockTrustedOrigins, + i+1, + this.publicKeyToBlockId + ); + world.add_rule((long)i+1, ruleTrustedOrigins, converted_rule); } - blockWorld.run(limits, symbols); - authorizedWorld.add_facts(blockWorld.facts()); - blockWorld.clearRules(); + world.run(limits, symbols); for (int j = 0; j < b.checks.size(); j++) { boolean successful = false; @@ -431,12 +513,19 @@ public Tuple2 authorize(RunLimits limits) throws Error.Ti for (int k = 0; k < check.queries().size(); k++) { boolean res = false; + com.clevercloud.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 = blockWorld.query_match(check.queries().get(k), symbols); + res = world.query_match(query, (long)i+1, ruleTrustedOrigins, symbols); break; case All: - res = blockWorld.query_match_all(check.queries().get(k), symbols); + res = world.query_match_all(query, ruleTrustedOrigins, symbols); break; } @@ -461,7 +550,7 @@ public Tuple2 authorize(RunLimits limits) throws Error.Ti Either e = policy_result.get(); if (e.isRight()) { if (errors.isEmpty()) { - return new Tuple2<>(Long.valueOf(e.get().longValue()), authorizedWorld); + return Long.valueOf(e.get().longValue()); } else { throw new Error.FailedLogic(new LogicError.Unauthorized(new LogicError.MatchedPolicy.Allow(e.get().intValue()), errors)); } @@ -474,8 +563,9 @@ public Tuple2 authorize(RunLimits limits) throws Error.Ti } public String print_world() { - final List facts = this.world.facts().stream().map((f) -> this.symbols.print_fact(f)).collect(Collectors.toList()); - final List rules = this.world.rules().stream().map((r) -> this.symbols.print_rule(r)).collect(Collectors.toList()); + //FIXME + final List facts = this.world.facts().iterator().map((f) -> this.symbols.print_fact(f)).collect(Collectors.toList()); + final List rules = this.world.rules().iterator().map((r) -> this.symbols.print_rule(r)).collect(Collectors.toList()); List checks = new ArrayList<>(); diff --git a/src/main/java/com/clevercloud/biscuit/token/UnverifiedBiscuit.java b/src/main/java/com/clevercloud/biscuit/token/UnverifiedBiscuit.java index b0d8afdf..88bdbe41 100644 --- a/src/main/java/com/clevercloud/biscuit/token/UnverifiedBiscuit.java +++ b/src/main/java/com/clevercloud/biscuit/token/UnverifiedBiscuit.java @@ -218,30 +218,6 @@ public List> checks() { return l; } - Either generate_world() { - World world = new World(); - - for (Fact fact : this.authority.facts) { - world.add_fact(fact); - } - - for (Rule rule : this.authority.rules) { - world.add_rule(rule); - } - - for (Block b : this.blocks) { - for (Fact fact : b.facts) { - world.add_fact(fact); - } - - for (Rule rule : b.rules) { - world.add_rule(rule); - } - } - - return Right(world); - } - public List> context() { ArrayList> res = new ArrayList<>(); if (this.authority.context.isEmpty()) { @@ -265,95 +241,6 @@ public Option root_key_id() { return this.root_key_id; } - HashMap> check(SymbolTable symbols, List ambient_facts, List ambient_rules, - List authorizer_checks, HashMap queries) throws Error { - Either wres = this.generate_world(); - - if (wres.isLeft()) { - Error e = wres.getLeft(); - throw e; - } - - World world = wres.get(); - - for (Fact fact : ambient_facts) { - world.add_fact(fact); - } - - for (Rule rule : ambient_rules) { - world.add_rule(rule); - } - - world.run(symbols); - - ArrayList errors = new ArrayList<>(); - for (int j = 0; j < this.authority.checks.size(); j++) { - boolean successful = false; - Check c = this.authority.checks.get(j); - - for (int k = 0; k < c.queries().size(); k++) { - Set res = world.query_rule(c.queries().get(k), symbols); - if (!res.isEmpty()) { - successful = true; - break; - } - } - - if (!successful) { - errors.add(new FailedCheck.FailedBlock(0, j, symbols.print_check(this.authority.checks.get(j)))); - } - } - - for (int j = 0; j < authorizer_checks.size(); j++) { - boolean successful = false; - Check c = authorizer_checks.get(j); - - for (int k = 0; k < c.queries().size(); k++) { - Set res = world.query_rule(c.queries().get(k), symbols); - if (!res.isEmpty()) { - successful = true; - break; - } - } - - if (!successful) { - errors.add(new FailedCheck.FailedAuthorizer(j + 1, symbols.print_check(authorizer_checks.get(j)))); - } - } - - for (int i = 0; i < this.blocks.size(); i++) { - Block b = this.blocks.get(i); - - for (int j = 0; j < b.checks.size(); j++) { - boolean successful = false; - Check c = b.checks.get(j); - - for (int k = 0; k < c.queries().size(); k++) { - Set res = world.query_rule(c.queries().get(k), symbols); - if (!res.isEmpty()) { - successful = true; - break; - } - } - - if (!successful) { - errors.add(new FailedCheck.FailedBlock(i + 1, j, symbols.print_check(b.checks.get(j)))); - } - } - } - - HashMap> query_results = new HashMap<>(); - for (String name : queries.keySet()) { - Set res = world.query_rule(queries.get(name), symbols); - query_results.put(name, res); - } - - if (errors.isEmpty()) { - return query_results; - } else { - throw new Error.FailedLogic(new LogicError.Unauthorized(new LogicError.MatchedPolicy.Allow(0), errors)); - } - } /** * Prints a token's content diff --git a/src/test/java/com/clevercloud/biscuit/builder/parser/ParserTest.java b/src/test/java/com/clevercloud/biscuit/builder/parser/ParserTest.java index 2eab7a35..2914b719 100644 --- a/src/test/java/com/clevercloud/biscuit/builder/parser/ParserTest.java +++ b/src/test/java/com/clevercloud/biscuit/builder/parser/ParserTest.java @@ -3,6 +3,7 @@ import biscuit.format.schema.Schema; import com.clevercloud.biscuit.crypto.PublicKey; import com.clevercloud.biscuit.datalog.SymbolTable; +import com.clevercloud.biscuit.datalog.TemporarySymbolTable; import com.clevercloud.biscuit.datalog.expressions.Op; import com.clevercloud.biscuit.token.builder.*; import com.clevercloud.biscuit.token.builder.parser.Error; @@ -314,7 +315,7 @@ void testParens() { ); Map variables = new HashMap<>(); - Option value = ex.evaluate(variables, s); + Option value = ex.evaluate(variables, new TemporarySymbolTable(s)); assertEquals(Option.some(new com.clevercloud.biscuit.datalog.Term.Integer(7)), value); assertEquals("1 + 2 * 3", ex.print(s).get()); @@ -356,7 +357,7 @@ void testParens() { ); Map variables2 = new HashMap<>(); - Option value2 = ex2.evaluate(variables2, s2); + Option value2 = ex2.evaluate(variables2, new TemporarySymbolTable(s2)); assertEquals(Option.some(new com.clevercloud.biscuit.datalog.Term.Integer(9)), value2); assertEquals("(1 + 2) * 3", ex2.print(s2).get()); } diff --git a/src/test/java/com/clevercloud/biscuit/datalog/ExpressionTest.java b/src/test/java/com/clevercloud/biscuit/datalog/ExpressionTest.java index 79fa9b53..bb671ae8 100644 --- a/src/test/java/com/clevercloud/biscuit/datalog/ExpressionTest.java +++ b/src/test/java/com/clevercloud/biscuit/datalog/ExpressionTest.java @@ -36,7 +36,7 @@ public void testNegate() { assertEquals( new Term.Bool(true), - e.evaluate(variables, symbols).get() + e.evaluate(variables, new TemporarySymbolTable(symbols)).get() ); } @@ -61,7 +61,7 @@ public void testAddsStr() { assertEquals( new Term.Str(SymbolTable.DEFAULT_SYMBOLS_OFFSET + 2), - e.evaluate(new HashMap<>(), symbols).get() + e.evaluate(new HashMap<>(), new TemporarySymbolTable(symbols)).get() ); } @@ -85,7 +85,7 @@ public void testContainsStr() { assertEquals( new Term.Bool(true), - e.evaluate(new HashMap<>(), symbols).get() + e.evaluate(new HashMap<>(), new TemporarySymbolTable(symbols)).get() ); } @@ -110,7 +110,7 @@ public void testNegativeContainsStr() { assertEquals( new Term.Bool(false), - e.evaluate(new HashMap<>(), symbols).get() + e.evaluate(new HashMap<>(), new TemporarySymbolTable(symbols)).get() ); } } diff --git a/src/test/java/com/clevercloud/biscuit/datalog/WorldTest.java b/src/test/java/com/clevercloud/biscuit/datalog/WorldTest.java index c477c4f5..ce566b2d 100644 --- a/src/test/java/com/clevercloud/biscuit/datalog/WorldTest.java +++ b/src/test/java/com/clevercloud/biscuit/datalog/WorldTest.java @@ -14,7 +14,7 @@ public class WorldTest { @Test - public void testFamily() throws Error.Timeout, Error.TooManyFacts, Error.TooManyIterations { + public void testFamily() throws Error { final World w = new World(); final SymbolTable syms = new SymbolTable(); final Term a = syms.add("A"); @@ -26,9 +26,9 @@ public void testFamily() throws Error.Timeout, Error.TooManyFacts, Error.TooMany final long grandparent = syms.insert("grandparent"); final long sibling = syms.insert("siblings"); - w.add_fact(new Fact(new Predicate(parent, Arrays.asList(a, b)))); - w.add_fact(new Fact(new Predicate(parent, Arrays.asList(b, c)))); - w.add_fact(new Fact(new Predicate(parent, Arrays.asList(c, d)))); + w.add_fact(new Origin(0), new Fact(new Predicate(parent, Arrays.asList(a, b)))); + w.add_fact(new Origin(0), new Fact(new Predicate(parent, Arrays.asList(b, c)))); + w.add_fact(new Origin(0), new Fact(new Predicate(parent, Arrays.asList(c, d)))); final Rule r1 = new Rule(new Predicate(grandparent, Arrays.asList(new Term.Variable(syms.insert("grandparent")), new Term.Variable(syms.insert("grandchild")))), Arrays.asList( @@ -37,9 +37,9 @@ public void testFamily() throws Error.Timeout, Error.TooManyFacts, Error.TooMany ), new ArrayList<>()); System.out.println("testing r1: " + syms.print_rule(r1)); - Set query_rule_result = w.query_rule(r1, syms); - System.out.println("grandparents query_rules: [" + String.join(", ", query_rule_result.stream().map((f) -> syms.print_fact(f)).collect(Collectors.toList())) + "]"); - System.out.println("current facts: [" + String.join(", ", w.facts().stream().map((f) -> syms.print_fact(f)).collect(Collectors.toList())) + "]"); + FactSet query_rule_result = w.query_rule(r1, (long)0, new TrustedOrigins(0), syms); + System.out.println("grandparents query_rules: [" + String.join(", ", query_rule_result.iterator().map((f) -> syms.print_fact(f)).collect(Collectors.toList())) + "]"); + System.out.println("current facts: [" + String.join(", ", w.facts().iterator().map((f) -> syms.print_fact(f)).collect(Collectors.toList())) + "]"); final Rule r2 = new Rule(new Predicate(grandparent, Arrays.asList(new Term.Variable(syms.insert("grandparent")), new Term.Variable(syms.insert("grandchild")))), Arrays.asList( @@ -48,52 +48,74 @@ public void testFamily() throws Error.Timeout, Error.TooManyFacts, Error.TooMany ), new ArrayList<>()); System.out.println("adding r2: " + syms.print_rule(r2)); - w.add_rule(r2); + w.add_rule((long)0, new TrustedOrigins(0), r2); w.run(syms); System.out.println("parents:"); - for (final Fact fact : w.query(new Predicate(parent, - Arrays.asList(new Term.Variable(syms.insert("parent")), new Term.Variable(syms.insert("child")))))) { + final Rule query1 = new Rule(new Predicate(parent, + Arrays.asList(new Term.Variable(syms.insert("parent")), new Term.Variable(syms.insert("child")))), + Arrays.asList(new Predicate(parent, + Arrays.asList(new Term.Variable(syms.insert("parent")), new Term.Variable(syms.insert("child"))))), + new ArrayList<>()); + + for (Iterator it = w.query_rule(query1, (long) 0, new TrustedOrigins(0), syms).iterator().iterator(); it.hasNext(); ) { + Fact fact = it.next(); System.out.println("\t" + syms.print_fact(fact)); } + final Rule query2 = new Rule(new Predicate(parent, Arrays.asList(new Term.Variable(syms.insert("parent")), b)), + Arrays.asList(new Predicate(parent, Arrays.asList(new Term.Variable(syms.insert("parent")), b))), + new ArrayList<>()); System.out.println("parents of B: [" + String.join(", ", - w.query(new Predicate(parent, Arrays.asList(new Term.Variable(syms.insert("parent")), b))) - .stream().map((f) -> syms.print_fact(f)).collect(Collectors.toSet())) + "]"); + w.query_rule(query2, (long) 0, new TrustedOrigins(0), syms) + .iterator().map((f) -> syms.print_fact(f)).collect(Collectors.toSet())) + "]"); + final Rule query3 = new Rule(new Predicate(grandparent, Arrays.asList(new Term.Variable(syms.insert("grandparent")), + new Term.Variable(syms.insert("grandchild")))), + Arrays.asList(new Predicate(grandparent, Arrays.asList(new Term.Variable(syms.insert("grandparent")), + new Term.Variable(syms.insert("grandchild"))))), + new ArrayList<>()); System.out.println("grandparents: [" + String.join(", ", - w.query(new Predicate(grandparent, Arrays.asList(new Term.Variable(syms.insert("grandparent")), - new Term.Variable(syms.insert("grandchild"))))) - .stream().map((f) -> syms.print_fact(f)).collect(Collectors.toSet())) + "]"); + w.query_rule(query3, (long) 0, new TrustedOrigins(0), syms) + .iterator().map((f) -> syms.print_fact(f)).collect(Collectors.toSet())) + "]"); - w.add_fact(new Fact(new Predicate(parent, Arrays.asList(c, e)))); + w.add_fact(new Origin(0), new Fact(new Predicate(parent, Arrays.asList(c, e)))); w.run(syms); - final Set res = w.query(new Predicate(grandparent, - Arrays.asList(new Term.Variable(syms.insert("grandparent")), new Term.Variable(syms.insert("grandchild"))))); + final Rule query4 = new Rule(new Predicate(grandparent, + Arrays.asList(new Term.Variable(syms.insert("grandparent")), new Term.Variable(syms.insert("grandchild")))), + Arrays.asList(new Predicate(grandparent, + Arrays.asList(new Term.Variable(syms.insert("grandparent")), new Term.Variable(syms.insert("grandchild"))))), + new ArrayList<>()); + final FactSet res = w.query_rule(query4, (long) 0, new TrustedOrigins(0), syms); System.out.println("grandparents after inserting parent(C, E): [" + String.join(", ", - res.stream().map((f) -> syms.print_fact(f)).collect(Collectors.toSet())) + "]"); + res.iterator().map((f) -> syms.print_fact(f)).collect(Collectors.toSet())) + "]"); - final Set expected = new HashSet<>(Arrays.asList( + final FactSet expected = new FactSet(new Origin(0), new HashSet<>(Arrays.asList( new Fact(new Predicate(grandparent, Arrays.asList(a, c))), new Fact(new Predicate(grandparent, Arrays.asList(b, d))), - new Fact(new Predicate(grandparent, Arrays.asList(b, e))))); + new Fact(new Predicate(grandparent, Arrays.asList(b, e)))))); assertEquals(expected, res); - w.add_rule(new Rule(new Predicate(sibling, + w.add_rule((long) 0, new TrustedOrigins(0), new Rule(new Predicate(sibling, Arrays.asList(new Term.Variable(syms.insert("sibling1")), new Term.Variable(syms.insert("sibling2")))), Arrays.asList( new Predicate(parent, Arrays.asList(new Term.Variable(syms.insert("parent")), new Term.Variable(syms.insert("sibling1")))), new Predicate(parent, Arrays.asList(new Term.Variable(syms.insert("parent")), new Term.Variable(syms.insert("sibling2")))) ), new ArrayList<>())); w.run(syms); - System.out.println("siblings: [" + String.join(", ", - w.query(new Predicate(sibling, Arrays.asList( + final Rule query5 = new Rule(new Predicate(sibling, Arrays.asList( + new Term.Variable(syms.insert("sibling1")), + new Term.Variable(syms.insert("sibling2")))), + Arrays.asList(new Predicate(sibling, Arrays.asList( new Term.Variable(syms.insert("sibling1")), - new Term.Variable(syms.insert("sibling2"))))) - .stream().map((f) -> syms.print_fact(f)).collect(Collectors.toSet())) + "]"); + new Term.Variable(syms.insert("sibling2"))))), + new ArrayList<>()); + System.out.println("siblings: [" + String.join(", ", + w.query_rule(query5, (long) 0, new TrustedOrigins(0), syms) + .iterator().map((f) -> syms.print_fact(f)).collect(Collectors.toSet())) + "]"); } @Test - public void testNumbers() { + public void testNumbers() throws Error { final World w = new World(); final SymbolTable syms = new SymbolTable(); @@ -109,17 +131,17 @@ public void testNumbers() { final long t2 = syms.insert("t2"); final long join = syms.insert("join"); - w.add_fact(new Fact(new Predicate(t1, Arrays.asList(new Term.Integer(0), abc)))); - w.add_fact(new Fact(new Predicate(t1, Arrays.asList(new Term.Integer(1), def)))); - w.add_fact(new Fact(new Predicate(t1, Arrays.asList(new Term.Integer(2), ghi)))); - w.add_fact(new Fact(new Predicate(t1, Arrays.asList(new Term.Integer(3), jkl)))); - w.add_fact(new Fact(new Predicate(t1, Arrays.asList(new Term.Integer(4), mno)))); + w.add_fact(new Origin(0), new Fact(new Predicate(t1, Arrays.asList(new Term.Integer(0), abc)))); + w.add_fact(new Origin(0), new Fact(new Predicate(t1, Arrays.asList(new Term.Integer(1), def)))); + w.add_fact(new Origin(0), new Fact(new Predicate(t1, Arrays.asList(new Term.Integer(2), ghi)))); + w.add_fact(new Origin(0), new Fact(new Predicate(t1, Arrays.asList(new Term.Integer(3), jkl)))); + w.add_fact(new Origin(0), new Fact(new Predicate(t1, Arrays.asList(new Term.Integer(4), mno)))); - w.add_fact(new Fact(new Predicate(t2, Arrays.asList(new Term.Integer(0), aaa, new Term.Integer(0))))); - w.add_fact(new Fact(new Predicate(t2, Arrays.asList(new Term.Integer(1), bbb, new Term.Integer(0))))); - w.add_fact(new Fact(new Predicate(t2, Arrays.asList(new Term.Integer(2), ccc, new Term.Integer(1))))); + w.add_fact(new Origin(0), new Fact(new Predicate(t2, Arrays.asList(new Term.Integer(0), aaa, new Term.Integer(0))))); + w.add_fact(new Origin(0), new Fact(new Predicate(t2, Arrays.asList(new Term.Integer(1), bbb, new Term.Integer(0))))); + w.add_fact(new Origin(0), new Fact(new Predicate(t2, Arrays.asList(new Term.Integer(2), ccc, new Term.Integer(1))))); - Set res = w.query_rule(new Rule(new Predicate(join, + FactSet res = w.query_rule(new Rule(new Predicate(join, Arrays.asList(new Term.Variable(syms.insert("left")), new Term.Variable(syms.insert("right"))) ), Arrays.asList(new Predicate(t1, Arrays.asList(new Term.Variable(syms.insert("id")), new Term.Variable(syms.insert("left")))), @@ -127,11 +149,15 @@ public void testNumbers() { Arrays.asList( new Term.Variable(syms.insert("t2_id")), new Term.Variable(syms.insert("right")), - new Term.Variable(syms.insert("id"))))), new ArrayList<>()), syms); - for (final Fact f : res) { - System.out.println("\t" + syms.print_fact(f)); - } - Set expected = new HashSet<>(Arrays.asList(new Fact(new Predicate(join, Arrays.asList(abc, aaa))), new Fact(new Predicate(join, Arrays.asList(abc, bbb))), new Fact(new Predicate(join, Arrays.asList(def, ccc))))); + new Term.Variable(syms.insert("id"))))), new ArrayList<>()), + (long) 0, new TrustedOrigins(0), syms); + for (Iterator it = res.iterator().iterator(); it.hasNext(); ) { + Fact f = it.next(); + System.out.println("\t" + syms.print_fact(f)); + } + FactSet expected = new FactSet(new Origin(0),new HashSet<>(Arrays.asList(new Fact(new Predicate(join, Arrays.asList(abc, aaa))), + new Fact(new Predicate(join, Arrays.asList(abc, bbb))), + new Fact(new Predicate(join, Arrays.asList(def, ccc)))))); assertEquals(expected, res); res = w.query_rule(new Rule(new Predicate(join, @@ -147,15 +173,17 @@ public void testNumbers() { new Op.Value(new Term.Integer(1)), new Op.Binary(Op.BinaryOp.LessThan) )))) - ), syms); - for (final Fact f : res) { - System.out.println("\t" + syms.print_fact(f)); - } - expected = new HashSet<>(Arrays.asList(new Fact(new Predicate(join, Arrays.asList(abc, aaa))), new Fact(new Predicate(join, Arrays.asList(abc, bbb))))); + ), (long) 0, new TrustedOrigins(0), syms); + for (Iterator it = res.iterator().iterator(); it.hasNext(); ) { + Fact f = it.next(); + System.out.println("\t" + syms.print_fact(f)); + } + expected = new FactSet(new Origin(0), + new HashSet<>(Arrays.asList(new Fact(new Predicate(join, Arrays.asList(abc, aaa))), new Fact(new Predicate(join, Arrays.asList(abc, bbb)))))); assertEquals(expected, res); } - private final Set testSuffix(final World w, SymbolTable syms, final long suff, final long route, final String suffix) { + private final FactSet testSuffix(final World w, SymbolTable syms, final long suff, final long route, final String suffix) throws Error { return w.query_rule(new Rule(new Predicate(suff, Arrays.asList(new Term.Variable(syms.insert("app_id")), new Term.Variable(syms.insert("domain")))), Arrays.asList( @@ -169,11 +197,11 @@ private final Set testSuffix(final World w, SymbolTable syms, final long s new Op.Value(syms.add(suffix)), new Op.Binary(Op.BinaryOp.Suffix) )))) - ), syms); + ), (long) 0, new TrustedOrigins(0), syms); } @Test - public void testStr() { + public void testStr() throws Error { final World w = new World(); final SymbolTable syms = new SymbolTable(); @@ -183,35 +211,38 @@ public void testStr() { final long route = syms.insert("route"); final long suff = syms.insert("route suffix"); - w.add_fact(new Fact(new Predicate(route, Arrays.asList(new Term.Integer(0), app_0, syms.add("example.com"))))); - w.add_fact(new Fact(new Predicate(route, Arrays.asList(new Term.Integer(1), app_1, syms.add("test.com"))))); - w.add_fact(new Fact(new Predicate(route, Arrays.asList(new Term.Integer(2), app_2, syms.add("test.fr"))))); - w.add_fact(new Fact(new Predicate(route, Arrays.asList(new Term.Integer(3), app_0, syms.add("www.example.com"))))); - w.add_fact(new Fact(new Predicate(route, Arrays.asList(new Term.Integer(4), app_1, syms.add("mx.example.com"))))); - - Set res = testSuffix(w, syms, suff, route, ".fr"); - for (final Fact f : res) { - System.out.println("\t" + syms.print_fact(f)); - } - Set expected = new HashSet<>(Arrays.asList(new Fact(new Predicate(suff, Arrays.asList(app_2, syms.add("test.fr")))))); + w.add_fact(new Origin(0), new Fact(new Predicate(route, Arrays.asList(new Term.Integer(0), app_0, syms.add("example.com"))))); + w.add_fact(new Origin(0), new Fact(new Predicate(route, Arrays.asList(new Term.Integer(1), app_1, syms.add("test.com"))))); + w.add_fact(new Origin(0), new Fact(new Predicate(route, Arrays.asList(new Term.Integer(2), app_2, syms.add("test.fr"))))); + w.add_fact(new Origin(0), new Fact(new Predicate(route, Arrays.asList(new Term.Integer(3), app_0, syms.add("www.example.com"))))); + w.add_fact(new Origin(0), new Fact(new Predicate(route, Arrays.asList(new Term.Integer(4), app_1, syms.add("mx.example.com"))))); + + FactSet res = testSuffix(w, syms, suff, route, ".fr"); + for (Iterator it = res.iterator().iterator(); it.hasNext(); ) { + Fact f = it.next(); + System.out.println("\t" + syms.print_fact(f)); + } + FactSet expected = new FactSet(new Origin(0), + new HashSet<>(Arrays.asList(new Fact(new Predicate(suff, Arrays.asList(app_2, syms.add("test.fr"))))))); assertEquals(expected, res); res = testSuffix(w, syms, suff, route, "example.com"); - for (final Fact f : res) { - System.out.println("\t" + syms.print_fact(f)); - } - expected = new HashSet<>(Arrays.asList(new Fact(new Predicate(suff, + for (Iterator it = res.iterator().iterator(); it.hasNext(); ) { + Fact f = it.next(); + System.out.println("\t" + syms.print_fact(f)); + } + expected = new FactSet(new Origin(0),new HashSet<>(Arrays.asList(new Fact(new Predicate(suff, Arrays.asList( app_0, syms.add("example.com")))), new Fact(new Predicate(suff, Arrays.asList(app_0, syms.add("www.example.com")))), - new Fact(new Predicate(suff, Arrays.asList(app_1, syms.add("mx.example.com")))))); + new Fact(new Predicate(suff, Arrays.asList(app_1, syms.add("mx.example.com"))))))); assertEquals(expected, res); } @Test - public void testDate() { + public void testDate() throws Error { final World w = new World(); final SymbolTable syms = new SymbolTable(); @@ -230,8 +261,8 @@ public void testDate() { final long before = syms.insert("before"); final long after = syms.insert("after"); - w.add_fact(new Fact(new Predicate(x, Arrays.asList(new Term.Date(t1.getEpochSecond()), abc)))); - w.add_fact(new Fact(new Predicate(x, Arrays.asList(new Term.Date(t3.getEpochSecond()), def)))); + w.add_fact(new Origin(0), new Fact(new Predicate(x, Arrays.asList(new Term.Date(t1.getEpochSecond()), abc)))); + w.add_fact(new Origin(0), new Fact(new Predicate(x, Arrays.asList(new Term.Date(t3.getEpochSecond()), def)))); final Rule r1 = new Rule(new Predicate( before, @@ -254,11 +285,12 @@ public void testDate() { ); System.out.println("testing r1: " + syms.print_rule(r1)); - Set res = w.query_rule(r1, syms); - for (final Fact f : res) { - System.out.println("\t" + syms.print_fact(f)); - } - Set expected = new HashSet<>(Arrays.asList(new Fact(new Predicate(before, Arrays.asList(new Term.Date(t1.getEpochSecond()), abc))))); + FactSet res = w.query_rule(r1, (long) 0, new TrustedOrigins(0), syms); + for (Iterator it = res.iterator().iterator(); it.hasNext(); ) { + Fact f = it.next(); + System.out.println("\t" + syms.print_fact(f)); + } + FactSet expected = new FactSet(new Origin(0),new HashSet<>(Arrays.asList(new Fact(new Predicate(before, Arrays.asList(new Term.Date(t1.getEpochSecond()), abc)))))); assertEquals(expected, res); final Rule r2 = new Rule(new Predicate( @@ -282,16 +314,17 @@ public void testDate() { ); System.out.println("testing r2: " + syms.print_rule(r2)); - res = w.query_rule(r2, syms); - for (final Fact f : res) { - System.out.println("\t" + syms.print_fact(f)); - } - expected = new HashSet<>(Arrays.asList(new Fact(new Predicate(after, Arrays.asList(new Term.Date(t3.getEpochSecond()), def))))); + res = w.query_rule(r2, (long) 0, new TrustedOrigins(0), syms); + for (Iterator it = res.iterator().iterator(); it.hasNext(); ) { + Fact f = it.next(); + System.out.println("\t" + syms.print_fact(f)); + } + expected = new FactSet(new Origin(0),new HashSet<>(Arrays.asList(new Fact(new Predicate(after, Arrays.asList(new Term.Date(t3.getEpochSecond()), def)))))); assertEquals(expected, res); } @Test - public void testSet() { + public void testSet() throws Error { final World w = new World(); final SymbolTable syms = new SymbolTable(); @@ -302,8 +335,8 @@ public void testSet() { final long symbol_set = syms.insert("symbol_set"); final long string_set = syms.insert("string_set"); - w.add_fact(new Fact(new Predicate(x, Arrays.asList(abc, new Term.Integer(0), syms.add("test"))))); - w.add_fact(new Fact(new Predicate(x, Arrays.asList(def, new Term.Integer(2), syms.add("hello"))))); + w.add_fact(new Origin(0), new Fact(new Predicate(x, Arrays.asList(abc, new Term.Integer(0), syms.add("test"))))); + w.add_fact(new Origin(0), new Fact(new Predicate(x, Arrays.asList(def, new Term.Integer(2), syms.add("hello"))))); final Rule r1 = new Rule(new Predicate( int_set, @@ -321,11 +354,12 @@ public void testSet() { ) ); System.out.println("testing r1: " + syms.print_rule(r1)); - Set res = w.query_rule(r1, syms); - for (final Fact f : res) { - System.out.println("\t" + syms.print_fact(f)); - } - Set expected = new HashSet<>(Arrays.asList(new Fact(new Predicate(int_set, Arrays.asList(abc, syms.add("test")))))); + FactSet res = w.query_rule(r1, (long) 0, new TrustedOrigins(0), syms); + for (Iterator it = res.iterator().iterator(); it.hasNext(); ) { + Fact f = it.next(); + System.out.println("\t" + syms.print_fact(f)); + } + FactSet expected = new FactSet(new Origin(0), new HashSet<>(Arrays.asList(new Fact(new Predicate(int_set, Arrays.asList(abc, syms.add("test"))))))); assertEquals(expected, res); final long abc_sym_id = syms.insert("abc"); @@ -346,11 +380,12 @@ public void testSet() { ); System.out.println("testing r2: " + syms.print_rule(r2)); - res = w.query_rule(r2, syms); - for (final Fact f : res) { - System.out.println("\t" + syms.print_fact(f)); - } - expected = new HashSet<>(Arrays.asList(new Fact(new Predicate(symbol_set, Arrays.asList(def, new Term.Integer(2), syms.add("hello")))))); + res = w.query_rule(r2, (long) 0, new TrustedOrigins(0), syms); + for (Iterator it = res.iterator().iterator(); it.hasNext(); ) { + Fact f = it.next(); + System.out.println("\t" + syms.print_fact(f)); + } + expected = new FactSet(new Origin(0),new HashSet<>(Arrays.asList(new Fact(new Predicate(symbol_set, Arrays.asList(def, new Term.Integer(2), syms.add("hello"))))))); assertEquals(expected, res); final Rule r3 = new Rule( @@ -365,16 +400,17 @@ public void testSet() { ) ); System.out.println("testing r3: " + syms.print_rule(r3)); - res = w.query_rule(r3, syms); - for (final Fact f : res) { - System.out.println("\t" + syms.print_fact(f)); - } - expected = new HashSet<>(Arrays.asList(new Fact(new Predicate(string_set, Arrays.asList(abc, new Term.Integer(0), syms.add("test")))))); + res = w.query_rule(r3, (long) 0, new TrustedOrigins(0), syms); + for (Iterator it = res.iterator().iterator(); it.hasNext(); ) { + Fact f = it.next(); + System.out.println("\t" + syms.print_fact(f)); + } + expected = new FactSet(new Origin(0),new HashSet<>(Arrays.asList(new Fact(new Predicate(string_set, Arrays.asList(abc, new Term.Integer(0), syms.add("test"))))))); assertEquals(expected, res); } @Test - public void testResource() { + public void testResource() throws Error { final World w = new World(); final SymbolTable syms = new SymbolTable(); @@ -389,9 +425,9 @@ public void testResource() { final Term write = syms.add("write"); - w.add_fact(new Fact(new Predicate(right, Arrays.asList(file1, read)))); - w.add_fact(new Fact(new Predicate(right, Arrays.asList(file2, read)))); - w.add_fact(new Fact(new Predicate(right, Arrays.asList(file1, write)))); + w.add_fact(new Origin(0), new Fact(new Predicate(right, Arrays.asList(file1, read)))); + w.add_fact(new Origin(0), new Fact(new Predicate(right, Arrays.asList(file2, read)))); + w.add_fact(new Origin(0), new Fact(new Predicate(right, Arrays.asList(file1, write)))); final long caveat1 = syms.insert("caveat1"); //r1: caveat2(#file1) <- resource(#ambient, #file1) @@ -401,12 +437,13 @@ public void testResource() { ), new ArrayList<>()); System.out.println("testing caveat 1(should return nothing): " + syms.print_rule(r1)); - Setres = w.query_rule(r1, syms); + FactSet res = w.query_rule(r1, (long) 0, new TrustedOrigins(0), syms); System.out.println(res); - for (final Fact f : res) { - System.out.println("\t" + syms.print_fact(f)); - } - assertTrue(res.isEmpty()); + for (Iterator it = res.iterator().iterator(); it.hasNext(); ) { + Fact f = it.next(); + System.out.println("\t" + syms.print_fact(f)); + } + assertTrue(res.size() == 0); final long caveat2 = syms.insert("caveat2"); final long var0_id = syms.insert("var0"); @@ -421,11 +458,12 @@ public void testResource() { ), new ArrayList<>()); System.out.println("testing caveat 2: " + syms.print_rule(r2)); - res = w.query_rule(r2, syms); + res = w.query_rule(r2, (long) 0, new TrustedOrigins(0), syms); System.out.println(res); - for (final Fact f : res) { - System.out.println("\t" + syms.print_fact(f)); - } - assertTrue(res.isEmpty()); + for (Iterator it = res.iterator().iterator(); it.hasNext(); ) { + Fact f = it.next(); + System.out.println("\t" + syms.print_fact(f)); + } + assertTrue(res.size() == 0); } } diff --git a/src/test/java/com/clevercloud/biscuit/token/BiscuitTest.java b/src/test/java/com/clevercloud/biscuit/token/BiscuitTest.java index 70204d11..6848428f 100644 --- a/src/test/java/com/clevercloud/biscuit/token/BiscuitTest.java +++ b/src/test/java/com/clevercloud/biscuit/token/BiscuitTest.java @@ -132,27 +132,21 @@ public void testBasic() throws NoSuchAlgorithmException, SignatureException, Inv // check System.out.println("will check the token for resource=file1 and operation=read"); - SymbolTable check_symbols = new SymbolTable(final_token.symbols); - List ambient_facts = Arrays.asList( - fact("resource", Arrays.asList(s("file1"))).convert(check_symbols), - fact("operation", Arrays.asList(s("read"))).convert(check_symbols) - ); - - final_token.check(check_symbols, ambient_facts, - new ArrayList<>(), new ArrayList<>(), new HashMap<>()); + Authorizer authorizer = final_token.authorizer(); + authorizer.add_fact("resource(\"file1\")"); + authorizer.add_fact("operation(\"read\")"); + authorizer.add_policy("allow if true"); + authorizer.authorize(new RunLimits(500, 100, Duration.ofMillis(500))); System.out.println("will check the token for resource=file2 and operation=write"); - SymbolTable check_symbols2 = new SymbolTable(final_token.symbols); - List ambient_facts2 = Arrays.asList( - fact("resource", Arrays.asList(s("file2"))).convert(check_symbols2), - fact("operation", Arrays.asList(s("write"))).convert(check_symbols2) - ); + Authorizer authorizer2 = final_token.authorizer(); + authorizer2.add_fact("resource(\"file2\")"); + authorizer2.add_fact("operation(\"write\")"); + authorizer2.add_policy("allow if true"); try { - final_token.check(check_symbols2, ambient_facts2, - new ArrayList<>(), new ArrayList<>(), new HashMap<>()); - fail(); + authorizer2.authorize(new RunLimits(500, 100, Duration.ofMillis(500))); } catch (Error e) { System.out.println(e); assertEquals( @@ -305,7 +299,7 @@ public void testReset() throws NoSuchAlgorithmException, SignatureException, Inv v3.add_fact("resource(\"/folder2/file3\")"); v3.add_fact("operation(\"read\")"); - Try> res = Try.of(() -> v3.authorize()); + Try res = Try.of(() -> v3.authorize()); System.out.println(v3.print_world()); assertTrue(res.isFailure()); @@ -470,27 +464,21 @@ public void testBasicWithNamespaces() throws NoSuchAlgorithmException, Signature // check System.out.println("will check the token for resource=file1 and operation=read"); - SymbolTable check_symbols = new SymbolTable(final_token.symbols); - List ambient_facts = Arrays.asList( - fact("resource", Arrays.asList(s("file1"))).convert(check_symbols), - fact("operation", Arrays.asList(s("read"))).convert(check_symbols) - ); - - final_token.check(check_symbols, ambient_facts, - new ArrayList<>(), new ArrayList<>(), new HashMap<>()); + Authorizer authorizer = final_token.authorizer(); + authorizer.add_fact("resource(\"file1\")"); + authorizer.add_fact("operation(\"read\")"); + authorizer.add_policy("allow if true"); + authorizer.authorize(new RunLimits(500, 100, Duration.ofMillis(500))); System.out.println("will check the token for resource=file2 and operation=write"); - SymbolTable check_symbols2 = new SymbolTable(final_token.symbols); - List ambient_facts2 = Arrays.asList( - fact("resource", Arrays.asList(s("file2"))).convert(check_symbols2), - fact("operation", Arrays.asList(s("write"))).convert(check_symbols2) - ); + Authorizer authorizer2 = final_token.authorizer(); + authorizer2.add_fact("resource(\"file2\")"); + authorizer2.add_fact("operation(\"write\")"); + authorizer2.add_policy("allow if true"); try { - final_token.check(check_symbols2, ambient_facts2, - new ArrayList<>(), new ArrayList<>(), new HashMap<>()); - fail(); + authorizer2.authorize(new RunLimits(500, 100, Duration.ofMillis(500))); } catch (Error e) { System.out.println(e); assertEquals( @@ -600,27 +588,20 @@ public void testBasicWithNamespacesWithAddAuthorityFact() throws NoSuchAlgorithm // check System.out.println("will check the token for resource=file1 and operation=read"); - SymbolTable check_symbols = new SymbolTable(final_token.symbols); - List ambient_facts = Arrays.asList( - fact("resource", Arrays.asList(s("file1"))).convert(check_symbols), - fact("operation", Arrays.asList(s("read"))).convert(check_symbols) - ); - - final_token.check(check_symbols, ambient_facts, - new ArrayList<>(), new ArrayList<>(), new HashMap<>()); + Authorizer authorizer = final_token.authorizer(); + authorizer.add_fact("resource(\"file1\")"); + authorizer.add_fact("operation(\"read\")"); + authorizer.add_policy("allow if true"); + authorizer.authorize(new RunLimits(500, 100, Duration.ofMillis(500))); System.out.println("will check the token for resource=file2 and operation=write"); - SymbolTable check_symbols2 = new SymbolTable(final_token.symbols); - List ambient_facts2 = Arrays.asList( - fact("resource", Arrays.asList(s("file2"))).convert(check_symbols2), - fact("operation", Arrays.asList(s("write"))).convert(check_symbols2) - ); - + Authorizer authorizer2 = final_token.authorizer(); + authorizer2.add_fact("resource(\"file2\")"); + authorizer2.add_fact("operation(\"write\")"); + authorizer2.add_policy("allow if true"); try { - final_token.check(check_symbols2, ambient_facts2, - new ArrayList<>(), new ArrayList<>(), new HashMap<>()); - fail(); + authorizer2.authorize(new RunLimits(500, 100, Duration.ofMillis(500))); } catch (Error e) { System.out.println(e); assertEquals( diff --git a/src/test/java/com/clevercloud/biscuit/token/ExampleTest.java b/src/test/java/com/clevercloud/biscuit/token/ExampleTest.java index 78c87cd1..790d55a9 100644 --- a/src/test/java/com/clevercloud/biscuit/token/ExampleTest.java +++ b/src/test/java/com/clevercloud/biscuit/token/ExampleTest.java @@ -33,7 +33,7 @@ public Biscuit createToken(KeyPair root) throws Error { .build(); } - public Tuple2 authorize(KeyPair root, byte[] serializedToken) throws NoSuchAlgorithmException, SignatureException, InvalidKeyException, Error { + public Long authorize(KeyPair root, byte[] serializedToken) throws NoSuchAlgorithmException, SignatureException, InvalidKeyException, Error { return Biscuit.from_bytes(serializedToken, root.public_key()).authorizer() .add_fact("resource(\"/folder1/file1\")") .add_fact("operation(\"read\")") @@ -47,7 +47,7 @@ public Biscuit attenuate(KeyPair root, byte[] serializedToken) throws NoSuchAlgo return token.attenuate(block); } - public Set query(Authorizer authorizer) throws Error.Timeout, Error.TooManyFacts, Error.TooManyIterations, Error.Parser { + /*public Set query(Authorizer authorizer) throws Error.Timeout, Error.TooManyFacts, Error.TooManyIterations, Error.Parser { return authorizer.query("data($name, $id) <- user($name, $id)"); - } + }*/ } diff --git a/src/test/java/com/clevercloud/biscuit/token/SamplesJsonV2Test.java b/src/test/java/com/clevercloud/biscuit/token/SamplesJsonV2Test.java index 01f419a8..7e42990e 100644 --- a/src/test/java/com/clevercloud/biscuit/token/SamplesJsonV2Test.java +++ b/src/test/java/com/clevercloud/biscuit/token/SamplesJsonV2Test.java @@ -47,7 +47,7 @@ DynamicTest process_testcase(final TestCase testCase, final PublicKey publicKey, World world = new Gson().fromJson(validation, World.class); JsonObject expected_result = validation.getAsJsonObject("result"); String[] authorizer_facts = validation.getAsJsonPrimitive("authorizer_code").getAsString().split(";"); - Either> res = Try.of(() -> { + Either res = Try.of(() -> { byte[] data = new byte[inputStream.available()]; inputStream.read(data); Biscuit token = Biscuit.from_bytes(data, publicKey); @@ -80,7 +80,7 @@ DynamicTest process_testcase(final TestCase testCase, final PublicKey publicKey, JsonElement err_json = e.toJson(); assertEquals(expected_result.get("Err"),err_json); } else { - assertEquals(expected_result.getAsJsonPrimitive("Ok").getAsLong(), res.get()._1); + assertEquals(expected_result.getAsJsonPrimitive("Ok").getAsLong(), res.get()); } }); } diff --git a/src/test/java/com/clevercloud/biscuit/token/SamplesV2Test.java b/src/test/java/com/clevercloud/biscuit/token/SamplesV2Test.java index 7bca5c21..de96809c 100644 --- a/src/test/java/com/clevercloud/biscuit/token/SamplesV2Test.java +++ b/src/test/java/com/clevercloud/biscuit/token/SamplesV2Test.java @@ -322,8 +322,9 @@ public void test13_BlockRules() throws IOException, NoSuchAlgorithmException, Si Authorizer v1 = token.authorizer(); v1.add_fact("resource(\"file1\")"); + v1.add_fact("time(2020-12-21T09:23:12Z)"); //v1.add_fact(fact("time", Arrays.asList(new Term.Date(1608542592)))); - v1.set_time(); + //v1.set_time(); v1.allow(); v1.authorize(new RunLimits(500, 100, Duration.ofMillis(500))); System.out.println(v1.print_world()); @@ -450,9 +451,9 @@ public void test17_Expressions() throws IOException, NoSuchAlgorithmException, S Authorizer v1 = token.authorizer(); v1.allow(); - Tuple2 result = v1.authorize(new RunLimits(500, 100, Duration.ofMillis(500))); - assertEquals(0L, v1.authorize(new RunLimits(500, 100, Duration.ofMillis(500)))._1); - assertEquals(0L, result._2.facts().size()); + Long result = v1.authorize(new RunLimits(500, 100, Duration.ofMillis(500))); + assertEquals(0L, v1.authorize(new RunLimits(500, 100, Duration.ofMillis(500)))); + assertEquals(0L, v1.world.facts().size()); } @Test @@ -513,10 +514,10 @@ public void test20_sealed_token() throws IOException, NoSuchAlgorithmException, v1.add_fact("operation(\"read\")"); v1.add_fact("resource(\"file1\")"); v1.allow(); - Tuple2 result = v1.authorize(new RunLimits(500, 100, Duration.ofMillis(500))); + Long result = v1.authorize(new RunLimits(500, 100, Duration.ofMillis(500))); System.out.println("result: " + result); - assertEquals(0L, v1.authorize(new RunLimits(500, 100, Duration.ofMillis(500)))._1); - assertEquals(5, result._2.facts().size()); + assertEquals(0L, v1.authorize(new RunLimits(500, 100, Duration.ofMillis(500)))); + assertEquals(8, v1.world.facts().size()); } @Test @@ -542,9 +543,9 @@ public void test21_parsing() throws IOException, NoSuchAlgorithmException, Signa ) ))); v1.allow(); - Tuple2 result = v1.authorize(new RunLimits(500, 100, Duration.ofMillis(500))); + Long result = v1.authorize(new RunLimits(500, 100, Duration.ofMillis(500))); System.out.println("result: " + result); - assertEquals(0L, v1.authorize(new RunLimits(500, 100, Duration.ofMillis(500)))._1); - assertEquals(1, result._2.facts().size()); + assertEquals(0L, v1.authorize(new RunLimits(500, 100, Duration.ofMillis(500)))); + assertEquals(2, v1.world.facts().size()); } } diff --git a/src/test/java/com/clevercloud/biscuit/token/UnverifiedBiscuitTest.java b/src/test/java/com/clevercloud/biscuit/token/UnverifiedBiscuitTest.java index cb6644f0..566bf56f 100644 --- a/src/test/java/com/clevercloud/biscuit/token/UnverifiedBiscuitTest.java +++ b/src/test/java/com/clevercloud/biscuit/token/UnverifiedBiscuitTest.java @@ -2,6 +2,7 @@ import com.clevercloud.biscuit.crypto.KeyPair; import com.clevercloud.biscuit.datalog.Fact; +import com.clevercloud.biscuit.datalog.RunLimits; import com.clevercloud.biscuit.datalog.SymbolTable; import com.clevercloud.biscuit.error.Error; import com.clevercloud.biscuit.error.FailedCheck; @@ -13,7 +14,9 @@ import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; import java.security.SignatureException; +import java.time.Duration; import java.util.ArrayList; +import java.util.Arrays; import java.util.HashMap; import java.util.List; @@ -121,31 +124,25 @@ public void testBasic() throws Error, NoSuchAlgorithmException, SignatureExcepti // check System.out.println("will check the token for resource=file1 and operation=read"); - SymbolTable check_symbols = new SymbolTable(finalBiscuit.symbols); - List ambient_facts = List.of( - fact("resource", List.of(s("file1"))).convert(check_symbols), - fact("operation", List.of(s("read"))).convert(check_symbols) - ); - - finalBiscuit.check(check_symbols, ambient_facts, - new ArrayList<>(), new ArrayList<>(), new HashMap<>()); + Authorizer authorizer = finalBiscuit.authorizer(); + authorizer.add_fact("resource(\"file1\")"); + authorizer.add_fact("operation(\"read\")"); + authorizer.add_policy("allow if true"); + authorizer.authorize(new RunLimits(500, 100, Duration.ofMillis(500))); System.out.println("will check the token for resource=file2 and operation=write"); - SymbolTable check_symbols2 = new SymbolTable(finalBiscuit.symbols); - List ambient_facts2 = List.of( - fact("resource", List.of(s("file2"))).convert(check_symbols2), - fact("operation", List.of(s("write"))).convert(check_symbols2) - ); + Authorizer authorizer2 = finalBiscuit.authorizer(); + authorizer2.add_fact("resource(\"file2\")"); + authorizer2.add_fact("operation(\"write\")"); + authorizer2.add_policy("allow if true"); try { - finalBiscuit.check(check_symbols2, ambient_facts2, - new ArrayList<>(), new ArrayList<>(), new HashMap<>()); - fail(); + authorizer2.authorize(new RunLimits(500, 100, Duration.ofMillis(500))); } catch (Error e) { System.out.println(e); assertEquals( - new Error.FailedLogic(new LogicError.Unauthorized(new LogicError.MatchedPolicy.Allow(0), List.of( + new Error.FailedLogic(new LogicError.Unauthorized(new LogicError.MatchedPolicy.Allow(0), Arrays.asList( new FailedCheck.FailedBlock(1, 0, "check if resource($resource), operation(\"read\"), right($resource, \"read\")"), new FailedCheck.FailedBlock(2, 0, "check if resource(\"file1\")") ))), From c6d27a168e6a30fb29f8920f30dbde39311e3c7d Mon Sep 17 00:00:00 2001 From: Geoffroy Couprie Date: Wed, 3 Jan 2024 10:29:37 +0100 Subject: [PATCH 4/6] cleanup --- .../biscuit/datalog/AuthorizedWorld.java | 14 --- .../biscuit/datalog/Combinator.java | 18 +-- .../clevercloud/biscuit/datalog/FactSet.java | 26 ++-- .../clevercloud/biscuit/datalog/Origin.java | 7 +- .../com/clevercloud/biscuit/datalog/Rule.java | 96 +++++++------- .../clevercloud/biscuit/datalog/RuleSet.java | 16 +-- .../biscuit/datalog/SchemaVersion.java | 29 +++-- .../clevercloud/biscuit/datalog/Scope.java | 9 +- .../biscuit/datalog/SymbolTable.java | 4 +- .../biscuit/datalog/TemporarySymbolTable.java | 10 +- .../biscuit/datalog/TrustedOrigins.java | 18 +-- .../clevercloud/biscuit/datalog/World.java | 41 +----- .../clevercloud/biscuit/token/Authorizer.java | 66 +++++----- .../biscuit/token/builder/Scope.java | 20 ++- .../token/format/SerializedBiscuit.java | 119 +++++++----------- .../biscuit/datalog/WorldTest.java | 36 +++--- .../biscuit/token/BiscuitTest.java | 3 - .../biscuit/token/ExampleTest.java | 10 -- .../biscuit/token/SamplesJsonV2Test.java | 2 - .../biscuit/token/SamplesV2Test.java | 3 - 20 files changed, 216 insertions(+), 331 deletions(-) delete mode 100644 src/main/java/com/clevercloud/biscuit/datalog/AuthorizedWorld.java diff --git a/src/main/java/com/clevercloud/biscuit/datalog/AuthorizedWorld.java b/src/main/java/com/clevercloud/biscuit/datalog/AuthorizedWorld.java deleted file mode 100644 index 0058fb0a..00000000 --- a/src/main/java/com/clevercloud/biscuit/datalog/AuthorizedWorld.java +++ /dev/null @@ -1,14 +0,0 @@ -package com.clevercloud.biscuit.datalog; - -import java.util.Set; - -public class AuthorizedWorld extends World { - - public AuthorizedWorld(FactSet facts) { - super(facts); - } - - /*public final Set queryAll(final Rule rule, SymbolTable symbols) { - return this.query_rule(rule, symbols); - }*/ -} diff --git a/src/main/java/com/clevercloud/biscuit/datalog/Combinator.java b/src/main/java/com/clevercloud/biscuit/datalog/Combinator.java index 3f11261a..bcde41cf 100644 --- a/src/main/java/com/clevercloud/biscuit/datalog/Combinator.java +++ b/src/main/java/com/clevercloud/biscuit/datalog/Combinator.java @@ -22,7 +22,7 @@ public final class Combinator implements Serializable, Iterator> next() { - if(this.nextElement == null || !this.nextElement.isDefined()) { + if (this.nextElement == null || !this.nextElement.isDefined()) { this.nextElement = getNext(); } - if(this.nextElement == null || !this.nextElement.isDefined()) { + if (this.nextElement == null || !this.nextElement.isDefined()) { throw new NoSuchElementException(); } else { Tuple2> t = this.nextElement.get(); @@ -46,7 +46,7 @@ public Tuple2> next() { public Option>> getNext() { if (this.predicates.isEmpty()) { final Option> v_opt = this.variables.complete(); - if(v_opt.isEmpty()) { + if (v_opt.isEmpty()) { return Option.none(); } else { Map variables = v_opt.get(); @@ -54,15 +54,15 @@ public Option>> getNext() { // we should return a value, but only once. To prevent further // successful calls, we create a set of variables that cannot // possibly be completed, so the next call will fail - Set set = new HashSet(); - set.add((long)0); + Set set = new HashSet<>(); + set.add((long) 0); this.variables = new MatchedVariables(set); - return Option.some(new Tuple2(new Origin(), variables)); + return Option.some(new Tuple2<>(new Origin(), variables)); } } - while(true) { + while (true) { if (this.currentIt == null) { Predicate predicate = this.predicates.get(0); @@ -105,7 +105,7 @@ public Option>> getNext() { if (v_opt.isEmpty()) { continue; } else { - return Option.some(new Tuple2(currentOrigin, v_opt.get())); + return Option.some(new Tuple2<>(currentOrigin, v_opt.get())); } } else { this.currentOrigin = currentOrigin; diff --git a/src/main/java/com/clevercloud/biscuit/datalog/FactSet.java b/src/main/java/com/clevercloud/biscuit/datalog/FactSet.java index 07e41e40..ea68118e 100644 --- a/src/main/java/com/clevercloud/biscuit/datalog/FactSet.java +++ b/src/main/java/com/clevercloud/biscuit/datalog/FactSet.java @@ -3,24 +3,23 @@ import io.vavr.Tuple2; import java.util.*; -import java.util.stream.Collectors; import java.util.stream.Stream; public class FactSet { private final HashMap> facts; public FactSet() { - facts = new HashMap(); + facts = new HashMap<>(); } public FactSet(Origin o, HashSet factSet) { - facts = new HashMap(); + facts = new HashMap<>(); facts.put(o, factSet); } public void add(Origin origin, Fact fact) { if(!facts.containsKey(origin)) { - facts.put(origin, new HashSet()); + facts.put(origin, new HashSet<>()); } facts.get(origin).add(fact); } @@ -38,8 +37,7 @@ public FactSet clone() { FactSet newFacts = new FactSet(); for(Map.Entry> entry: this.facts.entrySet()) { - HashSet h = new HashSet<>(); - h.addAll(entry.getValue()); + HashSet h = new HashSet<>(entry.getValue()); newFacts.facts.put(entry.getKey(), h); } @@ -55,7 +53,7 @@ public void merge(FactSet other) { } } } - public Stream iterator(TrustedOrigins blockIds) { + public Stream stream(TrustedOrigins blockIds) { return facts.entrySet() .stream() .filter(entry -> { @@ -67,7 +65,7 @@ public Stream iterator(TrustedOrigins blockIds) { .map(fact -> new Tuple2(entry.getKey(), fact))); } - public Stream iterator() { + public Stream stream() { return facts.entrySet() .stream() .flatMap(entry -> entry.getValue() @@ -93,17 +91,17 @@ public int hashCode() { @Override public String toString() { - String res = "FactSet {"; + StringBuilder res = new StringBuilder("FactSet {"); for(Map.Entry> entry: this.facts.entrySet()) { - res += "\n\t"+entry.getKey()+"["; + res.append("\n\t").append(entry.getKey()).append("["); for(Fact fact: entry.getValue()) { - res += "\n\t\t"+fact; + res.append("\n\t\t").append(fact); } - res +="\n]"; + res.append("\n]"); } - res += "\n}"; + res.append("\n}"); - return res; + return res.toString(); } } diff --git a/src/main/java/com/clevercloud/biscuit/datalog/Origin.java b/src/main/java/com/clevercloud/biscuit/datalog/Origin.java index c5279b9e..d18df2d7 100644 --- a/src/main/java/com/clevercloud/biscuit/datalog/Origin.java +++ b/src/main/java/com/clevercloud/biscuit/datalog/Origin.java @@ -27,10 +27,10 @@ public static Origin authorizer() { return new Origin(Long.MAX_VALUE); } public void add(int i) { - inner.add(new Long(i)); + inner.add((long) i); } public void add(long i) { - inner.add(new Long(i)); + inner.add(i); } public void union(Origin other) { @@ -38,8 +38,7 @@ public void union(Origin other) { } public Origin clone() { - final HashSet newInner = new HashSet(); - newInner.addAll(this.inner); + final HashSet newInner = new HashSet<>(this.inner); return new Origin(newInner); } diff --git a/src/main/java/com/clevercloud/biscuit/datalog/Rule.java b/src/main/java/com/clevercloud/biscuit/datalog/Rule.java index ba367c66..8c4834be 100644 --- a/src/main/java/com/clevercloud/biscuit/datalog/Rule.java +++ b/src/main/java/com/clevercloud/biscuit/datalog/Rule.java @@ -50,56 +50,50 @@ public Stream>> apply( .spliteratorUnknownSize(combinator, Spliterator.ORDERED); Stream>> stream = StreamSupport.stream(splitItr, false); - //somehow we have inference errors when writing this as a lambda - return stream.map(t -> { - Origin origin = t._1; - Map generatedVariables = t._2; - TemporarySymbolTable temporarySymbols = new TemporarySymbolTable(symbols); - for (Expression e : this.expressions) { - Option res = e.evaluate(generatedVariables, temporarySymbols); - if (res.isDefined()) { - Term term = res.get(); - if (term instanceof Term.Bool) { - Term.Bool b = (Term.Bool) term; - if (!b.value()) { - return Either.right(new Tuple3(origin, generatedVariables, false)); - } - // continue evaluating if true - } else { - return Either.left(new InvalidType()); - } - } - } - return Either.right(new Tuple3(origin, generatedVariables, true)); - /* - .filter((java.util.function.Predicate>) new java.util.function.Predicate, Boolean>>>() { - //somehow we have inference errors when writing this as a lambda - @Override - public boolean test(Either, Boolean>> res) { - return res.isRight() & res.get()._3.booleanValue(); - } - })*/ - }).filter((java.util.function.Predicate>) - res -> res.isRight() & ((Tuple3, Boolean>)res.get())._3.booleanValue()).map(res -> { - Tuple3, Boolean> t = (Tuple3, Boolean>) res.get(); - Origin origin = t._1; - Map generatedVariables = t._2; - - Predicate p = this.head.clone(); - for (int index = 0; index < p.terms().size(); index++) { - if (p.terms().get(index) instanceof Term.Variable) { - Term.Variable var = (Term.Variable) p.terms().get(index); - if(!generatedVariables.containsKey(var.value())) { - //throw new Error("variables that appear in the head should appear in the body as well"); - return Either.left(new Error.InternalError()); - } - p.terms().set(index, generatedVariables.get(var.value())); - } - } + //somehow we have inference errors when writing this as a lambda + return stream.map(t -> { + Origin origin = t._1; + Map generatedVariables = t._2; + TemporarySymbolTable temporarySymbols = new TemporarySymbolTable(symbols); + for (Expression e : this.expressions) { + Option res = e.evaluate(generatedVariables, temporarySymbols); + if (res.isDefined()) { + Term term = res.get(); + if (term instanceof Term.Bool) { + Term.Bool b = (Term.Bool) term; + if (!b.value()) { + return Either.right(new Tuple3(origin, generatedVariables, false)); + } + // continue evaluating if true + } else { + return Either.left(new InvalidType()); + } + } + } + return Either.right(new Tuple3(origin, generatedVariables, true)); + }) + // sometimes we need to make the compiler happy + .filter((java.util.function.Predicate>) + res -> res.isRight() & ((Tuple3, Boolean>) res.get())._3.booleanValue()).map(res -> { + Tuple3, Boolean> t = (Tuple3, Boolean>) res.get(); + Origin origin = t._1; + Map generatedVariables = t._2; + + Predicate p = this.head.clone(); + for (int index = 0; index < p.terms().size(); index++) { + if (p.terms().get(index) instanceof Term.Variable) { + Term.Variable var = (Term.Variable) p.terms().get(index); + if (!generatedVariables.containsKey(var.value())) { + //throw new Error("variables that appear in the head should appear in the body as well"); + return Either.left(new Error.InternalError()); + } + p.terms().set(index, generatedVariables.get(var.value())); + } + } - origin.add(ruleOrigin); - return Either.right(new Tuple2(origin, new Fact(p))); - }); + origin.add(ruleOrigin); + return Either.right(new Tuple2(origin, new Fact(p))); + }); } private MatchedVariables variablesSet() { @@ -119,7 +113,7 @@ public boolean find_match(final FactSet facts, Long origin, TrustedOrigins scope return variables.check_expressions(this.expressions, symbols).isDefined(); } - Supplier>> factsSupplier = () -> facts.iterator(scope); + Supplier>> factsSupplier = () -> facts.stream(scope); Stream>> stream = this.apply(factsSupplier, origin, symbols); Iterator>> it = stream.iterator(); @@ -144,7 +138,7 @@ public boolean check_match_all(final FactSet facts, TrustedOrigins scope, Symbol return variables.check_expressions(this.expressions, symbols).isDefined(); } - Supplier>> factsSupplier = () -> facts.iterator(scope); + Supplier>> factsSupplier = () -> facts.stream(scope); Combinator combinator = new Combinator(variables, this.body, factsSupplier, symbols); boolean found = false; diff --git a/src/main/java/com/clevercloud/biscuit/datalog/RuleSet.java b/src/main/java/com/clevercloud/biscuit/datalog/RuleSet.java index e4bd56f1..748d642b 100644 --- a/src/main/java/com/clevercloud/biscuit/datalog/RuleSet.java +++ b/src/main/java/com/clevercloud/biscuit/datalog/RuleSet.java @@ -9,36 +9,36 @@ public class RuleSet { public final HashMap>> rules; public RuleSet() { - rules = new HashMap(); + rules = new HashMap<>(); } public void add(Long origin, TrustedOrigins scope, Rule rule) { - if(!rules.containsKey(scope)) { - rules.put(scope, Arrays.asList(new Tuple2(origin, rule))); + if (!rules.containsKey(scope)) { + rules.put(scope, List.of(new Tuple2<>(origin, rule))); } else { - rules.get(scope).add(new Tuple2(origin, rule)); + rules.get(scope).add(new Tuple2<>(origin, rule)); } } public RuleSet clone() { RuleSet newRules = new RuleSet(); - for(Map.Entry>> entry: this.rules.entrySet()) { - List> l = new ArrayList<>(); - l.addAll(entry.getValue()); + for (Map.Entry>> entry : this.rules.entrySet()) { + List> l = new ArrayList<>(entry.getValue()); newRules.rules.put(entry.getKey(), l); } return newRules; } - public Stream iterator() { + public Stream stream() { return rules.entrySet() .stream() .flatMap(entry -> entry.getValue() .stream() .map(t -> t._2)); } + public void clear() { rules.clear(); } diff --git a/src/main/java/com/clevercloud/biscuit/datalog/SchemaVersion.java b/src/main/java/com/clevercloud/biscuit/datalog/SchemaVersion.java index 6c185231..ed7fed73 100644 --- a/src/main/java/com/clevercloud/biscuit/datalog/SchemaVersion.java +++ b/src/main/java/com/clevercloud/biscuit/datalog/SchemaVersion.java @@ -18,21 +18,20 @@ public class SchemaVersion { private boolean containsV4; public SchemaVersion(List facts, List rules, List checks, List scopes) { - // TODO containsScopes = !scopes.isEmpty(); - if(!containsScopes) { - for(Rule r: rules) { + if (!containsScopes) { + for (Rule r : rules) { if (!r.scopes().isEmpty()) { containsScopes = true; break; } } } - if(!containsScopes) { - for(Check check: checks) { - for(Rule query: check.queries()) { - if(!query.scopes().isEmpty()) { + if (!containsScopes) { + for (Check check : checks) { + for (Rule query : check.queries()) { + if (!query.scopes().isEmpty()) { containsScopes = true; break; } @@ -41,7 +40,7 @@ public SchemaVersion(List facts, List rules, List checks, Lis } containsCheckAll = false; - for(Check check: checks) { + for (Check check : checks) { if (check.kind() == All) { containsCheckAll = true; break; @@ -49,8 +48,8 @@ public SchemaVersion(List facts, List rules, List checks, Lis } containsV4 = false; - for(Check check: checks) { - for(Rule query: check.queries()) { + for (Check check : checks) { + for (Rule query : check.queries()) { if (containsV4Ops(query.expressions())) { containsV4 = true; break; @@ -61,7 +60,7 @@ public SchemaVersion(List facts, List rules, List checks, Lis public int version() { if (containsScopes || containsV4 || containsCheckAll) { - return 4; + return 4; } else { return MIN_SCHEMA_VERSION; } @@ -72,10 +71,10 @@ public Either checkCompatibility(int version) { if (containsScopes) { return Left(new Error.FormatError.DeserializationError("v3 blocks must not have scopes")); } - if(containsV4) { + if (containsV4) { return Left(new Error.FormatError.DeserializationError("v3 blocks must not have v4 operators (bitwise operators or !=")); } - if(containsCheckAll) { + if (containsCheckAll) { return Left(new Error.FormatError.DeserializationError("v3 blocks must not use check all")); } } @@ -84,8 +83,8 @@ public Either checkCompatibility(int version) { } public static boolean containsV4Ops(List expressions) { - for(Expression e: expressions) { - for (Op op: e.getOps()) { + for (Expression e : expressions) { + for (Op op : e.getOps()) { if (op instanceof Op.Binary) { Op.Binary b = (Op.Binary) op; switch (b.getOp()) { diff --git a/src/main/java/com/clevercloud/biscuit/datalog/Scope.java b/src/main/java/com/clevercloud/biscuit/datalog/Scope.java index 9ca131cc..6a9e045c 100644 --- a/src/main/java/com/clevercloud/biscuit/datalog/Scope.java +++ b/src/main/java/com/clevercloud/biscuit/datalog/Scope.java @@ -1,11 +1,9 @@ package com.clevercloud.biscuit.datalog; import biscuit.format.schema.Schema; -import com.clevercloud.biscuit.datalog.expressions.Expression; import com.clevercloud.biscuit.error.Error; import io.vavr.control.Either; -import java.util.ArrayList; import static io.vavr.API.Left; import static io.vavr.API.Right; @@ -24,6 +22,7 @@ private Scope(Kind kind, long publicKey) { this.kind = kind; this.publicKey = publicKey; } + public static Scope authority() { return new Scope(Kind.Authority, 0); } @@ -47,7 +46,7 @@ public long publicKey() { public Schema.Scope serialize() { Schema.Scope.Builder b = Schema.Scope.newBuilder(); - switch(this.kind) { + switch (this.kind) { case Authority: b.setScopeType(Schema.Scope.ScopeType.Authority); break; @@ -66,8 +65,8 @@ static public Either deserialize(Schema.Scope scope) { long publicKey = scope.getPublicKey(); return Right(Scope.publicKey(publicKey)); } - if(scope.hasScopeType()) { - switch(scope.getScopeType()) { + if (scope.hasScopeType()) { + switch (scope.getScopeType()) { case Authority: return Right(Scope.authority()); case Previous: diff --git a/src/main/java/com/clevercloud/biscuit/datalog/SymbolTable.java b/src/main/java/com/clevercloud/biscuit/datalog/SymbolTable.java index 480c8d5d..69b38976 100644 --- a/src/main/java/com/clevercloud/biscuit/datalog/SymbolTable.java +++ b/src/main/java/com/clevercloud/biscuit/datalog/SymbolTable.java @@ -229,8 +229,8 @@ public String print_check(final Check c) { } public String print_world(final World w) { - final List facts = w.facts().iterator().map((f) -> this.print_fact(f)).collect(Collectors.toList()); - final List rules = w.rules().iterator().map((r) -> this.print_rule(r)).collect(Collectors.toList()); + final List facts = w.facts().stream().map((f) -> this.print_fact(f)).collect(Collectors.toList()); + final List rules = w.rules().stream().map((r) -> this.print_rule(r)).collect(Collectors.toList()); StringBuilder b = new StringBuilder(); b.append("World {\n\tfacts: [\n\t\t"); diff --git a/src/main/java/com/clevercloud/biscuit/datalog/TemporarySymbolTable.java b/src/main/java/com/clevercloud/biscuit/datalog/TemporarySymbolTable.java index d9055085..6c359d08 100644 --- a/src/main/java/com/clevercloud/biscuit/datalog/TemporarySymbolTable.java +++ b/src/main/java/com/clevercloud/biscuit/datalog/TemporarySymbolTable.java @@ -19,8 +19,8 @@ public TemporarySymbolTable(SymbolTable base) { } public Option get_s(int i) { - if(i >= this.offset) { - if(i - this.offset < this.symbols.size()) { + if (i >= this.offset) { + if (i - this.offset < this.symbols.size()) { return Option.some(this.symbols.get(i - this.offset)); } else { return Option.none(); @@ -32,12 +32,12 @@ public Option get_s(int i) { public long insert(final String symbol) { Option opt = this.base.get(symbol); - if(opt.isDefined()) { - return opt.get().longValue(); + if (opt.isDefined()) { + return opt.get(); } int index = this.symbols.indexOf(symbol); - if(index != -1) { + if (index != -1) { return (long) (this.offset + index); } this.symbols.add(symbol); diff --git a/src/main/java/com/clevercloud/biscuit/datalog/TrustedOrigins.java b/src/main/java/com/clevercloud/biscuit/datalog/TrustedOrigins.java index 6597a24e..eea8b2d5 100644 --- a/src/main/java/com/clevercloud/biscuit/datalog/TrustedOrigins.java +++ b/src/main/java/com/clevercloud/biscuit/datalog/TrustedOrigins.java @@ -1,15 +1,14 @@ package com.clevercloud.biscuit.datalog; import java.util.HashMap; -import java.util.HashSet; import java.util.List; public class TrustedOrigins { - private Origin inner; + private final Origin inner; - public TrustedOrigins(int ... origins) { + public TrustedOrigins(int... origins) { Origin origin = new Origin(); - for(int i: origins) { + for (int i : origins) { origin.add(i); } inner = origin; @@ -20,7 +19,7 @@ private TrustedOrigins() { } private TrustedOrigins(Origin inner) { - if(inner == null) { + if (inner == null) { throw new RuntimeException(); } this.inner = inner; @@ -29,6 +28,7 @@ private TrustedOrigins(Origin inner) { public TrustedOrigins clone() { return new TrustedOrigins(this.inner.clone()); } + public static TrustedOrigins defaultOrigins() { TrustedOrigins origins = new TrustedOrigins(); origins.inner.add(0); @@ -51,21 +51,21 @@ public static TrustedOrigins fromScopes(List ruleScopes, origins.inner.add(currentBlock); origins.inner.add(Long.MAX_VALUE); - for(Scope scope: ruleScopes) { + for (Scope scope : ruleScopes) { switch (scope.kind()) { case Authority: origins.inner.add(0); break; case Previous: - if(currentBlock != Long.MAX_VALUE) { - for(long i = 0; i < currentBlock+1; i++) { + if (currentBlock != Long.MAX_VALUE) { + for (long i = 0; i < currentBlock + 1; i++) { origins.inner.add(i); } } break; case PublicKey: List blockIds = publicKeyToBlockId.get(scope.publicKey()); - if(blockIds != null) { + if (blockIds != null) { origins.inner.inner.addAll(blockIds); } } diff --git a/src/main/java/com/clevercloud/biscuit/datalog/World.java b/src/main/java/com/clevercloud/biscuit/datalog/World.java index 357b6d85..c04fb912 100644 --- a/src/main/java/com/clevercloud/biscuit/datalog/World.java +++ b/src/main/java/com/clevercloud/biscuit/datalog/World.java @@ -1,6 +1,5 @@ package com.clevercloud.biscuit.datalog; -import com.clevercloud.biscuit.datalog.expressions.Expression; import com.clevercloud.biscuit.error.Error; import io.vavr.Tuple2; import io.vavr.control.Either; @@ -9,7 +8,6 @@ import java.time.Instant; import java.util.*; import java.util.function.Supplier; -import java.util.stream.Collectors; import java.util.stream.Stream; public class World implements Serializable { @@ -20,9 +18,6 @@ public void add_fact(final Origin origin, final Fact fact) { this.facts.add(origin, fact); } - /*public void add_facts(final Set facts) { - this.facts.addAll(facts); - }*/ public void add_rule(Long origin, TrustedOrigins scope, Rule rule) { this.rules.add(origin, scope, rule); @@ -45,7 +40,7 @@ public void run(RunLimits limits, final SymbolTable symbols) throws Error { for(Map.Entry>> entry: this.rules.rules.entrySet()) { for(Tuple2 t: entry.getValue()) { - Supplier>> factsSupplier = () -> this.facts.iterator(entry.getKey()); + Supplier>> factsSupplier = () -> this.facts.stream(entry.getKey()); Stream>> stream = t._2.apply(factsSupplier, t._1, symbols); for (Iterator>> it = stream.iterator(); it.hasNext(); ) { @@ -89,33 +84,10 @@ public final FactSet facts() { public RuleSet rules() { return this.rules; } - /*public final Set query(final Predicate pred) { - return this.facts.stream().filter((f) -> { - if (f.predicate().name() != pred.name()) { - return false; - } - final int min_size = Math.min(f.predicate().terms().size(), pred.terms().size()); - for (int i = 0; i < min_size; ++i) { - final Term fid = f.predicate().terms().get(i); - final Term pid = pred.terms().get(i); - if ((fid instanceof Term.Integer || fid instanceof Term.Str || fid instanceof Term.Date) - && fid.getClass() == pid.getClass()) { - if (!fid.equals(pid)) { - return false; - } - // FIXME: is it still necessary? - //} else if (!(fid instanceof Term.Symbol && pid instanceof Term.Variable)) { - // return false; - } - } - return true; - }).collect(Collectors.toSet()); - }*/ - public final FactSet query_rule(final Rule rule, Long origin, TrustedOrigins scope, SymbolTable symbols) throws Error { final FactSet newFacts = new FactSet(); - Supplier>> factsSupplier = () -> this.facts.iterator(scope); + Supplier>> factsSupplier = () -> this.facts.stream(scope); Stream>> stream = rule.apply(factsSupplier, origin, symbols); for (Iterator>> it = stream.iterator(); it.hasNext(); ) { @@ -157,11 +129,6 @@ public World(FactSet facts, RuleSet rules) { this.rules = rules.clone(); } - /*public World(Set facts, List rules, List checks) { - this.facts = facts; - this.rules = rules; - }*/ - public World(World w) { this.facts = w.facts.clone(); this.rules = w.rules.clone(); @@ -171,14 +138,14 @@ public String print(SymbolTable symbol_table) { StringBuilder s = new StringBuilder(); s.append("World {\n\t\tfacts: ["); - for (Iterator it = this.facts.iterator().iterator(); it.hasNext(); ) { + for (Iterator it = this.facts.stream().iterator(); it.hasNext(); ) { Fact f = it.next(); s.append("\n\t\t\t"); s.append(symbol_table.print_fact(f)); } s.append("\n\t\t]\n\t\trules: ["); - for (Iterator it = this.rules.iterator().iterator(); it.hasNext(); ) { + for (Iterator it = this.rules.stream().iterator(); it.hasNext(); ) { Rule r = it.next(); s.append("\n\t\t\t"); s.append(symbol_table.print_rule(r)); diff --git a/src/main/java/com/clevercloud/biscuit/token/Authorizer.java b/src/main/java/com/clevercloud/biscuit/token/Authorizer.java index 27bb5ce1..aaf68dcc 100644 --- a/src/main/java/com/clevercloud/biscuit/token/Authorizer.java +++ b/src/main/java/com/clevercloud/biscuit/token/Authorizer.java @@ -51,7 +51,7 @@ private Authorizer(Biscuit token, World w) throws Error.FailedLogic { * Creates an empty authorizer *

* used to apply policies when unauthenticated (no token) - * and to preload a authorizer that is cloned for each new request + * and to preload an authorizer that is cloned for each new request */ public Authorizer() { this.world = new World(); @@ -81,7 +81,7 @@ private Authorizer(Biscuit token, List checks, List policies, * also checks that the token is valid for this root public key * * @param token - * @return + * @return Authorizer */ static public Authorizer make(Biscuit token) throws Error.FailedLogic { return new Authorizer(token, new World()); @@ -191,7 +191,7 @@ public Authorizer add_check(String s) throws Error.Parser { } public Authorizer set_time() throws Error.Language { - world.add_fact(Origin.authorizer(), fact("time", Arrays.asList(date(new Date()))).convert(symbols)); + world.add_fact(Origin.authorizer(), fact("time", List.of(date(new Date()))).convert(symbols)); return this; } @@ -200,8 +200,8 @@ public List get_revocation_ids() throws Error { final Rule getRevocationIds = rule( "revocation_id", - Arrays.asList(var("id")), - Arrays.asList(pred("revocation_id", Arrays.asList(var("id")))) + List.of(var("id")), + List.of(pred("revocation_id", List.of(var("id")))) ); this.query(getRevocationIds).stream().forEach(fact -> { @@ -222,7 +222,7 @@ public Authorizer allow() { "allow", new ArrayList<>(), new ArrayList<>(), - Arrays.asList(new Expression.Value(new Term.Bool(true))) + List.of(new Expression.Value(new Term.Bool(true))) )); this.policies.add(new Policy(q, Policy.Kind.Allow)); @@ -236,7 +236,7 @@ public Authorizer deny() { "deny", new ArrayList<>(), new ArrayList<>(), - Arrays.asList(new Expression.Value(new Term.Bool(true))) + List.of(new Expression.Value(new Term.Bool(true))) )); this.policies.add(new Policy(q, Policy.Kind.Deny)); @@ -262,6 +262,11 @@ public Authorizer add_policy(Policy p) { return this; } + public Authorizer add_scope(Scope s) { + this.scopes.add(s); + return this; + } + public Set query(Rule query) throws Error { return this.query(query, new RunLimits()); } @@ -282,17 +287,6 @@ public Set query(String s) throws Error { public Set query(Rule query, RunLimits limits) throws Error { world.run(limits, symbols); - /* - let rule_trusted_origins = TrustedOrigins::from_scopes( - &rule.scopes, - &TrustedOrigins::default(), // for queries, we don't want to default on the authorizer trust - // queries are there to explore the final state of the world, - // whereas authorizer contents are there to authorize or not - // a token - usize::MAX, - &self.public_key_to_block_id, - ); - */ com.clevercloud.biscuit.datalog.Rule rule = query.convert(symbols); TrustedOrigins ruleTrustedorigins = TrustedOrigins.fromScopes( rule.scopes(), @@ -305,8 +299,8 @@ public Set query(Rule query, RunLimits limits) throws Error { ruleTrustedorigins, symbols); Set s = new HashSet<>(); - for (Iterator it = facts.iterator().iterator(); it.hasNext(); ) { - com.clevercloud.biscuit.datalog.Fact f = (com.clevercloud.biscuit.datalog.Fact) it.next(); + for (Iterator it = facts.stream().iterator(); it.hasNext(); ) { + com.clevercloud.biscuit.datalog.Fact f = it.next(); s.add(Fact.convert_from(f, symbols)); } @@ -394,7 +388,6 @@ public Long authorize(RunLimits limits) throws Error { } if (token != null) { - TrustedOrigins authorityTrustedOrigins = TrustedOrigins.fromScopes( token.authority.scopes, TrustedOrigins.defaultOrigins(), @@ -462,9 +455,9 @@ public Long authorize(RunLimits limits) throws Error { if (res) { if (this.policies.get(i).kind == Policy.Kind.Allow) { - policy_result = Option.some(Right(Integer.valueOf(i))); + policy_result = Option.some(Right(i)); } else { - policy_result = Option.some(Left(Integer.valueOf(i))); + policy_result = Option.some(Left(i)); } break policies_test; } @@ -550,12 +543,12 @@ public Long authorize(RunLimits limits) throws Error { Either e = policy_result.get(); if (e.isRight()) { if (errors.isEmpty()) { - return Long.valueOf(e.get().longValue()); + return e.get().longValue(); } else { - throw new Error.FailedLogic(new LogicError.Unauthorized(new LogicError.MatchedPolicy.Allow(e.get().intValue()), errors)); + throw new Error.FailedLogic(new LogicError.Unauthorized(new LogicError.MatchedPolicy.Allow(e.get()), errors)); } } else { - throw new Error.FailedLogic(new LogicError.Unauthorized(new LogicError.MatchedPolicy.Deny(e.getLeft().intValue()), errors)); + throw new Error.FailedLogic(new LogicError.Unauthorized(new LogicError.MatchedPolicy.Deny(e.getLeft()), errors)); } } else { throw new Error.FailedLogic(new LogicError.NoMatchingPolicy(errors)); @@ -564,8 +557,8 @@ public Long authorize(RunLimits limits) throws Error { public String print_world() { //FIXME - final List facts = this.world.facts().iterator().map((f) -> this.symbols.print_fact(f)).collect(Collectors.toList()); - final List rules = this.world.rules().iterator().map((r) -> this.symbols.print_rule(r)).collect(Collectors.toList()); + final List facts = this.world.facts().stream().map((f) -> this.symbols.print_fact(f)).collect(Collectors.toList()); + final List rules = this.world.rules().stream().map((r) -> this.symbols.print_rule(r)).collect(Collectors.toList()); List checks = new ArrayList<>(); @@ -587,15 +580,12 @@ public String print_world() { } } - StringBuilder b = new StringBuilder(); - b.append("World {\n\tfacts: [\n\t\t"); - b.append(String.join(",\n\t\t", facts)); - b.append("\n\t],\n\trules: [\n\t\t"); - b.append(String.join(",\n\t\t", rules)); - b.append("\n\t],\n\tchecks: [\n\t\t"); - b.append(String.join(",\n\t\t", checks)); - b.append("\n\t]\n}"); - - return b.toString(); + return "World {\n\tfacts: [\n\t\t" + + String.join(",\n\t\t", facts) + + "\n\t],\n\trules: [\n\t\t" + + String.join(",\n\t\t", rules) + + "\n\t],\n\tchecks: [\n\t\t" + + String.join(",\n\t\t", checks) + + "\n\t]\n}"; } } diff --git a/src/main/java/com/clevercloud/biscuit/token/builder/Scope.java b/src/main/java/com/clevercloud/biscuit/token/builder/Scope.java index bec24afc..30092854 100644 --- a/src/main/java/com/clevercloud/biscuit/token/builder/Scope.java +++ b/src/main/java/com/clevercloud/biscuit/token/builder/Scope.java @@ -1,18 +1,10 @@ package com.clevercloud.biscuit.token.builder; -import biscuit.format.schema.Schema; import com.clevercloud.biscuit.crypto.PublicKey; import com.clevercloud.biscuit.datalog.SymbolTable; -import com.clevercloud.biscuit.error.Error; -import io.vavr.control.Either; - -import java.util.ArrayList; import java.util.Objects; -import static io.vavr.API.Left; -import static io.vavr.API.Right; - public class Scope { enum Kind { Authority, @@ -28,19 +20,21 @@ enum Kind { private Scope(Kind kind) { this.kind = kind; this.publicKey = null; - this.parameter = new String(); + this.parameter = ""; } private Scope(Kind kind, PublicKey publicKey) { this.kind = kind; this.publicKey = publicKey; - this.parameter = new String(); + this.parameter = ""; } + private Scope(Kind kind, String parameter) { this.kind = kind; this.publicKey = null; this.parameter = parameter; } + public static Scope authority() { return new Scope(Kind.Authority); } @@ -66,7 +60,7 @@ public com.clevercloud.biscuit.datalog.Scope convert(SymbolTable symbols) { case Parameter: //FIXME return null; - //throw new Exception("Remaining parameter: "+this.parameter); + //throw new Exception("Remaining parameter: "+this.parameter); case PublicKey: return com.clevercloud.biscuit.datalog.Scope.publicKey(symbols.insert(this.publicKey)); } @@ -75,7 +69,7 @@ public com.clevercloud.biscuit.datalog.Scope convert(SymbolTable symbols) { } public static Scope convert_from(com.clevercloud.biscuit.datalog.Scope scope, SymbolTable symbols) { - switch(scope.kind()) { + switch (scope.kind()) { case Authority: return new Scope(Kind.Authority); case Previous: @@ -119,7 +113,7 @@ public String toString() { case Previous: return "previous"; case Parameter: - return "{"+this.parameter+"}"; + return "{" + this.parameter + "}"; case PublicKey: return this.publicKey.toString(); } diff --git a/src/main/java/com/clevercloud/biscuit/token/format/SerializedBiscuit.java b/src/main/java/com/clevercloud/biscuit/token/format/SerializedBiscuit.java index 78a785c2..7ff6e057 100644 --- a/src/main/java/com/clevercloud/biscuit/token/format/SerializedBiscuit.java +++ b/src/main/java/com/clevercloud/biscuit/token/format/SerializedBiscuit.java @@ -38,6 +38,7 @@ public class SerializedBiscuit { /** * Deserializes a SerializedBiscuit from a byte array + * * @param slice * @return */ @@ -53,6 +54,7 @@ static public SerializedBiscuit from_bytes(byte[] slice, PublicKey root) throws /** * Deserializes a SerializedBiscuit from a byte array + * * @param slice * @return */ @@ -61,12 +63,12 @@ static public SerializedBiscuit from_bytes(byte[] slice, KeyDelegate delegate) t Schema.Biscuit data = Schema.Biscuit.parseFrom(slice); Option root_key_id = Option.none(); - if(data.hasRootKeyId()) { + if (data.hasRootKeyId()) { root_key_id = Option.some(data.getRootKeyId()); } Option root = delegate.root_key(root_key_id); - if(root.isEmpty()) { + if (root.isEmpty()) { throw new InvalidKeyException("unknown root key id"); } @@ -77,6 +79,46 @@ static public SerializedBiscuit from_bytes(byte[] slice, KeyDelegate delegate) t } static SerializedBiscuit from_bytes_inner(Schema.Biscuit data, PublicKey root) throws NoSuchAlgorithmException, SignatureException, InvalidKeyException, Error { + SerializedBiscuit b = SerializedBiscuit.deserialize(data); + if (data.hasRootKeyId()) { + b.root_key_id = Option.some(data.getRootKeyId()); + } + + Either res = b.verify(root); + if (res.isLeft()) { + Error e = res.getLeft(); + //System.out.println("verification error: "+e.toString()); + throw e; + } else { + return b; + } + + } + + /** + * Warning: this deserializes without verifying the signature + * + * @param slice + * @return SerializedBiscuit + * @throws Error.FormatError.DeserializationError + */ + static public SerializedBiscuit unsafe_deserialize(byte[] slice) throws Error.FormatError.DeserializationError { + try { + Schema.Biscuit data = Schema.Biscuit.parseFrom(slice); + return SerializedBiscuit.deserialize(data); + } catch (InvalidProtocolBufferException e) { + throw new Error.FormatError.DeserializationError(e.toString()); + } + } + + /** + * Warning: this deserializes without verifying the signature + * + * @param data + * @return SerializedBiscuit + * @throws Error.FormatError.DeserializationError + */ + static private SerializedBiscuit deserialize(Schema.Biscuit data) throws Error.FormatError.DeserializationError { SignedBlock authority = new SignedBlock( data.getAuthority().getBlock().toByteArray(), new PublicKey(data.getAuthority().getNextKey().getAlgorithm(), data.getAuthority().getNextKey().getKey().toByteArray()), @@ -92,8 +134,6 @@ static SerializedBiscuit from_bytes_inner(Schema.Biscuit data, PublicKey root) t )); } - //System.out.println("parsed blocks"); - Option secretKey = Option.none(); if (data.getProof().hasNextSecret()) { secretKey = Option.some(new KeyPair(data.getProof().getNextSecret().toByteArray())); @@ -109,71 +149,8 @@ static SerializedBiscuit from_bytes_inner(Schema.Biscuit data, PublicKey root) t } Proof proof = new Proof(secretKey, signature); - Option root_key_id = Option.none(); - if(data.hasRootKeyId()) { - root_key_id = Option.some(data.getRootKeyId()); - } - //System.out.println("parse proof"); - - SerializedBiscuit b = new SerializedBiscuit(authority, blocks, proof, root_key_id); - - Either res = b.verify(root); - if (res.isLeft()) { - Error e = res.getLeft(); - //System.out.println("verification error: "+e.toString()); - throw e; - } else { - return b; - } - - } - - /** - * Warning: this deserializes without verifying the signature - * - * @param slice - * @return SerializedBiscuit - * @throws Error.FormatError.DeserializationError - */ - static public SerializedBiscuit unsafe_deserialize(byte[] slice) throws Error.FormatError.DeserializationError { - try { - Schema.Biscuit data = Schema.Biscuit.parseFrom(slice); - - SignedBlock authority = new SignedBlock( - data.getAuthority().getBlock().toByteArray(), - new PublicKey(data.getAuthority().getNextKey().getAlgorithm(), data.getAuthority().getNextKey().getKey().toByteArray()), - data.getAuthority().getSignature().toByteArray() - ); - - ArrayList blocks = new ArrayList<>(); - for (Schema.SignedBlock block : data.getBlocksList()) { - blocks.add(new SignedBlock( - block.getBlock().toByteArray(), - new PublicKey(block.getNextKey().getAlgorithm(), block.getNextKey().getKey().toByteArray()), - block.getSignature().toByteArray() - )); - } - - Option secretKey = Option.none(); - if (data.getProof().hasNextSecret()) { - secretKey = Option.some(new KeyPair(data.getProof().getNextSecret().toByteArray())); - } - - Option signature = Option.none(); - if (data.getProof().hasFinalSignature()) { - signature = Option.some(data.getProof().getFinalSignature().toByteArray()); - } - - if (secretKey.isEmpty() && signature.isEmpty()) { - throw new Error.FormatError.DeserializationError("empty proof"); - } - Proof proof = new Proof(secretKey, signature); - - SerializedBiscuit b = new SerializedBiscuit(authority, blocks, proof); - return b; - } catch (InvalidProtocolBufferException e) { - throw new Error.FormatError.DeserializationError(e.toString()); - } + SerializedBiscuit b = new SerializedBiscuit(authority, blocks, proof); + return b; } @@ -314,7 +291,7 @@ public Either verify(PublicKey root) throws NoSuchAlgorithmExceptio byte[] block = this.authority.block; PublicKey next_key = this.authority.key; byte[] signature = this.authority.signature; - if(signature.length != 64){ + if (signature.length != 64) { return Either.left(new Error.FormatError.Signature.InvalidSignatureSize(signature.length)); } algo_buf.putInt(Integer.valueOf(next_key.algorithm.getNumber())); @@ -339,7 +316,7 @@ public Either verify(PublicKey root) throws NoSuchAlgorithmExceptio byte[] block = b.block; PublicKey next_key = b.key; byte[] signature = b.signature; - if(signature.length != 64){ + if (signature.length != 64) { return Either.left(new Error.FormatError.Signature.InvalidSignatureSize(signature.length)); } algo_buf.clear(); diff --git a/src/test/java/com/clevercloud/biscuit/datalog/WorldTest.java b/src/test/java/com/clevercloud/biscuit/datalog/WorldTest.java index ce566b2d..6c25f4a7 100644 --- a/src/test/java/com/clevercloud/biscuit/datalog/WorldTest.java +++ b/src/test/java/com/clevercloud/biscuit/datalog/WorldTest.java @@ -38,8 +38,8 @@ public void testFamily() throws Error { System.out.println("testing r1: " + syms.print_rule(r1)); FactSet query_rule_result = w.query_rule(r1, (long)0, new TrustedOrigins(0), syms); - System.out.println("grandparents query_rules: [" + String.join(", ", query_rule_result.iterator().map((f) -> syms.print_fact(f)).collect(Collectors.toList())) + "]"); - System.out.println("current facts: [" + String.join(", ", w.facts().iterator().map((f) -> syms.print_fact(f)).collect(Collectors.toList())) + "]"); + System.out.println("grandparents query_rules: [" + String.join(", ", query_rule_result.stream().map((f) -> syms.print_fact(f)).collect(Collectors.toList())) + "]"); + System.out.println("current facts: [" + String.join(", ", w.facts().stream().map((f) -> syms.print_fact(f)).collect(Collectors.toList())) + "]"); final Rule r2 = new Rule(new Predicate(grandparent, Arrays.asList(new Term.Variable(syms.insert("grandparent")), new Term.Variable(syms.insert("grandchild")))), Arrays.asList( @@ -58,7 +58,7 @@ public void testFamily() throws Error { Arrays.asList(new Term.Variable(syms.insert("parent")), new Term.Variable(syms.insert("child"))))), new ArrayList<>()); - for (Iterator it = w.query_rule(query1, (long) 0, new TrustedOrigins(0), syms).iterator().iterator(); it.hasNext(); ) { + for (Iterator it = w.query_rule(query1, (long) 0, new TrustedOrigins(0), syms).stream().iterator(); it.hasNext(); ) { Fact fact = it.next(); System.out.println("\t" + syms.print_fact(fact)); } @@ -67,7 +67,7 @@ public void testFamily() throws Error { new ArrayList<>()); System.out.println("parents of B: [" + String.join(", ", w.query_rule(query2, (long) 0, new TrustedOrigins(0), syms) - .iterator().map((f) -> syms.print_fact(f)).collect(Collectors.toSet())) + "]"); + .stream().map((f) -> syms.print_fact(f)).collect(Collectors.toSet())) + "]"); final Rule query3 = new Rule(new Predicate(grandparent, Arrays.asList(new Term.Variable(syms.insert("grandparent")), new Term.Variable(syms.insert("grandchild")))), Arrays.asList(new Predicate(grandparent, Arrays.asList(new Term.Variable(syms.insert("grandparent")), @@ -75,7 +75,7 @@ public void testFamily() throws Error { new ArrayList<>()); System.out.println("grandparents: [" + String.join(", ", w.query_rule(query3, (long) 0, new TrustedOrigins(0), syms) - .iterator().map((f) -> syms.print_fact(f)).collect(Collectors.toSet())) + "]"); + .stream().map((f) -> syms.print_fact(f)).collect(Collectors.toSet())) + "]"); w.add_fact(new Origin(0), new Fact(new Predicate(parent, Arrays.asList(c, e)))); w.run(syms); @@ -87,7 +87,7 @@ public void testFamily() throws Error { new ArrayList<>()); final FactSet res = w.query_rule(query4, (long) 0, new TrustedOrigins(0), syms); System.out.println("grandparents after inserting parent(C, E): [" + String.join(", ", - res.iterator().map((f) -> syms.print_fact(f)).collect(Collectors.toSet())) + "]"); + res.stream().map((f) -> syms.print_fact(f)).collect(Collectors.toSet())) + "]"); final FactSet expected = new FactSet(new Origin(0), new HashSet<>(Arrays.asList( new Fact(new Predicate(grandparent, Arrays.asList(a, c))), @@ -111,7 +111,7 @@ public void testFamily() throws Error { new ArrayList<>()); System.out.println("siblings: [" + String.join(", ", w.query_rule(query5, (long) 0, new TrustedOrigins(0), syms) - .iterator().map((f) -> syms.print_fact(f)).collect(Collectors.toSet())) + "]"); + .stream().map((f) -> syms.print_fact(f)).collect(Collectors.toSet())) + "]"); } @Test @@ -151,7 +151,7 @@ public void testNumbers() throws Error { new Term.Variable(syms.insert("right")), new Term.Variable(syms.insert("id"))))), new ArrayList<>()), (long) 0, new TrustedOrigins(0), syms); - for (Iterator it = res.iterator().iterator(); it.hasNext(); ) { + for (Iterator it = res.stream().iterator(); it.hasNext(); ) { Fact f = it.next(); System.out.println("\t" + syms.print_fact(f)); } @@ -174,7 +174,7 @@ public void testNumbers() throws Error { new Op.Binary(Op.BinaryOp.LessThan) )))) ), (long) 0, new TrustedOrigins(0), syms); - for (Iterator it = res.iterator().iterator(); it.hasNext(); ) { + for (Iterator it = res.stream().iterator(); it.hasNext(); ) { Fact f = it.next(); System.out.println("\t" + syms.print_fact(f)); } @@ -218,7 +218,7 @@ public void testStr() throws Error { w.add_fact(new Origin(0), new Fact(new Predicate(route, Arrays.asList(new Term.Integer(4), app_1, syms.add("mx.example.com"))))); FactSet res = testSuffix(w, syms, suff, route, ".fr"); - for (Iterator it = res.iterator().iterator(); it.hasNext(); ) { + for (Iterator it = res.stream().iterator(); it.hasNext(); ) { Fact f = it.next(); System.out.println("\t" + syms.print_fact(f)); } @@ -227,7 +227,7 @@ public void testStr() throws Error { assertEquals(expected, res); res = testSuffix(w, syms, suff, route, "example.com"); - for (Iterator it = res.iterator().iterator(); it.hasNext(); ) { + for (Iterator it = res.stream().iterator(); it.hasNext(); ) { Fact f = it.next(); System.out.println("\t" + syms.print_fact(f)); } @@ -286,7 +286,7 @@ public void testDate() throws Error { System.out.println("testing r1: " + syms.print_rule(r1)); FactSet res = w.query_rule(r1, (long) 0, new TrustedOrigins(0), syms); - for (Iterator it = res.iterator().iterator(); it.hasNext(); ) { + for (Iterator it = res.stream().iterator(); it.hasNext(); ) { Fact f = it.next(); System.out.println("\t" + syms.print_fact(f)); } @@ -315,7 +315,7 @@ public void testDate() throws Error { System.out.println("testing r2: " + syms.print_rule(r2)); res = w.query_rule(r2, (long) 0, new TrustedOrigins(0), syms); - for (Iterator it = res.iterator().iterator(); it.hasNext(); ) { + for (Iterator it = res.stream().iterator(); it.hasNext(); ) { Fact f = it.next(); System.out.println("\t" + syms.print_fact(f)); } @@ -355,7 +355,7 @@ public void testSet() throws Error { ); System.out.println("testing r1: " + syms.print_rule(r1)); FactSet res = w.query_rule(r1, (long) 0, new TrustedOrigins(0), syms); - for (Iterator it = res.iterator().iterator(); it.hasNext(); ) { + for (Iterator it = res.stream().iterator(); it.hasNext(); ) { Fact f = it.next(); System.out.println("\t" + syms.print_fact(f)); } @@ -381,7 +381,7 @@ public void testSet() throws Error { System.out.println("testing r2: " + syms.print_rule(r2)); res = w.query_rule(r2, (long) 0, new TrustedOrigins(0), syms); - for (Iterator it = res.iterator().iterator(); it.hasNext(); ) { + for (Iterator it = res.stream().iterator(); it.hasNext(); ) { Fact f = it.next(); System.out.println("\t" + syms.print_fact(f)); } @@ -401,7 +401,7 @@ public void testSet() throws Error { ); System.out.println("testing r3: " + syms.print_rule(r3)); res = w.query_rule(r3, (long) 0, new TrustedOrigins(0), syms); - for (Iterator it = res.iterator().iterator(); it.hasNext(); ) { + for (Iterator it = res.stream().iterator(); it.hasNext(); ) { Fact f = it.next(); System.out.println("\t" + syms.print_fact(f)); } @@ -439,7 +439,7 @@ public void testResource() throws Error { System.out.println("testing caveat 1(should return nothing): " + syms.print_rule(r1)); FactSet res = w.query_rule(r1, (long) 0, new TrustedOrigins(0), syms); System.out.println(res); - for (Iterator it = res.iterator().iterator(); it.hasNext(); ) { + for (Iterator it = res.stream().iterator(); it.hasNext(); ) { Fact f = it.next(); System.out.println("\t" + syms.print_fact(f)); } @@ -460,7 +460,7 @@ public void testResource() throws Error { System.out.println("testing caveat 2: " + syms.print_rule(r2)); res = w.query_rule(r2, (long) 0, new TrustedOrigins(0), syms); System.out.println(res); - for (Iterator it = res.iterator().iterator(); it.hasNext(); ) { + for (Iterator it = res.stream().iterator(); it.hasNext(); ) { Fact f = it.next(); System.out.println("\t" + syms.print_fact(f)); } diff --git a/src/test/java/com/clevercloud/biscuit/token/BiscuitTest.java b/src/test/java/com/clevercloud/biscuit/token/BiscuitTest.java index 6848428f..fca634b5 100644 --- a/src/test/java/com/clevercloud/biscuit/token/BiscuitTest.java +++ b/src/test/java/com/clevercloud/biscuit/token/BiscuitTest.java @@ -3,8 +3,6 @@ import com.clevercloud.biscuit.crypto.KeyDelegate; import com.clevercloud.biscuit.crypto.KeyPair; import com.clevercloud.biscuit.crypto.PublicKey; -import com.clevercloud.biscuit.datalog.AuthorizedWorld; -import com.clevercloud.biscuit.datalog.Fact; import com.clevercloud.biscuit.datalog.RunLimits; import com.clevercloud.biscuit.datalog.SymbolTable; import com.clevercloud.biscuit.error.Error; @@ -12,7 +10,6 @@ import com.clevercloud.biscuit.error.LogicError; import com.clevercloud.biscuit.token.builder.Block; -import io.vavr.Tuple2; import io.vavr.control.Option; import io.vavr.control.Try; diff --git a/src/test/java/com/clevercloud/biscuit/token/ExampleTest.java b/src/test/java/com/clevercloud/biscuit/token/ExampleTest.java index 790d55a9..aea687e3 100644 --- a/src/test/java/com/clevercloud/biscuit/token/ExampleTest.java +++ b/src/test/java/com/clevercloud/biscuit/token/ExampleTest.java @@ -1,22 +1,12 @@ package com.clevercloud.biscuit.token; import com.clevercloud.biscuit.crypto.KeyPair; -import com.clevercloud.biscuit.datalog.AuthorizedWorld; import com.clevercloud.biscuit.error.Error; -import com.clevercloud.biscuit.error.LogicError; import com.clevercloud.biscuit.token.builder.Block; -import com.clevercloud.biscuit.token.builder.Fact; -import com.clevercloud.biscuit.token.builder.parser.Parser; -import io.vavr.Tuple2; -import io.vavr.control.Either; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; -import java.security.SecureRandom; import java.security.SignatureException; -import java.util.Set; - -import static io.vavr.API.Left; /* example code for the documentation at https://www.biscuitsec.org * if these functions change, please send a PR to update them at https://github.com/biscuit-auth/website diff --git a/src/test/java/com/clevercloud/biscuit/token/SamplesJsonV2Test.java b/src/test/java/com/clevercloud/biscuit/token/SamplesJsonV2Test.java index 7e42990e..8e172db5 100644 --- a/src/test/java/com/clevercloud/biscuit/token/SamplesJsonV2Test.java +++ b/src/test/java/com/clevercloud/biscuit/token/SamplesJsonV2Test.java @@ -3,13 +3,11 @@ import biscuit.format.schema.Schema; import com.clevercloud.biscuit.crypto.KeyPair; import com.clevercloud.biscuit.crypto.PublicKey; -import com.clevercloud.biscuit.datalog.AuthorizedWorld; import com.clevercloud.biscuit.datalog.RunLimits; import com.clevercloud.biscuit.error.Error; import com.google.gson.Gson; import com.google.gson.JsonElement; import com.google.gson.JsonObject; -import io.vavr.Tuple2; import io.vavr.control.Either; import io.vavr.control.Try; import org.junit.jupiter.api.DynamicTest; diff --git a/src/test/java/com/clevercloud/biscuit/token/SamplesV2Test.java b/src/test/java/com/clevercloud/biscuit/token/SamplesV2Test.java index de96809c..1a20ce65 100644 --- a/src/test/java/com/clevercloud/biscuit/token/SamplesV2Test.java +++ b/src/test/java/com/clevercloud/biscuit/token/SamplesV2Test.java @@ -2,14 +2,12 @@ import biscuit.format.schema.Schema; import com.clevercloud.biscuit.crypto.PublicKey; -import com.clevercloud.biscuit.datalog.AuthorizedWorld; import com.clevercloud.biscuit.datalog.RunLimits; import com.clevercloud.biscuit.error.Error; import com.clevercloud.biscuit.error.FailedCheck; import com.clevercloud.biscuit.error.LogicError; import com.clevercloud.biscuit.token.builder.Check; import com.clevercloud.biscuit.token.builder.Rule; -import io.vavr.Tuple2; import io.vavr.control.Try; import org.junit.jupiter.api.Test; @@ -28,7 +26,6 @@ import static com.clevercloud.biscuit.crypto.TokenSignature.fromHex; import static com.clevercloud.biscuit.token.builder.Utils.*; -import static io.vavr.API.Right; public class SamplesV2Test { From 6878e8211854a0ea5a4d05ecf3175416083c6797 Mon Sep 17 00:00:00 2001 From: Geoffroy Couprie Date: Thu, 4 Jan 2024 16:03:39 +0100 Subject: [PATCH 5/6] verify third party block signatures --- .../clevercloud/biscuit/crypto/PublicKey.java | 22 ++ .../clevercloud/biscuit/datalog/FactSet.java | 4 + .../biscuit/datalog/SymbolTable.java | 20 ++ .../clevercloud/biscuit/datalog/World.java | 12 +- .../clevercloud/biscuit/token/Authorizer.java | 15 +- .../clevercloud/biscuit/token/Biscuit.java | 97 ++------ .../com/clevercloud/biscuit/token/Block.java | 34 ++- .../biscuit/token/UnverifiedBiscuit.java | 55 ++--- .../biscuit/token/builder/Biscuit.java | 10 +- .../biscuit/token/builder/Block.java | 10 +- .../token/format/ExternalSignature.java | 13 + .../token/format/SerializedBiscuit.java | 226 ++++++++++++++---- .../biscuit/token/format/SignedBlock.java | 5 +- 13 files changed, 341 insertions(+), 182 deletions(-) create mode 100644 src/main/java/com/clevercloud/biscuit/token/format/ExternalSignature.java diff --git a/src/main/java/com/clevercloud/biscuit/crypto/PublicKey.java b/src/main/java/com/clevercloud/biscuit/crypto/PublicKey.java index e6e13505..ef29034f 100644 --- a/src/main/java/com/clevercloud/biscuit/crypto/PublicKey.java +++ b/src/main/java/com/clevercloud/biscuit/crypto/PublicKey.java @@ -2,11 +2,17 @@ import biscuit.format.schema.Schema; import biscuit.format.schema.Schema.PublicKey.Algorithm; +import com.clevercloud.biscuit.datalog.Scope; +import com.clevercloud.biscuit.error.Error; import com.clevercloud.biscuit.token.builder.Utils; +import com.google.protobuf.ByteString; +import io.vavr.control.Either; import net.i2p.crypto.eddsa.EdDSAPublicKey; import net.i2p.crypto.eddsa.spec.EdDSAPublicKeySpec; import static com.clevercloud.biscuit.crypto.KeyPair.ed25519; +import static io.vavr.API.Left; +import static io.vavr.API.Right; public class PublicKey { @@ -41,6 +47,22 @@ public PublicKey(Algorithm algorithm, String hex) { this.algorithm = algorithm; } + public Schema.PublicKey serialize() { + Schema.PublicKey.Builder publicKey = Schema.PublicKey.newBuilder(); + publicKey.setKey(ByteString.copyFrom(this.toBytes())); + publicKey.setAlgorithm(this.algorithm); + return publicKey.build(); + } + + static public PublicKey deserialize(Schema.PublicKey pk) throws Error.FormatError.DeserializationError { + if(!pk.hasAlgorithm() || !pk.hasKey() || pk.getAlgorithm() != Algorithm.Ed25519) { + throw new Error.FormatError.DeserializationError("Invalid " + + "public key"); + } + + return new PublicKey(pk.getAlgorithm(), pk.getKey().toByteArray()); + } + @Override public boolean equals(Object o) { if (this == o) return true; diff --git a/src/main/java/com/clevercloud/biscuit/datalog/FactSet.java b/src/main/java/com/clevercloud/biscuit/datalog/FactSet.java index ea68118e..29374f61 100644 --- a/src/main/java/com/clevercloud/biscuit/datalog/FactSet.java +++ b/src/main/java/com/clevercloud/biscuit/datalog/FactSet.java @@ -17,6 +17,10 @@ public FactSet(Origin o, HashSet factSet) { facts.put(o, factSet); } + public HashMap> facts() { + return this.facts; + } + public void add(Origin origin, Fact fact) { if(!facts.containsKey(origin)) { facts.put(origin, new HashSet<>()); diff --git a/src/main/java/com/clevercloud/biscuit/datalog/SymbolTable.java b/src/main/java/com/clevercloud/biscuit/datalog/SymbolTable.java index 69b38976..0b73726f 100644 --- a/src/main/java/com/clevercloud/biscuit/datalog/SymbolTable.java +++ b/src/main/java/com/clevercloud/biscuit/datalog/SymbolTable.java @@ -173,6 +173,12 @@ public String print_rule_body(final Rule r) { } res += String.join(", ", expressions); } + + if(!r.scopes().isEmpty()) { + res += " trusting "; + final List scopes = r.scopes().stream().map((s) -> this.print_scope(s)).collect(Collectors.toList()); + res += String.join(", ", scopes); + } return res; } @@ -180,6 +186,20 @@ public String print_expression(final Expression e) { return e.print(this).get(); } + public String print_scope(final Scope scope) { + switch(scope.kind) { + case Authority: + return "authority"; + case Previous: + return "previous"; + case PublicKey: + Option pk = this.get_pk((int) scope.publicKey); + if(pk.isDefined()) { + return pk.toString(); + } + } + return "?"; + } public String print_predicate(final Predicate p) { List ids = p.terms().stream().map((t) -> { diff --git a/src/main/java/com/clevercloud/biscuit/datalog/World.java b/src/main/java/com/clevercloud/biscuit/datalog/World.java index c04fb912..0eaf2741 100644 --- a/src/main/java/com/clevercloud/biscuit/datalog/World.java +++ b/src/main/java/com/clevercloud/biscuit/datalog/World.java @@ -138,11 +138,13 @@ public String print(SymbolTable symbol_table) { StringBuilder s = new StringBuilder(); s.append("World {\n\t\tfacts: ["); - for (Iterator it = this.facts.stream().iterator(); it.hasNext(); ) { - Fact f = it.next(); - s.append("\n\t\t\t"); - s.append(symbol_table.print_fact(f)); - } + for(Map.Entry> entry: this.facts.facts().entrySet()) { + s.append("\n\t\t\t"+entry.getKey()+":"); + for(Fact f: entry.getValue()) { + s.append("\n\t\t\t\t"); + s.append(symbol_table.print_fact(f)); + } + } s.append("\n\t\t]\n\t\trules: ["); for (Iterator it = this.rules.stream().iterator(); it.hasNext(); ) { diff --git a/src/main/java/com/clevercloud/biscuit/token/Authorizer.java b/src/main/java/com/clevercloud/biscuit/token/Authorizer.java index aaf68dcc..62905d34 100644 --- a/src/main/java/com/clevercloud/biscuit/token/Authorizer.java +++ b/src/main/java/com/clevercloud/biscuit/token/Authorizer.java @@ -557,7 +557,15 @@ public Long authorize(RunLimits limits) throws Error { public String print_world() { //FIXME - final List facts = this.world.facts().stream().map((f) -> this.symbols.print_fact(f)).collect(Collectors.toList()); + StringBuilder facts = new StringBuilder(); + for(Map.Entry> entry: this.world.facts().facts().entrySet()) { + facts.append("\n\t\t"+entry.getKey()+":"); + for(com.clevercloud.biscuit.datalog.Fact f: entry.getValue()) { + facts.append("\n\t\t\t"); + facts.append(this.symbols.print_fact(f)); + } + } + //final List facts = this.world.facts().facts().entrySet().stream().map((f) -> this.symbols.print_fact(f)).collect(Collectors.toList()); final List rules = this.world.rules().stream().map((r) -> this.symbols.print_rule(r)).collect(Collectors.toList()); List checks = new ArrayList<>(); @@ -580,8 +588,9 @@ public String print_world() { } } - return "World {\n\tfacts: [\n\t\t" + - String.join(",\n\t\t", facts) + + return "World {\n\tfacts: [" + + facts.toString() + + //String.join(",\n\t\t", facts) + "\n\t],\n\trules: [\n\t\t" + String.join(",\n\t\t", rules) + "\n\t],\n\tchecks: [\n\t\t" + diff --git a/src/main/java/com/clevercloud/biscuit/token/Biscuit.java b/src/main/java/com/clevercloud/biscuit/token/Biscuit.java index 804a761b..9e9bd17a 100644 --- a/src/main/java/com/clevercloud/biscuit/token/Biscuit.java +++ b/src/main/java/com/clevercloud/biscuit/token/Biscuit.java @@ -7,6 +7,7 @@ import com.clevercloud.biscuit.error.Error; import com.clevercloud.biscuit.token.format.SerializedBiscuit; import com.clevercloud.biscuit.token.format.SignedBlock; +import io.vavr.Tuple3; import io.vavr.control.Either; import io.vavr.control.Option; @@ -14,10 +15,7 @@ import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; import java.security.SignatureException; -import java.util.ArrayList; -import java.util.Base64; -import java.util.Collections; -import java.util.List; +import java.util.*; /** * Biscuit auth token @@ -122,18 +120,21 @@ static private Biscuit make(final SecureRandom rng, final KeyPair root, final Op } else { SerializedBiscuit s = container.get(); List revocation_ids = s.revocation_identifiers(); + HashMap> publicKeyToBlockId = new HashMap<>(); Option c = Option.some(s); - return new Biscuit(authority, blocks, symbols, s, revocation_ids, root_key_id); + return new Biscuit(authority, blocks, symbols, s, publicKeyToBlockId, revocation_ids, root_key_id); } } - Biscuit(Block authority, List blocks, SymbolTable symbols, SerializedBiscuit serializedBiscuit, List revocation_ids) { - super(authority, blocks, symbols, serializedBiscuit, revocation_ids); + Biscuit(Block authority, List blocks, SymbolTable symbols, SerializedBiscuit serializedBiscuit, + HashMap> publicKeyToBlockId, List revocation_ids) { + super(authority, blocks, symbols, serializedBiscuit, publicKeyToBlockId, revocation_ids); } - Biscuit(Block authority, List blocks, SymbolTable symbols, SerializedBiscuit serializedBiscuit, List revocation_ids, Option root_key_id) { - super(authority, blocks, symbols, serializedBiscuit, revocation_ids, root_key_id); + Biscuit(Block authority, List blocks, SymbolTable symbols, SerializedBiscuit serializedBiscuit, + HashMap> publicKeyToBlockId, List revocation_ids, Option root_key_id) { + super(authority, blocks, symbols, serializedBiscuit, publicKeyToBlockId, revocation_ids, root_key_id); } /** @@ -260,81 +261,20 @@ static public Biscuit from_bytes_with_symbols(byte[] data, KeyDelegate delegate, return Biscuit.from_serialized_biscuit(ser, symbols); } - /** - * Fills a Biscuit structure from a deserialized token - * - * @return - */ - @Deprecated - static Biscuit from_serialize_biscuit(SerializedBiscuit ser, SymbolTable symbols) throws Error { - Either authRes = Block.from_bytes(ser.authority.block); - if (authRes.isLeft()) { - Error e = authRes.getLeft(); - throw e; - } - Block authority = authRes.get(); - - ArrayList blocks = new ArrayList<>(); - for (SignedBlock bdata : ser.blocks) { - Either blockRes = Block.from_bytes(bdata.block); - if (blockRes.isLeft()) { - Error e = blockRes.getLeft(); - throw e; - } - blocks.add(blockRes.get()); - } - - for (String s : authority.symbols.symbols) { - symbols.add(s); - } - - for (Block b : blocks) { - for (String s : b.symbols.symbols) { - symbols.add(s); - } - } - - List revocation_ids = ser.revocation_identifiers(); - - return new Biscuit(authority, blocks, symbols, ser, revocation_ids); - } - /** * Fills a Biscuit structure from a deserialized token * * @return */ static Biscuit from_serialized_biscuit(SerializedBiscuit ser, SymbolTable symbols) throws Error { - Either authRes = Block.from_bytes(ser.authority.block); - if (authRes.isLeft()) { - Error e = authRes.getLeft(); - throw e; - } - Block authority = authRes.get(); - - ArrayList blocks = new ArrayList<>(); - for (SignedBlock bdata : ser.blocks) { - Either blockRes = Block.from_bytes(bdata.block); - if (blockRes.isLeft()) { - Error e = blockRes.getLeft(); - throw e; - } - blocks.add(blockRes.get()); - } - - for (String s : authority.symbols.symbols) { - symbols.add(s); - } - - for (Block b : blocks) { - for (String s : b.symbols.symbols) { - symbols.add(s); - } - } + Tuple3, HashMap>> t = ser.extractBlocks(symbols); + Block authority = t._1; + ArrayList blocks = t._2; + HashMap> publicKeyToBlockId = t._3; List revocation_ids = ser.revocation_identifiers(); - return new Biscuit(authority, blocks, symbols, ser, revocation_ids); + return new Biscuit(authority, blocks, symbols, ser, publicKeyToBlockId, revocation_ids); } /** @@ -424,7 +364,10 @@ public Biscuit attenuate(final SecureRandom rng, final KeyPair keypair, Block bl List revocation_ids = container.revocation_identifiers(); - return new Biscuit(copiedBiscuit.authority, blocks, symbols, container, revocation_ids); + HashMap> publicKeyToBlockId = new HashMap<>(); + publicKeyToBlockId.putAll(this.publicKeyToBlockId); + + return new Biscuit(copiedBiscuit.authority, blocks, symbols, container, publicKeyToBlockId, revocation_ids); } /** @@ -448,6 +391,6 @@ public String print() { } public Biscuit copy() throws Error { - return Biscuit.from_serialize_biscuit(this.serializedBiscuit, this.symbols); + return Biscuit.from_serialized_biscuit(this.serializedBiscuit, this.symbols); } } diff --git a/src/main/java/com/clevercloud/biscuit/token/Block.java b/src/main/java/com/clevercloud/biscuit/token/Block.java index b2ab3453..430b800d 100644 --- a/src/main/java/com/clevercloud/biscuit/token/Block.java +++ b/src/main/java/com/clevercloud/biscuit/token/Block.java @@ -1,20 +1,18 @@ package com.clevercloud.biscuit.token; import biscuit.format.schema.Schema; +import com.clevercloud.biscuit.crypto.PublicKey; import com.clevercloud.biscuit.error.Error; import com.clevercloud.biscuit.datalog.*; -import com.clevercloud.biscuit.error.FailedCheck; -import com.clevercloud.biscuit.error.LogicError; import com.clevercloud.biscuit.token.format.SerializedBiscuit; import com.google.protobuf.InvalidProtocolBufferException; import io.vavr.control.Either; +import io.vavr.control.Option; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.util.ArrayList; -import java.util.HashMap; import java.util.List; -import java.util.Set; import static io.vavr.API.Left; import static io.vavr.API.Right; @@ -29,6 +27,7 @@ public class Block { final List rules; final List checks; final List scopes; + final List publicKeys; final long version; /** @@ -43,6 +42,7 @@ public Block(SymbolTable base_symbols) { this.rules = new ArrayList<>(); this.checks = new ArrayList<>(); this.scopes = new ArrayList<>(); + this.publicKeys = new ArrayList<>(); this.version = SerializedBiscuit.MAX_SCHEMA_VERSION; } @@ -54,7 +54,7 @@ public Block(SymbolTable base_symbols) { * @param checks */ public Block(SymbolTable base_symbols, String context, List facts, List rules, List checks, - List scopes, int version) { + List scopes, List publicKeys, int version) { this.symbols = base_symbols; this.context = context; this.facts = facts; @@ -62,6 +62,15 @@ public Block(SymbolTable base_symbols, String context, List facts, List publicKeys() { + return publicKeys; } /** @@ -130,6 +139,10 @@ public Schema.Block serialize() { b.addScope(scope.serialize()); } + for(PublicKey pk: this.publicKeys) { + b.addPublicKeys(pk.serialize()); + } + b.setVersion(SerializedBiscuit.MAX_SCHEMA_VERSION); return b.build(); } @@ -198,6 +211,15 @@ static public Either deserialize(Schema.Block b) { } } + ArrayList publicKeys = new ArrayList<>(); + for (Schema.PublicKey pk: b.getPublicKeysList()) { + try { + publicKeys.add(PublicKey.deserialize(pk)); + } catch(Error.FormatError e) { + return Left(e); + } + } + SchemaVersion schemaVersion = new SchemaVersion(facts, rules, checks, scopes); Either res = schemaVersion.checkCompatibility(version); if (res.isLeft()) { @@ -205,7 +227,7 @@ static public Either deserialize(Schema.Block b) { return Left(e); } - return Right(new Block(symbols, b.getContext(), facts, rules, checks, scopes, version)); + return Right(new Block(symbols, b.getContext(), facts, rules, checks, scopes, publicKeys, version)); } /** diff --git a/src/main/java/com/clevercloud/biscuit/token/UnverifiedBiscuit.java b/src/main/java/com/clevercloud/biscuit/token/UnverifiedBiscuit.java index 88bdbe41..c018d96e 100644 --- a/src/main/java/com/clevercloud/biscuit/token/UnverifiedBiscuit.java +++ b/src/main/java/com/clevercloud/biscuit/token/UnverifiedBiscuit.java @@ -5,10 +5,8 @@ import com.clevercloud.biscuit.crypto.PublicKey; import com.clevercloud.biscuit.datalog.*; import com.clevercloud.biscuit.error.Error; -import com.clevercloud.biscuit.error.FailedCheck; -import com.clevercloud.biscuit.error.LogicError; import com.clevercloud.biscuit.token.format.SerializedBiscuit; -import com.clevercloud.biscuit.token.format.SignedBlock; +import io.vavr.Tuple3; import io.vavr.control.Either; import io.vavr.control.Option; @@ -19,8 +17,6 @@ import java.util.*; import java.util.stream.Collectors; -import static io.vavr.API.Right; - /** * UnverifiedBiscuit auth token. UnverifiedBiscuit means it's deserialized without checking signatures. */ @@ -31,21 +27,27 @@ public class UnverifiedBiscuit { final SerializedBiscuit serializedBiscuit; final List revocation_ids; final Option root_key_id; + final HashMap> publicKeyToBlockId; - UnverifiedBiscuit(Block authority, List blocks, SymbolTable symbols, SerializedBiscuit serializedBiscuit, List revocation_ids) { + UnverifiedBiscuit(Block authority, List blocks, SymbolTable symbols, SerializedBiscuit serializedBiscuit, + HashMap> publicKeyToBlockId, List revocation_ids) { this.authority = authority; this.blocks = blocks; this.symbols = symbols; this.serializedBiscuit = serializedBiscuit; + this.publicKeyToBlockId = publicKeyToBlockId; this.revocation_ids = revocation_ids; this.root_key_id = Option.none(); } - UnverifiedBiscuit(Block authority, List blocks, SymbolTable symbols, SerializedBiscuit serializedBiscuit, List revocation_ids, Option root_key_id) { + UnverifiedBiscuit(Block authority, List blocks, SymbolTable symbols, SerializedBiscuit serializedBiscuit, + HashMap> publicKeyToBlockId, List revocation_ids, + Option root_key_id) { this.authority = authority; this.blocks = blocks; this.symbols = symbols; this.serializedBiscuit = serializedBiscuit; + this.publicKeyToBlockId = publicKeyToBlockId; this.revocation_ids = revocation_ids; this.root_key_id = root_key_id; } @@ -91,36 +93,14 @@ static public UnverifiedBiscuit from_bytes_with_symbols(byte[] data, SymbolTable * @return UnverifiedBiscuit */ static private UnverifiedBiscuit from_serialized_biscuit(SerializedBiscuit ser, SymbolTable symbols) throws Error { - Either authRes = Block.from_bytes(ser.authority.block); - if (authRes.isLeft()) { - Error e = authRes.getLeft(); - throw e; - } - Block authority = authRes.get(); - - ArrayList blocks = new ArrayList<>(); - for (SignedBlock bdata : ser.blocks) { - Either blockRes = Block.from_bytes(bdata.block); - if (blockRes.isLeft()) { - Error e = blockRes.getLeft(); - throw e; - } - blocks.add(blockRes.get()); - } - - for (String s : authority.symbols.symbols) { - symbols.add(s); - } - - for (Block b : blocks) { - for (String s : b.symbols.symbols) { - symbols.add(s); - } - } + Tuple3, HashMap>> t = ser.extractBlocks(symbols); + Block authority = t._1; + ArrayList blocks = t._2; + HashMap> publicKeyToBlockId = t._3; List revocation_ids = ser.revocation_identifiers(); - return new UnverifiedBiscuit(authority, blocks, symbols, ser, revocation_ids); + return new UnverifiedBiscuit(authority, blocks, symbols, ser, publicKeyToBlockId, revocation_ids); } /** @@ -198,8 +178,13 @@ public UnverifiedBiscuit attenuate(final SecureRandom rng, final KeyPair keypair List revocation_ids = container.revocation_identifiers(); - return new UnverifiedBiscuit(copiedBiscuit.authority, blocks, symbols, container, revocation_ids); + HashMap> publicKeyToBlockId = new HashMap<>(); + publicKeyToBlockId.putAll(this.publicKeyToBlockId); + + return new UnverifiedBiscuit(copiedBiscuit.authority, blocks, symbols, container, + publicKeyToBlockId, revocation_ids); } + //FIXME: attenuate 3rd Party public List revocation_identifiers() { return this.revocation_ids.stream() diff --git a/src/main/java/com/clevercloud/biscuit/token/builder/Biscuit.java b/src/main/java/com/clevercloud/biscuit/token/builder/Biscuit.java index 36db1a6e..ee93d09c 100644 --- a/src/main/java/com/clevercloud/biscuit/token/builder/Biscuit.java +++ b/src/main/java/com/clevercloud/biscuit/token/builder/Biscuit.java @@ -1,6 +1,7 @@ package com.clevercloud.biscuit.token.builder; import com.clevercloud.biscuit.crypto.KeyPair; +import com.clevercloud.biscuit.crypto.PublicKey; import com.clevercloud.biscuit.datalog.*; import com.clevercloud.biscuit.datalog.Check; import com.clevercloud.biscuit.datalog.Fact; @@ -24,6 +25,7 @@ public class Biscuit { SecureRandom rng; KeyPair root; int symbol_start; + int publicKeyStart; SymbolTable symbols; String context; List facts; @@ -36,6 +38,7 @@ public Biscuit(final SecureRandom rng, final KeyPair root, SymbolTable base_symb this.rng = rng; this.root = root; this.symbol_start = base_symbols.symbols.size(); + this.publicKeyStart = base_symbols.publicKeys.size(); this.symbols = new SymbolTable(base_symbols); this.context = ""; this.facts = new ArrayList<>(); @@ -141,10 +144,15 @@ public com.clevercloud.biscuit.token.Biscuit build() throws Error { symbols.add(this.symbols.symbols.get(i)); } + List publicKeys = new ArrayList<>(); + for (int i = this.publicKeyStart; i < this.symbols.publicKeys.size(); i++) { + publicKeys.add(this.symbols.publicKeys.get(i)); + } + SchemaVersion schemaVersion = new SchemaVersion(this.facts, this.rules, this.checks, this.scopes); Block authority_block = new com.clevercloud.biscuit.token.Block(symbols, context, this.facts, this.rules, - this.checks, scopes, schemaVersion.version()); + this.checks, scopes, publicKeys, schemaVersion.version()); if (this.root_key_id.isDefined()) { return make(this.rng, this.root, this.root_key_id.get(), base_symbols, authority_block); diff --git a/src/main/java/com/clevercloud/biscuit/token/builder/Block.java b/src/main/java/com/clevercloud/biscuit/token/builder/Block.java index 80d16a6c..095da661 100644 --- a/src/main/java/com/clevercloud/biscuit/token/builder/Block.java +++ b/src/main/java/com/clevercloud/biscuit/token/builder/Block.java @@ -1,6 +1,7 @@ package com.clevercloud.biscuit.token.builder; +import com.clevercloud.biscuit.crypto.PublicKey; import com.clevercloud.biscuit.datalog.*; import com.clevercloud.biscuit.datalog.Check; import com.clevercloud.biscuit.datalog.Fact; @@ -22,6 +23,7 @@ public class Block { long index; int symbol_start; + int publicKeyStart; SymbolTable symbols; String context; List facts; @@ -32,6 +34,7 @@ public class Block { public Block(long index, SymbolTable base_symbols) { this.index = index; this.symbol_start = base_symbols.symbols.size(); + this.publicKeyStart = base_symbols.publicKeys.size(); this.symbols = new SymbolTable(base_symbols); this.context = ""; this.facts = new ArrayList<>(); @@ -111,10 +114,15 @@ public com.clevercloud.biscuit.token.Block build() { symbols.add(this.symbols.symbols.get(i)); } + List publicKeys = new ArrayList<>(); + for (int i = this.publicKeyStart; i < this.symbols.publicKeys.size(); i++) { + publicKeys.add(this.symbols.publicKeys.get(i)); + } + SchemaVersion schemaVersion = new SchemaVersion(this.facts, this.rules, this.checks, this.scopes); return new com.clevercloud.biscuit.token.Block(symbols, this.context, this.facts, this.rules, this.checks, - this.scopes, schemaVersion.version()); + this.scopes, publicKeys, schemaVersion.version()); } public Block check_right(String right) { diff --git a/src/main/java/com/clevercloud/biscuit/token/format/ExternalSignature.java b/src/main/java/com/clevercloud/biscuit/token/format/ExternalSignature.java new file mode 100644 index 00000000..c816566a --- /dev/null +++ b/src/main/java/com/clevercloud/biscuit/token/format/ExternalSignature.java @@ -0,0 +1,13 @@ +package com.clevercloud.biscuit.token.format; + +import com.clevercloud.biscuit.crypto.PublicKey; + +public class ExternalSignature { + public PublicKey key; + public byte[] signature; + + public ExternalSignature(PublicKey key, byte[] signature) { + this.key = key; + this.signature = signature; + } +} diff --git a/src/main/java/com/clevercloud/biscuit/token/format/SerializedBiscuit.java b/src/main/java/com/clevercloud/biscuit/token/format/SerializedBiscuit.java index 7ff6e057..f8f7938f 100644 --- a/src/main/java/com/clevercloud/biscuit/token/format/SerializedBiscuit.java +++ b/src/main/java/com/clevercloud/biscuit/token/format/SerializedBiscuit.java @@ -4,10 +4,12 @@ import com.clevercloud.biscuit.crypto.KeyDelegate; import com.clevercloud.biscuit.crypto.KeyPair; import com.clevercloud.biscuit.crypto.PublicKey; +import com.clevercloud.biscuit.datalog.SymbolTable; import com.clevercloud.biscuit.error.Error; import com.clevercloud.biscuit.token.Block; import com.google.protobuf.ByteString; import com.google.protobuf.InvalidProtocolBufferException; +import io.vavr.Tuple3; import io.vavr.control.Either; import io.vavr.control.Option; import net.i2p.crypto.eddsa.EdDSAEngine; @@ -17,8 +19,7 @@ import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.security.*; -import java.util.ArrayList; -import java.util.List; +import java.util.*; import static com.clevercloud.biscuit.crypto.KeyPair.ed25519; import static io.vavr.API.Left; @@ -119,18 +120,33 @@ static public SerializedBiscuit unsafe_deserialize(byte[] slice) throws Error.Fo * @throws Error.FormatError.DeserializationError */ static private SerializedBiscuit deserialize(Schema.Biscuit data) throws Error.FormatError.DeserializationError { + if(data.getAuthority().hasExternalSignature()) { + throw new Error.FormatError.DeserializationError("the authority block must not contain an external signature"); + } + SignedBlock authority = new SignedBlock( data.getAuthority().getBlock().toByteArray(), - new PublicKey(data.getAuthority().getNextKey().getAlgorithm(), data.getAuthority().getNextKey().getKey().toByteArray()), - data.getAuthority().getSignature().toByteArray() + PublicKey.deserialize(data.getAuthority().getNextKey()), + data.getAuthority().getSignature().toByteArray(), + Option.none() ); ArrayList blocks = new ArrayList<>(); for (Schema.SignedBlock block : data.getBlocksList()) { + Option external = Option.none(); + if(block.hasExternalSignature() && block.getExternalSignature().hasPublicKey() + && block.getExternalSignature().hasSignature()) { + Schema.ExternalSignature ex = block.getExternalSignature(); + external = Option.some(new ExternalSignature( + PublicKey.deserialize(ex.getPublicKey()), + ex.getSignature().toByteArray())); + + } blocks.add(new SignedBlock( block.getBlock().toByteArray(), - new PublicKey(block.getNextKey().getAlgorithm(), block.getNextKey().getKey().toByteArray()), - block.getSignature().toByteArray() + PublicKey.deserialize(block.getNextKey()), + block.getSignature().toByteArray(), + external )); } @@ -164,22 +180,16 @@ public byte[] serialize() throws Error.FormatError.SerializationError { Schema.SignedBlock.Builder authorityBuilder = Schema.SignedBlock.newBuilder(); { SignedBlock block = this.authority; - Schema.PublicKey.Builder publicKey = Schema.PublicKey.newBuilder(); - publicKey.setKey(ByteString.copyFrom(block.key.toBytes())); - publicKey.setAlgorithm(block.key.algorithm); authorityBuilder.setBlock(ByteString.copyFrom(block.block)); - authorityBuilder.setNextKey(publicKey.build()); + authorityBuilder.setNextKey(block.key.serialize()); authorityBuilder.setSignature(ByteString.copyFrom(block.signature)); } biscuitBuilder.setAuthority(authorityBuilder.build()); for (SignedBlock block : this.blocks) { Schema.SignedBlock.Builder blockBuilder = Schema.SignedBlock.newBuilder(); - Schema.PublicKey.Builder publicKey = Schema.PublicKey.newBuilder(); - publicKey.setKey(ByteString.copyFrom(block.key.toBytes())); - publicKey.setAlgorithm(block.key.algorithm); blockBuilder.setBlock(ByteString.copyFrom(block.block)); - blockBuilder.setNextKey(publicKey.build()); + blockBuilder.setNextKey(block.key.serialize()); blockBuilder.setSignature(ByteString.copyFrom(block.signature)); biscuitBuilder.addBlocks(blockBuilder.build()); @@ -235,7 +245,7 @@ static public Either make(final KeyPair ro sgr.update(next_key.toBytes()); byte[] signature = sgr.sign(); - SignedBlock signedBlock = new SignedBlock(block, next_key, signature); + SignedBlock signedBlock = new SignedBlock(block, next_key, signature, Option.none()); Proof proof = new Proof(next); return Right(new SerializedBiscuit(signedBlock, new ArrayList<>(), proof, root_key_id)); @@ -268,7 +278,7 @@ public Either append(final KeyPair next, sgr.update(next_key.toBytes()); byte[] signature = sgr.sign(); - SignedBlock signedBlock = new SignedBlock(block, next_key, signature); + SignedBlock signedBlock = new SignedBlock(block, next_key, signature, Option.none()); ArrayList blocks = new ArrayList<>(); for (SignedBlock bl : this.blocks) { @@ -284,56 +294,65 @@ public Either append(final KeyPair next, } } - public Either verify(PublicKey root) throws NoSuchAlgorithmException, InvalidKeyException, SignatureException { - PublicKey current_key = root; - ByteBuffer algo_buf = ByteBuffer.allocate(4).order(ByteOrder.LITTLE_ENDIAN); - { - byte[] block = this.authority.block; - PublicKey next_key = this.authority.key; - byte[] signature = this.authority.signature; - if (signature.length != 64) { - return Either.left(new Error.FormatError.Signature.InvalidSignatureSize(signature.length)); - } + public Either appendThirdParty(final KeyPair next, + final Block newBlock) { + /*if (this.proof.secretKey.isEmpty()) { + return Left(new Error.FormatError.SerializationError("the token is sealed")); + } + + Schema.Block b = newBlock.serialize(); + try { + ByteArrayOutputStream stream = new ByteArrayOutputStream(); + b.writeTo(stream); + + byte[] block = stream.toByteArray(); + PublicKey next_key = next.public_key(); + ByteBuffer algo_buf = ByteBuffer.allocate(4).order(ByteOrder.LITTLE_ENDIAN); algo_buf.putInt(Integer.valueOf(next_key.algorithm.getNumber())); algo_buf.flip(); - //System.out.println("verifying block "+"authority"+" with current key "+current_key.toHex()+" block "+block+" next key "+next_key.toHex()+" signature "+signature); - Signature sgr = new EdDSAEngine(MessageDigest.getInstance(ed25519.getHashAlgorithm())); - - sgr.initVerify(current_key.key); + sgr.initSign(this.proof.secretKey.get().private_key); sgr.update(block); sgr.update(algo_buf); sgr.update(next_key.toBytes()); - if (sgr.verify(signature)) { - current_key = next_key; + byte[] signature = sgr.sign(); + + SignedBlock signedBlock = new SignedBlock(block, next_key, signature); + + ArrayList blocks = new ArrayList<>(); + for (SignedBlock bl : this.blocks) { + blocks.add(bl); + } + blocks.add(signedBlock); + + Proof proof = new Proof(next); + + return Right(new SerializedBiscuit(this.authority, blocks, proof, root_key_id)); + } catch (IOException | NoSuchAlgorithmException | SignatureException | InvalidKeyException e) { + return Left(new Error.FormatError.SerializationError(e.toString())); + }*/ + throw new RuntimeException("todo"); + } + + public Either verify(PublicKey root) throws NoSuchAlgorithmException, InvalidKeyException, SignatureException { + PublicKey current_key = root; + ByteBuffer algo_buf = ByteBuffer.allocate(4).order(ByteOrder.LITTLE_ENDIAN); + { + Either res = verifyBlockSignature(this.authority, current_key); + if(res.isRight()) { + current_key = res.get(); } else { - return Left(new Error.FormatError.Signature.InvalidSignature("signature error: Verification equation was not satisfied")); + return Left(res.getLeft()); } } for (SignedBlock b : this.blocks) { - byte[] block = b.block; - PublicKey next_key = b.key; - byte[] signature = b.signature; - if (signature.length != 64) { - return Either.left(new Error.FormatError.Signature.InvalidSignatureSize(signature.length)); - } - algo_buf.clear(); - algo_buf.putInt(Integer.valueOf(next_key.algorithm.getNumber())); - algo_buf.flip(); - //System.out.println("verifying block ? with current key "+current_key.toHex()+" block "+block+" next key "+next_key.toHex()+" signature "+signature); - - Signature sgr = new EdDSAEngine(MessageDigest.getInstance(ed25519.getHashAlgorithm())); - - sgr.initVerify(current_key.key); - sgr.update(block); - sgr.update(algo_buf); - sgr.update(next_key.toBytes()); - if (sgr.verify(signature)) { - current_key = next_key; + Either res = verifyBlockSignature(b, current_key); + if(res.isRight()) { + current_key = res.get(); } else { - return Left(new Error.FormatError.Signature.InvalidSignature("signature error: Verification equation was not satisfied")); + return Left(res.getLeft()); } } @@ -386,7 +405,108 @@ public Either verify(PublicKey root) throws NoSuchAlgorithmExceptio } } + } + + static Either verifyBlockSignature(SignedBlock signedBlock, PublicKey publicKey) + throws NoSuchAlgorithmException, InvalidKeyException, SignatureException { + + byte[] block = signedBlock.block; + PublicKey next_key = signedBlock.key; + byte[] signature = signedBlock.signature; + if (signature.length != 64) { + return Either.left(new Error.FormatError.Signature.InvalidSignatureSize(signature.length)); + } + ByteBuffer algo_buf = ByteBuffer.allocate(4).order(ByteOrder.LITTLE_ENDIAN); + algo_buf.putInt(Integer.valueOf(next_key.algorithm.getNumber())); + algo_buf.flip(); + + Signature sgr = new EdDSAEngine(MessageDigest.getInstance(ed25519.getHashAlgorithm())); + sgr.initVerify(publicKey.key); + sgr.update(block); + if(signedBlock.externalSignature.isDefined()) { + sgr.update(signedBlock.externalSignature.get().signature); + } + sgr.update(algo_buf); + sgr.update(next_key.toBytes()); + if (!sgr.verify(signature)) { + return Left(new Error.FormatError.Signature.InvalidSignature("signature error: Verification equation was not satisfied")); + } + + if(signedBlock.externalSignature.isDefined()) { + ByteBuffer algo_buf2 = ByteBuffer.allocate(4).order(ByteOrder.LITTLE_ENDIAN); + algo_buf2.putInt(Integer.valueOf(publicKey.algorithm.getNumber())); + algo_buf2.flip(); + + Signature sgr2 = new EdDSAEngine(MessageDigest.getInstance(ed25519.getHashAlgorithm())); + sgr2.initVerify(signedBlock.externalSignature.get().key.key); + sgr2.update(block); + sgr2.update(algo_buf2); + sgr2.update(publicKey.toBytes()); + if (!sgr2.verify(signedBlock.externalSignature.get().signature)) { + return Left(new Error.FormatError.Signature.InvalidSignature("external signature error: Verification equation was not satisfied")); + } + } + + return Right(next_key); + } + + public Tuple3, HashMap>> extractBlocks(SymbolTable symbols) throws Error { + ArrayList> blockExternalKeys = new ArrayList<>(); + Either authRes = Block.from_bytes(this.authority.block); + if (authRes.isLeft()) { + Error e = authRes.getLeft(); + throw e; + } + Block authority = authRes.get(); + for(PublicKey pk: authority.publicKeys()) { + symbols.publicKeys.add(pk); + } + blockExternalKeys.add(Option.none()); + + + for (String s : authority.symbols().symbols) { + symbols.add(s); + } + + + ArrayList blocks = new ArrayList<>(); + for (SignedBlock bdata : this.blocks) { + Either blockRes = Block.from_bytes(bdata.block); + if (blockRes.isLeft()) { + Error e = blockRes.getLeft(); + throw e; + } + Block block = blockRes.get(); + + // blocks with external signatures keep their own symbol table + if(bdata.externalSignature.isDefined()) { + symbols.publicKeys.add(bdata.externalSignature.get().key); + blockExternalKeys.add(Option.some(bdata.externalSignature.get().key)); + } else { + blockExternalKeys.add(Option.none()); + for (String s : block.symbols().symbols) { + symbols.add(s); + } + } + for(PublicKey pk: block.publicKeys()) { + symbols.publicKeys.add(pk); + } + blocks.add(block); + } + + HashMap> publicKeyToBlockId = new HashMap<>(); + for(int blockIndex = 0; blockIndex < blockExternalKeys.size(); blockIndex++) { + if(blockExternalKeys.get(blockIndex).isDefined()) { + PublicKey pk = blockExternalKeys.get(blockIndex).get(); + long keyIndex = symbols.insert(pk); + if(!publicKeyToBlockId.containsKey(keyIndex)) { + publicKeyToBlockId.put(keyIndex, new ArrayList<>()); + } + publicKeyToBlockId.get(keyIndex).add((long) blockIndex); + } + } + return new Tuple3<>(authority, blocks, publicKeyToBlockId); } public Either seal() throws InvalidKeyException, NoSuchAlgorithmException, SignatureException { diff --git a/src/main/java/com/clevercloud/biscuit/token/format/SignedBlock.java b/src/main/java/com/clevercloud/biscuit/token/format/SignedBlock.java index 1503ab68..9419f277 100644 --- a/src/main/java/com/clevercloud/biscuit/token/format/SignedBlock.java +++ b/src/main/java/com/clevercloud/biscuit/token/format/SignedBlock.java @@ -2,6 +2,7 @@ import com.clevercloud.biscuit.crypto.KeyPair; import com.clevercloud.biscuit.crypto.PublicKey; +import io.vavr.control.Option; import java.util.List; @@ -9,10 +10,12 @@ public class SignedBlock { public byte[] block; public PublicKey key; public byte[] signature; + public Option externalSignature; - public SignedBlock(byte[] block, PublicKey key, byte[] signature) { + public SignedBlock(byte[] block, PublicKey key, byte[] signature, Option externalSignature) { this.block = block; this.key = key; this.signature = signature; + this.externalSignature = externalSignature; } } From 32a53d4fdab932011e495c3c916fd1c197695760 Mon Sep 17 00:00:00 2001 From: Geoffroy Couprie Date: Thu, 4 Jan 2024 16:51:58 +0100 Subject: [PATCH 6/6] authorize third party blocks --- .../clevercloud/biscuit/crypto/PublicKey.java | 3 +- .../biscuit/datalog/Combinator.java | 5 +- .../clevercloud/biscuit/datalog/Origin.java | 6 +- .../clevercloud/biscuit/datalog/Scope.java | 8 ++ .../biscuit/datalog/SymbolTable.java | 16 +++- .../biscuit/datalog/TrustedOrigins.java | 7 ++ .../clevercloud/biscuit/token/Authorizer.java | 94 +++++++++++++------ .../com/clevercloud/biscuit/token/Block.java | 17 +++- .../biscuit/token/builder/Biscuit.java | 10 +- .../biscuit/token/builder/Block.java | 11 ++- .../token/format/SerializedBiscuit.java | 16 ++-- .../biscuit/token/SamplesV2Test.java | 4 +- 12 files changed, 137 insertions(+), 60 deletions(-) diff --git a/src/main/java/com/clevercloud/biscuit/crypto/PublicKey.java b/src/main/java/com/clevercloud/biscuit/crypto/PublicKey.java index ef29034f..640b04b5 100644 --- a/src/main/java/com/clevercloud/biscuit/crypto/PublicKey.java +++ b/src/main/java/com/clevercloud/biscuit/crypto/PublicKey.java @@ -56,8 +56,7 @@ public Schema.PublicKey serialize() { static public PublicKey deserialize(Schema.PublicKey pk) throws Error.FormatError.DeserializationError { if(!pk.hasAlgorithm() || !pk.hasKey() || pk.getAlgorithm() != Algorithm.Ed25519) { - throw new Error.FormatError.DeserializationError("Invalid " + - "public key"); + throw new Error.FormatError.DeserializationError("Invalid public key"); } return new PublicKey(pk.getAlgorithm(), pk.getKey().toByteArray()); diff --git a/src/main/java/com/clevercloud/biscuit/datalog/Combinator.java b/src/main/java/com/clevercloud/biscuit/datalog/Combinator.java index bcde41cf..e32026c3 100644 --- a/src/main/java/com/clevercloud/biscuit/datalog/Combinator.java +++ b/src/main/java/com/clevercloud/biscuit/datalog/Combinator.java @@ -70,7 +70,7 @@ public Option>> getNext() { // we iterate over the facts that match the current predicate if (this.currentFacts.hasNext()) { final Tuple2 t = this.currentFacts.next(); - Origin currentOrigin = t._1; + Origin currentOrigin = t._1.clone(); Fact fact = t._2; // create a new MatchedVariables in which we fix variables we could unify from our first predicate and the current fact @@ -129,8 +129,7 @@ public Option>> getNext() { if (opt.isDefined()) { Tuple2> t = opt.get(); - t._1.union(currentOrigin); - return Option.some(t); + return Option.some(new Tuple2<>(t._1.union(currentOrigin), t._2)); } else { currentOrigin = null; currentIt = null; diff --git a/src/main/java/com/clevercloud/biscuit/datalog/Origin.java b/src/main/java/com/clevercloud/biscuit/datalog/Origin.java index d18df2d7..8f1b86e9 100644 --- a/src/main/java/com/clevercloud/biscuit/datalog/Origin.java +++ b/src/main/java/com/clevercloud/biscuit/datalog/Origin.java @@ -33,8 +33,10 @@ public void add(long i) { inner.add(i); } - public void union(Origin other) { - this.inner.addAll(other.inner); + public Origin union(Origin other) { + Origin o = this.clone(); + o.inner.addAll(other.inner); + return o; } public Origin clone() { diff --git a/src/main/java/com/clevercloud/biscuit/datalog/Scope.java b/src/main/java/com/clevercloud/biscuit/datalog/Scope.java index 6a9e045c..0658bfde 100644 --- a/src/main/java/com/clevercloud/biscuit/datalog/Scope.java +++ b/src/main/java/com/clevercloud/biscuit/datalog/Scope.java @@ -75,4 +75,12 @@ static public Either deserialize(Schema.Scope scope) { } return Left(new Error.FormatError.DeserializationError("invalid Scope")); } + + @Override + public String toString() { + return "Scope{" + + "kind=" + kind + + ", publicKey=" + publicKey + + '}'; + } } diff --git a/src/main/java/com/clevercloud/biscuit/datalog/SymbolTable.java b/src/main/java/com/clevercloud/biscuit/datalog/SymbolTable.java index 0b73726f..6bdeef92 100644 --- a/src/main/java/com/clevercloud/biscuit/datalog/SymbolTable.java +++ b/src/main/java/com/clevercloud/biscuit/datalog/SymbolTable.java @@ -61,7 +61,7 @@ private String fromEpochIsoDate(long epochSec) { "query" ); public final List symbols; - public final List publicKeys; + private final List publicKeys; public long insert(final String symbol) { int index = this.defaultSymbols.indexOf(symbol); @@ -81,6 +81,13 @@ public long insert(final String symbol) { public int currentOffset() { return this.symbols.size(); } + public int currentPublicKeyOffset() { + return this.publicKeys.size(); + } + + public List publicKeys() { + return publicKeys; + } public long insert(final PublicKey publicKey) { int index = this.publicKeys.indexOf(publicKey); @@ -278,6 +285,13 @@ public SymbolTable(SymbolTable s) { publicKeys.addAll(s.publicKeys); } + public SymbolTable(List symbols, List publicKeys) { + this.symbols = new ArrayList<>(); + this.symbols.addAll(symbols); + this.publicKeys = new ArrayList<>(); + this.publicKeys.addAll(publicKeys); + } + public List getAllSymbols() { ArrayList allSymbols = new ArrayList<>(); allSymbols.addAll(defaultSymbols); diff --git a/src/main/java/com/clevercloud/biscuit/datalog/TrustedOrigins.java b/src/main/java/com/clevercloud/biscuit/datalog/TrustedOrigins.java index eea8b2d5..8d1d14f2 100644 --- a/src/main/java/com/clevercloud/biscuit/datalog/TrustedOrigins.java +++ b/src/main/java/com/clevercloud/biscuit/datalog/TrustedOrigins.java @@ -77,4 +77,11 @@ public static TrustedOrigins fromScopes(List ruleScopes, public boolean contains(Origin factOrigin) { return this.inner.inner.containsAll(factOrigin.inner); } + + @Override + public String toString() { + return "TrustedOrigins{" + + "inner=" + inner + + '}'; + } } diff --git a/src/main/java/com/clevercloud/biscuit/token/Authorizer.java b/src/main/java/com/clevercloud/biscuit/token/Authorizer.java index 62905d34..162c82dc 100644 --- a/src/main/java/com/clevercloud/biscuit/token/Authorizer.java +++ b/src/main/java/com/clevercloud/biscuit/token/Authorizer.java @@ -1,5 +1,6 @@ package com.clevercloud.biscuit.token; +import com.clevercloud.biscuit.crypto.PublicKey; import com.clevercloud.biscuit.datalog.*; import com.clevercloud.biscuit.datalog.Scope; import com.clevercloud.biscuit.error.Error; @@ -107,6 +108,11 @@ public void update_on_token() throws Error.FailedLogic { throw new Error.FailedLogic(new LogicError.InvalidBlockRule(0, token.symbols.print_rule(converted_rule))); } } + this.publicKeyToBlockId.putAll(token.publicKeyToBlockId); + for(Long keyId: token.publicKeyToBlockId.keySet()) { + PublicKey pk = token.symbols.get_pk((int) keyId.longValue()).get(); + this.symbols.insert(pk); + } } } @@ -335,8 +341,16 @@ public Long authorize(RunLimits limits) throws Error { if (token != null) { for (com.clevercloud.biscuit.datalog.Fact fact : token.authority.facts) { com.clevercloud.biscuit.datalog.Fact converted_fact = Fact.convert_from(fact, token.symbols).convert(this.symbols); - world.add_fact(authorizerOrigin, converted_fact); + world.add_fact(new Origin(0), converted_fact); } + + TrustedOrigins authorityTrustedOrigins = TrustedOrigins.fromScopes( + token.authority.scopes, + TrustedOrigins.defaultOrigins(), + 0, + this.publicKeyToBlockId + ); + for (com.clevercloud.biscuit.datalog.Rule rule : token.authority.rules) { com.clevercloud.biscuit.token.builder.Rule _rule = Rule.convert_from(rule, token.symbols); com.clevercloud.biscuit.datalog.Rule converted_rule = _rule.convert(this.symbols); @@ -345,6 +359,50 @@ public Long authorize(RunLimits limits) throws Error { if(res.isLeft()){ throw new Error.FailedLogic(new LogicError.InvalidBlockRule(0, token.symbols.print_rule(converted_rule))); } + TrustedOrigins ruleTrustedOrigins = TrustedOrigins.fromScopes( + converted_rule.scopes(), + authorityTrustedOrigins, + 0, + this.publicKeyToBlockId + ); + world.add_rule((long) 0, ruleTrustedOrigins, converted_rule); + } + + for (int i = 0; i < token.blocks.size(); i++) { + Block block = token.blocks.get(i); + TrustedOrigins blockTrustedOrigins = TrustedOrigins.fromScopes( + block.scopes, + TrustedOrigins.defaultOrigins(), + i + 1, + this.publicKeyToBlockId + ); + SymbolTable blockSymbols = token.symbols; + + if (block.externalKey.isDefined()) { + blockSymbols = new SymbolTable(block.symbols.symbols, token.symbols.publicKeys()); + } + + for (com.clevercloud.biscuit.datalog.Fact fact : block.facts) { + com.clevercloud.biscuit.datalog.Fact converted_fact = Fact.convert_from(fact, blockSymbols).convert(this.symbols); + world.add_fact(new Origin(i + 1), converted_fact); + } + + for (com.clevercloud.biscuit.datalog.Rule rule : block.rules) { + com.clevercloud.biscuit.token.builder.Rule _rule = Rule.convert_from(rule, blockSymbols); + com.clevercloud.biscuit.datalog.Rule converted_rule = _rule.convert(this.symbols); + + Either res = _rule.validate_variables(); + if (res.isLeft()) { + throw new Error.FailedLogic(new LogicError.InvalidBlockRule(0, this.symbols.print_rule(converted_rule))); + } + TrustedOrigins ruleTrustedOrigins = TrustedOrigins.fromScopes( + converted_rule.scopes(), + blockTrustedOrigins, + i + 1, + this.publicKeyToBlockId + ); + world.add_rule((long) i + 1, ruleTrustedOrigins, converted_rule); + } } } @@ -398,7 +456,7 @@ public Long authorize(RunLimits limits) throws Error { for (int j = 0; j < token.authority.checks.size(); j++) { boolean successful = false; - Check c = Check.convert_from(token.authority.checks.get(j), symbols); + Check c = Check.convert_from(token.authority.checks.get(j), token.symbols); com.clevercloud.biscuit.datalog.Check check = c.convert(symbols); for (int k = 0; k < check.queries().size(); k++) { @@ -441,13 +499,13 @@ public Long authorize(RunLimits limits) throws Error { for (int j = 0; j < policy.queries.size(); j++) { com.clevercloud.biscuit.datalog.Rule query = policy.queries.get(j).convert(symbols); - TrustedOrigins ruleTrustedOrigins = TrustedOrigins.fromScopes( + TrustedOrigins policyTrustedOrigins = TrustedOrigins.fromScopes( query.scopes(), authorizerTrustedOrigins, Long.MAX_VALUE, this.publicKeyToBlockId ); - boolean res = world.query_match(query, Long.MAX_VALUE, ruleTrustedOrigins, symbols); + boolean res = world.query_match(query, Long.MAX_VALUE, policyTrustedOrigins, symbols); if (Instant.now().compareTo(timeLimit) >= 0) { throw new Error.Timeout(); @@ -473,35 +531,15 @@ public Long authorize(RunLimits limits) throws Error { i+1, this.publicKeyToBlockId ); - - for (com.clevercloud.biscuit.datalog.Fact fact : b.facts) { - com.clevercloud.biscuit.datalog.Fact converted_fact = Fact.convert_from(fact, token.symbols).convert(this.symbols); - world.add_fact(new Origin(i+1), converted_fact); + SymbolTable blockSymbols = token.symbols; + if(b.externalKey.isDefined()) { + blockSymbols = new SymbolTable(b.symbols.symbols, token.symbols.publicKeys()); } - for (com.clevercloud.biscuit.datalog.Rule rule : b.rules) { - com.clevercloud.biscuit.token.builder.Rule _rule = Rule.convert_from(rule, token.symbols); - com.clevercloud.biscuit.datalog.Rule converted_rule = _rule.convert(this.symbols); - - Either res = _rule.validate_variables(); - if(res.isLeft()){ - throw new Error.FailedLogic(new LogicError.InvalidBlockRule(0, token.symbols.print_rule(converted_rule))); - } - TrustedOrigins ruleTrustedOrigins = TrustedOrigins.fromScopes( - converted_rule.scopes(), - blockTrustedOrigins, - i+1, - this.publicKeyToBlockId - ); - world.add_rule((long)i+1, ruleTrustedOrigins, converted_rule); - } - - world.run(limits, symbols); - for (int j = 0; j < b.checks.size(); j++) { boolean successful = false; - Check c = Check.convert_from(b.checks.get(j),symbols); + Check c = Check.convert_from(b.checks.get(j), blockSymbols); com.clevercloud.biscuit.datalog.Check check = c.convert(symbols); for (int k = 0; k < check.queries().size(); k++) { diff --git a/src/main/java/com/clevercloud/biscuit/token/Block.java b/src/main/java/com/clevercloud/biscuit/token/Block.java index 430b800d..4e90f9fc 100644 --- a/src/main/java/com/clevercloud/biscuit/token/Block.java +++ b/src/main/java/com/clevercloud/biscuit/token/Block.java @@ -28,6 +28,7 @@ public class Block { final List checks; final List scopes; final List publicKeys; + final Option externalKey; final long version; /** @@ -43,6 +44,7 @@ public Block(SymbolTable base_symbols) { this.checks = new ArrayList<>(); this.scopes = new ArrayList<>(); this.publicKeys = new ArrayList<>(); + this.externalKey = Option.none(); this.version = SerializedBiscuit.MAX_SCHEMA_VERSION; } @@ -54,7 +56,7 @@ public Block(SymbolTable base_symbols) { * @param checks */ public Block(SymbolTable base_symbols, String context, List facts, List rules, List checks, - List scopes, List publicKeys, int version) { + List scopes, List publicKeys, Option externalKey, int version) { this.symbols = base_symbols; this.context = context; this.facts = facts; @@ -63,6 +65,7 @@ public Block(SymbolTable base_symbols, String context, List facts, List deserialize(Schema.Block b) { + static public Either deserialize(Schema.Block b, Option externalKey) { int version = b.getVersion(); if (version < SerializedBiscuit.MIN_SCHEMA_VERSION || version > SerializedBiscuit.MAX_SCHEMA_VERSION) { return Left(new Error.FormatError.Version(SerializedBiscuit.MIN_SCHEMA_VERSION, SerializedBiscuit.MAX_SCHEMA_VERSION, version)); @@ -227,7 +234,7 @@ static public Either deserialize(Schema.Block b) { return Left(e); } - return Right(new Block(symbols, b.getContext(), facts, rules, checks, scopes, publicKeys, version)); + return Right(new Block(symbols, b.getContext(), facts, rules, checks, scopes, publicKeys, externalKey, version)); } /** @@ -236,10 +243,10 @@ static public Either deserialize(Schema.Block b) { * @param slice * @return */ - static public Either from_bytes(byte[] slice) { + static public Either from_bytes(byte[] slice, Option externalKey) { try { Schema.Block data = Schema.Block.parseFrom(slice); - return Block.deserialize(data); + return Block.deserialize(data, externalKey); } catch (InvalidProtocolBufferException e) { return Left(new Error.FormatError.DeserializationError(e.toString())); } diff --git a/src/main/java/com/clevercloud/biscuit/token/builder/Biscuit.java b/src/main/java/com/clevercloud/biscuit/token/builder/Biscuit.java index ee93d09c..ac6e3ad2 100644 --- a/src/main/java/com/clevercloud/biscuit/token/builder/Biscuit.java +++ b/src/main/java/com/clevercloud/biscuit/token/builder/Biscuit.java @@ -37,8 +37,8 @@ public class Biscuit { public Biscuit(final SecureRandom rng, final KeyPair root, SymbolTable base_symbols) { this.rng = rng; this.root = root; - this.symbol_start = base_symbols.symbols.size(); - this.publicKeyStart = base_symbols.publicKeys.size(); + this.symbol_start = base_symbols.currentOffset(); + this.publicKeyStart = base_symbols.currentPublicKeyOffset(); this.symbols = new SymbolTable(base_symbols); this.context = ""; this.facts = new ArrayList<>(); @@ -145,14 +145,14 @@ public com.clevercloud.biscuit.token.Biscuit build() throws Error { } List publicKeys = new ArrayList<>(); - for (int i = this.publicKeyStart; i < this.symbols.publicKeys.size(); i++) { - publicKeys.add(this.symbols.publicKeys.get(i)); + for (int i = this.publicKeyStart; i < this.symbols.currentPublicKeyOffset(); i++) { + publicKeys.add(this.symbols.publicKeys().get(i)); } SchemaVersion schemaVersion = new SchemaVersion(this.facts, this.rules, this.checks, this.scopes); Block authority_block = new com.clevercloud.biscuit.token.Block(symbols, context, this.facts, this.rules, - this.checks, scopes, publicKeys, schemaVersion.version()); + this.checks, scopes, publicKeys, Option.none(), schemaVersion.version()); if (this.root_key_id.isDefined()) { return make(this.rng, this.root, this.root_key_id.get(), base_symbols, authority_block); diff --git a/src/main/java/com/clevercloud/biscuit/token/builder/Block.java b/src/main/java/com/clevercloud/biscuit/token/builder/Block.java index 095da661..5d639efb 100644 --- a/src/main/java/com/clevercloud/biscuit/token/builder/Block.java +++ b/src/main/java/com/clevercloud/biscuit/token/builder/Block.java @@ -10,6 +10,7 @@ import com.clevercloud.biscuit.error.Error; import io.vavr.Tuple2; import io.vavr.control.Either; +import io.vavr.control.Option; import static com.clevercloud.biscuit.datalog.Check.Kind.One; import static com.clevercloud.biscuit.token.builder.Utils.*; @@ -33,8 +34,8 @@ public class Block { public Block(long index, SymbolTable base_symbols) { this.index = index; - this.symbol_start = base_symbols.symbols.size(); - this.publicKeyStart = base_symbols.publicKeys.size(); + this.symbol_start = base_symbols.currentOffset(); + this.publicKeyStart = base_symbols.currentPublicKeyOffset(); this.symbols = new SymbolTable(base_symbols); this.context = ""; this.facts = new ArrayList<>(); @@ -115,14 +116,14 @@ public com.clevercloud.biscuit.token.Block build() { } List publicKeys = new ArrayList<>(); - for (int i = this.publicKeyStart; i < this.symbols.publicKeys.size(); i++) { - publicKeys.add(this.symbols.publicKeys.get(i)); + for (int i = this.publicKeyStart; i < this.symbols.currentPublicKeyOffset(); i++) { + publicKeys.add(this.symbols.publicKeys().get(i)); } SchemaVersion schemaVersion = new SchemaVersion(this.facts, this.rules, this.checks, this.scopes); return new com.clevercloud.biscuit.token.Block(symbols, this.context, this.facts, this.rules, this.checks, - this.scopes, publicKeys, schemaVersion.version()); + this.scopes, publicKeys, Option.none(), schemaVersion.version()); } public Block check_right(String right) { diff --git a/src/main/java/com/clevercloud/biscuit/token/format/SerializedBiscuit.java b/src/main/java/com/clevercloud/biscuit/token/format/SerializedBiscuit.java index f8f7938f..8f4d6094 100644 --- a/src/main/java/com/clevercloud/biscuit/token/format/SerializedBiscuit.java +++ b/src/main/java/com/clevercloud/biscuit/token/format/SerializedBiscuit.java @@ -453,26 +453,28 @@ static Either verifyBlockSignature(SignedBlock signedBlock, Pu public Tuple3, HashMap>> extractBlocks(SymbolTable symbols) throws Error { ArrayList> blockExternalKeys = new ArrayList<>(); - Either authRes = Block.from_bytes(this.authority.block); + Either authRes = Block.from_bytes(this.authority.block, Option.none()); if (authRes.isLeft()) { Error e = authRes.getLeft(); throw e; } Block authority = authRes.get(); for(PublicKey pk: authority.publicKeys()) { - symbols.publicKeys.add(pk); + symbols.insert(pk); } blockExternalKeys.add(Option.none()); - for (String s : authority.symbols().symbols) { symbols.add(s); } - ArrayList blocks = new ArrayList<>(); for (SignedBlock bdata : this.blocks) { - Either blockRes = Block.from_bytes(bdata.block); + Option externalKey = Option.none(); + if(bdata.externalSignature.isDefined()) { + externalKey = Option.some(bdata.externalSignature.get().key); + } + Either blockRes = Block.from_bytes(bdata.block, externalKey); if (blockRes.isLeft()) { Error e = blockRes.getLeft(); throw e; @@ -481,7 +483,7 @@ public Tuple3, HashMap>> extractBlocks( // blocks with external signatures keep their own symbol table if(bdata.externalSignature.isDefined()) { - symbols.publicKeys.add(bdata.externalSignature.get().key); + symbols.insert(bdata.externalSignature.get().key); blockExternalKeys.add(Option.some(bdata.externalSignature.get().key)); } else { blockExternalKeys.add(Option.none()); @@ -490,7 +492,7 @@ public Tuple3, HashMap>> extractBlocks( } } for(PublicKey pk: block.publicKeys()) { - symbols.publicKeys.add(pk); + symbols.insert(pk); } blocks.add(block); } diff --git a/src/test/java/com/clevercloud/biscuit/token/SamplesV2Test.java b/src/test/java/com/clevercloud/biscuit/token/SamplesV2Test.java index 1a20ce65..9fc14b51 100644 --- a/src/test/java/com/clevercloud/biscuit/token/SamplesV2Test.java +++ b/src/test/java/com/clevercloud/biscuit/token/SamplesV2Test.java @@ -514,7 +514,7 @@ public void test20_sealed_token() throws IOException, NoSuchAlgorithmException, Long result = v1.authorize(new RunLimits(500, 100, Duration.ofMillis(500))); System.out.println("result: " + result); assertEquals(0L, v1.authorize(new RunLimits(500, 100, Duration.ofMillis(500)))); - assertEquals(8, v1.world.facts().size()); + assertEquals(5, v1.world.facts().size()); } @Test @@ -543,6 +543,6 @@ public void test21_parsing() throws IOException, NoSuchAlgorithmException, Signa Long result = v1.authorize(new RunLimits(500, 100, Duration.ofMillis(500))); System.out.println("result: " + result); assertEquals(0L, v1.authorize(new RunLimits(500, 100, Duration.ofMillis(500)))); - assertEquals(2, v1.world.facts().size()); + assertEquals(1, v1.world.facts().size()); } }