diff --git a/src/main/java/org/biscuitsec/biscuit/token/Block.java b/src/main/java/org/biscuitsec/biscuit/token/Block.java index 382224a8..659ee075 100644 --- a/src/main/java/org/biscuitsec/biscuit/token/Block.java +++ b/src/main/java/org/biscuitsec/biscuit/token/Block.java @@ -90,11 +90,16 @@ public String print(SymbolTable symbol_table) { s.append(this.symbols.symbols); s.append("\n\t\tcontext: "); s.append(this.context); + s.append("\n\t\tscopes: ["); + for (Scope scope : this.scopes) { + s.append("\n\t\t\t"); + s.append(symbol_table.print_scope(scope)); + } if(this.externalKey.isDefined()) { s.append("\n\t\texternal key: "); s.append(this.externalKey.get().toString()); } - s.append("\n\t\tfacts: ["); + s.append("\n\t\t]\n\t\tfacts: ["); for (Fact f : this.facts) { s.append("\n\t\t\t"); s.append(symbol_table.print_fact(f)); @@ -208,6 +213,7 @@ static public Either deserialize(Schema.Block b, Optio } ArrayList scopes = new ArrayList<>(); + System.out.println(b.getScopeList()); for (Schema.Scope scope: b.getScopeList()) { Either res = Scope.deserialize(scope); if(res.isLeft()) { diff --git a/src/main/java/org/biscuitsec/biscuit/token/builder/Block.java b/src/main/java/org/biscuitsec/biscuit/token/builder/Block.java index 6a17bbdf..c4591e03 100644 --- a/src/main/java/org/biscuitsec/biscuit/token/builder/Block.java +++ b/src/main/java/org/biscuitsec/biscuit/token/builder/Block.java @@ -150,8 +150,6 @@ public org.biscuitsec.biscuit.token.Block build() { publicKeys.add(this.symbols.publicKeys().get(i)); } - publicKeys.addAll(this.publicKeys); - SchemaVersion schemaVersion = new SchemaVersion(this.facts, this.rules, this.checks, this.scopes); return new org.biscuitsec.biscuit.token.Block(symbols, this.context, this.facts, this.rules, this.checks, diff --git a/src/main/java/org/biscuitsec/biscuit/token/builder/parser/Parser.java b/src/main/java/org/biscuitsec/biscuit/token/builder/parser/Parser.java index 25a42464..e7b9958c 100644 --- a/src/main/java/org/biscuitsec/biscuit/token/builder/parser/Parser.java +++ b/src/main/java/org/biscuitsec/biscuit/token/builder/parser/Parser.java @@ -16,6 +16,10 @@ import java.util.function.Function; public class Parser { + public static Either>, Block> datalog(long index, SymbolTable baseSymbols, String s) { + return datalog(index, baseSymbols, null, s); + } + /** * Takes a datalog string with \n as datalog line separator. It tries to parse * each line using fact, rule, check and scope sequentially. @@ -25,24 +29,37 @@ public class Parser { * * @param index block index * @param baseSymbols symbols table + * @param blockSymbols block's custom symbols table (added to baseSymbols) * @param s datalog string to parse * @return Either>, Block> */ - public static Either>, Block> datalog(long index, SymbolTable baseSymbols, String s) { + public static Either>, Block> datalog(long index, SymbolTable baseSymbols, SymbolTable blockSymbols, String s) { Block blockBuilder = new Block(index, baseSymbols); + + // empty block code + if (s.isEmpty()) { + return Either.right(blockBuilder); + } + + if (blockSymbols != null) { + blockSymbols.symbols.forEach(blockBuilder::addSymbol); + } + Map> errors = new HashMap<>(); - Stream.of(s.split("\n")).zipWithIndex().forEach(indexedLine -> { + Stream.of(s.replace(";", "").replace("\\\"","\"").split("\n")).zipWithIndex().forEach(indexedLine -> { Integer lineNumber = indexedLine._2; String codeLine = indexedLine._1; List lineErrors = new ArrayList<>(); - fact(codeLine).bimap(lineErrors::add, r -> r._2).map(blockBuilder::add_fact); - rule(codeLine).bimap(lineErrors::add, r -> r._2).map(blockBuilder::add_rule); - check(codeLine).bimap(lineErrors::add, r -> r._2).map(blockBuilder::add_check); - scope(codeLine).bimap(lineErrors::add, r -> r._2).map(blockBuilder::add_scope); + scope(codeLine).bimap(lineErrors::add, r -> r._2).forEach(blockBuilder::add_scope); + fact(codeLine).bimap(lineErrors::add, r -> r._2).forEach(blockBuilder::add_fact); + rule(codeLine).bimap(lineErrors::add, r -> r._2).forEach(blockBuilder::add_rule); + check(codeLine).bimap(lineErrors::add, r -> r._2).forEach(blockBuilder::add_check); if (lineErrors.size() > 3) { + System.out.println(codeLine); + System.out.println(lineErrors.get(lineErrors.size() - 1)); errors.put(lineNumber, lineErrors); } }); diff --git a/src/test/java/org/biscuitsec/biscuit/builder/parser/ParserTest.java b/src/test/java/org/biscuitsec/biscuit/builder/parser/ParserTest.java index fff15f76..c39fcb3c 100644 --- a/src/test/java/org/biscuitsec/biscuit/builder/parser/ParserTest.java +++ b/src/test/java/org/biscuitsec/biscuit/builder/parser/ParserTest.java @@ -366,7 +366,7 @@ void testParens() throws org.biscuitsec.biscuit.error.Error.Execution { void testDatalogSucceeds() throws org.biscuitsec.biscuit.error.Error.Parser { SymbolTable symbols = Biscuit.default_symbol_table(); - String l1 = "fact1(1)"; + String l1 = "fact1(1, 2)"; String l2 = "fact2(\"2\")"; String l3 = "rule1(2) <- fact2(\"2\")"; String l4 = "check if rule1(2)"; diff --git a/src/test/java/org/biscuitsec/biscuit/token/SamplesTest.java b/src/test/java/org/biscuitsec/biscuit/token/SamplesTest.java index a670c875..465bced1 100644 --- a/src/test/java/org/biscuitsec/biscuit/token/SamplesTest.java +++ b/src/test/java/org/biscuitsec/biscuit/token/SamplesTest.java @@ -4,15 +4,20 @@ import com.google.gson.*; import com.google.protobuf.MapEntry; import io.vavr.Tuple2; +import io.vavr.control.Option; import org.biscuitsec.biscuit.crypto.KeyPair; import org.biscuitsec.biscuit.crypto.PublicKey; import org.biscuitsec.biscuit.datalog.Rule; import org.biscuitsec.biscuit.datalog.RunLimits; +import org.biscuitsec.biscuit.datalog.SymbolTable; import org.biscuitsec.biscuit.datalog.TrustedOrigins; import org.biscuitsec.biscuit.error.Error; import io.vavr.control.Either; import io.vavr.control.Try; import org.biscuitsec.biscuit.token.builder.Check; +import org.biscuitsec.biscuit.token.builder.Expression; +import org.biscuitsec.biscuit.token.builder.parser.ExpressionParser; +import org.biscuitsec.biscuit.token.builder.parser.Parser; import org.junit.jupiter.api.DynamicTest; import org.junit.jupiter.api.TestFactory; @@ -22,6 +27,7 @@ import java.time.Duration; import java.util.*; import java.util.stream.Collectors; +import java.util.stream.IntStream; import java.util.stream.Stream; import static org.junit.jupiter.api.Assertions.*; @@ -39,6 +45,48 @@ Stream jsonTest() { return sample.testcases.stream().map(t -> process_testcase(t, publicKey, keyPair)); } + void compareBlocks(List sampleBlocks, List blocks) { + assertEquals(sampleBlocks.size(), blocks.size()); + List> comparisonList = IntStream.range(0, sampleBlocks.size()) + .mapToObj(i -> new Tuple2<>(sampleBlocks.get(i), blocks.get(i))) + .collect(Collectors.toList()); + + // for each token we start from the base symbol table + SymbolTable baseSymbols = new SymbolTable(); + + io.vavr.collection.Stream.ofAll(comparisonList).zipWithIndex().forEach(indexedItem -> { + compareBlock(baseSymbols, indexedItem._2, indexedItem._1._1, indexedItem._1._2); + }); + } + + void compareBlock(SymbolTable baseSymbols, long sampleBlockIndex, Block sampleBlock, org.biscuitsec.biscuit.token.Block block) { + Option sampleExternalKey = sampleBlock.getExternalKey(); + List samplePublicKeys = sampleBlock.getPublicKeys(); + String sampleDatalog = sampleBlock.getCode().replace("\"","\\\""); + SymbolTable sampleSymbols = new SymbolTable(sampleBlock.symbols); + + Either>, org.biscuitsec.biscuit.token.builder.Block> outputSample = Parser.datalog(sampleBlockIndex, baseSymbols, sampleDatalog); + assertTrue(outputSample.isRight()); + + if (!block.publicKeys.isEmpty()) { + outputSample.get().addPublicKeys(samplePublicKeys); + } + + if (!block.externalKey.isDefined()) { + sampleSymbols.symbols.forEach(baseSymbols::add); + } else { + SymbolTable freshSymbols = new SymbolTable(); + sampleSymbols.symbols.forEach(freshSymbols::add); + outputSample.get().setExternalKey(sampleExternalKey); + } + + System.out.println("mdr"); + System.out.println(outputSample.get().build().print(sampleSymbols)); + System.out.println(block.symbols.symbols); + System.out.println(block.print(sampleSymbols)); + assertArrayEquals(outputSample.get().build().to_bytes().get(), block.to_bytes().get()); + } + DynamicTest process_testcase(final TestCase testCase, final PublicKey publicKey, final KeyPair privateKey) { return DynamicTest.dynamicTest(testCase.title + ": "+testCase.filename, () -> { System.out.println("Testcase name: \""+testCase.title+"\""); @@ -57,6 +105,12 @@ DynamicTest process_testcase(final TestCase testCase, final PublicKey publicKey, Biscuit token = Biscuit.from_bytes(data, publicKey); assertArrayEquals(token.serialize(), data); + List allBlocks = new ArrayList<>(); + allBlocks.add(token.authority); + allBlocks.addAll(token.blocks); + + compareBlocks(testCase.token, allBlocks); + List revocationIds = token.revocation_identifiers(); JsonArray validationRevocationIds = validation.getAsJsonArray("revocation_ids"); assertEquals(revocationIds.size(), validationRevocationIds.size()); @@ -142,19 +196,11 @@ DynamicTest process_testcase(final TestCase testCase, final PublicKey publicKey, }); } - class Block { List symbols; - - public String getCode() { - return code; - } - - public void setCode(String code) { - this.code = code; - } - String code; + List public_keys; + String external_key; public List getSymbols() { return symbols; @@ -163,6 +209,38 @@ public List getSymbols() { public void setSymbols(List symbols) { this.symbols = symbols; } + + public String getCode() { return code; } + + public void setCode(String code) { this.code = code; } + + public List getPublicKeys() { + return this.public_keys.stream() + .map(pk -> { + String[] parts = pk.split("/"); + return new PublicKey(Schema.PublicKey.Algorithm.Ed25519, parts[1]); + }) + .collect(Collectors.toList()); + } + + public void setPublicKeys(List publicKeys) { + this.public_keys = publicKeys.stream() + .map(PublicKey::toString) + .collect(Collectors.toList()); + } + + public Option getExternalKey() { + if (this.external_key != null) { + String[] parts = this.external_key.split("/"); + return Option.of(new PublicKey(Schema.PublicKey.Algorithm.Ed25519, parts[1])); + } else { + return Option.none(); + } + } + + public void setExternalKey(Option externalKey) { + this.external_key = externalKey.map(PublicKey::toString).getOrElse((String) null); + } } class TestCase {