diff --git a/src/main/java/com/clevercloud/biscuit/crypto/PublicKey.java b/src/main/java/com/clevercloud/biscuit/crypto/PublicKey.java index 602f7622..640b04b5 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,21 @@ 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; @@ -55,4 +76,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/AuthorizedWorld.java b/src/main/java/com/clevercloud/biscuit/datalog/AuthorizedWorld.java deleted file mode 100644 index b0b3266d..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(Set 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 cc07de51..e32026c3 100644 --- a/src/main/java/com/clevercloud/biscuit/datalog/Combinator.java +++ b/src/main/java/com/clevercloud/biscuit/datalog/Combinator.java @@ -1,112 +1,152 @@ 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.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 + 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(); + return Option.some(new Tuple2<>(t._1.union(currentOrigin), t._2)); + } 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..29374f61 --- /dev/null +++ b/src/main/java/com/clevercloud/biscuit/datalog/FactSet.java @@ -0,0 +1,111 @@ +package com.clevercloud.biscuit.datalog; + +import io.vavr.Tuple2; + +import java.util.*; +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 HashMap> facts() { + return this.facts; + } + + 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<>(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 stream(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 stream() { + 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() { + StringBuilder res = new StringBuilder("FactSet {"); + for(Map.Entry> entry: this.facts.entrySet()) { + res.append("\n\t").append(entry.getKey()).append("["); + for(Fact fact: entry.getValue()) { + res.append("\n\t\t").append(fact); + } + res.append("\n]"); + } + res.append("\n}"); + + return res.toString(); + } +} + 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..8f1b86e9 --- /dev/null +++ b/src/main/java/com/clevercloud/biscuit/datalog/Origin.java @@ -0,0 +1,68 @@ +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((long) i); + } + public void add(long i) { + inner.add(i); + } + + public Origin union(Origin other) { + Origin o = this.clone(); + o.inner.addAll(other.inner); + return o; + } + + public Origin clone() { + final HashSet newInner = new HashSet<>(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 1d208055..8c4834be 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; @@ -17,6 +23,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,130 +37,149 @@ public final List expressions() { return this.expressions; } - 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); - } - } - - new_facts.add(new Fact(p)); - } - } + public List scopes() { + return scopes; + } - Combinator c = new Combinator(variables, this.body, facts, symbols); - while (true) { - final Option vars_opt = c.next(); - if(!vars_opt.isDefined()) { - break; - } - 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; - } - } - } - - if (!unbound_variable) { - new_facts.add(new Fact(p)); - } - } - } + 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()); + } + } + } + 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))); + }); } - // 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.stream(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.stream(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(); + 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; + } - // the expression must succeed for all the matching sets of facts - if (!vars.check_expressions(this.expressions, symbols).isDefined()) { - return false; - } - } else { - return found; - } - } + 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) { + 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 +194,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 +224,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/RuleSet.java b/src/main/java/com/clevercloud/biscuit/datalog/RuleSet.java new file mode 100644 index 00000000..748d642b --- /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, List.of(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<>(entry.getValue()); + newRules.rules.put(entry.getKey(), l); + } + + return newRules; + } + + 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 a92c5702..ed7fed73 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,19 +17,30 @@ public class SchemaVersion { private boolean containsCheckAll; private boolean containsV4; - public SchemaVersion(List facts, List rules, List checks) { - // 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())); - */ + public SchemaVersion(List facts, List rules, List checks, List scopes) { + 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) { + for (Check check : checks) { if (check.kind() == All) { containsCheckAll = true; break; @@ -40,8 +48,8 @@ public SchemaVersion(List facts, List rules, List checks) { } 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; @@ -52,7 +60,7 @@ public SchemaVersion(List facts, List rules, List checks) { public int version() { if (containsScopes || containsV4 || containsCheckAll) { - return 4; + return 4; } else { return MIN_SCHEMA_VERSION; } @@ -63,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")); } } @@ -75,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 new file mode 100644 index 00000000..0658bfde --- /dev/null +++ b/src/main/java/com/clevercloud/biscuit/datalog/Scope.java @@ -0,0 +1,86 @@ +package com.clevercloud.biscuit.datalog; + +import biscuit.format.schema.Schema; +import com.clevercloud.biscuit.error.Error; +import io.vavr.control.Either; + + +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")); + } + + @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 0b945efa..6bdeef92 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; + private final List publicKeys; public long insert(final String symbol) { int index = this.defaultSymbols.indexOf(symbol); @@ -76,6 +78,27 @@ 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); + 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 +129,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) { @@ -149,6 +180,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; } @@ -156,6 +193,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) -> { @@ -224,11 +275,21 @@ 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 SymbolTable(List symbols, List publicKeys) { + this.symbols = new ArrayList<>(); + this.symbols.addAll(symbols); + this.publicKeys = new ArrayList<>(); + this.publicKeys.addAll(publicKeys); } public List getAllSymbols() { 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..6c359d08 --- /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(); + } + + 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..8d1d14f2 --- /dev/null +++ b/src/main/java/com/clevercloud/biscuit/datalog/TrustedOrigins.java @@ -0,0 +1,87 @@ +package com.clevercloud.biscuit.datalog; + +import java.util.HashMap; +import java.util.List; + +public class TrustedOrigins { + private final 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); + } + + @Override + public String toString() { + return "TrustedOrigins{" + + "inner=" + 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..0eaf2741 100644 --- a/src/main/java/com/clevercloud/biscuit/datalog/World.java +++ b/src/main/java/com/clevercloud/biscuit/datalog/World.java @@ -1,56 +1,68 @@ package com.clevercloud.biscuit.datalog; 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.stream.Collectors; +import java.util.*; +import java.util.function.Supplier; +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) { - 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.stream(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,98 +78,80 @@ 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) { - 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;*/ - } + 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.stream(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; } - return true; - }).collect(Collectors.toSet()); - } + } - 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<>(); - } - - public World(Set facts) { - this.facts = new HashSet<>(); - this.facts.addAll(facts); - this.rules = new ArrayList<>(); + this.facts = new FactSet(); + this.rules = new RuleSet(); } - public World(Set facts, List rules) { - this.facts = facts; - this.rules = rules; + public World(FactSet facts) { + this.facts = facts.clone(); + this.rules = new RuleSet(); } - public World(Set facts, List rules, List checks) { - this.facts = facts; - this.rules = rules; + public World(FactSet facts, RuleSet rules) { + this.facts = facts.clone(); + this.rules = rules.clone(); } 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(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(Rule r: this.rules) { - s.append("\n\t\t\t"); - s.append(symbol_table.print_rule(r)); - } + 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)); + } 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..162c82dc 100644 --- a/src/main/java/com/clevercloud/biscuit/token/Authorizer.java +++ b/src/main/java/com/clevercloud/biscuit/token/Authorizer.java @@ -1,13 +1,16 @@ 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.crypto.PublicKey; +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 +31,8 @@ public class Authorizer { List checks; List> token_checks; List policies; + List scopes; + HashMap> publicKeyToBlockId; World world; SymbolTable symbols; @@ -37,7 +42,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(); } @@ -45,7 +52,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(); @@ -53,6 +60,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 +72,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,8 +82,7 @@ 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 + * @return Authorizer */ static public Authorizer make(Biscuit token) throws Error.FailedLogic { return new Authorizer(token, new World()); @@ -87,7 +97,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); @@ -98,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); + } } } @@ -112,7 +127,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 +145,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,17 +197,17 @@ 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", List.of(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( "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 -> { @@ -197,7 +228,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)); @@ -211,7 +242,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)); @@ -237,11 +268,16 @@ public Authorizer add_policy(Policy p) { return this; } - public Set query(Rule query) throws Error.TooManyFacts, Error.TooManyIterations, Error.Timeout { + 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()); } - 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 +290,30 @@ 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); + 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.stream().iterator(); it.hasNext(); ) { + com.clevercloud.biscuit.datalog.Fact f = 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,20 +326,31 @@ 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(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); @@ -302,12 +359,54 @@ 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))); } + 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); + } } } 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 +414,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; } @@ -340,21 +446,34 @@ 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; - 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++) { 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 +498,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 policyTrustedOrigins = TrustedOrigins.fromScopes( + query.scopes(), + authorizerTrustedOrigins, + Long.MAX_VALUE, + this.publicKeyToBlockId + ); + boolean res = world.query_match(query, Long.MAX_VALUE, policyTrustedOrigins, symbols); if (Instant.now().compareTo(timeLimit) >= 0) { throw new Error.Timeout(); @@ -388,9 +513,9 @@ public Tuple2 authorize(RunLimits limits) throws Error.Ti 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; } @@ -400,43 +525,38 @@ 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); - - 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); + TrustedOrigins blockTrustedOrigins = TrustedOrigins.fromScopes( + b.scopes, + TrustedOrigins.defaultOrigins(), + i+1, + this.publicKeyToBlockId + ); + 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))); - } - blockWorld.add_rule(converted_rule); - } - - blockWorld.run(limits, symbols); - authorizedWorld.add_facts(blockWorld.facts()); - blockWorld.clearRules(); - 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++) { 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,12 +581,12 @@ 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 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)); @@ -474,7 +594,16 @@ 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()); + //FIXME + 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<>(); @@ -497,15 +626,13 @@ 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: [" + + 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" + + String.join(",\n\t\t", checks) + + "\n\t]\n}"; } } 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 edfd1f15..4e90f9fc 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; @@ -28,6 +26,9 @@ public class Block { final List facts; final List rules; final List checks; + final List scopes; + final List publicKeys; + final Option externalKey; final long version; /** @@ -41,6 +42,9 @@ public Block(SymbolTable base_symbols) { this.facts = new ArrayList<>(); this.rules = new ArrayList<>(); this.checks = new ArrayList<>(); + this.scopes = new ArrayList<>(); + this.publicKeys = new ArrayList<>(); + this.externalKey = Option.none(); this.version = SerializedBiscuit.MAX_SCHEMA_VERSION; } @@ -51,13 +55,25 @@ 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, List publicKeys, Option externalKey, int version) { this.symbols = base_symbols; this.context = context; this.facts = facts; this.rules = rules; this.checks = checks; + this.scopes = scopes; this.version = version; + this.publicKeys = publicKeys; + this.externalKey = externalKey; + } + + public SymbolTable symbols() { + return symbols; + } + + public List publicKeys() { + return publicKeys; } /** @@ -74,6 +90,10 @@ public String print(SymbolTable symbol_table) { s.append(this.symbols.symbols); s.append("\n\t\tcontext: "); s.append(this.context); + if(this.externalKey.isDefined()) { + s.append("\n\t\texternal key: "); + s.append(this.externalKey.get().toString()); + } s.append("\n\t\tfacts: ["); for (Fact f : this.facts) { s.append("\n\t\t\t"); @@ -122,6 +142,14 @@ public Schema.Block serialize() { b.addChecksV2(this.checks.get(i).serialize()); } + for (Scope scope: this.scopes) { + b.addScope(scope.serialize()); + } + + for(PublicKey pk: this.publicKeys) { + b.addPublicKeys(pk.serialize()); + } + b.setVersion(SerializedBiscuit.MAX_SCHEMA_VERSION); return b.build(); } @@ -132,7 +160,7 @@ public Schema.Block serialize() { * @param b * @return */ - static public Either 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)); @@ -179,14 +207,34 @@ 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()); + } + } + + 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()) { 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, publicKeys, externalKey, version)); } /** @@ -195,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/UnverifiedBiscuit.java b/src/main/java/com/clevercloud/biscuit/token/UnverifiedBiscuit.java index b0d8afdf..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() @@ -218,30 +203,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 +226,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/main/java/com/clevercloud/biscuit/token/builder/Biscuit.java b/src/main/java/com/clevercloud/biscuit/token/builder/Biscuit.java index 67e81f75..ac6e3ad2 100644 --- a/src/main/java/com/clevercloud/biscuit/token/builder/Biscuit.java +++ b/src/main/java/com/clevercloud/biscuit/token/builder/Biscuit.java @@ -1,10 +1,12 @@ 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; 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; @@ -23,22 +25,26 @@ public class Biscuit { SecureRandom rng; KeyPair root; int symbol_start; + int publicKeyStart; SymbolTable symbols; String context; List facts; List rules; List checks; + List scopes; Option root_key_id; 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.symbol_start = base_symbols.currentOffset(); + this.publicKeyStart = base_symbols.currentPublicKeyOffset(); this.symbols = new SymbolTable(base_symbols); this.context = ""; this.facts = new ArrayList<>(); this.rules = new ArrayList<>(); this.checks = new ArrayList<>(); + this.scopes = new ArrayList<>(); this.root_key_id = Option.none(); } @@ -51,6 +57,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 +123,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 +143,16 @@ 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); + + List publicKeys = new ArrayList<>(); + 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, 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 67751931..5d639efb 100644 --- a/src/main/java/com/clevercloud/biscuit/token/builder/Block.java +++ b/src/main/java/com/clevercloud/biscuit/token/builder/Block.java @@ -1,13 +1,16 @@ 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; +import com.clevercloud.biscuit.datalog.Scope; import com.clevercloud.biscuit.datalog.Rule; 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.*; @@ -21,20 +24,24 @@ public class Block { long index; int symbol_start; + int publicKeyStart; SymbolTable symbols; String context; List facts; List rules; List checks; + List scopes; public Block(long index, SymbolTable base_symbols) { this.index = index; - this.symbol_start = base_symbols.symbols.size(); + this.symbol_start = base_symbols.currentOffset(); + this.publicKeyStart = base_symbols.currentPublicKeyOffset(); this.symbols = new SymbolTable(base_symbols); this.context = ""; this.facts = new ArrayList<>(); this.rules = new ArrayList<>(); this.checks = new ArrayList<>(); + this.scopes = new ArrayList<>(); } public Block add_fact(com.clevercloud.biscuit.token.builder.Fact f) { @@ -91,6 +98,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 +115,15 @@ public com.clevercloud.biscuit.token.Block build() { symbols.add(this.symbols.symbols.get(i)); } - SchemaVersion schemaVersion = new SchemaVersion(this.facts, this.rules, this.checks); + List publicKeys = new ArrayList<>(); + 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, - 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/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 006521e8..19c7deca 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 { @@ -105,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) { @@ -118,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()); } @@ -127,6 +137,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 +148,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 +160,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 +171,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 @@ -165,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; } @@ -173,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; } @@ -188,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 new file mode 100644 index 00000000..30092854 --- /dev/null +++ b/src/main/java/com/clevercloud/biscuit/token/builder/Scope.java @@ -0,0 +1,122 @@ +package com.clevercloud.biscuit.token.builder; + +import com.clevercloud.biscuit.crypto.PublicKey; +import com.clevercloud.biscuit.datalog.SymbolTable; + +import java.util.Objects; + +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 = ""; + } + + private Scope(Kind kind, PublicKey publicKey) { + this.kind = kind; + this.publicKey = publicKey; + 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); + } + + public static Scope previous() { + return new Scope(Kind.Previous); + } + + 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: + return com.clevercloud.biscuit.datalog.Scope.authority(); + case Previous: + return com.clevercloud.biscuit.datalog.Scope.previous(); + case 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) { + 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"); + 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 35406c33..3d04e6ac 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) { @@ -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/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 78a785c2..8f4d6094 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; @@ -38,6 +39,7 @@ public class SerializedBiscuit { /** * Deserializes a SerializedBiscuit from a byte array + * * @param slice * @return */ @@ -53,6 +55,7 @@ static public SerializedBiscuit from_bytes(byte[] slice, PublicKey root) throws /** * Deserializes a SerializedBiscuit from a byte array + * * @param slice * @return */ @@ -61,12 +64,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,46 +80,11 @@ 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 { - 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() - )); + SerializedBiscuit b = SerializedBiscuit.deserialize(data); + if (data.hasRootKeyId()) { + b.root_key_id = Option.some(data.getRootKeyId()); } - //System.out.println("parsed blocks"); - - 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); - - 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(); @@ -138,42 +106,67 @@ static SerializedBiscuit from_bytes_inner(Schema.Biscuit data, PublicKey root) t 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 { + 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() - ); + SignedBlock authority = new SignedBlock( + data.getAuthority().getBlock().toByteArray(), + PublicKey.deserialize(data.getAuthority().getNextKey()), + data.getAuthority().getSignature().toByteArray(), + Option.none() + ); - 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() - )); - } + 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())); - Option secretKey = Option.none(); - if (data.getProof().hasNextSecret()) { - secretKey = Option.some(new KeyPair(data.getProof().getNextSecret().toByteArray())); } + blocks.add(new SignedBlock( + block.getBlock().toByteArray(), + PublicKey.deserialize(block.getNextKey()), + block.getSignature().toByteArray(), + external + )); + } - Option signature = Option.none(); - if (data.getProof().hasFinalSignature()) { - signature = Option.some(data.getProof().getFinalSignature().toByteArray()); - } + Option secretKey = Option.none(); + if (data.getProof().hasNextSecret()) { + secretKey = Option.some(new KeyPair(data.getProof().getNextSecret().toByteArray())); + } - if (secretKey.isEmpty() && signature.isEmpty()) { - throw new Error.FormatError.DeserializationError("empty proof"); - } - Proof proof = new Proof(secretKey, signature); + Option signature = Option.none(); + if (data.getProof().hasFinalSignature()) { + signature = Option.some(data.getProof().getFinalSignature().toByteArray()); + } - SerializedBiscuit b = new SerializedBiscuit(authority, blocks, proof); - return b; - } catch (InvalidProtocolBufferException e) { - throw new Error.FormatError.DeserializationError(e.toString()); + 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; } @@ -187,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()); @@ -258,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)); @@ -291,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) { @@ -307,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()); } } @@ -409,7 +405,110 @@ 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, Option.none()); + if (authRes.isLeft()) { + Error e = authRes.getLeft(); + throw e; + } + Block authority = authRes.get(); + for(PublicKey pk: authority.publicKeys()) { + 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) { + 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; + } + Block block = blockRes.get(); + + // blocks with external signatures keep their own symbol table + if(bdata.externalSignature.isDefined()) { + symbols.insert(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.insert(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; } } 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..2914b719 100644 --- a/src/test/java/com/clevercloud/biscuit/builder/parser/ParserTest.java +++ b/src/test/java/com/clevercloud/biscuit/builder/parser/ParserTest.java @@ -1,6 +1,9 @@ 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.TemporarySymbolTable; import com.clevercloud.biscuit.datalog.expressions.Op; import com.clevercloud.biscuit.token.builder.*; import com.clevercloud.biscuit.token.builder.parser.Error; @@ -130,7 +133,6 @@ void testRuleWithExpressionOrdering() { res); } - @Test void ruleWithFreeExpressionVariables() { Either> res = @@ -143,6 +145,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 = @@ -291,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()); @@ -333,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..6c25f4a7 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,7 +37,7 @@ 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); + 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.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())) + "]"); @@ -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).stream().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))) + w.query_rule(query2, (long) 0, new TrustedOrigins(0), syms) .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")), + 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"))))) + w.query_rule(query3, (long) 0, new TrustedOrigins(0), syms) .stream().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())) + "]"); - 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"))))) + new Term.Variable(syms.insert("sibling2"))))), + new ArrayList<>()); + System.out.println("siblings: [" + String.join(", ", + w.query_rule(query5, (long) 0, new TrustedOrigins(0), syms) .stream().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.stream().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.stream().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.stream().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.stream().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.stream().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.stream().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.stream().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.stream().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.stream().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.stream().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.stream().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..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; @@ -132,27 +129,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 +296,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 +461,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 +585,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..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 @@ -33,7 +23,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 +37,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..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; @@ -47,7 +45,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 +78,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..9fc14b51 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 { @@ -322,8 +319,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 +448,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 +511,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(5, v1.world.facts().size()); } @Test @@ -542,9 +540,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(1, 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\")") ))),