Skip to content

Commit

Permalink
Merge pull request #76 from biscuit-auth/3rd-party-blocks
Browse files Browse the repository at this point in the history
3rd party block support
  • Loading branch information
Geal authored Jan 5, 2024
2 parents 763b143 + 32a53d4 commit cc6cf61
Show file tree
Hide file tree
Showing 39 changed files with 2,061 additions and 986 deletions.
26 changes: 26 additions & 0 deletions src/main/java/com/clevercloud/biscuit/crypto/PublicKey.java
Original file line number Diff line number Diff line change
Expand Up @@ -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 {

Expand Down Expand Up @@ -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;
Expand All @@ -55,4 +76,9 @@ public boolean equals(Object o) {
public int hashCode() {
return key.hashCode();
}

@Override
public String toString() {
return "ed25519/" + toHex();
}
}
14 changes: 0 additions & 14 deletions src/main/java/com/clevercloud/biscuit/datalog/AuthorizedWorld.java

This file was deleted.

200 changes: 120 additions & 80 deletions src/main/java/com/clevercloud/biscuit/datalog/Combinator.java
Original file line number Diff line number Diff line change
@@ -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<Predicate> next_predicates;
private final Set<Fact> all_facts;
private final Predicate pred;
private final Iterator<Fact> fit;
private Combinator current_it;
import java.util.function.Supplier;
import java.util.stream.Stream;

public final class Combinator implements Serializable, Iterator<Tuple2<Origin, Map<Long, Term>>> {
private MatchedVariables variables;
private final Supplier<Stream<Tuple2<Origin, Fact>>> allFacts;
private final List<Predicate> predicates;
private final Iterator<Tuple2<Origin, Fact>> currentFacts;
private Combinator currentIt;
private final SymbolTable symbols;

public Option<MatchedVariables> next() {
while(true) {
if (this.current_it != null) {
Option<MatchedVariables> 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<Tuple2<Origin, Map<Long, Term>>> 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<Origin, Map<Long, Term>> next() {
if (this.nextElement == null || !this.nextElement.isDefined()) {
this.nextElement = getNext();
}
if (this.nextElement == null || !this.nextElement.isDefined()) {
throw new NoSuchElementException();
} else {
Tuple2<Origin, Map<Long, Term>> 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<Tuple2<Origin, Map<Long, Term>>> getNext() {
if (this.predicates.isEmpty()) {
final Option<Map<Long, Term>> v_opt = this.variables.complete();
if (v_opt.isEmpty()) {
return Option.none();
} else {
Map<Long, Term> 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<Long> 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<Origin, Fact> 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<Map<Long, Term>> 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<Map<Long, Term>> 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<Map<Long, Term>> combine() {
final List<Map<Long, Term>> variables = new ArrayList<>();

while(true) {
Option<MatchedVariables> res = this.next();

if(res.isEmpty()) {
return variables;
if (this.currentIt == null) {
return Option.none();
}

Option<Map<Long, Term>> vars = res.get().complete();
if(vars.isDefined()) {
variables.add(vars.get());
Option<Tuple2<Origin, Map<Long, Term>>> opt = this.currentIt.getNext();

if (opt.isDefined()) {
Tuple2<Origin, Map<Long, Term>> 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<Predicate> predicates,
final Set<Fact> all_facts, final SymbolTable symbols) {
Supplier<Stream<Tuple2<Origin, Fact>>> 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<Predicate> 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;
}
}
Loading

0 comments on commit cc6cf61

Please sign in to comment.