From 8ffb155cb535eb6b40c83855a89489ffe4ba93e4 Mon Sep 17 00:00:00 2001 From: Malcolm Still Date: Sat, 30 Mar 2024 02:43:39 +0000 Subject: [PATCH 01/52] Add all files to main.zig --- biscuit-datalog/src/expression.zig | 25 +++++++++++++------------ biscuit-datalog/src/main.zig | 5 +++++ 2 files changed, 18 insertions(+), 12 deletions(-) diff --git a/biscuit-datalog/src/expression.zig b/biscuit-datalog/src/expression.zig index 74ee6ea..1f30fee 100644 --- a/biscuit-datalog/src/expression.zig +++ b/biscuit-datalog/src/expression.zig @@ -311,29 +311,30 @@ fn concat(allocator: std.mem.Allocator, left: []const u8, right: []const u8) ![] test { const testing = std.testing; + const allocator = testing.allocator; + const t1: Term = .{ .integer = 10 }; const t2: Term = .{ .integer = 22 }; - try testing.expectEqual(@as(Term, .{ .bool = false }), try Binary.equal.evaluate(t1, t2, SymbolTable.init("test", testing.allocator))); - try testing.expectEqual(@as(Term, .{ .bool = true }), try Binary.equal.evaluate(t1, t1, SymbolTable.init("test", testing.allocator))); - - try testing.expectEqual(@as(Term, .{ .integer = 32 }), try Binary.add.evaluate(t1, t2, SymbolTable.init("test", testing.allocator))); - try testing.expectEqual(@as(Term, .{ .integer = 220 }), try Binary.mul.evaluate(t1, t2, SymbolTable.init("test", testing.allocator))); - var symbols = SymbolTable.init("test", testing.allocator); defer symbols.deinit(); + try testing.expectEqual(@as(Term, .{ .bool = false }), try Binary.equal.evaluate(allocator, t1, t2, &symbols)); + try testing.expectEqual(@as(Term, .{ .bool = true }), try Binary.equal.evaluate(allocator, t1, t1, &symbols)); + try testing.expectEqual(@as(Term, .{ .integer = 32 }), try Binary.add.evaluate(allocator, t1, t2, &symbols)); + try testing.expectEqual(@as(Term, .{ .integer = 220 }), try Binary.mul.evaluate(allocator, t1, t2, &symbols)); + const s = .{ .string = try symbols.insert("prefix_middle_suffix") }; const prefix = .{ .string = try symbols.insert("prefix") }; const suffix = .{ .string = try symbols.insert("suffix") }; const middle = .{ .string = try symbols.insert("middle") }; - try testing.expectEqual(@as(Term, .{ .bool = true }), try Binary.equal.evaluate(s, s, symbols)); - try testing.expectEqual(@as(Term, .{ .bool = false }), try Binary.equal.evaluate(s, prefix, symbols)); - try testing.expectEqual(@as(Term, .{ .bool = true }), try Binary.not_equal.evaluate(s, prefix, symbols)); - try testing.expectEqual(@as(Term, .{ .bool = true }), try Binary.prefix.evaluate(s, prefix, symbols)); - try testing.expectEqual(@as(Term, .{ .bool = true }), try Binary.suffix.evaluate(s, suffix, symbols)); - try testing.expectEqual(@as(Term, .{ .bool = true }), try Binary.contains.evaluate(s, middle, symbols)); + try testing.expectEqual(@as(Term, .{ .bool = true }), try Binary.equal.evaluate(allocator, s, s, &symbols)); + try testing.expectEqual(@as(Term, .{ .bool = false }), try Binary.equal.evaluate(allocator, s, prefix, &symbols)); + try testing.expectEqual(@as(Term, .{ .bool = true }), try Binary.not_equal.evaluate(allocator, s, prefix, &symbols)); + try testing.expectEqual(@as(Term, .{ .bool = true }), try Binary.prefix.evaluate(allocator, s, prefix, &symbols)); + try testing.expectEqual(@as(Term, .{ .bool = true }), try Binary.suffix.evaluate(allocator, s, suffix, &symbols)); + try testing.expectEqual(@as(Term, .{ .bool = true }), try Binary.contains.evaluate(allocator, s, middle, &symbols)); } // test "negate" { diff --git a/biscuit-datalog/src/main.zig b/biscuit-datalog/src/main.zig index bed063a..3d629ca 100644 --- a/biscuit-datalog/src/main.zig +++ b/biscuit-datalog/src/main.zig @@ -20,13 +20,18 @@ test { _ = @import("check.zig"); _ = @import("combinator.zig"); _ = @import("expression.zig"); + _ = @import("fact_set.zig"); _ = @import("fact.zig"); _ = @import("matched_variables.zig"); + _ = @import("origin.zig"); _ = @import("predicate.zig"); + _ = @import("rule_set.zig"); _ = @import("rule.zig"); _ = @import("run_limits.zig"); + _ = @import("scope.zig"); _ = @import("set.zig"); _ = @import("symbol_table.zig"); _ = @import("term.zig"); + _ = @import("trusted_origins.zig"); _ = @import("world.zig"); } From 8101ae48ce97aad8236e50a9fb831af99d2ec8ab Mon Sep 17 00:00:00 2001 From: Malcolm Still Date: Sat, 30 Mar 2024 02:50:42 +0000 Subject: [PATCH 02/52] Rename builder convert to toDatalog --- biscuit-builder/src/check.zig | 4 ++-- biscuit-builder/src/expression.zig | 4 ++-- biscuit-builder/src/fact.zig | 4 ++-- biscuit-builder/src/policy.zig | 2 +- biscuit-builder/src/predicate.zig | 4 ++-- biscuit-builder/src/rule.zig | 10 +++++----- biscuit-builder/src/scope.zig | 2 +- biscuit-builder/src/term.zig | 2 +- biscuit/src/authorizer.zig | 6 +++--- 9 files changed, 19 insertions(+), 19 deletions(-) diff --git a/biscuit-builder/src/check.zig b/biscuit-builder/src/check.zig index 6e3f2e9..df77a62 100644 --- a/biscuit-builder/src/check.zig +++ b/biscuit-builder/src/check.zig @@ -16,11 +16,11 @@ pub const Check = struct { check.queries.deinit(); } - pub fn convert(check: Check, allocator: std.mem.Allocator, symbols: *datalog.SymbolTable) !datalog.Check { + pub fn toDatalog(check: Check, allocator: std.mem.Allocator, symbols: *datalog.SymbolTable) !datalog.Check { var queries = std.ArrayList(datalog.Rule).init(allocator); for (check.queries.items) |query| { - try queries.append(try query.convert(allocator, symbols)); + try queries.append(try query.toDatalog(allocator, symbols)); } return .{ .kind = check.kind, .queries = queries }; diff --git a/biscuit-builder/src/expression.zig b/biscuit-builder/src/expression.zig index e324e26..4cd7955 100644 --- a/biscuit-builder/src/expression.zig +++ b/biscuit-builder/src/expression.zig @@ -58,7 +58,7 @@ pub const Expression = union(ExpressionType) { }; /// convert to datalog fact - pub fn convert(expression: Expression, allocator: std.mem.Allocator, symbols: *datalog.SymbolTable) !datalog.Expression { + pub fn toDatalog(expression: Expression, allocator: std.mem.Allocator, symbols: *datalog.SymbolTable) !datalog.Expression { var ops = std.ArrayList(datalog.Op).init(allocator); try expression.toOpcodes(allocator, &ops, symbols); @@ -68,7 +68,7 @@ pub const Expression = union(ExpressionType) { pub fn toOpcodes(expression: Expression, allocator: std.mem.Allocator, ops: *std.ArrayList(datalog.Op), symbols: *datalog.SymbolTable) !void { switch (expression) { - .value => |v| try ops.append(.{ .value = try v.convert(allocator, symbols) }), + .value => |v| try ops.append(.{ .value = try v.toDatalog(allocator, symbols) }), .unary => |u| { try u.expression.toOpcodes(allocator, ops, symbols); diff --git a/biscuit-builder/src/fact.zig b/biscuit-builder/src/fact.zig index 0b1cd19..1eff884 100644 --- a/biscuit-builder/src/fact.zig +++ b/biscuit-builder/src/fact.zig @@ -12,8 +12,8 @@ pub const Fact = struct { } /// convert to datalog fact - pub fn convert(fact: Fact, allocator: std.mem.Allocator, symbols: *datalog.SymbolTable) !datalog.Fact { - return .{ .predicate = try fact.predicate.convert(allocator, symbols) }; + pub fn toDatalog(fact: Fact, allocator: std.mem.Allocator, symbols: *datalog.SymbolTable) !datalog.Fact { + return .{ .predicate = try fact.predicate.toDatalog(allocator, symbols) }; } pub fn format(fact: Fact, comptime _: []const u8, _: std.fmt.FormatOptions, writer: anytype) !void { diff --git a/biscuit-builder/src/policy.zig b/biscuit-builder/src/policy.zig index 637ac63..190046c 100644 --- a/biscuit-builder/src/policy.zig +++ b/biscuit-builder/src/policy.zig @@ -19,7 +19,7 @@ pub const Policy = struct { policy.queries.deinit(); } - // pub fn convert(policy: Policy, allocator: std.mem.Allocator, symbols: *datalog.SymbolTable) !Policy { + // pub fn toDatalog(policy: Policy, allocator: std.mem.Allocator, symbols: *datalog.SymbolTable) !Policy { // var queries = std.ArrayList(Rule).init(allocator); // for (policy.queries.items) |query| { diff --git a/biscuit-builder/src/predicate.zig b/biscuit-builder/src/predicate.zig index 8eec66f..1f91274 100644 --- a/biscuit-builder/src/predicate.zig +++ b/biscuit-builder/src/predicate.zig @@ -15,13 +15,13 @@ pub const Predicate = struct { } /// convert to datalog predicate - pub fn convert(predicate: Predicate, allocator: std.mem.Allocator, symbols: *datalog.SymbolTable) !datalog.Predicate { + pub fn toDatalog(predicate: Predicate, allocator: std.mem.Allocator, symbols: *datalog.SymbolTable) !datalog.Predicate { const name = try symbols.insert(predicate.name); var terms = std.ArrayList(datalog.Term).init(allocator); for (predicate.terms.items) |term| { - try terms.append(try term.convert(allocator, symbols)); + try terms.append(try term.toDatalog(allocator, symbols)); } return .{ .name = name, .terms = terms }; diff --git a/biscuit-builder/src/rule.zig b/biscuit-builder/src/rule.zig index ab4540a..adb2857 100644 --- a/biscuit-builder/src/rule.zig +++ b/biscuit-builder/src/rule.zig @@ -29,23 +29,23 @@ pub const Rule = struct { } /// convert to datalog predicate - pub fn convert(rule: Rule, allocator: std.mem.Allocator, symbols: *datalog.SymbolTable) !datalog.Rule { - const head = try rule.head.convert(allocator, symbols); + pub fn toDatalog(rule: Rule, allocator: std.mem.Allocator, symbols: *datalog.SymbolTable) !datalog.Rule { + const head = try rule.head.toDatalog(allocator, symbols); var body = std.ArrayList(datalog.Predicate).init(allocator); var expressions = std.ArrayList(datalog.Expression).init(allocator); var scopes = std.ArrayList(datalog.Scope).init(allocator); for (rule.body.items) |predicate| { - try body.append(try predicate.convert(allocator, symbols)); + try body.append(try predicate.toDatalog(allocator, symbols)); } for (rule.expressions.items) |expression| { - try expressions.append(try expression.convert(allocator, symbols)); + try expressions.append(try expression.toDatalog(allocator, symbols)); } for (rule.scopes.items) |scope| { - try scopes.append(try scope.convert(allocator, symbols)); + try scopes.append(try scope.toDatalog(allocator, symbols)); } return .{ diff --git a/biscuit-builder/src/scope.zig b/biscuit-builder/src/scope.zig index eae862c..da52d6c 100644 --- a/biscuit-builder/src/scope.zig +++ b/biscuit-builder/src/scope.zig @@ -19,7 +19,7 @@ pub const Scope = union(ScopeKind) { parameter: []const u8, /// convert to datalog fact - pub fn convert(scope: Scope, _: std.mem.Allocator, symbols: *datalog.SymbolTable) !datalog.Scope { + pub fn toDatalog(scope: Scope, _: std.mem.Allocator, symbols: *datalog.SymbolTable) !datalog.Scope { return switch (scope) { .authority => .{ .authority = {} }, .previous => .{ .previous = {} }, diff --git a/biscuit-builder/src/term.zig b/biscuit-builder/src/term.zig index f3ffa45..11178c2 100644 --- a/biscuit-builder/src/term.zig +++ b/biscuit-builder/src/term.zig @@ -19,7 +19,7 @@ pub const Term = union(TermTag) { pub fn deinit(_: Term) void {} - pub fn convert(term: Term, _: std.mem.Allocator, symbols: *datalog.SymbolTable) !datalog.Term { + pub fn toDatalog(term: Term, _: std.mem.Allocator, symbols: *datalog.SymbolTable) !datalog.Term { return switch (term) { .variable => |s| .{ .variable = @truncate(try symbols.insert(s)) }, // FIXME: assert symbol fits in u32 .string => |s| .{ .string = try symbols.insert(s) }, diff --git a/biscuit/src/authorizer.zig b/biscuit/src/authorizer.zig index 82942ea..d27ddfe 100644 --- a/biscuit/src/authorizer.zig +++ b/biscuit/src/authorizer.zig @@ -95,7 +95,7 @@ pub const Authorizer = struct { const origin = try Origin.initWithId(authorizer.allocator, Origin.AUTHORIZER_ID); - try authorizer.world.addFact(origin, try fact.convert(authorizer.allocator, &authorizer.symbols)); + try authorizer.world.addFact(origin, try fact.toDatalog(authorizer.allocator, &authorizer.symbols)); } /// Add check from string to authorizer @@ -238,7 +238,7 @@ pub const Authorizer = struct { std.debug.print("\nAUTHORIZER CHECKS\n", .{}); for (authorizer.checks.items) |c| { std.debug.print("authorizer check = {any}\n", .{c}); - const check = try c.convert(authorizer.allocator, &authorizer.symbols); + const check = try c.toDatalog(authorizer.allocator, &authorizer.symbols); for (check.queries.items, 0..) |*query, check_id| { const rule_trusted_origins = try TrustedOrigins.fromScopes( @@ -300,7 +300,7 @@ pub const Authorizer = struct { std.debug.print("authorizer policy = {any}\n", .{policy}); for (policy.queries.items, 0..) |*q, policy_id| { - var query = try q.convert(authorizer.allocator, &authorizer.symbols); + var query = try q.toDatalog(authorizer.allocator, &authorizer.symbols); const rule_trusted_origins = try TrustedOrigins.fromScopes( authorizer.allocator, From 0aedfda009b75e29bdbb9ebefec055b80d2e3f61 Mon Sep 17 00:00:00 2001 From: Malcolm Still Date: Sat, 30 Mar 2024 02:53:29 +0000 Subject: [PATCH 03/52] datalog convert rename to remapSymbols --- biscuit-builder/src/policy.zig | 2 +- biscuit-datalog/src/check.zig | 4 ++-- biscuit-datalog/src/expression.zig | 4 ++-- biscuit-datalog/src/fact.zig | 4 ++-- biscuit-datalog/src/predicate.zig | 4 ++-- biscuit-datalog/src/rule.zig | 10 +++++----- biscuit-datalog/src/scope.zig | 2 +- biscuit-datalog/src/term.zig | 4 ++-- biscuit/src/authorizer.zig | 12 ++++++------ 9 files changed, 23 insertions(+), 23 deletions(-) diff --git a/biscuit-builder/src/policy.zig b/biscuit-builder/src/policy.zig index 190046c..25f4e61 100644 --- a/biscuit-builder/src/policy.zig +++ b/biscuit-builder/src/policy.zig @@ -23,7 +23,7 @@ pub const Policy = struct { // var queries = std.ArrayList(Rule).init(allocator); // for (policy.queries.items) |query| { - // try queries.append(try query.convert(allocator, symbols)); + // try queries.append(try query.toDatalog(allocator, symbols)); // } // return .{ .kind = policy.kind, .queries = queries }; diff --git a/biscuit-datalog/src/check.zig b/biscuit-datalog/src/check.zig index 5a4564e..701162d 100644 --- a/biscuit-datalog/src/check.zig +++ b/biscuit-datalog/src/check.zig @@ -41,11 +41,11 @@ pub const Check = struct { return writer.print("", .{}); } - pub fn convert(check: Check, old_symbols: *const SymbolTable, new_symbols: *SymbolTable) !Check { + pub fn remapSymbols(check: Check, old_symbols: *const SymbolTable, new_symbols: *SymbolTable) !Check { var queries = try check.queries.clone(); for (queries.items, 0..) |query, i| { - queries.items[i] = try query.convert(old_symbols, new_symbols); + queries.items[i] = try query.remapSymbols(old_symbols, new_symbols); } return .{ diff --git a/biscuit-datalog/src/expression.zig b/biscuit-datalog/src/expression.zig index 1f30fee..c2c1bab 100644 --- a/biscuit-datalog/src/expression.zig +++ b/biscuit-datalog/src/expression.zig @@ -99,13 +99,13 @@ pub const Expression = struct { return stack.items[0]; } - pub fn convert(expression: Expression, old_symbols: *const SymbolTable, new_symbols: *SymbolTable) !Expression { + pub fn remapSymbols(expression: Expression, old_symbols: *const SymbolTable, new_symbols: *SymbolTable) !Expression { // std.debug.print("Converting expression\n", .{}); const ops = try expression.ops.clone(); for (ops.items, 0..) |op, i| { ops.items[i] = switch (op) { - .value => |trm| .{ .value = try trm.convert(old_symbols, new_symbols) }, + .value => |trm| .{ .value = try trm.remapSymbols(old_symbols, new_symbols) }, else => op, }; } diff --git a/biscuit-datalog/src/fact.zig b/biscuit-datalog/src/fact.zig index 27aa944..a20db81 100644 --- a/biscuit-datalog/src/fact.zig +++ b/biscuit-datalog/src/fact.zig @@ -22,8 +22,8 @@ pub const Fact = struct { } /// Convert fact to new symbol space - pub fn convert(fact: Fact, old_symbols: *const SymbolTable, new_symbols: *SymbolTable) !Fact { - return .{ .predicate = try fact.predicate.convert(old_symbols, new_symbols) }; + pub fn remapSymbols(fact: Fact, old_symbols: *const SymbolTable, new_symbols: *SymbolTable) !Fact { + return .{ .predicate = try fact.predicate.remapSymbols(old_symbols, new_symbols) }; } pub fn clone(fact: Fact) !Fact { diff --git a/biscuit-datalog/src/predicate.zig b/biscuit-datalog/src/predicate.zig index 612260f..1d41cd9 100644 --- a/biscuit-datalog/src/predicate.zig +++ b/biscuit-datalog/src/predicate.zig @@ -67,12 +67,12 @@ pub const Predicate = struct { /// Convert predicate to new symbol space /// /// Equivalent to clone but with the symbol rewriting - pub fn convert(predicate: Predicate, old_symbols: *const SymbolTable, new_symbols: *SymbolTable) !Predicate { + pub fn remapSymbols(predicate: Predicate, old_symbols: *const SymbolTable, new_symbols: *SymbolTable) !Predicate { const name = try old_symbols.getString(predicate.name); var terms = try predicate.terms.clone(); for (terms.items, 0..) |term, i| { - terms.items[i] = try term.convert(old_symbols, new_symbols); + terms.items[i] = try term.remapSymbols(old_symbols, new_symbols); } return .{ diff --git a/biscuit-datalog/src/rule.zig b/biscuit-datalog/src/rule.zig index b1fb127..4e43a01 100644 --- a/biscuit-datalog/src/rule.zig +++ b/biscuit-datalog/src/rule.zig @@ -292,25 +292,25 @@ pub const Rule = struct { } // Convert datalog fact from old symbol space to new symbol space - pub fn convert(rule: Rule, old_symbols: *const SymbolTable, new_symbols: *SymbolTable) !Rule { + pub fn remapSymbols(rule: Rule, old_symbols: *const SymbolTable, new_symbols: *SymbolTable) !Rule { var body = try rule.body.clone(); var expressions = try rule.expressions.clone(); var scopes = try rule.scopes.clone(); for (body.items, 0..) |predicate, i| { - body.items[i] = try predicate.convert(old_symbols, new_symbols); + body.items[i] = try predicate.remapSymbols(old_symbols, new_symbols); } for (expressions.items, 0..) |expression, i| { - expressions.items[i] = try expression.convert(old_symbols, new_symbols); + expressions.items[i] = try expression.remapSymbols(old_symbols, new_symbols); } for (scopes.items, 0..) |scope, i| { - scopes.items[i] = try scope.convert(old_symbols, new_symbols); + scopes.items[i] = try scope.remapSymbols(old_symbols, new_symbols); } return .{ - .head = try rule.head.convert(old_symbols, new_symbols), + .head = try rule.head.remapSymbols(old_symbols, new_symbols), .body = body, .expressions = expressions, .scopes = scopes, diff --git a/biscuit-datalog/src/scope.zig b/biscuit-datalog/src/scope.zig index 5eb6546..2670816 100644 --- a/biscuit-datalog/src/scope.zig +++ b/biscuit-datalog/src/scope.zig @@ -20,7 +20,7 @@ pub const Scope = union(ScopeTag) { }; } - pub fn convert(scope: Scope, old_symbols: *const SymbolTable, new_symbols: *SymbolTable) !Scope { + pub fn remapSymbols(scope: Scope, old_symbols: *const SymbolTable, new_symbols: *SymbolTable) !Scope { return switch (scope) { .authority => .authority, .previous => .previous, diff --git a/biscuit-datalog/src/term.zig b/biscuit-datalog/src/term.zig index 01e711a..c043900 100644 --- a/biscuit-datalog/src/term.zig +++ b/biscuit-datalog/src/term.zig @@ -44,7 +44,7 @@ pub const Term = union(TermKind) { }; } - pub fn convert(term: Term, old_symbols: *const SymbolTable, new_symbols: *SymbolTable) !Term { + pub fn remapSymbols(term: Term, old_symbols: *const SymbolTable, new_symbols: *SymbolTable) !Term { return switch (term) { .variable => |id| .{ .variable = std.math.cast(u32, try new_symbols.insert(try old_symbols.getString(id))) orelse return error.VariableIdTooLarge }, .string => |id| .{ .string = try new_symbols.insert(try old_symbols.getString(id)) }, @@ -54,7 +54,7 @@ pub const Term = union(TermKind) { var it = s.iterator(); while (it.next()) |term_ptr| { - try set.add(try term_ptr.convert(old_symbols, new_symbols)); + try set.add(try term_ptr.remapSymbols(old_symbols, new_symbols)); } break :blk .{ .set = set }; diff --git a/biscuit/src/authorizer.zig b/biscuit/src/authorizer.zig index d27ddfe..45eaff8 100644 --- a/biscuit/src/authorizer.zig +++ b/biscuit/src/authorizer.zig @@ -158,7 +158,7 @@ pub const Authorizer = struct { std.debug.print(" [{}]: {x}\n", .{ i, pk.bytes }); } for (biscuit.authority.facts.items) |authority_fact| { - const fact = try authority_fact.convert(&biscuit.symbols, &authorizer.symbols); + const fact = try authority_fact.remapSymbols(&biscuit.symbols, &authorizer.symbols); const origin = try Origin.initWithId(authorizer.allocator, 0); try authorizer.world.addFact(origin, fact); @@ -174,7 +174,7 @@ pub const Authorizer = struct { for (biscuit.authority.rules.items) |authority_rule| { // Map from biscuit symbol space to authorizer symbol space - const rule = try authority_rule.convert(&biscuit.symbols, &authorizer.symbols); + const rule = try authority_rule.remapSymbols(&biscuit.symbols, &authorizer.symbols); if (!rule.validateVariables()) { try errors.append(.unbound_variable); @@ -194,7 +194,7 @@ pub const Authorizer = struct { for (biscuit.blocks.items, 1..) |block, block_id| { for (block.facts.items) |block_fact| { - const fact = try block_fact.convert(&biscuit.symbols, &authorizer.symbols); + const fact = try block_fact.remapSymbols(&biscuit.symbols, &authorizer.symbols); const origin = try Origin.initWithId(authorizer.allocator, block_id); try authorizer.world.addFact(origin, fact); @@ -209,7 +209,7 @@ pub const Authorizer = struct { ); for (block.rules.items) |block_rule| { - const rule = try block_rule.convert(&biscuit.symbols, &authorizer.symbols); + const rule = try block_rule.remapSymbols(&biscuit.symbols, &authorizer.symbols); std.debug.print("block rule {any} CONVERTED to rule = {any}\n", .{ block_rule, rule }); if (!rule.validateVariables()) { @@ -271,7 +271,7 @@ pub const Authorizer = struct { ); for (biscuit.authority.checks.items, 0..) |c, check_id| { - const check = try c.convert(&biscuit.symbols, &authorizer.symbols); + const check = try c.remapSymbols(&biscuit.symbols, &authorizer.symbols); std.debug.print("{}: {any}\n", .{ check_id, check }); for (check.queries.items) |*query| { @@ -343,7 +343,7 @@ pub const Authorizer = struct { std.debug.print("block = {any}\n", .{block}); for (block.checks.items, 0..) |c, check_id| { - const check = try c.convert(&biscuit.symbols, &authorizer.symbols); + const check = try c.remapSymbols(&biscuit.symbols, &authorizer.symbols); std.debug.print("check = {any}\n", .{check}); From e224e1784a7a2c0269dff835ea138cbabf343a35 Mon Sep 17 00:00:00 2001 From: Malcolm Still Date: Sat, 30 Mar 2024 02:54:51 +0000 Subject: [PATCH 04/52] remapSymbols -> remap --- biscuit-datalog/src/check.zig | 4 ++-- biscuit-datalog/src/expression.zig | 4 ++-- biscuit-datalog/src/fact.zig | 4 ++-- biscuit-datalog/src/predicate.zig | 4 ++-- biscuit-datalog/src/rule.zig | 10 +++++----- biscuit-datalog/src/scope.zig | 2 +- biscuit-datalog/src/term.zig | 4 ++-- biscuit/src/authorizer.zig | 12 ++++++------ 8 files changed, 22 insertions(+), 22 deletions(-) diff --git a/biscuit-datalog/src/check.zig b/biscuit-datalog/src/check.zig index 701162d..9815499 100644 --- a/biscuit-datalog/src/check.zig +++ b/biscuit-datalog/src/check.zig @@ -41,11 +41,11 @@ pub const Check = struct { return writer.print("", .{}); } - pub fn remapSymbols(check: Check, old_symbols: *const SymbolTable, new_symbols: *SymbolTable) !Check { + pub fn remap(check: Check, old_symbols: *const SymbolTable, new_symbols: *SymbolTable) !Check { var queries = try check.queries.clone(); for (queries.items, 0..) |query, i| { - queries.items[i] = try query.remapSymbols(old_symbols, new_symbols); + queries.items[i] = try query.remap(old_symbols, new_symbols); } return .{ diff --git a/biscuit-datalog/src/expression.zig b/biscuit-datalog/src/expression.zig index c2c1bab..a86ade8 100644 --- a/biscuit-datalog/src/expression.zig +++ b/biscuit-datalog/src/expression.zig @@ -99,13 +99,13 @@ pub const Expression = struct { return stack.items[0]; } - pub fn remapSymbols(expression: Expression, old_symbols: *const SymbolTable, new_symbols: *SymbolTable) !Expression { + pub fn remap(expression: Expression, old_symbols: *const SymbolTable, new_symbols: *SymbolTable) !Expression { // std.debug.print("Converting expression\n", .{}); const ops = try expression.ops.clone(); for (ops.items, 0..) |op, i| { ops.items[i] = switch (op) { - .value => |trm| .{ .value = try trm.remapSymbols(old_symbols, new_symbols) }, + .value => |trm| .{ .value = try trm.remap(old_symbols, new_symbols) }, else => op, }; } diff --git a/biscuit-datalog/src/fact.zig b/biscuit-datalog/src/fact.zig index a20db81..9f32113 100644 --- a/biscuit-datalog/src/fact.zig +++ b/biscuit-datalog/src/fact.zig @@ -22,8 +22,8 @@ pub const Fact = struct { } /// Convert fact to new symbol space - pub fn remapSymbols(fact: Fact, old_symbols: *const SymbolTable, new_symbols: *SymbolTable) !Fact { - return .{ .predicate = try fact.predicate.remapSymbols(old_symbols, new_symbols) }; + pub fn remap(fact: Fact, old_symbols: *const SymbolTable, new_symbols: *SymbolTable) !Fact { + return .{ .predicate = try fact.predicate.remap(old_symbols, new_symbols) }; } pub fn clone(fact: Fact) !Fact { diff --git a/biscuit-datalog/src/predicate.zig b/biscuit-datalog/src/predicate.zig index 1d41cd9..3c95db4 100644 --- a/biscuit-datalog/src/predicate.zig +++ b/biscuit-datalog/src/predicate.zig @@ -67,12 +67,12 @@ pub const Predicate = struct { /// Convert predicate to new symbol space /// /// Equivalent to clone but with the symbol rewriting - pub fn remapSymbols(predicate: Predicate, old_symbols: *const SymbolTable, new_symbols: *SymbolTable) !Predicate { + pub fn remap(predicate: Predicate, old_symbols: *const SymbolTable, new_symbols: *SymbolTable) !Predicate { const name = try old_symbols.getString(predicate.name); var terms = try predicate.terms.clone(); for (terms.items, 0..) |term, i| { - terms.items[i] = try term.remapSymbols(old_symbols, new_symbols); + terms.items[i] = try term.remap(old_symbols, new_symbols); } return .{ diff --git a/biscuit-datalog/src/rule.zig b/biscuit-datalog/src/rule.zig index 4e43a01..310809c 100644 --- a/biscuit-datalog/src/rule.zig +++ b/biscuit-datalog/src/rule.zig @@ -292,25 +292,25 @@ pub const Rule = struct { } // Convert datalog fact from old symbol space to new symbol space - pub fn remapSymbols(rule: Rule, old_symbols: *const SymbolTable, new_symbols: *SymbolTable) !Rule { + pub fn remap(rule: Rule, old_symbols: *const SymbolTable, new_symbols: *SymbolTable) !Rule { var body = try rule.body.clone(); var expressions = try rule.expressions.clone(); var scopes = try rule.scopes.clone(); for (body.items, 0..) |predicate, i| { - body.items[i] = try predicate.remapSymbols(old_symbols, new_symbols); + body.items[i] = try predicate.remap(old_symbols, new_symbols); } for (expressions.items, 0..) |expression, i| { - expressions.items[i] = try expression.remapSymbols(old_symbols, new_symbols); + expressions.items[i] = try expression.remap(old_symbols, new_symbols); } for (scopes.items, 0..) |scope, i| { - scopes.items[i] = try scope.remapSymbols(old_symbols, new_symbols); + scopes.items[i] = try scope.remap(old_symbols, new_symbols); } return .{ - .head = try rule.head.remapSymbols(old_symbols, new_symbols), + .head = try rule.head.remap(old_symbols, new_symbols), .body = body, .expressions = expressions, .scopes = scopes, diff --git a/biscuit-datalog/src/scope.zig b/biscuit-datalog/src/scope.zig index 2670816..60efa7a 100644 --- a/biscuit-datalog/src/scope.zig +++ b/biscuit-datalog/src/scope.zig @@ -20,7 +20,7 @@ pub const Scope = union(ScopeTag) { }; } - pub fn remapSymbols(scope: Scope, old_symbols: *const SymbolTable, new_symbols: *SymbolTable) !Scope { + pub fn remap(scope: Scope, old_symbols: *const SymbolTable, new_symbols: *SymbolTable) !Scope { return switch (scope) { .authority => .authority, .previous => .previous, diff --git a/biscuit-datalog/src/term.zig b/biscuit-datalog/src/term.zig index c043900..9cc8f48 100644 --- a/biscuit-datalog/src/term.zig +++ b/biscuit-datalog/src/term.zig @@ -44,7 +44,7 @@ pub const Term = union(TermKind) { }; } - pub fn remapSymbols(term: Term, old_symbols: *const SymbolTable, new_symbols: *SymbolTable) !Term { + pub fn remap(term: Term, old_symbols: *const SymbolTable, new_symbols: *SymbolTable) !Term { return switch (term) { .variable => |id| .{ .variable = std.math.cast(u32, try new_symbols.insert(try old_symbols.getString(id))) orelse return error.VariableIdTooLarge }, .string => |id| .{ .string = try new_symbols.insert(try old_symbols.getString(id)) }, @@ -54,7 +54,7 @@ pub const Term = union(TermKind) { var it = s.iterator(); while (it.next()) |term_ptr| { - try set.add(try term_ptr.remapSymbols(old_symbols, new_symbols)); + try set.add(try term_ptr.remap(old_symbols, new_symbols)); } break :blk .{ .set = set }; diff --git a/biscuit/src/authorizer.zig b/biscuit/src/authorizer.zig index 45eaff8..9f5cfc5 100644 --- a/biscuit/src/authorizer.zig +++ b/biscuit/src/authorizer.zig @@ -158,7 +158,7 @@ pub const Authorizer = struct { std.debug.print(" [{}]: {x}\n", .{ i, pk.bytes }); } for (biscuit.authority.facts.items) |authority_fact| { - const fact = try authority_fact.remapSymbols(&biscuit.symbols, &authorizer.symbols); + const fact = try authority_fact.remap(&biscuit.symbols, &authorizer.symbols); const origin = try Origin.initWithId(authorizer.allocator, 0); try authorizer.world.addFact(origin, fact); @@ -174,7 +174,7 @@ pub const Authorizer = struct { for (biscuit.authority.rules.items) |authority_rule| { // Map from biscuit symbol space to authorizer symbol space - const rule = try authority_rule.remapSymbols(&biscuit.symbols, &authorizer.symbols); + const rule = try authority_rule.remap(&biscuit.symbols, &authorizer.symbols); if (!rule.validateVariables()) { try errors.append(.unbound_variable); @@ -194,7 +194,7 @@ pub const Authorizer = struct { for (biscuit.blocks.items, 1..) |block, block_id| { for (block.facts.items) |block_fact| { - const fact = try block_fact.remapSymbols(&biscuit.symbols, &authorizer.symbols); + const fact = try block_fact.remap(&biscuit.symbols, &authorizer.symbols); const origin = try Origin.initWithId(authorizer.allocator, block_id); try authorizer.world.addFact(origin, fact); @@ -209,7 +209,7 @@ pub const Authorizer = struct { ); for (block.rules.items) |block_rule| { - const rule = try block_rule.remapSymbols(&biscuit.symbols, &authorizer.symbols); + const rule = try block_rule.remap(&biscuit.symbols, &authorizer.symbols); std.debug.print("block rule {any} CONVERTED to rule = {any}\n", .{ block_rule, rule }); if (!rule.validateVariables()) { @@ -271,7 +271,7 @@ pub const Authorizer = struct { ); for (biscuit.authority.checks.items, 0..) |c, check_id| { - const check = try c.remapSymbols(&biscuit.symbols, &authorizer.symbols); + const check = try c.remap(&biscuit.symbols, &authorizer.symbols); std.debug.print("{}: {any}\n", .{ check_id, check }); for (check.queries.items) |*query| { @@ -343,7 +343,7 @@ pub const Authorizer = struct { std.debug.print("block = {any}\n", .{block}); for (block.checks.items, 0..) |c, check_id| { - const check = try c.remapSymbols(&biscuit.symbols, &authorizer.symbols); + const check = try c.remap(&biscuit.symbols, &authorizer.symbols); std.debug.print("check = {any}\n", .{check}); From 8781af02c4b2272ef94a21c2e6633207143134d7 Mon Sep 17 00:00:00 2001 From: Malcolm Still Date: Sat, 30 Mar 2024 16:37:00 +0000 Subject: [PATCH 05/52] Clean up --- DESIGN.md | 33 +++++ biscuit-builder/src/check.zig | 10 +- biscuit-builder/src/expression.zig | 32 ++-- biscuit-builder/src/fact.zig | 4 +- biscuit-builder/src/policy.zig | 10 +- biscuit-builder/src/predicate.zig | 10 +- biscuit-builder/src/rule.zig | 22 +-- biscuit-datalog/src/check.zig | 10 +- biscuit-datalog/src/combinator.zig | 4 +- biscuit-datalog/src/expression.zig | 13 +- biscuit-datalog/src/fact.zig | 4 +- biscuit-datalog/src/fact_set.zig | 14 +- biscuit-datalog/src/matched_variables.zig | 4 +- biscuit-datalog/src/origin.zig | 4 +- biscuit-datalog/src/predicate.zig | 14 +- biscuit-datalog/src/rule.zig | 93 ++++++------ biscuit-datalog/src/rule_set.zig | 18 ++- biscuit-datalog/src/set.zig | 8 +- biscuit-datalog/src/symbol_table.zig | 8 +- biscuit-datalog/src/trusted_origins.zig | 4 +- biscuit-datalog/src/world.zig | 69 ++++----- biscuit-format/src/serialized_biscuit.zig | 20 ++- biscuit-format/src/signed_block.zig | 2 +- biscuit-parser/src/parser.zig | 52 +------ biscuit-samples/src/main.zig | 6 +- biscuit/src/authorizer.zig | 171 ++++++++++++---------- biscuit/src/biscuit.zig | 146 +++++++++--------- biscuit/src/block.zig | 4 +- 28 files changed, 408 insertions(+), 381 deletions(-) create mode 100644 DESIGN.md diff --git a/DESIGN.md b/DESIGN.md new file mode 100644 index 0000000..bcfa3a2 --- /dev/null +++ b/DESIGN.md @@ -0,0 +1,33 @@ +# Design + +## Memory Management + +Authorizing a biscuit is a short-lived single pass operation. This makes arena allocation +a great candidate for large parts of biscuit memory management: + +- It can greatly reduce the complexity of code that would otherwise have to carefully clone + / move memory to avoid leaks and double frees. +- Potentially code can be faster because we don't do granular deallocation and we can avoid + some copying. +- Again, because we're not necessarily copying, there is the potential for reduced memory usage + in places. + +The disadvantage would be that: + +- We potentially over allocate, i.e. we are storing more in memory than we technically need to. + +We create a toplevel arena and into it allocate all: + +- facts +- predicates +- rules +- queries +- policies +- expressions +- ops +- terms + +i.e. all the domain level objects are arena allocated. This means we don't have to do +complex reasoning about scope / lifetimes of these objects, they are all valid until +the toplevel arena is deallocated. If we are careful to always copy when modifying +one of these resources we can also share resources. diff --git a/biscuit-builder/src/check.zig b/biscuit-builder/src/check.zig index df77a62..b4d395a 100644 --- a/biscuit-builder/src/check.zig +++ b/biscuit-builder/src/check.zig @@ -8,12 +8,12 @@ pub const Check = struct { kind: datalog.Check.Kind, queries: std.ArrayList(Rule), - pub fn deinit(check: Check) void { - for (check.queries.items) |query| { - query.deinit(); - } + pub fn deinit(_: Check) void { + // for (check.queries.items) |query| { + // query.deinit(); + // } - check.queries.deinit(); + // check.queries.deinit(); } pub fn toDatalog(check: Check, allocator: std.mem.Allocator, symbols: *datalog.SymbolTable) !datalog.Check { diff --git a/biscuit-builder/src/expression.zig b/biscuit-builder/src/expression.zig index 4cd7955..6515038 100644 --- a/biscuit-builder/src/expression.zig +++ b/biscuit-builder/src/expression.zig @@ -113,22 +113,22 @@ pub const Expression = union(ExpressionType) { } } - pub fn deinit(expression: *Expression) void { - switch (expression.*) { - .value => |v| v.deinit(), - .unary => |*u| { - u.expression.deinit(); - - u.allocator.destroy(u.expression); - }, - .binary => |*b| { - b.left.deinit(); - b.right.deinit(); - - b.allocator.destroy(b.left); - b.allocator.destroy(b.right); - }, - } + pub fn deinit(_: *Expression) void { + // switch (expression.*) { + // .value => |v| v.deinit(), + // .unary => |*u| { + // u.expression.deinit(); + + // u.allocator.destroy(u.expression); + // }, + // .binary => |*b| { + // b.left.deinit(); + // b.right.deinit(); + + // b.allocator.destroy(b.left); + // b.allocator.destroy(b.right); + // }, + // } } pub fn value(term: Term) !Expression { diff --git a/biscuit-builder/src/fact.zig b/biscuit-builder/src/fact.zig index 1eff884..0fcc54c 100644 --- a/biscuit-builder/src/fact.zig +++ b/biscuit-builder/src/fact.zig @@ -7,8 +7,8 @@ pub const Fact = struct { predicate: Predicate, variables: ?std.StringHashMap(?Term), - pub fn deinit(fact: Fact) void { - fact.predicate.deinit(); + pub fn deinit(_: Fact) void { + // fact.predicate.deinit(); } /// convert to datalog fact diff --git a/biscuit-builder/src/policy.zig b/biscuit-builder/src/policy.zig index 25f4e61..badcd45 100644 --- a/biscuit-builder/src/policy.zig +++ b/biscuit-builder/src/policy.zig @@ -11,12 +11,12 @@ pub const Policy = struct { deny, }; - pub fn deinit(policy: Policy) void { - for (policy.queries.items) |query| { - query.deinit(); - } + pub fn deinit(_: Policy) void { + // for (policy.queries.items) |query| { + // query.deinit(); + // } - policy.queries.deinit(); + // policy.queries.deinit(); } // pub fn toDatalog(policy: Policy, allocator: std.mem.Allocator, symbols: *datalog.SymbolTable) !Policy { diff --git a/biscuit-builder/src/predicate.zig b/biscuit-builder/src/predicate.zig index 1f91274..739c658 100644 --- a/biscuit-builder/src/predicate.zig +++ b/biscuit-builder/src/predicate.zig @@ -6,12 +6,12 @@ pub const Predicate = struct { name: []const u8, terms: std.ArrayList(Term), - pub fn deinit(predicate: Predicate) void { - for (predicate.terms.items) |term| { - term.deinit(); - } + pub fn deinit(_: Predicate) void { + // for (predicate.terms.items) |term| { + // term.deinit(); + // } - predicate.terms.deinit(); + // predicate.terms.deinit(); } /// convert to datalog predicate diff --git a/biscuit-builder/src/rule.zig b/biscuit-builder/src/rule.zig index adb2857..b9e6238 100644 --- a/biscuit-builder/src/rule.zig +++ b/biscuit-builder/src/rule.zig @@ -12,20 +12,20 @@ pub const Rule = struct { variables: ?std.StringHashMap(?Term), scopes: std.ArrayList(Scope), - pub fn deinit(rule: Rule) void { - rule.head.deinit(); + pub fn deinit(_: Rule) void { + // rule.head.deinit(); - for (rule.body.items) |predicate| { - predicate.deinit(); - } + // for (rule.body.items) |predicate| { + // predicate.deinit(); + // } - for (rule.expressions.items) |*expression| { - expression.deinit(); - } + // for (rule.expressions.items) |*expression| { + // expression.deinit(); + // } - rule.body.deinit(); - rule.expressions.deinit(); - rule.scopes.deinit(); + // rule.body.deinit(); + // rule.expressions.deinit(); + // rule.scopes.deinit(); } /// convert to datalog predicate diff --git a/biscuit-datalog/src/check.zig b/biscuit-datalog/src/check.zig index 9815499..be91c27 100644 --- a/biscuit-datalog/src/check.zig +++ b/biscuit-datalog/src/check.zig @@ -24,12 +24,12 @@ pub const Check = struct { return .{ .queries = rules, .kind = kind }; } - pub fn deinit(check: *Check) void { - for (check.queries.items) |*query| { - query.deinit(); - } + pub fn deinit(_: *Check) void { + // for (check.queries.items) |*query| { + // query.deinit(); + // } - check.queries.deinit(); + // check.queries.deinit(); } pub fn format(check: Check, comptime _: []const u8, _: std.fmt.FormatOptions, writer: anytype) !void { diff --git a/biscuit-datalog/src/combinator.zig b/biscuit-datalog/src/combinator.zig index baf4ad5..22cb184 100644 --- a/biscuit-datalog/src/combinator.zig +++ b/biscuit-datalog/src/combinator.zig @@ -11,6 +11,8 @@ const Expression = @import("expression.zig").Expression; const MatchedVariables = @import("matched_variables.zig").MatchedVariables; const SymbolTable = @import("symbol_table.zig").SymbolTable; +const log = std.log.scoped(.combinator); + /// Combinator is an iterator that will generate MatchedVariables from /// the body of a rule. /// @@ -103,7 +105,7 @@ pub const Combinator = struct { // Lookup the next (trusted) fact const origin_fact = combinator.trusted_fact_iterator.next() orelse return null; - std.debug.print("combinator next trusted fact: {any}\n", .{origin_fact.fact}); + log.debug("[{}] next trusted fact: {any}", .{ combinator.id, origin_fact.fact }); const origin = origin_fact.origin.*; const fact = origin_fact.fact.*; diff --git a/biscuit-datalog/src/expression.zig b/biscuit-datalog/src/expression.zig index a86ade8..47b604c 100644 --- a/biscuit-datalog/src/expression.zig +++ b/biscuit-datalog/src/expression.zig @@ -5,16 +5,18 @@ const Regex = @import("regex").Regex; const Term = @import("term.zig").Term; const SymbolTable = @import("symbol_table.zig").SymbolTable; +const log = std.log.scoped(.expression); + pub const Expression = struct { ops: std.ArrayList(Op), - pub fn fromSchema(allocator: std.mem.Allocator, schema_expression: schema.ExpressionV2) !Expression { - var ops = std.ArrayList(Op).init(allocator); + pub fn fromSchema(arena: std.mem.Allocator, schema_expression: schema.ExpressionV2) !Expression { + var ops = try std.ArrayList(Op).initCapacity(arena, schema_expression.ops.items.len); for (schema_expression.ops.items) |schema_op| { const schema_op_content = schema_op.Content orelse return error.ExpectedOp; const op: Op = switch (schema_op_content) { - .value => |term| .{ .value = try Term.fromSchema(allocator, term) }, + .value => |term| .{ .value = try Term.fromSchema(arena, term) }, .unary => |unary_op| .{ .unary = switch (unary_op.kind) { .Negate => .negate, @@ -57,8 +59,8 @@ pub const Expression = struct { return .{ .ops = ops }; } - pub fn deinit(expression: *Expression) void { - expression.ops.deinit(); + pub fn deinit(_: *Expression) void { + // expression.ops.deinit(); } pub fn evaluate(expr: Expression, allocator: mem.Allocator, values: std.AutoHashMap(u32, Term), symbols: *SymbolTable) !Term { @@ -100,7 +102,6 @@ pub const Expression = struct { } pub fn remap(expression: Expression, old_symbols: *const SymbolTable, new_symbols: *SymbolTable) !Expression { - // std.debug.print("Converting expression\n", .{}); const ops = try expression.ops.clone(); for (ops.items, 0..) |op, i| { diff --git a/biscuit-datalog/src/fact.zig b/biscuit-datalog/src/fact.zig index 9f32113..cbeaf75 100644 --- a/biscuit-datalog/src/fact.zig +++ b/biscuit-datalog/src/fact.zig @@ -17,8 +17,8 @@ pub const Fact = struct { return .{ .predicate = predicate }; } - pub fn deinit(fact: *Fact) void { - fact.predicate.deinit(); + pub fn deinit(_: *Fact) void { + // fact.predicate.deinit(); } /// Convert fact to new symbol space diff --git a/biscuit-datalog/src/fact_set.zig b/biscuit-datalog/src/fact_set.zig index a0fd9f4..e5fd040 100644 --- a/biscuit-datalog/src/fact_set.zig +++ b/biscuit-datalog/src/fact_set.zig @@ -38,15 +38,15 @@ pub const FactSet = struct { // value as a key, and we try to insert into hashmap that already contains that value, // we will leak the key if we don't detect the existing version and deallocate one of the // keys. - pub fn deinit(fact_set: *FactSet) void { - var it = fact_set.sets.iterator(); + pub fn deinit(_: *FactSet) void { + // var it = fact_set.sets.iterator(); - while (it.next()) |origin_facts| { - origin_facts.key_ptr.deinit(); // Okay, in practice this is also giving us incorrect alignment issues - origin_facts.value_ptr.deinit(); - } + // while (it.next()) |origin_facts| { + // origin_facts.key_ptr.deinit(); // Okay, in practice this is also giving us incorrect alignment issues + // origin_facts.value_ptr.deinit(); + // } - fact_set.sets.deinit(); + // fact_set.sets.deinit(); } pub const Iterator = struct { diff --git a/biscuit-datalog/src/matched_variables.zig b/biscuit-datalog/src/matched_variables.zig index b22dee6..ef3ab56 100644 --- a/biscuit-datalog/src/matched_variables.zig +++ b/biscuit-datalog/src/matched_variables.zig @@ -49,8 +49,8 @@ pub const MatchedVariables = struct { return .{ .variables = variables }; } - pub fn deinit(matched_variables: *MatchedVariables) void { - matched_variables.variables.deinit(); + pub fn deinit(_: *MatchedVariables) void { + // matched_variables.variables.deinit(); } pub fn clone(matched_variables: *const MatchedVariables) !MatchedVariables { diff --git a/biscuit-datalog/src/origin.zig b/biscuit-datalog/src/origin.zig index 1ed103d..686ce9f 100644 --- a/biscuit-datalog/src/origin.zig +++ b/biscuit-datalog/src/origin.zig @@ -23,8 +23,8 @@ pub const Origin = struct { return .{ .block_ids = block_ids }; } - pub fn deinit(origin: *Origin) void { - origin.block_ids.deinit(); + pub fn deinit(_: *Origin) void { + // origin.block_ids.deinit(); } pub fn format(origin: Origin, comptime _: []const u8, _: std.fmt.FormatOptions, writer: anytype) !void { diff --git a/biscuit-datalog/src/predicate.zig b/biscuit-datalog/src/predicate.zig index 3c95db4..539f6ae 100644 --- a/biscuit-datalog/src/predicate.zig +++ b/biscuit-datalog/src/predicate.zig @@ -26,11 +26,11 @@ pub const Predicate = struct { return writer.print(")", .{}); } - pub fn deinit(predicate: *Predicate) void { - for (predicate.terms.items) |*term| { - term.deinit(); - } - predicate.terms.deinit(); + pub fn deinit(_: *Predicate) void { + // for (predicate.terms.items) |*term| { + // term.deinit(); + // } + // predicate.terms.deinit(); } pub fn eql(predicate: Predicate, other_predicate: Predicate) bool { @@ -120,6 +120,8 @@ test { const testing = std.testing; const allocator = testing.allocator; + const test_log = std.log.scoped(.test_predicate); + var terms_1 = std.ArrayList(Term).init(allocator); defer terms_1.deinit(); try terms_1.insertSlice(0, &.{ .{ .string = 10 }, .{ .integer = 20 } }); @@ -151,5 +153,5 @@ test { try testing.expect(!p1.match(p4)); try testing.expect(p1.match(p5)); - std.debug.print("predicate = {any}\n", .{p1}); + test_log.debug("predicate = {any}\n", .{p1}); } diff --git a/biscuit-datalog/src/rule.zig b/biscuit-datalog/src/rule.zig index 310809c..e97bca8 100644 --- a/biscuit-datalog/src/rule.zig +++ b/biscuit-datalog/src/rule.zig @@ -15,6 +15,8 @@ const Scope = @import("scope.zig").Scope; const Expression = @import("expression.zig").Expression; const TrustedOrigins = @import("trusted_origins.zig").TrustedOrigins; +const log = std.log.scoped(.rule); + pub const Rule = struct { head: Predicate, body: std.ArrayList(Predicate), @@ -44,20 +46,20 @@ pub const Rule = struct { return .{ .head = head, .body = body, .expressions = expressions, .scopes = scopes }; } - pub fn deinit(rule: *Rule) void { - rule.head.deinit(); + pub fn deinit(_: *Rule) void { + // rule.head.deinit(); - for (rule.body.items) |*predicate| { - predicate.deinit(); - } + // for (rule.body.items) |*predicate| { + // predicate.deinit(); + // } - for (rule.expressions.items) |*expression| { - expression.deinit(); - } + // for (rule.expressions.items) |*expression| { + // expression.deinit(); + // } - rule.body.deinit(); - rule.expressions.deinit(); - rule.scopes.deinit(); + // rule.body.deinit(); + // rule.expressions.deinit(); + // rule.scopes.deinit(); } /// ### Generate new facts from this rule and the existing facts @@ -115,28 +117,25 @@ pub const Rule = struct { /// ``` /// /// ...and we add it to the set of facts (the set will take care of deduplication) - pub fn apply(rule: *const Rule, allocator: mem.Allocator, origin_id: u64, facts: *const FactSet, new_facts: *FactSet, symbols: *SymbolTable, trusted_origins: TrustedOrigins) !void { - var arena = std.heap.ArenaAllocator.init(allocator); - defer arena.deinit(); - - std.debug.print("\napplying rule (from origin {}):\n {any}\n", .{ origin_id, rule }); - const matched_variables = try MatchedVariables.init(arena.allocator(), rule); + pub fn apply(rule: *const Rule, arena: mem.Allocator, origin_id: u64, facts: *const FactSet, new_facts: *FactSet, symbols: *SymbolTable, trusted_origins: TrustedOrigins) !void { + log.debug("\napplying rule {any} (from block {})", .{ rule, origin_id }); + const matched_variables = try MatchedVariables.init(arena, rule); // TODO: if body is empty stuff - var it = Combinator.init(0, allocator, matched_variables, rule.body.items, rule.expressions.items, facts, symbols, trusted_origins); + var it = Combinator.init(0, arena, matched_variables, rule.body.items, rule.expressions.items, facts, symbols, trusted_origins); defer it.deinit(); blk: while (try it.next()) |*origin_bindings| { const origin: Origin = origin_bindings[0]; const bindings: MatchedVariables = origin_bindings[1]; - if (!try bindings.evaluateExpressions(allocator, rule.expressions.items, symbols)) continue; + if (!try bindings.evaluateExpressions(arena, rule.expressions.items, symbols)) continue; // TODO: Describe why clonedWithAllocator? More generally, describe in comment the overall // lifetimes / memory allocation approach during evaluation. - var predicate = try rule.head.cloneWithAllocator(allocator); - defer predicate.deinit(); + var predicate = try rule.head.clone(); + // defer predicate.deinit(); // Loop over terms in head predicate. Update all _variable_ terms with their value // from the binding. @@ -153,18 +152,18 @@ pub const Rule = struct { var new_origin = try origin.clone(); try new_origin.insert(origin_id); - std.debug.print("\nadding new fact:\n {any} with origin {any}\n", .{ fact, new_origin }); + log.debug("apply: adding new fact {any} with origin {any}", .{ fact, new_origin }); // Skip adding fact if we already have generated it. Because the // Set will clobber duplicate facts we'll lose a reference when // inserting a duplicate and then when we loop over the set to // deinit the facts we'll miss some. This ensures that the facts // can be freed purely from the Set. - if (new_facts.contains(new_origin, fact)) { - new_origin.deinit(); - continue; - } + // if (new_facts.contains(new_origin, fact)) { + // // new_origin.deinit(); + // continue; + // } - try new_facts.add(new_origin, try fact.clone()); + try new_facts.add(new_origin, fact); } } @@ -173,17 +172,17 @@ pub const Rule = struct { /// /// Note: whilst the combinator may return multiple valid matches, `findMatch` only requires a single match /// so stopping on the first `it.next()` that returns not-null is enough. - pub fn findMatch(rule: *Rule, allocator: mem.Allocator, facts: *const FactSet, symbols: *SymbolTable, trusted_origins: TrustedOrigins) !bool { - std.debug.print("\nrule.findMatch on {any} ({any})\n", .{ rule, trusted_origins }); - var arena = std.heap.ArenaAllocator.init(allocator); - defer arena.deinit(); + pub fn findMatch(rule: *Rule, arena: mem.Allocator, facts: *const FactSet, symbols: *SymbolTable, trusted_origins: TrustedOrigins) !bool { + log.debug("findMatch({any}, {any})", .{ rule, trusted_origins }); + // var arena = std.heap.ArenaAllocator.init(allocator); + // defer arena.deinit(); - const arena_allocator = arena.allocator(); + // const arena_allocator = arena.allocator(); if (rule.body.items.len == 0) { - const variables = std.AutoHashMap(u32, Term).init(allocator); + const variables = std.AutoHashMap(u32, Term).init(arena); for (rule.expressions.items) |expr| { - const result = try expr.evaluate(arena_allocator, variables, symbols); + const result = try expr.evaluate(arena, variables, symbols); switch (result) { .bool => |b| if (b) continue else return false, @@ -193,32 +192,32 @@ pub const Rule = struct { return true; } else { - const matched_variables = try MatchedVariables.init(arena_allocator, rule); + const matched_variables = try MatchedVariables.init(arena, rule); - var it = Combinator.init(0, allocator, matched_variables, rule.body.items, rule.expressions.items, facts, symbols, trusted_origins); + var it = Combinator.init(0, arena, matched_variables, rule.body.items, rule.expressions.items, facts, symbols, trusted_origins); defer it.deinit(); while (try it.next()) |*origin_bindings| { const bindings: MatchedVariables = origin_bindings[1]; - if (try bindings.evaluateExpressions(arena_allocator, rule.expressions.items, symbols)) return true; + if (try bindings.evaluateExpressions(arena, rule.expressions.items, symbols)) return true; } return false; } } - pub fn checkMatchAll(rule: *Rule, allocator: mem.Allocator, facts: *const FactSet, symbols: *SymbolTable, trusted_origins: TrustedOrigins) !bool { - std.debug.print("\nrule.checkMatchAll on {any} ({any})\n", .{ rule, trusted_origins }); - var arena = std.heap.ArenaAllocator.init(allocator); - defer arena.deinit(); + pub fn checkMatchAll(rule: *Rule, arena: mem.Allocator, facts: *const FactSet, symbols: *SymbolTable, trusted_origins: TrustedOrigins) !bool { + log.debug("checkMatchAll({any}, {any})", .{ rule, trusted_origins }); + // var arena = std.heap.ArenaAllocator.init(allocator); + // defer arena.deinit(); - const arena_allocator = arena.allocator(); + // const arena_allocator = arena.allocator(); if (rule.body.items.len == 0) { - const variables = std.AutoHashMap(u32, Term).init(allocator); + const variables = std.AutoHashMap(u32, Term).init(arena); for (rule.expressions.items) |expr| { - const result = try expr.evaluate(arena_allocator, variables, symbols); + const result = try expr.evaluate(arena, variables, symbols); switch (result) { .bool => |b| if (b) continue else return false, @@ -228,15 +227,15 @@ pub const Rule = struct { return true; } else { - const matched_variables = try MatchedVariables.init(arena_allocator, rule); + const matched_variables = try MatchedVariables.init(arena, rule); - var it = Combinator.init(0, allocator, matched_variables, rule.body.items, rule.expressions.items, facts, symbols, trusted_origins); + var it = Combinator.init(0, arena, matched_variables, rule.body.items, rule.expressions.items, facts, symbols, trusted_origins); defer it.deinit(); while (try it.next()) |*origin_bindings| { const bindings: MatchedVariables = origin_bindings[1]; - if (try bindings.evaluateExpressions(arena_allocator, rule.expressions.items, symbols)) continue; + if (try bindings.evaluateExpressions(arena, rule.expressions.items, symbols)) continue; return false; } diff --git a/biscuit-datalog/src/rule_set.zig b/biscuit-datalog/src/rule_set.zig index 5db8adc..61de157 100644 --- a/biscuit-datalog/src/rule_set.zig +++ b/biscuit-datalog/src/rule_set.zig @@ -15,15 +15,15 @@ pub const RuleSet = struct { }; } - pub fn deinit(rule_set: *RuleSet) void { - var it = rule_set.rules.iterator(); + pub fn deinit(_: *RuleSet) void { + // var it = rule_set.rules.iterator(); - while (it.next()) |entry| { - entry.key_ptr.deinit(); - entry.value_ptr.deinit(); - } + // while (it.next()) |entry| { + // entry.key_ptr.deinit(); + // entry.value_ptr.deinit(); + // } - rule_set.rules.deinit(); + // rule_set.rules.deinit(); } pub fn add(rule_set: *RuleSet, origin: u64, scope: TrustedOrigins, rule: Rule) !void { @@ -41,6 +41,8 @@ pub const RuleSet = struct { test "RuleSet" { const testing = std.testing; + const test_log = std.log.scoped(.test_rule_set); + var rs = RuleSet.init(testing.allocator); defer rs.deinit(); @@ -48,5 +50,5 @@ test "RuleSet" { const rule: Rule = undefined; try rs.add(0, default_origins, rule); - std.debug.print("rs = {any}\n", .{rs}); + test_log.debug("rs = {any}", .{rs}); } diff --git a/biscuit-datalog/src/set.zig b/biscuit-datalog/src/set.zig index 6d13db5..7c1b9a2 100644 --- a/biscuit-datalog/src/set.zig +++ b/biscuit-datalog/src/set.zig @@ -45,8 +45,8 @@ pub fn Set(comptime K: type) type { }; } - pub fn deinit(set: *Self) void { - set.inner.deinit(); + pub fn deinit(_: *Self) void { + // set.inner.deinit(); } pub fn clone(set: *const Self) !Self { @@ -171,6 +171,8 @@ test { const testing = std.testing; const allocator = testing.allocator; + const test_log = std.log.scoped(.test_set); + var s = Set(Fact).init(allocator); defer s.deinit(); @@ -187,7 +189,7 @@ test { try s.add(Fact{ .predicate = Predicate{ .name = 10, .terms = undefined } }); try testing.expectEqual(@as(u32, 2), s.count()); - std.debug.print("set = {any}\n", .{s}); + test_log.debug("set = {any}\n", .{s}); } test "hashing" { diff --git a/biscuit-datalog/src/symbol_table.zig b/biscuit-datalog/src/symbol_table.zig index d5243ab..c1693c2 100644 --- a/biscuit-datalog/src/symbol_table.zig +++ b/biscuit-datalog/src/symbol_table.zig @@ -3,6 +3,8 @@ const mem = std.mem; const Ed25519 = std.crypto.sign.Ed25519; +const log = std.log.scoped(.symbol_table); + pub const SymbolTable = struct { name: []const u8, allocator: mem.Allocator, @@ -41,7 +43,7 @@ pub const SymbolTable = struct { const index = symbol_table.symbols.items.len - 1 + NON_DEFAULT_SYMBOLS_OFFSET; - // std.debug.print("{s}: Inserting \"{s}\" at {}\n", .{ symbol_table.name, symbol, index }); + log.debug("inserting \"{s}\" at {} [{s}]", .{ symbol, index, symbol_table.name }); return index; } @@ -77,18 +79,14 @@ pub const SymbolTable = struct { pub fn getString(symbol_table: *const SymbolTable, sym_index: u64) ![]const u8 { if (indexToDefault(sym_index)) |sym| { - // std.debug.print("Found \"{s}\" at {} (default)\n", .{ sym, sym_index }); return sym; } if (sym_index >= NON_DEFAULT_SYMBOLS_OFFSET and sym_index < NON_DEFAULT_SYMBOLS_OFFSET + symbol_table.symbols.items.len) { const sym = symbol_table.symbols.items[sym_index - NON_DEFAULT_SYMBOLS_OFFSET]; - // std.debug.print("Found \"{s}\" at {}\n", .{ sym, sym_index }); return sym; } - // std.debug.print("Existing sym index {} not found\n", .{sym_index}); - return error.SymbolNotFound; } diff --git a/biscuit-datalog/src/trusted_origins.zig b/biscuit-datalog/src/trusted_origins.zig index fea1f85..38c50b9 100644 --- a/biscuit-datalog/src/trusted_origins.zig +++ b/biscuit-datalog/src/trusted_origins.zig @@ -14,8 +14,8 @@ pub const TrustedOrigins = struct { return .{ .ids = InnerSet.init(allocator) }; } - pub fn deinit(trusted_origins: *TrustedOrigins) void { - trusted_origins.ids.deinit(); + pub fn deinit(_: *TrustedOrigins) void { + // trusted_origins.ids.deinit(); } pub fn clone(trusted_origins: *const TrustedOrigins) !TrustedOrigins { diff --git a/biscuit-datalog/src/world.zig b/biscuit-datalog/src/world.zig index 1e70b39..ee925ae 100644 --- a/biscuit-datalog/src/world.zig +++ b/biscuit-datalog/src/world.zig @@ -10,47 +10,43 @@ const TrustedOrigins = @import("trusted_origins.zig").TrustedOrigins; const RunLimits = @import("run_limits.zig").RunLimits; const SymbolTable = @import("symbol_table.zig").SymbolTable; +const log = std.log.scoped(.world); + pub const World = struct { - allocator: mem.Allocator, + arena: mem.Allocator, fact_set: FactSet, rule_set: RuleSet, - symbols: std.ArrayList([]const u8), - /// init world - /// - /// Note: the allocator we pass in can be any allocator. This allocator - /// is used purely for the toplevel Set and ArrayLists. Any facts allocated - /// during world run will be allocated with a provided allocator that is - /// specifically an arena. The world and rule code will reflect that by - /// not doing explicit deallocation on the fact / predicate / term level. - /// - /// If we ever want to change away from that arena model, we'll have to - /// fix up some code internally to allow that. - pub fn init(allocator: mem.Allocator) World { + pub fn init(arena: mem.Allocator) World { return .{ - .allocator = allocator, - .fact_set = FactSet.init(allocator), - .rule_set = RuleSet.init(allocator), - .symbols = std.ArrayList([]const u8).init(allocator), + .arena = arena, + .fact_set = FactSet.init(arena), + .rule_set = RuleSet.init(arena), }; } - pub fn deinit(world: *World) void { - world.symbols.deinit(); - world.rule_set.deinit(); - world.fact_set.deinit(); + pub fn deinit(_: *World) void { + // world.symbols.deinit(); + // world.rule_set.deinit(); + // world.fact_set.deinit(); } + /// Generate all facts from rules and existing facts + /// + /// Uses default run limits. pub fn run(world: *World, symbols: *SymbolTable) !void { try world.runWithLimits(symbols, .{}); } + /// Generate all facts from rules and existing facts + /// + /// User specifies run limits. pub fn runWithLimits(world: *World, symbols: *SymbolTable, limits: RunLimits) !void { for (0..limits.max_iterations) |iteration| { - std.debug.print("\nrunWithLimits[{}]\n", .{iteration}); + log.debug("runWithLimits[{}]", .{iteration}); const starting_fact_count = world.fact_set.count(); - var new_fact_sets = FactSet.init(world.allocator); + var new_fact_sets = FactSet.init(world.arena); defer new_fact_sets.deinit(); // Iterate over rules to generate new facts @@ -65,30 +61,23 @@ pub const World = struct { const origin_id: u64 = origin_rule[0]; const rule: Rule = origin_rule[1]; - try rule.apply(world.allocator, origin_id, &world.fact_set, &new_fact_sets, symbols, trusted_origins); + try rule.apply(world.arena, origin_id, &world.fact_set, &new_fact_sets, symbols, trusted_origins); } } } var it = new_fact_sets.iterator(); while (it.next()) |origin_fact| { - const existing_origin = origin_fact.origin.*; + const origin = origin_fact.origin.*; const fact = origin_fact.fact.*; - var origin = try existing_origin.clone(); - - if (world.fact_set.contains(origin, fact)) { - origin.deinit(); - continue; - } - - try world.fact_set.add(origin, try fact.cloneWithAllocator(world.allocator)); + try world.fact_set.add(origin, fact); } - std.debug.print("starting_fact_count = {}, world.facts.count() = {}\n", .{ starting_fact_count, world.fact_set.count() }); + log.debug("starting_fact_count = {}, world.facts.count() = {}", .{ starting_fact_count, world.fact_set.count() }); // If we haven't generated any new facts, we're done. if (starting_fact_count == world.fact_set.count()) { - std.debug.print("No new facts!\n", .{}); + log.debug("No new facts!", .{}); return; } @@ -100,20 +89,22 @@ pub const World = struct { /// Add fact with origin to world pub fn addFact(world: *World, origin: Origin, fact: Fact) !void { - std.debug.print("\nworld: adding fact = {any} ({any}) \n", .{ fact, origin }); + log.debug("adding fact = {any}, origin = ({any})", .{ fact, origin }); + try world.fact_set.add(origin, fact); } + // Add rule trusting scope from origin pub fn addRule(world: *World, origin_id: usize, scope: TrustedOrigins, rule: Rule) !void { - std.debug.print("\nworld: adding rule (origin {}) = {any} (trusts {any})\n", .{ origin_id, rule, scope }); + log.debug("adding rule {any}, origin = {}, trusts {any}", .{ rule, origin_id, scope }); try world.rule_set.add(origin_id, scope, rule); } pub fn queryMatch(world: *World, rule: *Rule, symbols: *SymbolTable, trusted_origins: TrustedOrigins) !bool { - return rule.findMatch(world.allocator, &world.fact_set, symbols, trusted_origins); + return rule.findMatch(world.arena, &world.fact_set, symbols, trusted_origins); } pub fn queryMatchAll(world: *World, rule: *Rule, symbols: *SymbolTable, trusted_origins: TrustedOrigins) !bool { - return rule.checkMatchAll(world.allocator, &world.fact_set, symbols, trusted_origins); + return rule.checkMatchAll(world.arena, &world.fact_set, symbols, trusted_origins); } }; diff --git a/biscuit-format/src/serialized_biscuit.zig b/biscuit-format/src/serialized_biscuit.zig index c6f2a33..1c9e641 100644 --- a/biscuit-format/src/serialized_biscuit.zig +++ b/biscuit-format/src/serialized_biscuit.zig @@ -5,6 +5,8 @@ const schema = @import("biscuit-schema"); const SignedBlock = @import("signed_block.zig").SignedBlock; const Proof = @import("proof.zig").Proof; +const log = std.log.scoped(.serialized_biscuit); + pub const MIN_SCHEMA_VERSION = 3; pub const MAX_SCHEMA_VERSION = 4; @@ -70,10 +72,15 @@ pub const SerializedBiscuit = struct { /// public key is the public key of the private key in the /// the proof. fn verify(serialized_biscuit: *SerializedBiscuit, root_public_key: Ed25519.PublicKey) !void { + log.debug("verify()", .{}); + defer log.debug("end verify()", .{}); + var pk = root_public_key; // Verify the authority block's signature { + log.debug("verifying authority block", .{}); + errdefer log.debug("failed to verify authority block", .{}); if (serialized_biscuit.authority.external_signature != null) return error.AuthorityBlockMustNotHaveExternalSignature; var verifier = try serialized_biscuit.authority.signature.verifier(pk); @@ -88,9 +95,12 @@ pub const SerializedBiscuit = struct { } // Verify the other blocks' signatures - for (serialized_biscuit.blocks.items) |*block| { + for (serialized_biscuit.blocks.items, 1..) |*block, block_id| { // Verify the block signature { + log.debug("verifying block {}", .{block_id}); + errdefer log.debug("failed to verify block {}", .{block_id}); + var verifier = try block.signature.verifier(pk); verifier.update(block.block); @@ -105,6 +115,9 @@ pub const SerializedBiscuit = struct { // Verify the external signature (where one exists) if (block.external_signature) |external_signature| { + log.debug("verifying external signature on block {}", .{block_id}); + errdefer log.debug("failed to verify external signature on block {}", .{block_id}); + var external_verifier = try external_signature.signature.verifier(external_signature.public_key); external_verifier.update(block.block); external_verifier.update(&block.algorithm2Buf()); @@ -116,13 +129,18 @@ pub const SerializedBiscuit = struct { } // Check the proof + + log.debug("verifying proof", .{}); switch (serialized_biscuit.proof) { .next_secret => |next_secret| { if (!std.mem.eql(u8, &pk.bytes, &next_secret.publicKeyBytes())) { + log.debug("failed to verify proof (sealed)", .{}); return error.SecretKeyProofFailedMismatchedPublicKeys; } }, .final_signature => |final_signature| { + errdefer log.debug("failed to verify proof (attenuated)", .{}); + var last_block = if (serialized_biscuit.blocks.items.len == 0) serialized_biscuit.authority else serialized_biscuit.blocks.items[serialized_biscuit.blocks.items.len - 1]; var verifier = try final_signature.verifier(pk); diff --git a/biscuit-format/src/signed_block.zig b/biscuit-format/src/signed_block.zig index 7d6344e..1c7662c 100644 --- a/biscuit-format/src/signed_block.zig +++ b/biscuit-format/src/signed_block.zig @@ -34,7 +34,7 @@ pub const SignedBlock = struct { const algo = required_block_external_key.algorithm; - std.debug.print("ALGORITHM = {}\n", .{algo}); + _ = algo; // FIXME: we need to use algorithm (at least at the point that support for things other than Ed25519) if (block_external_signature.len != Ed25519.Signature.encoded_length) return error.IncorrectBlockExternalSignatureLength; if (block_external_public_key.len != Ed25519.PublicKey.encoded_length) return error.IncorrectBlockExternalPublicKeyLength; diff --git a/biscuit-parser/src/parser.zig b/biscuit-parser/src/parser.zig index 7e8e032..8ced3a6 100644 --- a/biscuit-parser/src/parser.zig +++ b/biscuit-parser/src/parser.zig @@ -12,6 +12,8 @@ const Date = @import("biscuit-builder").Date; const Policy = @import("biscuit-builder").Policy; const Ed25519 = std.crypto.sign.Ed25519; +const log = std.log.scoped(.parser); + pub const Parser = struct { input: []const u8, offset: usize = 0, @@ -28,8 +30,6 @@ pub const Parser = struct { pub fn factPredicate(parser: *Parser) !Predicate { const name = parser.readName(); - std.debug.print("name = {s}\n", .{name}); - parser.skipWhiteSpace(); // Consume left paren @@ -421,7 +421,6 @@ pub const Parser = struct { while (true) { parser.skipWhiteSpace(); - std.debug.print("{s}: \"{s}\"\n", .{ @src().fn_name, parser.rest() }); // Try parsing a predicate predicate_blk: { @@ -482,21 +481,15 @@ pub const Parser = struct { } fn expression(parser: *Parser) ParserError!Expression { - std.debug.print("Attempting to parser {s}\n", .{parser.rest()}); parser.skipWhiteSpace(); const e = try parser.expr(); - std.debug.print("parsed expression = {any}\n", .{e}); - return e; } fn expr(parser: *Parser) ParserError!Expression { - std.debug.print("[{s}]\n", .{@src().fn_name}); var e = try parser.expr1(); - std.debug.print("[{s}] e = {any}\n", .{ @src().fn_name, e }); - while (true) { parser.skipWhiteSpace(); if (parser.rest().len == 0) break; @@ -506,7 +499,6 @@ pub const Parser = struct { parser.skipWhiteSpace(); const e2 = try parser.expr1(); - std.debug.print("[{s}] e2 = {any}\n", .{ @src().fn_name, e2 }); e = try Expression.binary(parser.allocator, op, e, e2); } @@ -515,11 +507,8 @@ pub const Parser = struct { } fn expr1(parser: *Parser) ParserError!Expression { - std.debug.print("[{s}]\n", .{@src().fn_name}); var e = try parser.expr2(); - std.debug.print("[{s}] e = {any}\n", .{ @src().fn_name, e }); - while (true) { parser.skipWhiteSpace(); if (parser.rest().len == 0) break; @@ -529,7 +518,6 @@ pub const Parser = struct { parser.skipWhiteSpace(); const e2 = try parser.expr2(); - std.debug.print("[{s}] e2 = {any}\n", .{ @src().fn_name, e2 }); e = try Expression.binary(parser.allocator, op, e, e2); } @@ -538,11 +526,8 @@ pub const Parser = struct { } fn expr2(parser: *Parser) ParserError!Expression { - std.debug.print("[{s}]\n", .{@src().fn_name}); var e = try parser.expr3(); - std.debug.print("[{s}] e = {any}\n", .{ @src().fn_name, e }); - while (true) { parser.skipWhiteSpace(); if (parser.rest().len == 0) break; @@ -552,7 +537,6 @@ pub const Parser = struct { parser.skipWhiteSpace(); const e2 = try parser.expr3(); - std.debug.print("[{s}] e2 = {any}\n", .{ @src().fn_name, e2 }); e = try Expression.binary(parser.allocator, op, e, e2); } @@ -561,11 +545,8 @@ pub const Parser = struct { } fn expr3(parser: *Parser) ParserError!Expression { - std.debug.print("[{s}]\n", .{@src().fn_name}); var e = try parser.expr4(); - std.debug.print("[{s}] e = {any}\n", .{ @src().fn_name, e }); - while (true) { parser.skipWhiteSpace(); if (parser.rest().len == 0) break; @@ -575,7 +556,6 @@ pub const Parser = struct { parser.skipWhiteSpace(); const e2 = try parser.expr4(); - std.debug.print("[{s}] e2 = {any}\n", .{ @src().fn_name, e2 }); e = try Expression.binary(parser.allocator, op, e, e2); } @@ -584,11 +564,8 @@ pub const Parser = struct { } fn expr4(parser: *Parser) ParserError!Expression { - std.debug.print("[{s}]\n", .{@src().fn_name}); var e = try parser.expr5(); - std.debug.print("[{s}] e = {any}\n", .{ @src().fn_name, e }); - while (true) { parser.skipWhiteSpace(); if (parser.rest().len == 0) break; @@ -598,7 +575,6 @@ pub const Parser = struct { parser.skipWhiteSpace(); const e2 = try parser.expr5(); - std.debug.print("[{s}] e2 = {any}\n", .{ @src().fn_name, e2 }); e = try Expression.binary(parser.allocator, op, e, e2); } @@ -607,11 +583,8 @@ pub const Parser = struct { } fn expr5(parser: *Parser) ParserError!Expression { - std.debug.print("[{s}]\n", .{@src().fn_name}); var e = try parser.expr6(); - std.debug.print("[{s}] e = {any}\n", .{ @src().fn_name, e }); - while (true) { parser.skipWhiteSpace(); if (parser.rest().len == 0) break; @@ -622,8 +595,6 @@ pub const Parser = struct { const e2 = try parser.expr6(); - std.debug.print("[{s}] e2 = {any}\n", .{ @src().fn_name, e2 }); - e = try Expression.binary(parser.allocator, op, e, e2); } @@ -631,11 +602,8 @@ pub const Parser = struct { } fn expr6(parser: *Parser) ParserError!Expression { - std.debug.print("[{s}]\n", .{@src().fn_name}); var e = try parser.expr7(); - std.debug.print("[{s}] e = {any}\n", .{ @src().fn_name, e }); - while (true) { parser.skipWhiteSpace(); if (parser.rest().len == 0) break; @@ -645,7 +613,6 @@ pub const Parser = struct { parser.skipWhiteSpace(); const e2 = try parser.expr7(); - std.debug.print("[{s}] e2 = {any}\n", .{ @src().fn_name, e2 }); e = try Expression.binary(parser.allocator, op, e, e2); } @@ -654,11 +621,8 @@ pub const Parser = struct { } fn expr7(parser: *Parser) ParserError!Expression { - std.debug.print("[{s}]\n", .{@src().fn_name}); const e1 = try parser.exprTerm(); - std.debug.print("[{s}] e1 = {any}\n", .{ @src().fn_name, e1 }); - parser.skipWhiteSpace(); if (!parser.startsWith(".")) return e1; @@ -667,22 +631,14 @@ pub const Parser = struct { const op = try parser.binaryOp7(); parser.skipWhiteSpace(); - std.debug.print("[{s}] op = {any}, rest = \"{s}\"\n", .{ @src().fn_name, op, parser.rest() }); - - // if (!parser.startsWith("(")) return error.MissingLeftParen; try parser.expect('('); parser.skipWhiteSpace(); - std.debug.print("here\n", .{}); - const e2 = try parser.expr(); - std.debug.print("[{s}] e2 = {any}\n", .{ @src().fn_name, e2 }); - parser.skipWhiteSpace(); - // if (!parser.startsWith(")")) return error.MissingRightParen; try parser.expect(')'); parser.skipWhiteSpace(); @@ -942,8 +898,6 @@ pub const Parser = struct { const h = try parser.hex(); - std.debug.print("publickey = {s}\n", .{h}); - var out_buf: [Ed25519.PublicKey.encoded_length]u8 = undefined; _ = try std.fmt.hexToBytes(out_buf[0..], h); @@ -1108,6 +1062,4 @@ test "parse check with expression" { const r = try parser.check(); defer r.deinit(); - - std.debug.print("{any}\n", .{r}); } diff --git a/biscuit-samples/src/main.zig b/biscuit-samples/src/main.zig index a7dda48..735f261 100644 --- a/biscuit-samples/src/main.zig +++ b/biscuit-samples/src/main.zig @@ -6,6 +6,8 @@ const AuthorizerError = @import("biscuit").AuthorizerError; const Samples = @import("sample.zig").Samples; const Result = @import("sample.zig").Result; +const log = std.log.scoped(.samples); + var gpa = std.heap.GeneralPurposeAllocator(.{}){}; pub fn main() anyerror!void { @@ -41,7 +43,7 @@ pub fn main() anyerror!void { const token = try std.fs.cwd().readFileAlloc(alloc, testcase.filename, 0xFFFFFFF); for (testcase.validations.map.values(), 0..) |validation, i| { - errdefer std.debug.print("Error on validation {} of {s}\n", .{ i, testcase.filename }); + errdefer log.err("Error on validation {} of {s}\n", .{ i, testcase.filename }); try validate(alloc, token, public_key, validation.result, validation.authorizer_code); } } @@ -169,7 +171,7 @@ pub fn runValidation(alloc: mem.Allocator, token: []const u8, public_key: std.cr } _ = a.authorize(errors) catch |err| { - std.debug.print("Authorization failed with errors: {any}\n", .{errors.items}); + log.debug("authorize() returned with errors: {any}\n", .{errors.items}); return err; }; } diff --git a/biscuit/src/authorizer.zig b/biscuit/src/authorizer.zig index 9f5cfc5..984ca4f 100644 --- a/biscuit/src/authorizer.zig +++ b/biscuit/src/authorizer.zig @@ -11,8 +11,10 @@ const Parser = @import("biscuit-parser").Parser; const builder = @import("biscuit-builder"); const PolicyResult = @import("biscuit-builder").PolicyResult; +const log = std.log.scoped(.authorizer); + pub const Authorizer = struct { - allocator: mem.Allocator, + arena: mem.Allocator, checks: std.ArrayList(builder.Check), policies: std.ArrayList(builder.Policy), biscuit: ?Biscuit, @@ -21,9 +23,9 @@ pub const Authorizer = struct { public_key_to_block_id: std.AutoHashMap(usize, std.ArrayList(usize)), scopes: std.ArrayList(Scope), - pub fn init(allocator: std.mem.Allocator, biscuit: Biscuit) !Authorizer { - var symbols = SymbolTable.init("authorizer", allocator); - var public_key_to_block_id = std.AutoHashMap(usize, std.ArrayList(usize)).init(allocator); + pub fn init(arena: std.mem.Allocator, biscuit: Biscuit) !Authorizer { + var symbols = SymbolTable.init("authorizer", arena); + var public_key_to_block_id = std.AutoHashMap(usize, std.ArrayList(usize)).init(arena); // Map public key symbols into authorizer symbols and public_key_to_block_id map var it = biscuit.public_key_to_block_id.iterator(); @@ -39,46 +41,46 @@ pub const Authorizer = struct { } return .{ - .allocator = allocator, - .checks = std.ArrayList(builder.Check).init(allocator), - .policies = std.ArrayList(builder.Policy).init(allocator), + .arena = arena, + .checks = std.ArrayList(builder.Check).init(arena), + .policies = std.ArrayList(builder.Policy).init(arena), .biscuit = biscuit, - .world = World.init(allocator), + .world = World.init(arena), .symbols = symbols, .public_key_to_block_id = public_key_to_block_id, - .scopes = std.ArrayList(Scope).init(allocator), + .scopes = std.ArrayList(Scope).init(arena), }; } - pub fn deinit(authorizer: *Authorizer) void { - authorizer.world.deinit(); - authorizer.symbols.deinit(); - authorizer.scopes.deinit(); - - for (authorizer.checks.items) |check| { - check.deinit(); - } - authorizer.checks.deinit(); - - for (authorizer.policies.items) |policy| { - policy.deinit(); - } - authorizer.policies.deinit(); - - { - var it = authorizer.public_key_to_block_id.valueIterator(); - while (it.next()) |block_ids| { - block_ids.deinit(); - } - authorizer.public_key_to_block_id.deinit(); - } + pub fn deinit(_: *Authorizer) void { + // authorizer.world.deinit(); + // authorizer.symbols.deinit(); + // authorizer.scopes.deinit(); + + // for (authorizer.checks.items) |check| { + // check.deinit(); + // } + // authorizer.checks.deinit(); + + // for (authorizer.policies.items) |policy| { + // policy.deinit(); + // } + // authorizer.policies.deinit(); + + // { + // var it = authorizer.public_key_to_block_id.valueIterator(); + // while (it.next()) |block_ids| { + // block_ids.deinit(); + // } + // authorizer.public_key_to_block_id.deinit(); + // } } pub fn authorizerTrustedOrigins(authorizer: *Authorizer) !TrustedOrigins { return try TrustedOrigins.fromScopes( - authorizer.allocator, + authorizer.arena, authorizer.scopes.items, - try TrustedOrigins.defaultOrigins(authorizer.allocator), + try TrustedOrigins.defaultOrigins(authorizer.arena), Origin.AUTHORIZER_ID, authorizer.public_key_to_block_id, ); @@ -86,21 +88,20 @@ pub const Authorizer = struct { /// Add fact from string to authorizer pub fn addFact(authorizer: *Authorizer, input: []const u8) !void { - std.debug.print("authorizer.addFact = {s}\n", .{input}); - var parser = Parser.init(authorizer.allocator, input); + log.debug("addFact = {s}", .{input}); + var parser = Parser.init(authorizer.arena, input); const fact = try parser.fact(); - std.debug.print("fact = {any}\n", .{fact}); + const origin = try Origin.initWithId(authorizer.arena, Origin.AUTHORIZER_ID); - const origin = try Origin.initWithId(authorizer.allocator, Origin.AUTHORIZER_ID); - - try authorizer.world.addFact(origin, try fact.toDatalog(authorizer.allocator, &authorizer.symbols)); + try authorizer.world.addFact(origin, try fact.toDatalog(authorizer.arena, &authorizer.symbols)); } /// Add check from string to authorizer pub fn addCheck(authorizer: *Authorizer, input: []const u8) !void { - var parser = Parser.init(authorizer.allocator, input); + log.debug("addCheck = {s}", .{input}); + var parser = Parser.init(authorizer.arena, input); const check = try parser.check(); @@ -109,7 +110,8 @@ pub const Authorizer = struct { /// Add policy from string to authorizer pub fn addPolicy(authorizer: *Authorizer, input: []const u8) !void { - var parser = Parser.init(authorizer.allocator, input); + log.debug("addPolicy = {s}", .{input}); + var parser = Parser.init(authorizer.arena, input); const policy = try parser.policy(); @@ -131,17 +133,18 @@ pub const Authorizer = struct { /// 5. _authorizer_: Run the _authorizer's_ policies /// 6. _biscuit_ (where it exists): run the checks from all the non-authority blocks pub fn authorize(authorizer: *Authorizer, errors: *std.ArrayList(AuthorizerError)) !usize { - std.debug.print("\nAuthorizing biscuit:\n", .{}); + log.debug("Starting authorize()", .{}); + defer log.debug("Finished authorize()", .{}); - std.debug.print("authorizer public keys:\n", .{}); + log.debug("authorizer public keys:", .{}); for (authorizer.symbols.public_keys.items, 0..) |pk, i| { - std.debug.print(" [{}]: {x}\n", .{ i, pk.bytes }); + log.debug(" [{}]: {x}", .{ i, pk.bytes }); } { var it = authorizer.public_key_to_block_id.iterator(); while (it.next()) |entry| { - std.debug.print("public_key_to_block_id: public key id = {}, block_ids = {any}\n", .{ entry.key_ptr.*, entry.value_ptr.items }); + log.debug("public_key_to_block_id: public key id = {}, block_ids = {any}", .{ entry.key_ptr.*, entry.value_ptr.items }); } } @@ -153,21 +156,21 @@ pub const Authorizer = struct { // For example, the token may have a string "user123" which has id 12. But // when mapped into the world it may have id 5. if (authorizer.biscuit) |biscuit| { - std.debug.print("biscuit token public keys:\n", .{}); + log.debug("biscuit token public keys:", .{}); for (biscuit.symbols.public_keys.items, 0..) |pk, i| { - std.debug.print(" [{}]: {x}\n", .{ i, pk.bytes }); + log.debug(" [{}]: {x}", .{ i, pk.bytes }); } for (biscuit.authority.facts.items) |authority_fact| { const fact = try authority_fact.remap(&biscuit.symbols, &authorizer.symbols); - const origin = try Origin.initWithId(authorizer.allocator, 0); + const origin = try Origin.initWithId(authorizer.arena, 0); try authorizer.world.addFact(origin, fact); } const authority_trusted_origins = try TrustedOrigins.fromScopes( - authorizer.allocator, + authorizer.arena, biscuit.authority.scopes.items, - try TrustedOrigins.defaultOrigins(authorizer.allocator), + try TrustedOrigins.defaultOrigins(authorizer.arena), 0, authorizer.public_key_to_block_id, ); @@ -182,7 +185,7 @@ pub const Authorizer = struct { // A authority block's rule trusts const rule_trusted_origins = try TrustedOrigins.fromScopes( - authorizer.allocator, + authorizer.arena, rule.scopes.items, authority_trusted_origins, 0, @@ -195,29 +198,29 @@ pub const Authorizer = struct { for (biscuit.blocks.items, 1..) |block, block_id| { for (block.facts.items) |block_fact| { const fact = try block_fact.remap(&biscuit.symbols, &authorizer.symbols); - const origin = try Origin.initWithId(authorizer.allocator, block_id); + const origin = try Origin.initWithId(authorizer.arena, block_id); try authorizer.world.addFact(origin, fact); } const block_trusted_origins = try TrustedOrigins.fromScopes( - authorizer.allocator, + authorizer.arena, block.scopes.items, - try TrustedOrigins.defaultOrigins(authorizer.allocator), + try TrustedOrigins.defaultOrigins(authorizer.arena), block_id, authorizer.public_key_to_block_id, ); for (block.rules.items) |block_rule| { const rule = try block_rule.remap(&biscuit.symbols, &authorizer.symbols); - std.debug.print("block rule {any} CONVERTED to rule = {any}\n", .{ block_rule, rule }); + log.debug("block rule {any} CONVERTED to rule = {any}", .{ block_rule, rule }); if (!rule.validateVariables()) { try errors.append(.unbound_variable); } const block_rule_trusted_origins = try TrustedOrigins.fromScopes( - authorizer.allocator, + authorizer.arena, rule.scopes.items, block_trusted_origins, block_id, @@ -230,19 +233,19 @@ pub const Authorizer = struct { } // 2. Run the world to generate all facts - std.debug.print("\nGENERATING NEW FACTS\n", .{}); + log.debug("Run world", .{}); try authorizer.world.run(&authorizer.symbols); - std.debug.print("\nEND GENERATING NEW FACTS\n", .{}); + log.debug("Finished running world", .{}); // 3. Run checks that have been added to this authorizer - std.debug.print("\nAUTHORIZER CHECKS\n", .{}); + log.debug("AUTHORIZER CHECKS", .{}); for (authorizer.checks.items) |c| { - std.debug.print("authorizer check = {any}\n", .{c}); - const check = try c.toDatalog(authorizer.allocator, &authorizer.symbols); + log.debug("authorizer check = {any}", .{c}); + const check = try c.toDatalog(authorizer.arena, &authorizer.symbols); for (check.queries.items, 0..) |*query, check_id| { const rule_trusted_origins = try TrustedOrigins.fromScopes( - authorizer.allocator, + authorizer.arena, query.scopes.items, try authorizer.authorizerTrustedOrigins(), Origin.AUTHORIZER_ID, @@ -255,28 +258,28 @@ pub const Authorizer = struct { }; if (!is_match) try errors.append(.{ .failed_authorizer_check = .{ .check_id = check_id } }); - std.debug.print("match {any} = {}\n", .{ query, is_match }); + log.debug("match {any} = {}", .{ query, is_match }); } } - std.debug.print("END AUTHORIZER CHECKS\n", .{}); + log.debug("END AUTHORIZER CHECKS", .{}); // 4. Run checks in the biscuit's authority block if (authorizer.biscuit) |biscuit| { const authority_trusted_origins = try TrustedOrigins.fromScopes( - authorizer.allocator, + authorizer.arena, biscuit.authority.scopes.items, - try TrustedOrigins.defaultOrigins(authorizer.allocator), + try TrustedOrigins.defaultOrigins(authorizer.arena), 0, authorizer.public_key_to_block_id, ); for (biscuit.authority.checks.items, 0..) |c, check_id| { const check = try c.remap(&biscuit.symbols, &authorizer.symbols); - std.debug.print("{}: {any}\n", .{ check_id, check }); + log.debug("{}: {any}", .{ check_id, check }); for (check.queries.items) |*query| { const rule_trusted_origins = try TrustedOrigins.fromScopes( - authorizer.allocator, + authorizer.arena, query.scopes.items, authority_trusted_origins, 0, @@ -289,7 +292,7 @@ pub const Authorizer = struct { }; if (!is_match) try errors.append(.{ .failed_block_check = .{ .block_id = 0, .check_id = check_id } }); - std.debug.print("match {any} = {}\n", .{ query, is_match }); + log.debug("match {any} = {}", .{ query, is_match }); } } } @@ -297,13 +300,13 @@ pub const Authorizer = struct { // 5. run policies from the authorizer const allowed_policy_id: ?usize = policy_blk: { for (authorizer.policies.items) |policy| { - std.debug.print("authorizer policy = {any}\n", .{policy}); + log.debug("testing policy {any}", .{policy}); for (policy.queries.items, 0..) |*q, policy_id| { - var query = try q.toDatalog(authorizer.allocator, &authorizer.symbols); + var query = try q.toDatalog(authorizer.arena, &authorizer.symbols); const rule_trusted_origins = try TrustedOrigins.fromScopes( - authorizer.allocator, + authorizer.arena, query.scopes.items, try authorizer.authorizerTrustedOrigins(), Origin.AUTHORIZER_ID, @@ -311,7 +314,7 @@ pub const Authorizer = struct { ); const is_match = try authorizer.world.queryMatch(&query, &authorizer.symbols, rule_trusted_origins); - std.debug.print("match {any} = {}\n", .{ query, is_match }); + log.debug("match {any} = {}", .{ query, is_match }); if (is_match) { switch (policy.kind) { @@ -333,23 +336,21 @@ pub const Authorizer = struct { if (authorizer.biscuit) |biscuit| { for (biscuit.blocks.items, 1..) |block, block_id| { const block_trusted_origins = try TrustedOrigins.fromScopes( - authorizer.allocator, + authorizer.arena, block.scopes.items, - try TrustedOrigins.defaultOrigins(authorizer.allocator), + try TrustedOrigins.defaultOrigins(authorizer.arena), block_id, authorizer.public_key_to_block_id, ); - std.debug.print("block = {any}\n", .{block}); - for (block.checks.items, 0..) |c, check_id| { const check = try c.remap(&biscuit.symbols, &authorizer.symbols); - std.debug.print("check = {any}\n", .{check}); + log.debug("check = {any}", .{check}); for (check.queries.items) |*query| { const rule_trusted_origins = try TrustedOrigins.fromScopes( - authorizer.allocator, + authorizer.arena, query.scopes.items, block_trusted_origins, block_id, @@ -363,7 +364,7 @@ pub const Authorizer = struct { if (!is_match) try errors.append(.{ .failed_block_check = .{ .block_id = block_id, .check_id = check_id } }); - std.debug.print("match {any} = {}\n", .{ query, is_match }); + log.debug("match {any} = {}", .{ query, is_match }); } } } @@ -391,4 +392,14 @@ pub const AuthorizerError = union(AuthorizerErrorKind) { failed_authorizer_check: struct { check_id: usize }, failed_block_check: struct { block_id: usize, check_id: usize }, unbound_variable: void, + + pub fn format(authorization_error: AuthorizerError, comptime _: []const u8, _: std.fmt.FormatOptions, writer: anytype) !void { + switch (authorization_error) { + .no_matching_policy => try writer.print("no matching policy", .{}), + .denied_by_policy => |e| try writer.print("denied by policy {}", .{e.deny_policy_id}), + .failed_authorizer_check => |e| try writer.print("failed authorizer check {}", .{e.check_id}), + .failed_block_check => |e| try writer.print("failed check {} on block {}", .{ e.check_id, e.block_id }), + .unbound_variable => try writer.print("unbound variable", .{}), + } + } }; diff --git a/biscuit/src/biscuit.zig b/biscuit/src/biscuit.zig index bf00222..631dd5c 100644 --- a/biscuit/src/biscuit.zig +++ b/biscuit/src/biscuit.zig @@ -8,6 +8,8 @@ const SymbolTable = @import("biscuit-datalog").SymbolTable; const World = @import("biscuit-datalog").world.World; const SerializedBiscuit = @import("biscuit-format").SerializedBiscuit; +const log = std.log.scoped(.biscuit); + pub const Biscuit = struct { serialized: SerializedBiscuit, authority: Block, @@ -28,12 +30,12 @@ pub const Biscuit = struct { const authority = try Block.fromBytes(allocator, serialized.authority, &token_symbols); try block_external_keys.append(null); - std.debug.print("authority block =\n{any}\n", .{authority}); + log.debug("authority {any}", .{authority}); var blocks = std.ArrayList(Block).init(allocator); for (serialized.blocks.items) |signed_block| { const block = try Block.fromBytes(allocator, signed_block, &token_symbols); - std.debug.print("non-authority block =\n{any}\n", .{block}); + log.debug("{any}", .{block}); const external_key = if (signed_block.external_signature) |external_signature| external_signature.public_key else null; try block_external_keys.append(external_key); @@ -61,7 +63,7 @@ pub const Biscuit = struct { { var it = public_key_to_block_id.iterator(); while (it.next()) |entry| { - std.debug.print("public_key_to_block_id: public key id = {}, block_ids = {any}\n", .{ entry.key_ptr.*, entry.value_ptr.items }); + log.debug("public_key_to_block_id: public key id = {}, block_ids = {any}", .{ entry.key_ptr.*, entry.value_ptr.items }); } } @@ -96,75 +98,85 @@ pub const Biscuit = struct { } }; -test { - const decode = @import("biscuit-format").decode; - const testing = std.testing; - var allocator = testing.allocator; - - // Key - // private: 83e03c958f83085923f3cd091bab3c3b33a0c7f93f44889739fdb6c6fdb26f5b - // public: 49fe7ec1972952c8c92119def96235ad622d0d024f3042a49c7317f7d5baf3da - const tokens: [6][]const u8 = .{ - "EpACCqUBCgFhCgFiCgFjCgFkCgdtYWxjb2xtCgRqb2huCgEwCgExGAMiCQoHCAISAxiACCIJCgcIAhIDGIEIIgkKBwgCEgMYgggiCQoHCAISAxiDCCIOCgwIBxIDGIQIEgMYgAgiDgoMCAcSAxiECBIDGIIIIg4KDAgHEgMYhQgSAxiBCCokCgsIBBIDCIYIEgIYABIHCAISAwiGCBIMCAcSAwiHCBIDCIYIEiQIABIgnSmYbzjEQ2n09JhlmGs6j_ZhKYgj3nRkEMdGJJqQimwaQD4UTmEDtu5G8kRJZbNTcNuGg8Izb5ja2BSV3Rlkv1Y6IV_Nd00sIstiEq1RPH-M8xfFdWaW1gixH54Y5deHzwYiIgogFmxoQyXPm8ccNBKKh0hv8eRwrYjS56s0OTQWZShHoVw=", - "En0KEwoEMTIzNBgDIgkKBwgKEgMYgAgSJAgAEiCicdgxKsSQpGYPKcR7hmnI7WcRLaFNUNzqkCc92yZluhpAyMoux34FBhYaTsw32rddToN7qbl-XOAPQcaUALPg_SfmuxfXbU9aEIJGVCANQLUfoQwU1GAa8ZkXESkW1uCdCyIiCiCyJCJ0e-e00kyM_3O6IbbftDeYAnkoI8-G1x06NK283w==", - "En0KEwoEMTIzNBgDIgkKBwgKEgMYgAgSJAgAEiCicdgxKsSQpGYPKcR7hmnI7WcRLaFNUNzqkCc92yZluhpAyMoux34FBhYaTsw32rddToN7qbl-XOAPQcaUALPg_SfmuxfXbU9aEIJGVCANQLUfoQwU1GAa8ZkXESkW1uCdCxp9ChMKBGFiY2QYAyIJCgcIAhIDGIEIEiQIABIgkJwspMgTz4pW4hQ_Tkua7EdZ5AajdxV35q42IyXzAt0aQBH3kiLfP06W0dPlQeuxgLU26ssrjoK-v1vvw0dzQ2BtaQjPs8eKhsowhFCjQ6nnhSP0p7v4TaJHWeO2fPsbUQwiIgogeuDcbq6waTZ1HpYt_zYNtAy02gbnjV-5-juc9sdXNJg=", - "En0KEwoEMTIzNBgDIgkKBwgKEgMYgAgSJAgAEiCicdgxKsSQpGYPKcR7hmnI7WcRLaFNUNzqkCc92yZluhpAyMoux34FBhYaTsw32rddToN7qbl-XOAPQcaUALPg_SfmuxfXbU9aEIJGVCANQLUfoQwU1GAa8ZkXESkW1uCdCxp9ChMKBGFiY2QYAyIJCgcIAhIDGIEIEiQIABIgkJwspMgTz4pW4hQ_Tkua7EdZ5AajdxV35q42IyXzAt0aQBH3kiLfP06W0dPlQeuxgLU26ssrjoK-v1vvw0dzQ2BtaQjPs8eKhsowhFCjQ6nnhSP0p7v4TaJHWeO2fPsbUQwiQhJAfNph7vZIL6WSLwOCmMHkwb4OmCc5s7EByizwq6HZOF04SRwCF8THWcNImPj-5xWOuI3zVdxg11Qr6d0c5yxuCw==", - "Eq4BCkQKBDEyMzQKBmRvdWJsZQoBeAoBeRgDIgkKBwgKEgMYgAgqIQoNCIEIEgMIgggSAwiDCBIHCAoSAwiCCBIHCAoSAwiDCBIkCAASIHJpGIZ74pbiyybTMn2zrCqHf5t7ZUV9tMnT5xkLq5rsGkCnAznWzInI1-kJGuRUgluqmr96bJwKG3RT3iceJ3kzzzBWGT5dEFXYyIqWxpLDk9Qoy-AWpwS49SA5ynGKb5UGIiIKIESr7u80iDgTstDzVk6obTp6zJmVfBqNcBNtwjOQyVOr", - // Token with check in authority block (that should pass): - "Eq8CCsQBCgFhCgFiCgFjCgFkCgdtYWxjb2xtCgRqb2huCgExCgEwGAMiCQoHCAISAxiACCIJCgcIAhIDGIEIIgkKBwgCEgMYgggiCQoHCAISAxiDCCIOCgwIBxIDGIQIEgMYgAgiDgoMCAcSAxiECBIDGIIIIg4KDAgHEgMYhQgSAxiBCCopChAIBBIDCIYIEgMIhwgSAhgAEgcIAhIDCIcIEgwIBxIDCIYIEgMIhwgyGAoWCgIIGxIQCAQSAxiECBIDGIAIEgIYABIkCAASIGMjO8ucGcxZst9FINaf7EmOsWh8kW039G8TeV9BYIhTGkCrqL87m-bqFGxmNUobqmw7iWHViQN6DRDksNCJMfkC1zvwVdSZwZwtgQmr90amKCPjdXCD0bev53dNyIanRPoPIiIKIMAzV_GYyKdq9NeJ80-E-bGqGYD4nLXCDRnGpzThEglb", - }; - - var public_key_mem: [32]u8 = undefined; - _ = try std.fmt.hexToBytes(&public_key_mem, "49fe7ec1972952c8c92119def96235ad622d0d024f3042a49c7317f7d5baf3da"); - const public_key = try Ed25519.PublicKey.fromBytes(public_key_mem); - - for (tokens) |token| { - const bytes = try decode.urlSafeBase64ToBytes(allocator, token); - defer allocator.free(bytes); - - var b = try Biscuit.fromBytes(allocator, bytes, public_key); - defer b.deinit(); - - var a = try b.authorizer(allocator); - defer a.deinit(); - - var errors = std.ArrayList(AuthorizerError).init(allocator); - defer errors.deinit(); - - _ = try a.authorize(&errors); - } -} +// test { +// const decode = @import("biscuit-format").decode; +// const testing = std.testing; +// var allocator = testing.allocator; -test "Tokens that should fail to validate" { - const decode = @import("biscuit-format").decode; - const testing = std.testing; - var allocator = testing.allocator; +// // Key +// // private: 83e03c958f83085923f3cd091bab3c3b33a0c7f93f44889739fdb6c6fdb26f5b +// // public: 49fe7ec1972952c8c92119def96235ad622d0d024f3042a49c7317f7d5baf3da +// const tokens: [6][]const u8 = .{ +// "EpACCqUBCgFhCgFiCgFjCgFkCgdtYWxjb2xtCgRqb2huCgEwCgExGAMiCQoHCAISAxiACCIJCgcIAhIDGIEIIgkKBwgCEgMYgggiCQoHCAISAxiDCCIOCgwIBxIDGIQIEgMYgAgiDgoMCAcSAxiECBIDGIIIIg4KDAgHEgMYhQgSAxiBCCokCgsIBBIDCIYIEgIYABIHCAISAwiGCBIMCAcSAwiHCBIDCIYIEiQIABIgnSmYbzjEQ2n09JhlmGs6j_ZhKYgj3nRkEMdGJJqQimwaQD4UTmEDtu5G8kRJZbNTcNuGg8Izb5ja2BSV3Rlkv1Y6IV_Nd00sIstiEq1RPH-M8xfFdWaW1gixH54Y5deHzwYiIgogFmxoQyXPm8ccNBKKh0hv8eRwrYjS56s0OTQWZShHoVw=", +// "En0KEwoEMTIzNBgDIgkKBwgKEgMYgAgSJAgAEiCicdgxKsSQpGYPKcR7hmnI7WcRLaFNUNzqkCc92yZluhpAyMoux34FBhYaTsw32rddToN7qbl-XOAPQcaUALPg_SfmuxfXbU9aEIJGVCANQLUfoQwU1GAa8ZkXESkW1uCdCyIiCiCyJCJ0e-e00kyM_3O6IbbftDeYAnkoI8-G1x06NK283w==", +// "En0KEwoEMTIzNBgDIgkKBwgKEgMYgAgSJAgAEiCicdgxKsSQpGYPKcR7hmnI7WcRLaFNUNzqkCc92yZluhpAyMoux34FBhYaTsw32rddToN7qbl-XOAPQcaUALPg_SfmuxfXbU9aEIJGVCANQLUfoQwU1GAa8ZkXESkW1uCdCxp9ChMKBGFiY2QYAyIJCgcIAhIDGIEIEiQIABIgkJwspMgTz4pW4hQ_Tkua7EdZ5AajdxV35q42IyXzAt0aQBH3kiLfP06W0dPlQeuxgLU26ssrjoK-v1vvw0dzQ2BtaQjPs8eKhsowhFCjQ6nnhSP0p7v4TaJHWeO2fPsbUQwiIgogeuDcbq6waTZ1HpYt_zYNtAy02gbnjV-5-juc9sdXNJg=", +// "En0KEwoEMTIzNBgDIgkKBwgKEgMYgAgSJAgAEiCicdgxKsSQpGYPKcR7hmnI7WcRLaFNUNzqkCc92yZluhpAyMoux34FBhYaTsw32rddToN7qbl-XOAPQcaUALPg_SfmuxfXbU9aEIJGVCANQLUfoQwU1GAa8ZkXESkW1uCdCxp9ChMKBGFiY2QYAyIJCgcIAhIDGIEIEiQIABIgkJwspMgTz4pW4hQ_Tkua7EdZ5AajdxV35q42IyXzAt0aQBH3kiLfP06W0dPlQeuxgLU26ssrjoK-v1vvw0dzQ2BtaQjPs8eKhsowhFCjQ6nnhSP0p7v4TaJHWeO2fPsbUQwiQhJAfNph7vZIL6WSLwOCmMHkwb4OmCc5s7EByizwq6HZOF04SRwCF8THWcNImPj-5xWOuI3zVdxg11Qr6d0c5yxuCw==", +// "Eq4BCkQKBDEyMzQKBmRvdWJsZQoBeAoBeRgDIgkKBwgKEgMYgAgqIQoNCIEIEgMIgggSAwiDCBIHCAoSAwiCCBIHCAoSAwiDCBIkCAASIHJpGIZ74pbiyybTMn2zrCqHf5t7ZUV9tMnT5xkLq5rsGkCnAznWzInI1-kJGuRUgluqmr96bJwKG3RT3iceJ3kzzzBWGT5dEFXYyIqWxpLDk9Qoy-AWpwS49SA5ynGKb5UGIiIKIESr7u80iDgTstDzVk6obTp6zJmVfBqNcBNtwjOQyVOr", +// // Token with check in authority block (that should pass): +// "Eq8CCsQBCgFhCgFiCgFjCgFkCgdtYWxjb2xtCgRqb2huCgExCgEwGAMiCQoHCAISAxiACCIJCgcIAhIDGIEIIgkKBwgCEgMYgggiCQoHCAISAxiDCCIOCgwIBxIDGIQIEgMYgAgiDgoMCAcSAxiECBIDGIIIIg4KDAgHEgMYhQgSAxiBCCopChAIBBIDCIYIEgMIhwgSAhgAEgcIAhIDCIcIEgwIBxIDCIYIEgMIhwgyGAoWCgIIGxIQCAQSAxiECBIDGIAIEgIYABIkCAASIGMjO8ucGcxZst9FINaf7EmOsWh8kW039G8TeV9BYIhTGkCrqL87m-bqFGxmNUobqmw7iWHViQN6DRDksNCJMfkC1zvwVdSZwZwtgQmr90amKCPjdXCD0bev53dNyIanRPoPIiIKIMAzV_GYyKdq9NeJ80-E-bGqGYD4nLXCDRnGpzThEglb", +// }; - // Key - // private: 83e03c958f83085923f3cd091bab3c3b33a0c7f93f44889739fdb6c6fdb26f5b - // public: 49fe7ec1972952c8c92119def96235ad622d0d024f3042a49c7317f7d5baf3da - const tokens: [1][]const u8 = .{ - // Token with check (in authority block) that should pass and a check (in the authority block) that should fail - "Es8CCuQBCgFhCgFiCgFjCgFkCgdtYWxjb2xtCgRqb2huCgExCgEwCgRlcmljGAMiCQoHCAISAxiACCIJCgcIAhIDGIEIIgkKBwgCEgMYgggiCQoHCAISAxiDCCIOCgwIBxIDGIQIEgMYgAgiDgoMCAcSAxiECBIDGIIIIg4KDAgHEgMYhQgSAxiBCCopChAIBBIDCIYIEgMIhwgSAhgAEgcIAhIDCIcIEgwIBxIDCIYIEgMIhwgyGAoWCgIIGxIQCAQSAxiECBIDGIAIEgIYADIYChYKAggbEhAIBBIDGIgIEgMYgwgSAhgBEiQIABIgbACOx_sohlqZpzEwG23cKbN5wsUseLHHPt1tM8zVilIaQHMBawtn2NIa0jkJ38FR-uw7ncEAP1Qp_g6zctajVDLo1eMhBzjBO6lCddBHyEgvwZ9bufXYClHAwEZQyGKeEgwiIgogCfqPElEy9fyO6r-E5GT9-io3bhhSSe9wVAn6x6fsM7k=", - }; +// var public_key_mem: [32]u8 = undefined; +// _ = try std.fmt.hexToBytes(&public_key_mem, "49fe7ec1972952c8c92119def96235ad622d0d024f3042a49c7317f7d5baf3da"); +// const public_key = try Ed25519.PublicKey.fromBytes(public_key_mem); - var public_key_mem: [32]u8 = undefined; - _ = try std.fmt.hexToBytes(&public_key_mem, "49fe7ec1972952c8c92119def96235ad622d0d024f3042a49c7317f7d5baf3da"); - const public_key = try Ed25519.PublicKey.fromBytes(public_key_mem); +// for (tokens) |token| { +// const bytes = try decode.urlSafeBase64ToBytes(allocator, token); +// defer allocator.free(bytes); - for (tokens) |token| { - const bytes = try decode.urlSafeBase64ToBytes(allocator, token); - defer allocator.free(bytes); +// var arena_state = std.heap.ArenaAllocator.init(testing.allocator); +// defer arena_state.deinit(); - var b = try Biscuit.fromBytes(allocator, bytes, public_key); - defer b.deinit(); +// const arena = arena_state.allocator(); - var a = try b.authorizer(allocator); - defer a.deinit(); +// var b = try Biscuit.fromBytes(arena, bytes, public_key); +// defer b.deinit(); - var errors = std.ArrayList(AuthorizerError).init(allocator); - defer errors.deinit(); +// var a = try b.authorizer(arena); +// defer a.deinit(); - try testing.expectError(error.AuthorizationFailed, a.authorize(&errors)); - } -} +// var errors = std.ArrayList(AuthorizerError).init(allocator); +// defer errors.deinit(); + +// _ = try a.authorize(&errors); +// } +// } + +// test "Tokens that should fail to validate" { +// const decode = @import("biscuit-format").decode; +// const testing = std.testing; +// var allocator = testing.allocator; + +// // Key +// // private: 83e03c958f83085923f3cd091bab3c3b33a0c7f93f44889739fdb6c6fdb26f5b +// // public: 49fe7ec1972952c8c92119def96235ad622d0d024f3042a49c7317f7d5baf3da +// const tokens: [1][]const u8 = .{ +// // Token with check (in authority block) that should pass and a check (in the authority block) that should fail +// "Es8CCuQBCgFhCgFiCgFjCgFkCgdtYWxjb2xtCgRqb2huCgExCgEwCgRlcmljGAMiCQoHCAISAxiACCIJCgcIAhIDGIEIIgkKBwgCEgMYgggiCQoHCAISAxiDCCIOCgwIBxIDGIQIEgMYgAgiDgoMCAcSAxiECBIDGIIIIg4KDAgHEgMYhQgSAxiBCCopChAIBBIDCIYIEgMIhwgSAhgAEgcIAhIDCIcIEgwIBxIDCIYIEgMIhwgyGAoWCgIIGxIQCAQSAxiECBIDGIAIEgIYADIYChYKAggbEhAIBBIDGIgIEgMYgwgSAhgBEiQIABIgbACOx_sohlqZpzEwG23cKbN5wsUseLHHPt1tM8zVilIaQHMBawtn2NIa0jkJ38FR-uw7ncEAP1Qp_g6zctajVDLo1eMhBzjBO6lCddBHyEgvwZ9bufXYClHAwEZQyGKeEgwiIgogCfqPElEy9fyO6r-E5GT9-io3bhhSSe9wVAn6x6fsM7k=", +// }; + +// var public_key_mem: [32]u8 = undefined; +// _ = try std.fmt.hexToBytes(&public_key_mem, "49fe7ec1972952c8c92119def96235ad622d0d024f3042a49c7317f7d5baf3da"); +// const public_key = try Ed25519.PublicKey.fromBytes(public_key_mem); + +// for (tokens) |token| { +// const bytes = try decode.urlSafeBase64ToBytes(allocator, token); +// defer allocator.free(bytes); + +// var arena_state = std.heap.ArenaAllocator.init(testing.allocator); +// defer arena_state.deinit(); + +// const arena = arena_state.allocator(); + +// var b = try Biscuit.fromBytes(arena, bytes, public_key); +// defer b.deinit(); + +// var a = try b.authorizer(arena); +// defer a.deinit(); + +// var errors = std.ArrayList(AuthorizerError).init(allocator); +// defer errors.deinit(); + +// try testing.expectError(error.AuthorizationFailed, a.authorize(&errors)); +// } +// } diff --git a/biscuit/src/block.zig b/biscuit/src/block.zig index 304dbc3..567eec0 100644 --- a/biscuit/src/block.zig +++ b/biscuit/src/block.zig @@ -10,6 +10,8 @@ const Check = @import("biscuit-datalog").check.Check; const Scope = @import("biscuit-datalog").Scope; const SymbolTable = @import("biscuit-datalog").symbol_table.SymbolTable; +const log = std.log.scoped(.block); + pub const Block = struct { version: u32, context: []const u8, @@ -49,7 +51,7 @@ pub const Block = struct { /// Given a blocks contents as bytes, derserialize into runtime block pub fn fromBytes(allocator: std.mem.Allocator, signed_block: SignedBlock, token_symbols: *SymbolTable) !Block { const data = signed_block.block; - std.debug.print("Block.fromBytes\n", .{}); + const decoded_block = try schema.decodeBlock(allocator, data); defer decoded_block.deinit(); From 526383ec20b614f579af52aaff7036e5d4ae26a5 Mon Sep 17 00:00:00 2001 From: Malcolm Still Date: Sat, 30 Mar 2024 16:42:20 +0000 Subject: [PATCH 06/52] Replace std.mem.starsWith with parser.startsWith --- biscuit-parser/src/parser.zig | 38 ++++++++++++++++++++--------------- 1 file changed, 22 insertions(+), 16 deletions(-) diff --git a/biscuit-parser/src/parser.zig b/biscuit-parser/src/parser.zig index 8ced3a6..2fa8133 100644 --- a/biscuit-parser/src/parser.zig +++ b/biscuit-parser/src/parser.zig @@ -311,13 +311,15 @@ pub const Parser = struct { } fn boolean(parser: *Parser) !bool { - if (std.mem.startsWith(u8, parser.rest(), "true")) { - parser.offset += "term".len; + if (parser.startsWith("true")) { + try parser.expectString("true"); + return true; } - if (std.mem.startsWith(u8, parser.rest(), "false")) { - parser.offset += "false".len; + if (parser.startsWith("false")) { + try parser.expectString("false"); + return false; } @@ -327,11 +329,13 @@ pub const Parser = struct { pub fn policy(parser: *Parser) !Policy { var kind: Policy.Kind = undefined; - if (std.mem.startsWith(u8, parser.rest(), "allow if")) { - parser.offset += "allow if".len; + if (parser.startsWith("allow if")) { + try parser.expectString("allow if"); + kind = .allow; - } else if (std.mem.startsWith(u8, parser.rest(), "deny if")) { - parser.offset += "deny if".len; + } else if (parser.startsWith("deny if")) { + try parser.expectString("deny if"); + kind = .deny; } else { return error.UnexpectedPolicyKind; @@ -345,11 +349,13 @@ pub const Parser = struct { pub fn check(parser: *Parser) !Check { var kind: datalog.Check.Kind = undefined; - if (std.mem.startsWith(u8, parser.rest(), "check if")) { - parser.offset += "check if".len; + if (parser.startsWith("check if")) { + try parser.expectString("check if"); + kind = .one; - } else if (std.mem.startsWith(u8, parser.rest(), "check all")) { - parser.offset += "check all".len; + } else if (parser.startsWith("check all")) { + try parser.expectString("check all"); + kind = .all; } else { return error.UnexpectedCheckKind; @@ -376,9 +382,9 @@ pub const Parser = struct { while (true) { parser.skipWhiteSpace(); - if (!std.mem.startsWith(u8, parser.rest(), "or")) break; + if (!parser.startsWith("or")) break; - parser.offset += "or".len; + try parser.expectString("or"); const body = try parser.ruleBody(); @@ -399,9 +405,9 @@ pub const Parser = struct { parser.skipWhiteSpace(); - if (!std.mem.startsWith(u8, parser.rest(), "<-")) return error.ExpectedArrow; + if (!parser.startsWith("<-")) return error.ExpectedArrow; - parser.offset += "<-".len; + try parser.expectString("<-"); const body = try parser.ruleBody(); From 5c17672c7f0ecc005d8796e31b55ba10b936bb88 Mon Sep 17 00:00:00 2001 From: Malcolm Still Date: Sat, 30 Mar 2024 16:44:50 +0000 Subject: [PATCH 07/52] Replace expect / expectString with expect that just takes strings --- biscuit-parser/src/parser.zig | 116 ++++++++++++++++------------------ 1 file changed, 54 insertions(+), 62 deletions(-) diff --git a/biscuit-parser/src/parser.zig b/biscuit-parser/src/parser.zig index 2fa8133..15d8896 100644 --- a/biscuit-parser/src/parser.zig +++ b/biscuit-parser/src/parser.zig @@ -33,7 +33,7 @@ pub const Parser = struct { parser.skipWhiteSpace(); // Consume left paren - try parser.expect('('); + try parser.expect("("); // Parse terms var terms = std.ArrayList(Term).init(parser.allocator); @@ -49,7 +49,7 @@ pub const Parser = struct { } } - try parser.expect(')'); + try parser.expect(")"); return .{ .name = name, .terms = terms }; } @@ -192,7 +192,7 @@ pub const Parser = struct { parser.skipWhiteSpace(); // Consume left paren - try parser.expect('('); + try parser.expect("("); // Parse terms var terms = std.ArrayList(Term).init(parser.allocator); @@ -211,13 +211,13 @@ pub const Parser = struct { break; } - try parser.expect(')'); + try parser.expect(")"); return .{ .name = name, .terms = terms }; } fn variable(parser: *Parser) ![]const u8 { - try parser.expect('$'); + try parser.expect("$"); const start = parser.offset; @@ -235,7 +235,7 @@ pub const Parser = struct { // FIXME: properly implement string parsing fn string(parser: *Parser) ![]const u8 { - try parser.expect('"'); + try parser.expect("\""); const start = parser.offset; @@ -252,32 +252,32 @@ pub const Parser = struct { fn date(parser: *Parser) !u64 { const year = try parser.number(i32); - try parser.expect('-'); + try parser.expect("-"); const month = try parser.number(u8); if (month < 1 or month > 12) return error.MonthOutOfRange; - try parser.expect('-'); + try parser.expect("-"); const day = try parser.number(u8); if (!Date.isDayMonthYearValid(i32, year, month, day)) return error.InvalidDayMonthYearCombination; - try parser.expect('T'); + try parser.expect("T"); const hour = try parser.number(u8); if (hour > 23) return error.HoyrOutOfRange; - try parser.expect(':'); + try parser.expect(":"); const minute = try parser.number(u8); if (minute > 59) return error.MinuteOutOfRange; - try parser.expect(':'); + try parser.expect(":"); const second = try parser.number(u8); if (second > 59) return error.SecondOutOfRange; - try parser.expect('Z'); + try parser.expect("Z"); const d: Date = .{ .year = year, @@ -312,13 +312,13 @@ pub const Parser = struct { fn boolean(parser: *Parser) !bool { if (parser.startsWith("true")) { - try parser.expectString("true"); + try parser.expect("true"); return true; } if (parser.startsWith("false")) { - try parser.expectString("false"); + try parser.expect("false"); return false; } @@ -330,11 +330,11 @@ pub const Parser = struct { var kind: Policy.Kind = undefined; if (parser.startsWith("allow if")) { - try parser.expectString("allow if"); + try parser.expect("allow if"); kind = .allow; } else if (parser.startsWith("deny if")) { - try parser.expectString("deny if"); + try parser.expect("deny if"); kind = .deny; } else { @@ -350,11 +350,11 @@ pub const Parser = struct { var kind: datalog.Check.Kind = undefined; if (parser.startsWith("check if")) { - try parser.expectString("check if"); + try parser.expect("check if"); kind = .one; } else if (parser.startsWith("check all")) { - try parser.expectString("check all"); + try parser.expect("check all"); kind = .all; } else { @@ -384,7 +384,7 @@ pub const Parser = struct { if (!parser.startsWith("or")) break; - try parser.expectString("or"); + try parser.expect("or"); const body = try parser.ruleBody(); @@ -407,7 +407,7 @@ pub const Parser = struct { if (!parser.startsWith("<-")) return error.ExpectedArrow; - try parser.expectString("<-"); + try parser.expect("<-"); const body = try parser.ruleBody(); @@ -632,12 +632,12 @@ pub const Parser = struct { parser.skipWhiteSpace(); if (!parser.startsWith(".")) return e1; - try parser.expect('.'); + try parser.expect("."); const op = try parser.binaryOp7(); parser.skipWhiteSpace(); - try parser.expect('('); + try parser.expect("("); parser.skipWhiteSpace(); @@ -645,7 +645,7 @@ pub const Parser = struct { parser.skipWhiteSpace(); - try parser.expect(')'); + try parser.expect(")"); parser.skipWhiteSpace(); @@ -654,12 +654,12 @@ pub const Parser = struct { fn binaryOp0(parser: *Parser) ParserError!Expression.BinaryOp { if (parser.startsWith("&&")) { - try parser.expectString("&&"); + try parser.expect("&&"); return .@"and"; } if (parser.startsWith("||")) { - try parser.expectString("||"); + try parser.expect("||"); return .@"or"; } @@ -668,32 +668,32 @@ pub const Parser = struct { fn binaryOp1(parser: *Parser) ParserError!Expression.BinaryOp { if (parser.startsWith("<=")) { - try parser.expectString("<="); + try parser.expect("<="); return .less_or_equal; } if (parser.startsWith(">=")) { - try parser.expectString(">="); + try parser.expect(">="); return .greater_or_equal; } if (parser.startsWith("<")) { - try parser.expectString("<"); + try parser.expect("<"); return .less_than; } if (parser.startsWith(">")) { - try parser.expectString(">"); + try parser.expect(">"); return .greater_than; } if (parser.startsWith("==")) { - try parser.expectString("=="); + try parser.expect("=="); return .equal; } if (parser.startsWith("!=")) { - try parser.expectString("!="); + try parser.expect("!="); return .not_equal; } @@ -702,12 +702,12 @@ pub const Parser = struct { fn binaryOp2(parser: *Parser) ParserError!Expression.BinaryOp { if (parser.startsWith("+")) { - try parser.expectString("+"); + try parser.expect("+"); return .add; } if (parser.startsWith("-")) { - try parser.expectString("-"); + try parser.expect("-"); return .sub; } @@ -716,7 +716,7 @@ pub const Parser = struct { fn binaryOp3(parser: *Parser) ParserError!Expression.BinaryOp { if (parser.startsWith("^")) { - try parser.expectString("^"); + try parser.expect("^"); return .bitwise_xor; } @@ -725,7 +725,7 @@ pub const Parser = struct { fn binaryOp4(parser: *Parser) ParserError!Expression.BinaryOp { if (parser.startsWith("|") and !parser.startsWith("||")) { - try parser.expectString("|"); + try parser.expect("|"); return .bitwise_or; } @@ -734,7 +734,7 @@ pub const Parser = struct { fn binaryOp5(parser: *Parser) ParserError!Expression.BinaryOp { if (parser.startsWith("&") and !parser.startsWith("&&")) { - try parser.expectString("&"); + try parser.expect("&"); return .bitwise_and; } @@ -743,12 +743,12 @@ pub const Parser = struct { fn binaryOp6(parser: *Parser) ParserError!Expression.BinaryOp { if (parser.startsWith("*")) { - try parser.expectString("*"); + try parser.expect("*"); return .mul; } if (parser.startsWith("/")) { - try parser.expectString("/"); + try parser.expect("/"); return .div; } @@ -757,22 +757,22 @@ pub const Parser = struct { fn binaryOp7(parser: *Parser) ParserError!Expression.BinaryOp { if (parser.startsWith("contains")) { - try parser.expectString("contains"); + try parser.expect("contains"); return .contains; } if (parser.startsWith("starts_with")) { - try parser.expectString("starts_with"); + try parser.expect("starts_with"); return .prefix; } if (parser.startsWith("ends_with")) { - try parser.expectString("ends_with"); + try parser.expect("ends_with"); return .suffix; } if (parser.startsWith("matches")) { - try parser.expectString("matches"); + try parser.expect("matches"); return .regex; } @@ -802,7 +802,7 @@ pub const Parser = struct { if (parser.peek()) |c| { if (c == '!') { - try parser.expect('!'); + try parser.expect("!"); parser.skipWhiteSpace(); const e = try parser.expr(); @@ -824,7 +824,7 @@ pub const Parser = struct { parser.skipWhiteSpace(); } - if (parser.expectString(".length()")) |_| { + if (parser.expect(".length()")) |_| { return try Expression.unary(parser.allocator, .length, e); } else |_| { return error.UnexpectedToken; @@ -834,7 +834,7 @@ pub const Parser = struct { } fn unaryParens(parser: *Parser) ParserError!Expression { - try parser.expectString("("); + try parser.expect("("); parser.skipWhiteSpace(); @@ -842,13 +842,13 @@ pub const Parser = struct { parser.skipWhiteSpace(); - try parser.expectString(")"); + try parser.expect(")"); return try Expression.unary(parser.allocator, .parens, e); } fn scopes(parser: *Parser, allocator: std.mem.Allocator) !std.ArrayList(Scope) { - try parser.expectString("trusting"); + try parser.expect("trusting"); parser.skipWhiteSpace(); @@ -865,7 +865,7 @@ pub const Parser = struct { if (!parser.startsWith(",")) break; - try parser.expectString(","); + try parser.expect(","); } return scps; @@ -875,23 +875,23 @@ pub const Parser = struct { parser.skipWhiteSpace(); if (parser.startsWith("authority")) { - try parser.expectString("authority"); + try parser.expect("authority"); return .{ .authority = {} }; } if (parser.startsWith("previous")) { - try parser.expectString("previous"); + try parser.expect("previous"); return .{ .previous = {} }; } if (parser.startsWith("{")) { - try parser.expectString("{"); + try parser.expect("{"); const parameter = parser.readName(); - try parser.expectString("}"); + try parser.expect("}"); return .{ .parameter = parameter }; } @@ -900,7 +900,7 @@ pub const Parser = struct { } fn publicKey(parser: *Parser) !Ed25519.PublicKey { - try parser.expectString("ed25519/"); + try parser.expect("ed25519/"); const h = try parser.hex(); @@ -921,16 +921,8 @@ pub const Parser = struct { return parser.input[parser.offset..]; } - /// Expect (and consume) char. - fn expect(parser: *Parser, char: u8) !void { - const peeked = parser.peek() orelse return error.ExpectedMoreInput; - if (peeked != char) return error.ExpectedChar; - - parser.offset += 1; - } - /// Expect and consume string. - fn expectString(parser: *Parser, str: []const u8) !void { + fn expect(parser: *Parser, str: []const u8) !void { if (!std.mem.startsWith(u8, parser.rest(), str)) return error.UnexpectedString; parser.offset += str.len; From a0235d7b64677dffbe193a701f2ecff01cff6692 Mon Sep 17 00:00:00 2001 From: Malcolm Still Date: Sat, 30 Mar 2024 17:02:25 +0000 Subject: [PATCH 08/52] More parser clean up --- biscuit-parser/src/parser.zig | 178 ++++++++++------------------------ 1 file changed, 53 insertions(+), 125 deletions(-) diff --git a/biscuit-parser/src/parser.zig b/biscuit-parser/src/parser.zig index 15d8896..2b73df1 100644 --- a/biscuit-parser/src/parser.zig +++ b/biscuit-parser/src/parser.zig @@ -311,35 +311,20 @@ pub const Parser = struct { } fn boolean(parser: *Parser) !bool { - if (parser.startsWith("true")) { - try parser.expect("true"); + if (parser.startsWithConsuming("true")) return true; - return true; - } - - if (parser.startsWith("false")) { - try parser.expect("false"); - - return false; - } + if (parser.startsWithConsuming("false")) return false; return error.ExpectedBooleanTerm; } pub fn policy(parser: *Parser) !Policy { - var kind: Policy.Kind = undefined; - - if (parser.startsWith("allow if")) { - try parser.expect("allow if"); - - kind = .allow; - } else if (parser.startsWith("deny if")) { - try parser.expect("deny if"); - - kind = .deny; - } else { + const kind: Policy.Kind = if (parser.startsWithConsuming("allow if")) + .allow + else if (parser.startsWithConsuming("deny if")) + .deny + else return error.UnexpectedPolicyKind; - } const queries = try parser.checkBody(); @@ -347,19 +332,12 @@ pub const Parser = struct { } pub fn check(parser: *Parser) !Check { - var kind: datalog.Check.Kind = undefined; - - if (parser.startsWith("check if")) { - try parser.expect("check if"); - - kind = .one; - } else if (parser.startsWith("check all")) { - try parser.expect("check all"); - - kind = .all; - } else { + const kind: datalog.Check.Kind = if (parser.startsWithConsuming("check if")) + .one + else if (parser.startsWithConsuming("check all")) + .all + else return error.UnexpectedCheckKind; - } const queries = try parser.checkBody(); @@ -405,8 +383,6 @@ pub const Parser = struct { parser.skipWhiteSpace(); - if (!parser.startsWith("<-")) return error.ExpectedArrow; - try parser.expect("<-"); const body = try parser.ruleBody(); @@ -512,6 +488,13 @@ pub const Parser = struct { return e; } + fn binaryOp0(parser: *Parser) ParserError!Expression.BinaryOp { + if (parser.startsWithConsuming("&&")) return .@"and"; + if (parser.startsWithConsuming("||")) return .@"or"; + + return error.UnexpectedOp; + } + fn expr1(parser: *Parser) ParserError!Expression { var e = try parser.expr2(); @@ -531,6 +514,17 @@ pub const Parser = struct { return e; } + fn binaryOp1(parser: *Parser) ParserError!Expression.BinaryOp { + if (parser.startsWithConsuming("<=")) return .less_or_equal; + if (parser.startsWithConsuming(">=")) return .greater_or_equal; + if (parser.startsWithConsuming("<")) return .less_than; + if (parser.startsWithConsuming(">")) return .greater_than; + if (parser.startsWithConsuming("==")) return .equal; + if (parser.startsWithConsuming("!=")) return .not_equal; + + return error.UnexpectedOp; + } + fn expr2(parser: *Parser) ParserError!Expression { var e = try parser.expr3(); @@ -652,73 +646,15 @@ pub const Parser = struct { return try Expression.binary(parser.allocator, op, e1, e2); } - fn binaryOp0(parser: *Parser) ParserError!Expression.BinaryOp { - if (parser.startsWith("&&")) { - try parser.expect("&&"); - return .@"and"; - } - - if (parser.startsWith("||")) { - try parser.expect("||"); - return .@"or"; - } - - return error.UnexpectedOp; - } - - fn binaryOp1(parser: *Parser) ParserError!Expression.BinaryOp { - if (parser.startsWith("<=")) { - try parser.expect("<="); - return .less_or_equal; - } - - if (parser.startsWith(">=")) { - try parser.expect(">="); - return .greater_or_equal; - } - - if (parser.startsWith("<")) { - try parser.expect("<"); - return .less_than; - } - - if (parser.startsWith(">")) { - try parser.expect(">"); - return .greater_than; - } - - if (parser.startsWith("==")) { - try parser.expect("=="); - return .equal; - } - - if (parser.startsWith("!=")) { - try parser.expect("!="); - return .not_equal; - } - - return error.UnexpectedOp; - } - fn binaryOp2(parser: *Parser) ParserError!Expression.BinaryOp { - if (parser.startsWith("+")) { - try parser.expect("+"); - return .add; - } - - if (parser.startsWith("-")) { - try parser.expect("-"); - return .sub; - } + if (parser.startsWithConsuming("+")) return .add; + if (parser.startsWithConsuming("-")) return .sub; return error.UnexpectedOp; } fn binaryOp3(parser: *Parser) ParserError!Expression.BinaryOp { - if (parser.startsWith("^")) { - try parser.expect("^"); - return .bitwise_xor; - } + if (parser.startsWithConsuming("^")) return .bitwise_xor; return error.UnexpectedOp; } @@ -742,39 +678,17 @@ pub const Parser = struct { } fn binaryOp6(parser: *Parser) ParserError!Expression.BinaryOp { - if (parser.startsWith("*")) { - try parser.expect("*"); - return .mul; - } - - if (parser.startsWith("/")) { - try parser.expect("/"); - return .div; - } + if (parser.startsWithConsuming("*")) return .mul; + if (parser.startsWithConsuming("/")) return .div; return error.UnexpectedOp; } fn binaryOp7(parser: *Parser) ParserError!Expression.BinaryOp { - if (parser.startsWith("contains")) { - try parser.expect("contains"); - return .contains; - } - - if (parser.startsWith("starts_with")) { - try parser.expect("starts_with"); - return .prefix; - } - - if (parser.startsWith("ends_with")) { - try parser.expect("ends_with"); - return .suffix; - } - - if (parser.startsWith("matches")) { - try parser.expect("matches"); - return .regex; - } + if (parser.startsWithConsuming("contains")) return .contains; + if (parser.startsWithConsuming("starts_with")) return .prefix; + if (parser.startsWithConsuming("ends_with")) return .suffix; + if (parser.startsWithConsuming("matches")) return .regex; return error.UnexpectedOp; } @@ -928,10 +842,24 @@ pub const Parser = struct { parser.offset += str.len; } + /// Returns true if the remaining parser input starts with str + /// + /// Does not consume any input. fn startsWith(parser: *Parser, str: []const u8) bool { return std.mem.startsWith(u8, parser.rest(), str); } + /// Returns true if the remaining parse input starts with str. If + /// it does start with that string, the parser consumes the string. + fn startsWithConsuming(parser: *Parser, str: []const u8) bool { + if (parser.startsWith(str)) { + parser.offset += str.len; // Consume + return true; + } + + return false; + } + /// Reject char. Does not consume the character fn reject(parser: *Parser, char: u8) !void { const peeked = parser.peek() orelse return error.ExpectedMoreInput; From 55d4fce3f221bd55606576911a95b83b4317030b Mon Sep 17 00:00:00 2001 From: Malcolm Still Date: Sat, 30 Mar 2024 17:06:43 +0000 Subject: [PATCH 09/52] More parser clean up --- biscuit-parser/src/parser.zig | 314 +++++++++++++++++----------------- 1 file changed, 159 insertions(+), 155 deletions(-) diff --git a/biscuit-parser/src/parser.zig b/biscuit-parser/src/parser.zig index 2b73df1..a7f0b3e 100644 --- a/biscuit-parser/src/parser.zig +++ b/biscuit-parser/src/parser.zig @@ -216,108 +216,6 @@ pub const Parser = struct { return .{ .name = name, .terms = terms }; } - fn variable(parser: *Parser) ![]const u8 { - try parser.expect("$"); - - const start = parser.offset; - - for (parser.rest()) |c| { - if (ziglyph.isAlphaNum(c) or c == '_') { - parser.offset += 1; - continue; - } - - break; - } - - return parser.input[start..parser.offset]; - } - - // FIXME: properly implement string parsing - fn string(parser: *Parser) ![]const u8 { - try parser.expect("\""); - - const start = parser.offset; - - while (parser.peek()) |peeked| { - defer parser.offset += 1; - if (peeked == '"') { - return parser.input[start..parser.offset]; - } - } - - return error.ExpectedStringTerm; - } - - fn date(parser: *Parser) !u64 { - const year = try parser.number(i32); - - try parser.expect("-"); - - const month = try parser.number(u8); - if (month < 1 or month > 12) return error.MonthOutOfRange; - - try parser.expect("-"); - - const day = try parser.number(u8); - if (!Date.isDayMonthYearValid(i32, year, month, day)) return error.InvalidDayMonthYearCombination; - - try parser.expect("T"); - - const hour = try parser.number(u8); - if (hour > 23) return error.HoyrOutOfRange; - - try parser.expect(":"); - - const minute = try parser.number(u8); - if (minute > 59) return error.MinuteOutOfRange; - - try parser.expect(":"); - - const second = try parser.number(u8); - if (second > 59) return error.SecondOutOfRange; - - try parser.expect("Z"); - - const d: Date = .{ - .year = year, - .month = month, - .day = day, - .hour = hour, - .minute = minute, - .second = second, - .nanosecond = 0, - .utc_offset = 0, - }; - - return d.unixEpoch(); - } - - fn number(parser: *Parser, comptime T: type) !T { - const start = parser.offset; - - for (parser.rest()) |c| { - if (ziglyph.isAsciiDigit(c)) { - parser.offset += 1; - continue; - } - - break; - } - - const text = parser.input[start..parser.offset]; - - return try std.fmt.parseInt(T, text, 10); - } - - fn boolean(parser: *Parser) !bool { - if (parser.startsWithConsuming("true")) return true; - - if (parser.startsWithConsuming("false")) return false; - - return error.ExpectedBooleanTerm; - } - pub fn policy(parser: *Parser) !Policy { const kind: Policy.Kind = if (parser.startsWithConsuming("allow if")) .allow @@ -462,6 +360,123 @@ pub const Parser = struct { return .{ .predicates = predicates, .expressions = expressions, .scopes = scps }; } + fn variable(parser: *Parser) ![]const u8 { + try parser.expect("$"); + + const start = parser.offset; + + for (parser.rest()) |c| { + if (ziglyph.isAlphaNum(c) or c == '_') { + parser.offset += 1; + continue; + } + + break; + } + + return parser.input[start..parser.offset]; + } + + // FIXME: properly implement string parsing + fn string(parser: *Parser) ![]const u8 { + try parser.expect("\""); + + const start = parser.offset; + + while (parser.peek()) |peeked| { + defer parser.offset += 1; + if (peeked == '"') { + return parser.input[start..parser.offset]; + } + } + + return error.ExpectedStringTerm; + } + + fn date(parser: *Parser) !u64 { + const year = try parser.number(i32); + + try parser.expect("-"); + + const month = try parser.number(u8); + if (month < 1 or month > 12) return error.MonthOutOfRange; + + try parser.expect("-"); + + const day = try parser.number(u8); + if (!Date.isDayMonthYearValid(i32, year, month, day)) return error.InvalidDayMonthYearCombination; + + try parser.expect("T"); + + const hour = try parser.number(u8); + if (hour > 23) return error.HoyrOutOfRange; + + try parser.expect(":"); + + const minute = try parser.number(u8); + if (minute > 59) return error.MinuteOutOfRange; + + try parser.expect(":"); + + const second = try parser.number(u8); + if (second > 59) return error.SecondOutOfRange; + + try parser.expect("Z"); + + const d: Date = .{ + .year = year, + .month = month, + .day = day, + .hour = hour, + .minute = minute, + .second = second, + .nanosecond = 0, + .utc_offset = 0, + }; + + return d.unixEpoch(); + } + + fn number(parser: *Parser, comptime T: type) !T { + const start = parser.offset; + + for (parser.rest()) |c| { + if (ziglyph.isAsciiDigit(c)) { + parser.offset += 1; + continue; + } + + break; + } + + const text = parser.input[start..parser.offset]; + + return try std.fmt.parseInt(T, text, 10); + } + + fn boolean(parser: *Parser) !bool { + if (parser.startsWithConsuming("true")) return true; + + if (parser.startsWithConsuming("false")) return false; + + return error.ExpectedBooleanTerm; + } + + fn hex(parser: *Parser) ![]const u8 { + const start = parser.offset; + + for (parser.rest()) |c| { + if (ziglyph.isHexDigit(c)) { + parser.offset += 1; + continue; + } + + break; + } + + return parser.input[start..parser.offset]; + } + fn expression(parser: *Parser) ParserError!Expression { parser.skipWhiteSpace(); const e = try parser.expr(); @@ -544,6 +559,13 @@ pub const Parser = struct { return e; } + fn binaryOp2(parser: *Parser) ParserError!Expression.BinaryOp { + if (parser.startsWithConsuming("+")) return .add; + if (parser.startsWithConsuming("-")) return .sub; + + return error.UnexpectedOp; + } + fn expr3(parser: *Parser) ParserError!Expression { var e = try parser.expr4(); @@ -563,6 +585,12 @@ pub const Parser = struct { return e; } + fn binaryOp3(parser: *Parser) ParserError!Expression.BinaryOp { + if (parser.startsWithConsuming("^")) return .bitwise_xor; + + return error.UnexpectedOp; + } + fn expr4(parser: *Parser) ParserError!Expression { var e = try parser.expr5(); @@ -582,6 +610,15 @@ pub const Parser = struct { return e; } + fn binaryOp4(parser: *Parser) ParserError!Expression.BinaryOp { + if (parser.startsWith("|") and !parser.startsWith("||")) { + try parser.expect("|"); + return .bitwise_or; + } + + return error.UnexpectedOp; + } + fn expr5(parser: *Parser) ParserError!Expression { var e = try parser.expr6(); @@ -601,6 +638,15 @@ pub const Parser = struct { return e; } + fn binaryOp5(parser: *Parser) ParserError!Expression.BinaryOp { + if (parser.startsWith("&") and !parser.startsWith("&&")) { + try parser.expect("&"); + return .bitwise_and; + } + + return error.UnexpectedOp; + } + fn expr6(parser: *Parser) ParserError!Expression { var e = try parser.expr7(); @@ -620,6 +666,13 @@ pub const Parser = struct { return e; } + fn binaryOp6(parser: *Parser) ParserError!Expression.BinaryOp { + if (parser.startsWithConsuming("*")) return .mul; + if (parser.startsWithConsuming("/")) return .div; + + return error.UnexpectedOp; + } + fn expr7(parser: *Parser) ParserError!Expression { const e1 = try parser.exprTerm(); @@ -646,44 +699,6 @@ pub const Parser = struct { return try Expression.binary(parser.allocator, op, e1, e2); } - fn binaryOp2(parser: *Parser) ParserError!Expression.BinaryOp { - if (parser.startsWithConsuming("+")) return .add; - if (parser.startsWithConsuming("-")) return .sub; - - return error.UnexpectedOp; - } - - fn binaryOp3(parser: *Parser) ParserError!Expression.BinaryOp { - if (parser.startsWithConsuming("^")) return .bitwise_xor; - - return error.UnexpectedOp; - } - - fn binaryOp4(parser: *Parser) ParserError!Expression.BinaryOp { - if (parser.startsWith("|") and !parser.startsWith("||")) { - try parser.expect("|"); - return .bitwise_or; - } - - return error.UnexpectedOp; - } - - fn binaryOp5(parser: *Parser) ParserError!Expression.BinaryOp { - if (parser.startsWith("&") and !parser.startsWith("&&")) { - try parser.expect("&"); - return .bitwise_and; - } - - return error.UnexpectedOp; - } - - fn binaryOp6(parser: *Parser) ParserError!Expression.BinaryOp { - if (parser.startsWithConsuming("*")) return .mul; - if (parser.startsWithConsuming("/")) return .div; - - return error.UnexpectedOp; - } - fn binaryOp7(parser: *Parser) ParserError!Expression.BinaryOp { if (parser.startsWithConsuming("contains")) return .contains; if (parser.startsWithConsuming("starts_with")) return .prefix; @@ -845,12 +860,16 @@ pub const Parser = struct { /// Returns true if the remaining parser input starts with str /// /// Does not consume any input. + /// + /// See also `fn startsWithConsuming` fn startsWith(parser: *Parser, str: []const u8) bool { return std.mem.startsWith(u8, parser.rest(), str); } /// Returns true if the remaining parse input starts with str. If /// it does start with that string, the parser consumes the string. + /// + /// See also `fn startsWith` fn startsWithConsuming(parser: *Parser, str: []const u8) bool { if (parser.startsWith(str)) { parser.offset += str.len; // Consume @@ -866,21 +885,6 @@ pub const Parser = struct { if (peeked == char) return error.DisallowedChar; } - fn hex(parser: *Parser) ![]const u8 { - const start = parser.offset; - - for (parser.rest()) |c| { - if (ziglyph.isHexDigit(c)) { - parser.offset += 1; - continue; - } - - break; - } - - return parser.input[start..parser.offset]; - } - // FIXME: this should error? fn readName(parser: *Parser) []const u8 { const start = parser.offset; From 5b40aabc2f7bc8153fd7b3ae5a28a00950b540e0 Mon Sep 17 00:00:00 2001 From: Malcolm Still Date: Sat, 30 Mar 2024 17:08:44 +0000 Subject: [PATCH 10/52] Rename fn expect -> fn consume --- biscuit-parser/src/parser.zig | 65 ++++++++++++++++++----------------- 1 file changed, 33 insertions(+), 32 deletions(-) diff --git a/biscuit-parser/src/parser.zig b/biscuit-parser/src/parser.zig index a7f0b3e..c2e213d 100644 --- a/biscuit-parser/src/parser.zig +++ b/biscuit-parser/src/parser.zig @@ -33,7 +33,7 @@ pub const Parser = struct { parser.skipWhiteSpace(); // Consume left paren - try parser.expect("("); + try parser.consume("("); // Parse terms var terms = std.ArrayList(Term).init(parser.allocator); @@ -49,7 +49,7 @@ pub const Parser = struct { } } - try parser.expect(")"); + try parser.consume(")"); return .{ .name = name, .terms = terms }; } @@ -192,7 +192,7 @@ pub const Parser = struct { parser.skipWhiteSpace(); // Consume left paren - try parser.expect("("); + try parser.consume("("); // Parse terms var terms = std.ArrayList(Term).init(parser.allocator); @@ -211,7 +211,7 @@ pub const Parser = struct { break; } - try parser.expect(")"); + try parser.consume(")"); return .{ .name = name, .terms = terms }; } @@ -260,7 +260,7 @@ pub const Parser = struct { if (!parser.startsWith("or")) break; - try parser.expect("or"); + try parser.consume("or"); const body = try parser.ruleBody(); @@ -281,7 +281,7 @@ pub const Parser = struct { parser.skipWhiteSpace(); - try parser.expect("<-"); + try parser.consume("<-"); const body = try parser.ruleBody(); @@ -361,7 +361,7 @@ pub const Parser = struct { } fn variable(parser: *Parser) ![]const u8 { - try parser.expect("$"); + try parser.consume("$"); const start = parser.offset; @@ -379,7 +379,7 @@ pub const Parser = struct { // FIXME: properly implement string parsing fn string(parser: *Parser) ![]const u8 { - try parser.expect("\""); + try parser.consume("\""); const start = parser.offset; @@ -396,32 +396,32 @@ pub const Parser = struct { fn date(parser: *Parser) !u64 { const year = try parser.number(i32); - try parser.expect("-"); + try parser.consume("-"); const month = try parser.number(u8); if (month < 1 or month > 12) return error.MonthOutOfRange; - try parser.expect("-"); + try parser.consume("-"); const day = try parser.number(u8); if (!Date.isDayMonthYearValid(i32, year, month, day)) return error.InvalidDayMonthYearCombination; - try parser.expect("T"); + try parser.consume("T"); const hour = try parser.number(u8); if (hour > 23) return error.HoyrOutOfRange; - try parser.expect(":"); + try parser.consume(":"); const minute = try parser.number(u8); if (minute > 59) return error.MinuteOutOfRange; - try parser.expect(":"); + try parser.consume(":"); const second = try parser.number(u8); if (second > 59) return error.SecondOutOfRange; - try parser.expect("Z"); + try parser.consume("Z"); const d: Date = .{ .year = year, @@ -612,7 +612,7 @@ pub const Parser = struct { fn binaryOp4(parser: *Parser) ParserError!Expression.BinaryOp { if (parser.startsWith("|") and !parser.startsWith("||")) { - try parser.expect("|"); + try parser.consume("|"); return .bitwise_or; } @@ -640,7 +640,7 @@ pub const Parser = struct { fn binaryOp5(parser: *Parser) ParserError!Expression.BinaryOp { if (parser.startsWith("&") and !parser.startsWith("&&")) { - try parser.expect("&"); + try parser.consume("&"); return .bitwise_and; } @@ -679,12 +679,12 @@ pub const Parser = struct { parser.skipWhiteSpace(); if (!parser.startsWith(".")) return e1; - try parser.expect("."); + try parser.consume("."); const op = try parser.binaryOp7(); parser.skipWhiteSpace(); - try parser.expect("("); + try parser.consume("("); parser.skipWhiteSpace(); @@ -692,7 +692,7 @@ pub const Parser = struct { parser.skipWhiteSpace(); - try parser.expect(")"); + try parser.consume(")"); parser.skipWhiteSpace(); @@ -731,7 +731,7 @@ pub const Parser = struct { if (parser.peek()) |c| { if (c == '!') { - try parser.expect("!"); + try parser.consume("!"); parser.skipWhiteSpace(); const e = try parser.expr(); @@ -753,7 +753,7 @@ pub const Parser = struct { parser.skipWhiteSpace(); } - if (parser.expect(".length()")) |_| { + if (parser.consume(".length()")) |_| { return try Expression.unary(parser.allocator, .length, e); } else |_| { return error.UnexpectedToken; @@ -763,7 +763,7 @@ pub const Parser = struct { } fn unaryParens(parser: *Parser) ParserError!Expression { - try parser.expect("("); + try parser.consume("("); parser.skipWhiteSpace(); @@ -771,13 +771,13 @@ pub const Parser = struct { parser.skipWhiteSpace(); - try parser.expect(")"); + try parser.consume(")"); return try Expression.unary(parser.allocator, .parens, e); } fn scopes(parser: *Parser, allocator: std.mem.Allocator) !std.ArrayList(Scope) { - try parser.expect("trusting"); + try parser.consume("trusting"); parser.skipWhiteSpace(); @@ -794,7 +794,7 @@ pub const Parser = struct { if (!parser.startsWith(",")) break; - try parser.expect(","); + try parser.consume(","); } return scps; @@ -804,23 +804,23 @@ pub const Parser = struct { parser.skipWhiteSpace(); if (parser.startsWith("authority")) { - try parser.expect("authority"); + try parser.consume("authority"); return .{ .authority = {} }; } if (parser.startsWith("previous")) { - try parser.expect("previous"); + try parser.consume("previous"); return .{ .previous = {} }; } if (parser.startsWith("{")) { - try parser.expect("{"); + try parser.consume("{"); const parameter = parser.readName(); - try parser.expect("}"); + try parser.consume("}"); return .{ .parameter = parameter }; } @@ -829,7 +829,7 @@ pub const Parser = struct { } fn publicKey(parser: *Parser) !Ed25519.PublicKey { - try parser.expect("ed25519/"); + try parser.consume("ed25519/"); const h = try parser.hex(); @@ -850,8 +850,9 @@ pub const Parser = struct { return parser.input[parser.offset..]; } - /// Expect and consume string. - fn expect(parser: *Parser, str: []const u8) !void { + /// Expect and consume string. Return error.UnexpectedString if + /// str is not the start of remaining parser input. + fn consume(parser: *Parser, str: []const u8) !void { if (!std.mem.startsWith(u8, parser.rest(), str)) return error.UnexpectedString; parser.offset += str.len; From 694cc4bd840c70d57ff4548b16d1c0b55b464bac Mon Sep 17 00:00:00 2001 From: Malcolm Still Date: Sat, 30 Mar 2024 17:09:49 +0000 Subject: [PATCH 11/52] More cleanup --- biscuit-parser/src/parser.zig | 1 + 1 file changed, 1 insertion(+) diff --git a/biscuit-parser/src/parser.zig b/biscuit-parser/src/parser.zig index c2e213d..9057c98 100644 --- a/biscuit-parser/src/parser.zig +++ b/biscuit-parser/src/parser.zig @@ -828,6 +828,7 @@ pub const Parser = struct { return .{ .public_key = try parser.publicKey() }; } + /// Parser a public key. Currently only supports ed25519. fn publicKey(parser: *Parser) !Ed25519.PublicKey { try parser.consume("ed25519/"); From d2cb81ccd5d34144263a6235114c68acec2fcc72 Mon Sep 17 00:00:00 2001 From: Malcolm Still Date: Sat, 30 Mar 2024 17:16:55 +0000 Subject: [PATCH 12/52] More clean up --- biscuit-builder/src/check.zig | 14 ++++++++++++-- biscuit-parser/build.zig | 9 --------- biscuit-parser/build.zig.zon | 1 - biscuit-parser/src/parser.zig | 6 ++---- 4 files changed, 14 insertions(+), 16 deletions(-) diff --git a/biscuit-builder/src/check.zig b/biscuit-builder/src/check.zig index b4d395a..963fe1d 100644 --- a/biscuit-builder/src/check.zig +++ b/biscuit-builder/src/check.zig @@ -5,9 +5,14 @@ const Term = @import("term.zig").Term; const Rule = @import("rule.zig").Rule; pub const Check = struct { - kind: datalog.Check.Kind, + kind: Kind, queries: std.ArrayList(Rule), + pub const Kind = enum { + one, + all, + }; + pub fn deinit(_: Check) void { // for (check.queries.items) |query| { // query.deinit(); @@ -23,7 +28,12 @@ pub const Check = struct { try queries.append(try query.toDatalog(allocator, symbols)); } - return .{ .kind = check.kind, .queries = queries }; + const kind: datalog.Check.Kind = switch (check.kind) { + .one => .one, + .all => .all, + }; + + return .{ .kind = kind, .queries = queries }; } pub fn format(check: Check, comptime _: []const u8, _: std.fmt.FormatOptions, writer: anytype) !void { diff --git a/biscuit-parser/build.zig b/biscuit-parser/build.zig index e5e89f2..686f2cb 100644 --- a/biscuit-parser/build.zig +++ b/biscuit-parser/build.zig @@ -16,18 +16,12 @@ pub fn build(b: *std.Build) void { const optimize = b.standardOptimizeOption(.{}); const ziglyph = b.dependency("ziglyph", .{ .optimize = optimize, .target = target }); - const schema = b.dependency("biscuit-schema", .{ .target = target, .optimize = optimize }); - const format = b.dependency("biscuit-format", .{ .target = target, .optimize = optimize }); const builder = b.dependency("biscuit-builder", .{ .target = target, .optimize = optimize }); - const datalog = b.dependency("biscuit-datalog", .{ .target = target, .optimize = optimize }); _ = b.addModule("biscuit-parser", .{ .root_source_file = .{ .path = "src/parser.zig" }, .imports = &.{ - .{ .name = "biscuit-schema", .module = schema.module("biscuit-schema") }, - .{ .name = "biscuit-format", .module = format.module("biscuit-format") }, .{ .name = "biscuit-builder", .module = builder.module("biscuit-builder") }, - .{ .name = "biscuit-datalog", .module = datalog.module("biscuit-datalog") }, .{ .name = "ziglyph", .module = ziglyph.module("ziglyph") }, }, }); @@ -39,10 +33,7 @@ pub fn build(b: *std.Build) void { .target = target, .optimize = optimize, }); - lib_unit_tests.root_module.addImport("biscuit-schema", schema.module("biscuit-schema")); - lib_unit_tests.root_module.addImport("biscuit-format", format.module("biscuit-format")); lib_unit_tests.root_module.addImport("biscuit-builder", builder.module("biscuit-builder")); - lib_unit_tests.root_module.addImport("biscuit-datalog", datalog.module("biscuit-datalog")); lib_unit_tests.root_module.addImport("ziglyph", ziglyph.module("ziglyph")); const run_lib_unit_tests = b.addRunArtifact(lib_unit_tests); diff --git a/biscuit-parser/build.zig.zon b/biscuit-parser/build.zig.zon index 54f2748..eb581fc 100644 --- a/biscuit-parser/build.zig.zon +++ b/biscuit-parser/build.zig.zon @@ -41,7 +41,6 @@ .@"biscuit-schema" = .{ .path = "../biscuit-schema" }, .@"biscuit-format" = .{ .path = "../biscuit-format" }, .@"biscuit-builder" = .{ .path = "../biscuit-builder" }, - .@"biscuit-datalog" = .{ .path = "../biscuit-datalog" }, .ziglyph = .{ .url = "https://codeberg.org/dude_the_builder/ziglyph/archive/947ed39203bf90412e3d16cbcf936518b6f23af0.tar.gz", .hash = "12208b23d1eb6dcb929e85346524db8f8b8aa1401bdf8a97dee1e0cfb55da8d5fb42", diff --git a/biscuit-parser/src/parser.zig b/biscuit-parser/src/parser.zig index 9057c98..e0e23db 100644 --- a/biscuit-parser/src/parser.zig +++ b/biscuit-parser/src/parser.zig @@ -1,6 +1,5 @@ const std = @import("std"); const ziglyph = @import("ziglyph"); -const datalog = @import("biscuit-datalog"); const Term = @import("biscuit-builder").Term; const Fact = @import("biscuit-builder").Fact; const Check = @import("biscuit-builder").Check; @@ -32,12 +31,11 @@ pub const Parser = struct { parser.skipWhiteSpace(); - // Consume left paren try parser.consume("("); - // Parse terms var terms = std.ArrayList(Term).init(parser.allocator); + // Parse terms var it = parser.factTermsIterator(); while (try it.next()) |trm| { try terms.append(trm); @@ -230,7 +228,7 @@ pub const Parser = struct { } pub fn check(parser: *Parser) !Check { - const kind: datalog.Check.Kind = if (parser.startsWithConsuming("check if")) + const kind: Check.Kind = if (parser.startsWithConsuming("check if")) .one else if (parser.startsWithConsuming("check all")) .all From bc6ad3b4c9d3db9e1f72783cd63dc217739449d3 Mon Sep 17 00:00:00 2001 From: Malcolm Still Date: Sat, 30 Mar 2024 17:40:26 +0000 Subject: [PATCH 13/52] Split out authorize() functions --- biscuit/src/authorizer.zig | 327 +++++++++++++++++++------------------ 1 file changed, 172 insertions(+), 155 deletions(-) diff --git a/biscuit/src/authorizer.zig b/biscuit/src/authorizer.zig index 984ca4f..824fa05 100644 --- a/biscuit/src/authorizer.zig +++ b/biscuit/src/authorizer.zig @@ -136,109 +136,122 @@ pub const Authorizer = struct { log.debug("Starting authorize()", .{}); defer log.debug("Finished authorize()", .{}); - log.debug("authorizer public keys:", .{}); - for (authorizer.symbols.public_keys.items, 0..) |pk, i| { - log.debug(" [{}]: {x}", .{ i, pk.bytes }); - } + try authorizer.addBiscuitFactsAndRules(errors); // 1. Add tokens facts and rules to authorizer's world + try authorizer.generateFacts(); // 2. Run the world to generate all facts + try authorizer.authorizerChecks(errors); // 3. Run checks that have been added to this authorizer + try authorizer.authorityChecks(errors); // 4. Run checks in the biscuit's authority block + const allowed_policy_id: ?usize = try authorizer.authorizerPolicies(errors); // 5. run policies from the authorizer + try authorizer.blockChecks(errors); // 6. Run checks in the biscuit's other blocks - { - var it = authorizer.public_key_to_block_id.iterator(); - while (it.next()) |entry| { - log.debug("public_key_to_block_id: public key id = {}, block_ids = {any}", .{ entry.key_ptr.*, entry.value_ptr.items }); - } + if (allowed_policy_id) |policy_id| { + if (errors.items.len == 0) return policy_id; } - // 1. + return error.AuthorizationFailed; + } + + /// Step 1 in authorize() + /// + /// This should only be called from authorize() + fn addBiscuitFactsAndRules(authorizer: *Authorizer, errors: *std.ArrayList(AuthorizerError)) !void { // Load facts and rules from authority block into world. Our block's facts - // will have a particular symbol table that we map into the symvol table + // will have a particular symbol table that we map into the symbol table // of the world. // // For example, the token may have a string "user123" which has id 12. But // when mapped into the world it may have id 5. - if (authorizer.biscuit) |biscuit| { - log.debug("biscuit token public keys:", .{}); - for (biscuit.symbols.public_keys.items, 0..) |pk, i| { - log.debug(" [{}]: {x}", .{ i, pk.bytes }); + const biscuit = authorizer.biscuit orelse return; + + // Add authority block's facts + for (biscuit.authority.facts.items) |authority_fact| { + const fact = try authority_fact.remap(&biscuit.symbols, &authorizer.symbols); + const origin = try Origin.initWithId(authorizer.arena, 0); + + try authorizer.world.addFact(origin, fact); + } + + const authority_trusted_origins = try TrustedOrigins.fromScopes( + authorizer.arena, + biscuit.authority.scopes.items, + try TrustedOrigins.defaultOrigins(authorizer.arena), + 0, + authorizer.public_key_to_block_id, + ); + + // Add authority block's rules + for (biscuit.authority.rules.items) |authority_rule| { + // Map from biscuit symbol space to authorizer symbol space + const rule = try authority_rule.remap(&biscuit.symbols, &authorizer.symbols); + + if (!rule.validateVariables()) { + try errors.append(.unbound_variable); } - for (biscuit.authority.facts.items) |authority_fact| { - const fact = try authority_fact.remap(&biscuit.symbols, &authorizer.symbols); - const origin = try Origin.initWithId(authorizer.arena, 0); + + // A authority block's rule trusts + const rule_trusted_origins = try TrustedOrigins.fromScopes( + authorizer.arena, + rule.scopes.items, + authority_trusted_origins, + 0, + authorizer.public_key_to_block_id, + ); + + try authorizer.world.addRule(0, rule_trusted_origins, rule); + } + + for (biscuit.blocks.items, 1..) |block, block_id| { + // Add block facts + for (block.facts.items) |block_fact| { + const fact = try block_fact.remap(&biscuit.symbols, &authorizer.symbols); + const origin = try Origin.initWithId(authorizer.arena, block_id); try authorizer.world.addFact(origin, fact); } - const authority_trusted_origins = try TrustedOrigins.fromScopes( + const block_trusted_origins = try TrustedOrigins.fromScopes( authorizer.arena, - biscuit.authority.scopes.items, + block.scopes.items, try TrustedOrigins.defaultOrigins(authorizer.arena), - 0, + block_id, authorizer.public_key_to_block_id, ); - for (biscuit.authority.rules.items) |authority_rule| { - // Map from biscuit symbol space to authorizer symbol space - const rule = try authority_rule.remap(&biscuit.symbols, &authorizer.symbols); + // Add block rules + for (block.rules.items) |block_rule| { + const rule = try block_rule.remap(&biscuit.symbols, &authorizer.symbols); + log.debug("block rule {any} CONVERTED to rule = {any}", .{ block_rule, rule }); if (!rule.validateVariables()) { try errors.append(.unbound_variable); } - // A authority block's rule trusts - const rule_trusted_origins = try TrustedOrigins.fromScopes( + const block_rule_trusted_origins = try TrustedOrigins.fromScopes( authorizer.arena, rule.scopes.items, - authority_trusted_origins, - 0, - authorizer.public_key_to_block_id, - ); - - try authorizer.world.addRule(0, rule_trusted_origins, rule); - } - - for (biscuit.blocks.items, 1..) |block, block_id| { - for (block.facts.items) |block_fact| { - const fact = try block_fact.remap(&biscuit.symbols, &authorizer.symbols); - const origin = try Origin.initWithId(authorizer.arena, block_id); - - try authorizer.world.addFact(origin, fact); - } - - const block_trusted_origins = try TrustedOrigins.fromScopes( - authorizer.arena, - block.scopes.items, - try TrustedOrigins.defaultOrigins(authorizer.arena), + block_trusted_origins, block_id, authorizer.public_key_to_block_id, ); - for (block.rules.items) |block_rule| { - const rule = try block_rule.remap(&biscuit.symbols, &authorizer.symbols); - log.debug("block rule {any} CONVERTED to rule = {any}", .{ block_rule, rule }); - - if (!rule.validateVariables()) { - try errors.append(.unbound_variable); - } - - const block_rule_trusted_origins = try TrustedOrigins.fromScopes( - authorizer.arena, - rule.scopes.items, - block_trusted_origins, - block_id, - authorizer.public_key_to_block_id, - ); - - try authorizer.world.addRule(block_id, block_rule_trusted_origins, rule); - } + try authorizer.world.addRule(block_id, block_rule_trusted_origins, rule); } } + } - // 2. Run the world to generate all facts + /// Step 2 in authorize() + /// + /// Generate all new facts by running world. + fn generateFacts(authorizer: *Authorizer) !void { log.debug("Run world", .{}); + defer log.debug("Finished running world", .{}); + try authorizer.world.run(&authorizer.symbols); - log.debug("Finished running world", .{}); + } - // 3. Run checks that have been added to this authorizer + fn authorizerChecks(authorizer: *Authorizer, errors: *std.ArrayList(AuthorizerError)) !void { log.debug("AUTHORIZER CHECKS", .{}); + defer log.debug("END AUTHORIZER CHECKS", .{}); + for (authorizer.checks.items) |c| { log.debug("authorizer check = {any}", .{c}); const check = try c.toDatalog(authorizer.arena, &authorizer.symbols); @@ -261,120 +274,124 @@ pub const Authorizer = struct { log.debug("match {any} = {}", .{ query, is_match }); } } - log.debug("END AUTHORIZER CHECKS", .{}); + } - // 4. Run checks in the biscuit's authority block - if (authorizer.biscuit) |biscuit| { - const authority_trusted_origins = try TrustedOrigins.fromScopes( - authorizer.arena, - biscuit.authority.scopes.items, - try TrustedOrigins.defaultOrigins(authorizer.arena), - 0, - authorizer.public_key_to_block_id, - ); + /// Step 4 in authorize() + /// + /// Run checks from token's authority block + fn authorityChecks(authorizer: *Authorizer, errors: *std.ArrayList(AuthorizerError)) !void { + const biscuit = authorizer.biscuit orelse return; - for (biscuit.authority.checks.items, 0..) |c, check_id| { - const check = try c.remap(&biscuit.symbols, &authorizer.symbols); - log.debug("{}: {any}", .{ check_id, check }); + const authority_trusted_origins = try TrustedOrigins.fromScopes( + authorizer.arena, + biscuit.authority.scopes.items, + try TrustedOrigins.defaultOrigins(authorizer.arena), + 0, + authorizer.public_key_to_block_id, + ); - for (check.queries.items) |*query| { - const rule_trusted_origins = try TrustedOrigins.fromScopes( - authorizer.arena, - query.scopes.items, - authority_trusted_origins, - 0, - authorizer.public_key_to_block_id, - ); + for (biscuit.authority.checks.items, 0..) |c, check_id| { + const check = try c.remap(&biscuit.symbols, &authorizer.symbols); + log.debug("{}: {any}", .{ check_id, check }); - const is_match = switch (check.kind) { - .one => try authorizer.world.queryMatch(query, &authorizer.symbols, rule_trusted_origins), - .all => try authorizer.world.queryMatchAll(query, &authorizer.symbols, rule_trusted_origins), - }; + for (check.queries.items) |*query| { + const rule_trusted_origins = try TrustedOrigins.fromScopes( + authorizer.arena, + query.scopes.items, + authority_trusted_origins, + 0, + authorizer.public_key_to_block_id, + ); - if (!is_match) try errors.append(.{ .failed_block_check = .{ .block_id = 0, .check_id = check_id } }); - log.debug("match {any} = {}", .{ query, is_match }); - } + const is_match = switch (check.kind) { + .one => try authorizer.world.queryMatch(query, &authorizer.symbols, rule_trusted_origins), + .all => try authorizer.world.queryMatchAll(query, &authorizer.symbols, rule_trusted_origins), + }; + + if (!is_match) try errors.append(.{ .failed_block_check = .{ .block_id = 0, .check_id = check_id } }); + log.debug("match {any} = {}", .{ query, is_match }); } } + } - // 5. run policies from the authorizer - const allowed_policy_id: ?usize = policy_blk: { - for (authorizer.policies.items) |policy| { - log.debug("testing policy {any}", .{policy}); + /// Step 5 in authorize() + /// + /// Run authorizer's policies. + fn authorizerPolicies(authorizer: *Authorizer, errors: *std.ArrayList(AuthorizerError)) !?usize { + for (authorizer.policies.items) |policy| { + log.debug("testing policy {any}", .{policy}); - for (policy.queries.items, 0..) |*q, policy_id| { - var query = try q.toDatalog(authorizer.arena, &authorizer.symbols); + for (policy.queries.items, 0..) |*q, policy_id| { + var query = try q.toDatalog(authorizer.arena, &authorizer.symbols); - const rule_trusted_origins = try TrustedOrigins.fromScopes( - authorizer.arena, - query.scopes.items, - try authorizer.authorizerTrustedOrigins(), - Origin.AUTHORIZER_ID, - authorizer.public_key_to_block_id, - ); + const rule_trusted_origins = try TrustedOrigins.fromScopes( + authorizer.arena, + query.scopes.items, + try authorizer.authorizerTrustedOrigins(), + Origin.AUTHORIZER_ID, + authorizer.public_key_to_block_id, + ); - const is_match = try authorizer.world.queryMatch(&query, &authorizer.symbols, rule_trusted_origins); - log.debug("match {any} = {}", .{ query, is_match }); + const is_match = try authorizer.world.queryMatch(&query, &authorizer.symbols, rule_trusted_origins); + log.debug("match {any} = {}", .{ query, is_match }); - if (is_match) { - switch (policy.kind) { - .allow => break :policy_blk policy_id, - .deny => { - try errors.append(.{ .denied_by_policy = .{ .deny_policy_id = policy_id } }); - break :policy_blk null; - }, - } + if (is_match) { + switch (policy.kind) { + .allow => return policy_id, + .deny => { + try errors.append(.{ .denied_by_policy = .{ .deny_policy_id = policy_id } }); + return null; + }, } } } + } - try errors.append(.{ .no_matching_policy = {} }); - break :policy_blk null; - }; + try errors.append(.{ .no_matching_policy = {} }); - // 6. Run checks in the biscuit's other blocks - if (authorizer.biscuit) |biscuit| { - for (biscuit.blocks.items, 1..) |block, block_id| { - const block_trusted_origins = try TrustedOrigins.fromScopes( - authorizer.arena, - block.scopes.items, - try TrustedOrigins.defaultOrigins(authorizer.arena), - block_id, - authorizer.public_key_to_block_id, - ); + return null; + } - for (block.checks.items, 0..) |c, check_id| { - const check = try c.remap(&biscuit.symbols, &authorizer.symbols); + /// Step 6 in authorize() + /// + /// Run checks in all other blocks. + fn blockChecks(authorizer: *Authorizer, errors: *std.ArrayList(AuthorizerError)) !void { + const biscuit = authorizer.biscuit orelse return; - log.debug("check = {any}", .{check}); + for (biscuit.blocks.items, 1..) |block, block_id| { + const block_trusted_origins = try TrustedOrigins.fromScopes( + authorizer.arena, + block.scopes.items, + try TrustedOrigins.defaultOrigins(authorizer.arena), + block_id, + authorizer.public_key_to_block_id, + ); - for (check.queries.items) |*query| { - const rule_trusted_origins = try TrustedOrigins.fromScopes( - authorizer.arena, - query.scopes.items, - block_trusted_origins, - block_id, - authorizer.public_key_to_block_id, - ); + for (block.checks.items, 0..) |c, check_id| { + const check = try c.remap(&biscuit.symbols, &authorizer.symbols); - const is_match = switch (check.kind) { - .one => try authorizer.world.queryMatch(query, &authorizer.symbols, rule_trusted_origins), - .all => try authorizer.world.queryMatchAll(query, &authorizer.symbols, rule_trusted_origins), - }; + log.debug("check = {any}", .{check}); - if (!is_match) try errors.append(.{ .failed_block_check = .{ .block_id = block_id, .check_id = check_id } }); + for (check.queries.items) |*query| { + const rule_trusted_origins = try TrustedOrigins.fromScopes( + authorizer.arena, + query.scopes.items, + block_trusted_origins, + block_id, + authorizer.public_key_to_block_id, + ); - log.debug("match {any} = {}", .{ query, is_match }); - } + const is_match = switch (check.kind) { + .one => try authorizer.world.queryMatch(query, &authorizer.symbols, rule_trusted_origins), + .all => try authorizer.world.queryMatchAll(query, &authorizer.symbols, rule_trusted_origins), + }; + + if (!is_match) try errors.append(.{ .failed_block_check = .{ .block_id = block_id, .check_id = check_id } }); + + log.debug("match {any} = {}", .{ query, is_match }); } } } - - if (allowed_policy_id) |policy_id| { - if (errors.items.len == 0) return policy_id; - } - - return error.AuthorizationFailed; } }; From 33fd7ef6027a25fe8ac9a0d6cac1e785837d925b Mon Sep 17 00:00:00 2001 From: Malcolm Still Date: Sat, 30 Mar 2024 17:45:20 +0000 Subject: [PATCH 14/52] More clean up --- biscuit/src/authorizer.zig | 122 ++++++++++++++++++------------------- 1 file changed, 61 insertions(+), 61 deletions(-) diff --git a/biscuit/src/authorizer.zig b/biscuit/src/authorizer.zig index 824fa05..8a9d548 100644 --- a/biscuit/src/authorizer.zig +++ b/biscuit/src/authorizer.zig @@ -76,67 +76,12 @@ pub const Authorizer = struct { // } } - pub fn authorizerTrustedOrigins(authorizer: *Authorizer) !TrustedOrigins { - return try TrustedOrigins.fromScopes( - authorizer.arena, - authorizer.scopes.items, - try TrustedOrigins.defaultOrigins(authorizer.arena), - Origin.AUTHORIZER_ID, - authorizer.public_key_to_block_id, - ); - } - - /// Add fact from string to authorizer - pub fn addFact(authorizer: *Authorizer, input: []const u8) !void { - log.debug("addFact = {s}", .{input}); - var parser = Parser.init(authorizer.arena, input); - - const fact = try parser.fact(); - - const origin = try Origin.initWithId(authorizer.arena, Origin.AUTHORIZER_ID); - - try authorizer.world.addFact(origin, try fact.toDatalog(authorizer.arena, &authorizer.symbols)); - } - - /// Add check from string to authorizer - pub fn addCheck(authorizer: *Authorizer, input: []const u8) !void { - log.debug("addCheck = {s}", .{input}); - var parser = Parser.init(authorizer.arena, input); - - const check = try parser.check(); - - try authorizer.checks.append(check); - } - - /// Add policy from string to authorizer - pub fn addPolicy(authorizer: *Authorizer, input: []const u8) !void { - log.debug("addPolicy = {s}", .{input}); - var parser = Parser.init(authorizer.arena, input); - - const policy = try parser.policy(); - - try authorizer.policies.append(policy); - } - - /// authorize - /// - /// authorize the Authorizer - /// - /// The following high-level steps take place during authorization: - /// 1. _biscuit_ (where it exists): load _all_ of the facts and rules - /// in the biscuit. We can add all the facts and rules as this time because - /// the facts and rules are scoped, i.e. the facts / rules are added to particular - /// scopes within the world. - /// 2. Run the world to generate new facts. - /// 3. _authorizer_: Run the _authorizer's_ checks - /// 4. _biscuit_ (where it exists): run the authority block's checks - /// 5. _authorizer_: Run the _authorizer's_ policies - /// 6. _biscuit_ (where it exists): run the checks from all the non-authority blocks + /// Authorize token with authorizer pub fn authorize(authorizer: *Authorizer, errors: *std.ArrayList(AuthorizerError)) !usize { log.debug("Starting authorize()", .{}); defer log.debug("Finished authorize()", .{}); - try authorizer.addBiscuitFactsAndRules(errors); // 1. Add tokens facts and rules to authorizer's world + try authorizer.addTokenFactsAndRules(errors); // 1. Add tokens facts and rules to authorizer's world try authorizer.generateFacts(); // 2. Run the world to generate all facts try authorizer.authorizerChecks(errors); // 3. Run checks that have been added to this authorizer try authorizer.authorityChecks(errors); // 4. Run checks in the biscuit's authority block @@ -152,8 +97,10 @@ pub const Authorizer = struct { /// Step 1 in authorize() /// - /// This should only be called from authorize() - fn addBiscuitFactsAndRules(authorizer: *Authorizer, errors: *std.ArrayList(AuthorizerError)) !void { + /// Adds all the facts and rules from the token to the authorizer's world. + /// + /// Note: this should only be called from authorize() + fn addTokenFactsAndRules(authorizer: *Authorizer, errors: *std.ArrayList(AuthorizerError)) !void { // Load facts and rules from authority block into world. Our block's facts // will have a particular symbol table that we map into the symbol table // of the world. @@ -248,9 +195,14 @@ pub const Authorizer = struct { try authorizer.world.run(&authorizer.symbols); } + /// Step 3 in authorize() + /// + /// Runs the authorizer's checks + /// + /// Note: should only be called from authorize() fn authorizerChecks(authorizer: *Authorizer, errors: *std.ArrayList(AuthorizerError)) !void { - log.debug("AUTHORIZER CHECKS", .{}); - defer log.debug("END AUTHORIZER CHECKS", .{}); + log.debug("Start authorizerChecks()", .{}); + defer log.debug("End authorizerChecks()", .{}); for (authorizer.checks.items) |c| { log.debug("authorizer check = {any}", .{c}); @@ -279,6 +231,8 @@ pub const Authorizer = struct { /// Step 4 in authorize() /// /// Run checks from token's authority block + /// + /// Note: should only be called from authorizer() fn authorityChecks(authorizer: *Authorizer, errors: *std.ArrayList(AuthorizerError)) !void { const biscuit = authorizer.biscuit orelse return; @@ -317,6 +271,8 @@ pub const Authorizer = struct { /// Step 5 in authorize() /// /// Run authorizer's policies. + /// + /// Note: should only be called from authorizer() fn authorizerPolicies(authorizer: *Authorizer, errors: *std.ArrayList(AuthorizerError)) !?usize { for (authorizer.policies.items) |policy| { log.debug("testing policy {any}", .{policy}); @@ -355,6 +311,8 @@ pub const Authorizer = struct { /// Step 6 in authorize() /// /// Run checks in all other blocks. + /// + /// Note: should only be called from authorizer() fn blockChecks(authorizer: *Authorizer, errors: *std.ArrayList(AuthorizerError)) !void { const biscuit = authorizer.biscuit orelse return; @@ -393,6 +351,48 @@ pub const Authorizer = struct { } } } + + pub fn authorizerTrustedOrigins(authorizer: *Authorizer) !TrustedOrigins { + return try TrustedOrigins.fromScopes( + authorizer.arena, + authorizer.scopes.items, + try TrustedOrigins.defaultOrigins(authorizer.arena), + Origin.AUTHORIZER_ID, + authorizer.public_key_to_block_id, + ); + } + + /// Add fact from string to authorizer + pub fn addFact(authorizer: *Authorizer, input: []const u8) !void { + log.debug("addFact = {s}", .{input}); + var parser = Parser.init(authorizer.arena, input); + + const fact = try parser.fact(); + + const origin = try Origin.initWithId(authorizer.arena, Origin.AUTHORIZER_ID); + + try authorizer.world.addFact(origin, try fact.toDatalog(authorizer.arena, &authorizer.symbols)); + } + + /// Add check from string to authorizer + pub fn addCheck(authorizer: *Authorizer, input: []const u8) !void { + log.debug("addCheck = {s}", .{input}); + var parser = Parser.init(authorizer.arena, input); + + const check = try parser.check(); + + try authorizer.checks.append(check); + } + + /// Add policy from string to authorizer + pub fn addPolicy(authorizer: *Authorizer, input: []const u8) !void { + log.debug("addPolicy = {s}", .{input}); + var parser = Parser.init(authorizer.arena, input); + + const policy = try parser.policy(); + + try authorizer.policies.append(policy); + } }; const AuthorizerErrorKind = enum(u8) { From 6900188003a37f01aed125d89439755d056c25d9 Mon Sep 17 00:00:00 2001 From: Malcolm Still Date: Sat, 30 Mar 2024 17:48:43 +0000 Subject: [PATCH 15/52] Clean up --- biscuit/src/authorizer.zig | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/biscuit/src/authorizer.zig b/biscuit/src/authorizer.zig index 8a9d548..a0d1470 100644 --- a/biscuit/src/authorizer.zig +++ b/biscuit/src/authorizer.zig @@ -77,16 +77,21 @@ pub const Authorizer = struct { } /// Authorize token with authorizer + /// + /// Will return without error if there is a matching `allow` policy id and the `errors` + /// list is empty. + /// + /// Otherwise will return error.AuthorizationFailed with the reason(s) in `errors` pub fn authorize(authorizer: *Authorizer, errors: *std.ArrayList(AuthorizerError)) !usize { log.debug("Starting authorize()", .{}); defer log.debug("Finished authorize()", .{}); - try authorizer.addTokenFactsAndRules(errors); // 1. Add tokens facts and rules to authorizer's world - try authorizer.generateFacts(); // 2. Run the world to generate all facts - try authorizer.authorizerChecks(errors); // 3. Run checks that have been added to this authorizer - try authorizer.authorityChecks(errors); // 4. Run checks in the biscuit's authority block - const allowed_policy_id: ?usize = try authorizer.authorizerPolicies(errors); // 5. run policies from the authorizer - try authorizer.blockChecks(errors); // 6. Run checks in the biscuit's other blocks + try authorizer.addTokenFactsAndRules(errors); // Step 1 + try authorizer.generateFacts(); // Step 2 + try authorizer.authorizerChecks(errors); // Step 3 + try authorizer.authorityChecks(errors); // Step 4 + const allowed_policy_id: ?usize = try authorizer.authorizerPolicies(errors); // Step 5 + try authorizer.blockChecks(errors); // Step 6 if (allowed_policy_id) |policy_id| { if (errors.items.len == 0) return policy_id; From f4d2e443bb6b4e0d96b92c08e6756f8bf5bdbd9a Mon Sep 17 00:00:00 2001 From: Malcolm Still Date: Sat, 30 Mar 2024 17:55:54 +0000 Subject: [PATCH 16/52] Clean up --- biscuit-parser/src/parser.zig | 38 +++++++++-------------------------- 1 file changed, 10 insertions(+), 28 deletions(-) diff --git a/biscuit-parser/src/parser.zig b/biscuit-parser/src/parser.zig index e0e23db..eb29f43 100644 --- a/biscuit-parser/src/parser.zig +++ b/biscuit-parser/src/parser.zig @@ -199,12 +199,7 @@ pub const Parser = struct { while (try it.next()) |trm| { try terms.append(trm); - if (parser.peek()) |peeked| { - if (peeked == ',') { - parser.offset += 1; - continue; - } - } + if (parser.startsWithConsuming(",")) continue; break; } @@ -312,12 +307,7 @@ pub const Parser = struct { parser.skipWhiteSpace(); - if (parser.peek()) |peeked| { - if (peeked == ',') { - parser.offset += 1; - continue; - } - } + if (parser.startsWithConsuming(",")) continue; } // Otherwise try parsing an expression @@ -332,12 +322,7 @@ pub const Parser = struct { parser.skipWhiteSpace(); - if (parser.peek()) |peeked| { - if (peeked == ',') { - parser.offset += 1; - continue; - } - } + if (parser.startsWithConsuming(",")) continue; } // We haven't found a predicate or expression so we're done, @@ -727,19 +712,16 @@ pub const Parser = struct { fn unary(parser: *Parser) ParserError!Expression { parser.skipWhiteSpace(); - if (parser.peek()) |c| { - if (c == '!') { - try parser.consume("!"); - parser.skipWhiteSpace(); + if (parser.startsWithConsuming("!")) { + parser.skipWhiteSpace(); - const e = try parser.expr(); + const e = try parser.expr(); - return try Expression.unary(parser.allocator, .negate, e); - } + return try Expression.unary(parser.allocator, .negate, e); + } - if (c == '(') { - return try parser.unaryParens(); - } + if (parser.startsWith("(")) { + return try parser.unaryParens(); } var e: Expression = undefined; From eb475270642be45ec02f2a9256c9404e9bb160e5 Mon Sep 17 00:00:00 2001 From: Malcolm Still Date: Sat, 30 Mar 2024 17:57:57 +0000 Subject: [PATCH 17/52] Clean up --- biscuit-parser/src/parser.zig | 48 +++++++++++++++++------------------ 1 file changed, 23 insertions(+), 25 deletions(-) diff --git a/biscuit-parser/src/parser.zig b/biscuit-parser/src/parser.zig index eb29f43..d9e9bd5 100644 --- a/biscuit-parser/src/parser.zig +++ b/biscuit-parser/src/parser.zig @@ -52,6 +52,29 @@ pub const Parser = struct { return .{ .name = name, .terms = terms }; } + pub fn predicate(parser: *Parser) !Predicate { + const name = parser.readName(); + + parser.skipWhiteSpace(); + + try parser.consume("("); + + var terms = std.ArrayList(Term).init(parser.allocator); + + var it = parser.termsIterator(); + while (try it.next()) |trm| { + try terms.append(trm); + + if (parser.startsWithConsuming(",")) continue; + + break; + } + + try parser.consume(")"); + + return .{ .name = name, .terms = terms }; + } + const FactTermIterator = struct { parser: *Parser, @@ -184,31 +207,6 @@ pub const Parser = struct { return error.NoFactTermFound; } - pub fn predicate(parser: *Parser) !Predicate { - const name = parser.readName(); - - parser.skipWhiteSpace(); - - // Consume left paren - try parser.consume("("); - - // Parse terms - var terms = std.ArrayList(Term).init(parser.allocator); - - var it = parser.termsIterator(); - while (try it.next()) |trm| { - try terms.append(trm); - - if (parser.startsWithConsuming(",")) continue; - - break; - } - - try parser.consume(")"); - - return .{ .name = name, .terms = terms }; - } - pub fn policy(parser: *Parser) !Policy { const kind: Policy.Kind = if (parser.startsWithConsuming("allow if")) .allow From 2d4e11279675e5fe6bdb5113db2ff1f407b4b4ee Mon Sep 17 00:00:00 2001 From: Malcolm Still Date: Sat, 30 Mar 2024 18:03:02 +0000 Subject: [PATCH 18/52] Clean up --- biscuit-parser/src/parser.zig | 47 ++++++++--------------------------- 1 file changed, 10 insertions(+), 37 deletions(-) diff --git a/biscuit-parser/src/parser.zig b/biscuit-parser/src/parser.zig index d9e9bd5..21de49a 100644 --- a/biscuit-parser/src/parser.zig +++ b/biscuit-parser/src/parser.zig @@ -27,18 +27,18 @@ pub const Parser = struct { } pub fn factPredicate(parser: *Parser) !Predicate { + var terms = std.ArrayList(Term).init(parser.allocator); + const name = parser.readName(); parser.skipWhiteSpace(); try parser.consume("("); - var terms = std.ArrayList(Term).init(parser.allocator); + while (true) { + parser.skipWhiteSpace(); - // Parse terms - var it = parser.factTermsIterator(); - while (try it.next()) |trm| { - try terms.append(trm); + try terms.append(try parser.factTerm()); if (parser.peek()) |peeked| { if (peeked != ',') break; @@ -53,17 +53,18 @@ pub const Parser = struct { } pub fn predicate(parser: *Parser) !Predicate { + var terms = std.ArrayList(Term).init(parser.allocator); + const name = parser.readName(); parser.skipWhiteSpace(); try parser.consume("("); - var terms = std.ArrayList(Term).init(parser.allocator); + while (true) { + parser.skipWhiteSpace(); - var it = parser.termsIterator(); - while (try it.next()) |trm| { - try terms.append(trm); + try terms.append(try parser.term()); if (parser.startsWithConsuming(",")) continue; @@ -75,34 +76,6 @@ pub const Parser = struct { return .{ .name = name, .terms = terms }; } - const FactTermIterator = struct { - parser: *Parser, - - pub fn next(it: *FactTermIterator) !?Term { - it.parser.skipWhiteSpace(); - - return try it.parser.factTerm(); - } - }; - - pub fn factTermsIterator(parser: *Parser) FactTermIterator { - return .{ .parser = parser }; - } - - const TermIterator = struct { - parser: *Parser, - - pub fn next(it: *TermIterator) !?Term { - it.parser.skipWhiteSpace(); - - return try it.parser.term(); - } - }; - - pub fn termsIterator(parser: *Parser) TermIterator { - return .{ .parser = parser }; - } - pub fn term(parser: *Parser) !Term { const rst = parser.rest(); From 7690bdc95d7fb676e12a1dc61d6f616f6a2ae666 Mon Sep 17 00:00:00 2001 From: Malcolm Still Date: Sat, 30 Mar 2024 18:07:00 +0000 Subject: [PATCH 19/52] More clean up --- biscuit-parser/src/parser.zig | 31 +++++++++++++++++++++---------- 1 file changed, 21 insertions(+), 10 deletions(-) diff --git a/biscuit-parser/src/parser.zig b/biscuit-parser/src/parser.zig index 21de49a..846aba4 100644 --- a/biscuit-parser/src/parser.zig +++ b/biscuit-parser/src/parser.zig @@ -22,10 +22,22 @@ pub const Parser = struct { return .{ .input = input, .allocator = allocator }; } + /// Try to parse fact + /// + /// E.g. read(1, "hello") will parse successfully, but read($foo, "hello") + /// will fail because it contains a variable `$foo`. pub fn fact(parser: *Parser) !Fact { return .{ .predicate = try parser.factPredicate(), .variables = null }; } + /// Try to parse a fact predicate + /// + /// I.e. if we have a fact: read(1, "hello") we are attempting to + /// parse (1, "hello") having already parsed the "read" predicate + /// name. + /// + /// Note that we are specifically parsing a _fact's_ predicate, i.e. + /// the terms in the predicate may _not_ contain variables. pub fn factPredicate(parser: *Parser) !Predicate { var terms = std.ArrayList(Term).init(parser.allocator); @@ -879,19 +891,18 @@ const ParserError = error{ UnexpectedToken, }; -// test "parse fact predicate" { -// const testing = std.testing; -// const input: []const u8 = -// \\read(true) -// ; +test "parse fact predicate" { + const testing = std.testing; + const input: []const u8 = + \\read(true) + ; -// var parser = Parser.init(input); + var parser = Parser.init(testing.allocator, input); -// const r = try parser.factPredicate(testing.allocator); -// defer r.deinit(); + const r = try parser.factPredicate(); -// std.debug.print("{any}\n", .{r}); -// } + std.debug.print("{any}\n", .{r}); +} // test "parse rule body" { // const testing = std.testing; From 95520a0f3b3f6a1e92475ed3aba0364f4af7ce17 Mon Sep 17 00:00:00 2001 From: Malcolm Still Date: Sat, 30 Mar 2024 18:27:59 +0000 Subject: [PATCH 20/52] Parser fixes --- biscuit-parser/src/parser.zig | 84 ++++++++++++++++++++++++++--------- 1 file changed, 63 insertions(+), 21 deletions(-) diff --git a/biscuit-parser/src/parser.zig b/biscuit-parser/src/parser.zig index 846aba4..d476262 100644 --- a/biscuit-parser/src/parser.zig +++ b/biscuit-parser/src/parser.zig @@ -32,31 +32,29 @@ pub const Parser = struct { /// Try to parse a fact predicate /// - /// I.e. if we have a fact: read(1, "hello") we are attempting to - /// parse (1, "hello") having already parsed the "read" predicate - /// name. + /// E.g. read(1, "hello") will parse successfully, but read($foo, "hello") + /// will fail because it contains a variable `$foo`. /// - /// Note that we are specifically parsing a _fact's_ predicate, i.e. - /// the terms in the predicate may _not_ contain variables. + /// For parsing predicates containing variables see `fn predicate` pub fn factPredicate(parser: *Parser) !Predicate { var terms = std.ArrayList(Term).init(parser.allocator); const name = parser.readName(); - parser.skipWhiteSpace(); - try parser.consume("("); while (true) { parser.skipWhiteSpace(); - try terms.append(try parser.factTerm()); + const t = try parser.factTerm(); - if (parser.peek()) |peeked| { - if (peeked != ',') break; - } else { - break; - } + try terms.append(t); + + parser.skipWhiteSpace(); + + if (parser.startsWithConsuming(",")) continue; + + break; } try parser.consume(")"); @@ -891,17 +889,55 @@ const ParserError = error{ UnexpectedToken, }; -test "parse fact predicate" { +test "parse (fact) predicates" { const testing = std.testing; - const input: []const u8 = - \\read(true) - ; - var parser = Parser.init(testing.allocator, input); + var arena_state = std.heap.ArenaAllocator.init(testing.allocator); + defer arena_state.deinit(); + const arena = arena_state.allocator(); + + { + var parser = Parser.init(arena, "read(true)"); + const predicate = try parser.factPredicate(); - const r = try parser.factPredicate(); + try testing.expectEqualStrings("read", predicate.name); + try testing.expectEqual(true, predicate.terms.items[0].bool); + } - std.debug.print("{any}\n", .{r}); + { + var parser = Parser.init(arena, "read(true, false)"); + const predicate = try parser.factPredicate(); + + try testing.expectEqualStrings("read", predicate.name); + try testing.expectEqual(true, predicate.terms.items[0].bool); + try testing.expectEqual(false, predicate.terms.items[1].bool); + } + + { + var parser = Parser.init(arena, "read(true,false)"); + const predicate = try parser.factPredicate(); + + try testing.expectEqualStrings("read", predicate.name); + try testing.expectEqual(true, predicate.terms.items[0].bool); + try testing.expectEqual(false, predicate.terms.items[1].bool); + } + + { + // We are allowed spaces around predicate terms + var parser = Parser.init(arena, "read( true , false )"); + const predicate = try parser.factPredicate(); + + try testing.expectEqualStrings("read", predicate.name); + try testing.expectEqual(true, predicate.terms.items[0].bool); + try testing.expectEqual(false, predicate.terms.items[1].bool); + } + + { + // We don't allow a space between the predicate name and its opening paren + var parser = Parser.init(arena, "read (true, false )"); + + try testing.expectError(error.UnexpectedString, parser.factPredicate()); + } } // test "parse rule body" { @@ -948,11 +984,17 @@ test "parse fact predicate" { test "parse check with expression" { const testing = std.testing; + + var arena_state = std.heap.ArenaAllocator.init(testing.allocator); + defer arena_state.deinit(); + + const arena = arena_state.allocator(); + const input: []const u8 = \\check if right($0, $1), resource($0), operation($1), $0.contains("file") ; - var parser = Parser.init(testing.allocator, input); + var parser = Parser.init(arena, input); const r = try parser.check(); defer r.deinit(); From cdd1222e8afa8b7bc028d294a314213146337fad Mon Sep 17 00:00:00 2001 From: Malcolm Still Date: Sat, 30 Mar 2024 18:30:52 +0000 Subject: [PATCH 21/52] More parser tests --- biscuit-parser/src/parser.zig | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/biscuit-parser/src/parser.zig b/biscuit-parser/src/parser.zig index d476262..b6ae58a 100644 --- a/biscuit-parser/src/parser.zig +++ b/biscuit-parser/src/parser.zig @@ -67,8 +67,6 @@ pub const Parser = struct { const name = parser.readName(); - parser.skipWhiteSpace(); - try parser.consume("("); while (true) { @@ -76,6 +74,8 @@ pub const Parser = struct { try terms.append(try parser.term()); + parser.skipWhiteSpace(); + if (parser.startsWithConsuming(",")) continue; break; @@ -938,6 +938,20 @@ test "parse (fact) predicates" { try testing.expectError(error.UnexpectedString, parser.factPredicate()); } + + { + // We don't allow variables in fact predicates + var parser = Parser.init(arena, "read(true, $foo)"); + + try testing.expectError(error.DisallowedChar, parser.factPredicate()); + } + + { + // Facts must have at least one term + var parser = Parser.init(arena, "read()"); + + try testing.expectError(error.NoFactTermFound, parser.factPredicate()); + } } // test "parse rule body" { From 59e57be7f62f11614e92b42d9aa75dd8dd414f80 Mon Sep 17 00:00:00 2001 From: Malcolm Still Date: Sat, 30 Mar 2024 19:16:24 +0000 Subject: [PATCH 22/52] Clean up --- biscuit-parser/src/parser.zig | 125 +++++++--------------------------- 1 file changed, 26 insertions(+), 99 deletions(-) diff --git a/biscuit-parser/src/parser.zig b/biscuit-parser/src/parser.zig index b6ae58a..1e04a4c 100644 --- a/biscuit-parser/src/parser.zig +++ b/biscuit-parser/src/parser.zig @@ -27,42 +27,10 @@ pub const Parser = struct { /// E.g. read(1, "hello") will parse successfully, but read($foo, "hello") /// will fail because it contains a variable `$foo`. pub fn fact(parser: *Parser) !Fact { - return .{ .predicate = try parser.factPredicate(), .variables = null }; + return .{ .predicate = try parser.predicate(.fact), .variables = null }; } - /// Try to parse a fact predicate - /// - /// E.g. read(1, "hello") will parse successfully, but read($foo, "hello") - /// will fail because it contains a variable `$foo`. - /// - /// For parsing predicates containing variables see `fn predicate` - pub fn factPredicate(parser: *Parser) !Predicate { - var terms = std.ArrayList(Term).init(parser.allocator); - - const name = parser.readName(); - - try parser.consume("("); - - while (true) { - parser.skipWhiteSpace(); - - const t = try parser.factTerm(); - - try terms.append(t); - - parser.skipWhiteSpace(); - - if (parser.startsWithConsuming(",")) continue; - - break; - } - - try parser.consume(")"); - - return .{ .name = name, .terms = terms }; - } - - pub fn predicate(parser: *Parser) !Predicate { + pub fn predicate(parser: *Parser, kind: enum { fact, rule }) !Predicate { var terms = std.ArrayList(Term).init(parser.allocator); const name = parser.readName(); @@ -72,7 +40,10 @@ pub const Parser = struct { while (true) { parser.skipWhiteSpace(); - try terms.append(try parser.term()); + try terms.append(try parser.term(switch (kind) { + .rule => .allow_variables, + .fact => .disallow_variables, + })); parser.skipWhiteSpace(); @@ -86,67 +57,23 @@ pub const Parser = struct { return .{ .name = name, .terms = terms }; } - pub fn term(parser: *Parser) !Term { + fn term(parser: *Parser, variables: enum { allow_variables, disallow_variables }) !Term { const rst = parser.rest(); - variable_blk: { - var term_parser = Parser.init(parser.allocator, rst); - - const value = term_parser.variable() catch break :variable_blk; - - parser.offset += term_parser.offset; - - return .{ .variable = value }; - } - - string_blk: { - var term_parser = Parser.init(parser.allocator, rst); - - const value = term_parser.string() catch break :string_blk; - - parser.offset += term_parser.offset; - - return .{ .string = value }; - } - - date_blk: { - var term_parser = Parser.init(parser.allocator, rst); - - const value = term_parser.date() catch break :date_blk; - - parser.offset += term_parser.offset; - - return .{ .date = value }; - } - - number_blk: { - var term_parser = Parser.init(parser.allocator, rst); - - const value = term_parser.number(i64) catch break :number_blk; - - parser.offset += term_parser.offset; - - return .{ .integer = value }; - } + if (variables == .disallow_variables) { + try parser.reject('$'); // Variables are disallowed in a fact term + } else { + variable_blk: { + var term_parser = Parser.init(parser.allocator, rst); - bool_blk: { - var term_parser = Parser.init(parser.allocator, rst); + const value = term_parser.variable() catch break :variable_blk; - const value = term_parser.boolean() catch break :bool_blk; + parser.offset += term_parser.offset; - parser.offset += term_parser.offset; - - return .{ .bool = value }; + return .{ .variable = value }; + } } - return error.NoFactTermFound; - } - - pub fn factTerm(parser: *Parser) !Term { - const rst = parser.rest(); - - try parser.reject('$'); // Variables are disallowed in a fact term - string_blk: { var term_parser = Parser.init(parser.allocator, rst); @@ -280,7 +207,7 @@ pub const Parser = struct { predicate_blk: { var predicate_parser = Parser.init(parser.allocator, parser.rest()); - const p = predicate_parser.predicate() catch break :predicate_blk; + const p = predicate_parser.predicate(.rule) catch break :predicate_blk; parser.offset += predicate_parser.offset; @@ -685,7 +612,7 @@ pub const Parser = struct { } // Otherwise we expect term - const term1 = try parser.term(); + const term1 = try parser.term(.allow_variables); return try Expression.value(term1); } @@ -706,7 +633,7 @@ pub const Parser = struct { } var e: Expression = undefined; - if (parser.term()) |t1| { + if (parser.term(.allow_variables)) |t1| { parser.skipWhiteSpace(); e = try Expression.value(t1); } else |_| { @@ -898,7 +825,7 @@ test "parse (fact) predicates" { { var parser = Parser.init(arena, "read(true)"); - const predicate = try parser.factPredicate(); + const predicate = try parser.predicate(.fact); try testing.expectEqualStrings("read", predicate.name); try testing.expectEqual(true, predicate.terms.items[0].bool); @@ -906,7 +833,7 @@ test "parse (fact) predicates" { { var parser = Parser.init(arena, "read(true, false)"); - const predicate = try parser.factPredicate(); + const predicate = try parser.predicate(.fact); try testing.expectEqualStrings("read", predicate.name); try testing.expectEqual(true, predicate.terms.items[0].bool); @@ -915,7 +842,7 @@ test "parse (fact) predicates" { { var parser = Parser.init(arena, "read(true,false)"); - const predicate = try parser.factPredicate(); + const predicate = try parser.predicate(.fact); try testing.expectEqualStrings("read", predicate.name); try testing.expectEqual(true, predicate.terms.items[0].bool); @@ -925,7 +852,7 @@ test "parse (fact) predicates" { { // We are allowed spaces around predicate terms var parser = Parser.init(arena, "read( true , false )"); - const predicate = try parser.factPredicate(); + const predicate = try parser.predicate(.fact); try testing.expectEqualStrings("read", predicate.name); try testing.expectEqual(true, predicate.terms.items[0].bool); @@ -936,21 +863,21 @@ test "parse (fact) predicates" { // We don't allow a space between the predicate name and its opening paren var parser = Parser.init(arena, "read (true, false )"); - try testing.expectError(error.UnexpectedString, parser.factPredicate()); + try testing.expectError(error.UnexpectedString, parser.predicate(.fact)); } { // We don't allow variables in fact predicates var parser = Parser.init(arena, "read(true, $foo)"); - try testing.expectError(error.DisallowedChar, parser.factPredicate()); + try testing.expectError(error.DisallowedChar, parser.predicate(.fact)); } { // Facts must have at least one term var parser = Parser.init(arena, "read()"); - try testing.expectError(error.NoFactTermFound, parser.factPredicate()); + try testing.expectError(error.NoFactTermFound, parser.predicate(.fact)); } } From 120181b6308a2e8b002f7f0cd16a0735689e079c Mon Sep 17 00:00:00 2001 From: Malcolm Still Date: Sat, 30 Mar 2024 20:15:32 +0000 Subject: [PATCH 23/52] More parser work --- biscuit-parser/src/parser.zig | 72 +++++++++++++++++++++++++---------- 1 file changed, 52 insertions(+), 20 deletions(-) diff --git a/biscuit-parser/src/parser.zig b/biscuit-parser/src/parser.zig index 1e04a4c..8afee2f 100644 --- a/biscuit-parser/src/parser.zig +++ b/biscuit-parser/src/parser.zig @@ -33,7 +33,7 @@ pub const Parser = struct { pub fn predicate(parser: *Parser, kind: enum { fact, rule }) !Predicate { var terms = std.ArrayList(Term).init(parser.allocator); - const name = parser.readName(); + const predicate_name = try parser.name(); try parser.consume("("); @@ -54,7 +54,7 @@ pub const Parser = struct { try parser.consume(")"); - return .{ .name = name, .terms = terms }; + return .{ .name = predicate_name, .terms = terms }; } fn term(parser: *Parser, variables: enum { allow_variables, disallow_variables }) !Term { @@ -254,18 +254,7 @@ pub const Parser = struct { fn variable(parser: *Parser) ![]const u8 { try parser.consume("$"); - const start = parser.offset; - - for (parser.rest()) |c| { - if (ziglyph.isAlphaNum(c) or c == '_') { - parser.offset += 1; - continue; - } - - break; - } - - return parser.input[start..parser.offset]; + return try parser.name(); } // FIXME: properly implement string parsing @@ -357,7 +346,7 @@ pub const Parser = struct { const start = parser.offset; for (parser.rest()) |c| { - if (ziglyph.isHexDigit(c)) { + if (isHexDigit(c)) { parser.offset += 1; continue; } @@ -706,7 +695,7 @@ pub const Parser = struct { if (parser.startsWith("{")) { try parser.consume("{"); - const parameter = parser.readName(); + const parameter = try parser.name(); try parser.consume("}"); @@ -775,10 +764,15 @@ pub const Parser = struct { if (peeked == char) return error.DisallowedChar; } - // FIXME: this should error? - fn readName(parser: *Parser) []const u8 { + fn name(parser: *Parser) ![]const u8 { const start = parser.offset; + if (parser.rest().len == 0) return error.ParsingNameExpectsAtLeastOneCharacter; + + if (!ziglyph.isLetter(parser.rest()[0])) return error.ParsingNameFirstCharacterMustBeLetter; + + parser.offset += 1; + for (parser.rest()) |c| { if (ziglyph.isAlphaNum(c) or c == '_' or c == ':') { parser.offset += 1; @@ -793,7 +787,7 @@ pub const Parser = struct { fn skipWhiteSpace(parser: *Parser) void { for (parser.rest()) |c| { - if (ziglyph.isWhiteSpace(c)) { + if (c == ' ' or c == '\t' or c == '\n') { parser.offset += 1; continue; } @@ -803,6 +797,13 @@ pub const Parser = struct { } }; +fn isHexDigit(char: u8) bool { + switch (char) { + 'a', 'b', 'c', 'd', 'e', 'f', 'A', 'B', 'C', 'D', 'E', 'F' => return true, + else => return false, + } +} + const ParserError = error{ ExpectedMoreInput, DisallowedChar, @@ -816,7 +817,7 @@ const ParserError = error{ UnexpectedToken, }; -test "parse (fact) predicates" { +test "parse predicates" { const testing = std.testing; var arena_state = std.heap.ArenaAllocator.init(testing.allocator); @@ -873,12 +874,43 @@ test "parse (fact) predicates" { try testing.expectError(error.DisallowedChar, parser.predicate(.fact)); } + { + // Non-fact predicates can contain variables + var parser = Parser.init(arena, "read(true, $foo)"); + + const predicate = try parser.predicate(.rule); + + try testing.expectEqualStrings("read", predicate.name); + try testing.expectEqual(true, predicate.terms.items[0].bool); + try testing.expectEqualStrings("foo", predicate.terms.items[1].variable); + } + { // Facts must have at least one term var parser = Parser.init(arena, "read()"); try testing.expectError(error.NoFactTermFound, parser.predicate(.fact)); } + + { + // Facts must start with a (UTF-8) letter + var parser = Parser.init(arena, "3read(true)"); + + try testing.expectError(error.ParsingNameFirstCharacterMustBeLetter, parser.predicate(.fact)); + } + + // The specification states names can start with any UTF-8 letter. However, the rust implementation + // only seems to accept ASCII predicate names + // { + // const input = "こんにちは世界(true)"; + // var parser = Parser.init(arena, input); + + // const predicate = try parser.predicate(.fact); + // errdefer std.debug.print("Failed on input \"{s}\"", .{input}); + + // try testing.expectEqualStrings("こんにちは世界", predicate.name); + // try testing.expectEqual(true, predicate.terms.items[0].bool); + // } } // test "parse rule body" { From eae0ad36b12e7ef49e035ede538604c555e54212 Mon Sep 17 00:00:00 2001 From: Malcolm Still Date: Sat, 30 Mar 2024 20:21:13 +0000 Subject: [PATCH 24/52] Fix number parsing --- biscuit-parser/src/parser.zig | 37 ++++++++++++++++++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/biscuit-parser/src/parser.zig b/biscuit-parser/src/parser.zig index 8afee2f..b3a788f 100644 --- a/biscuit-parser/src/parser.zig +++ b/biscuit-parser/src/parser.zig @@ -320,8 +320,14 @@ pub const Parser = struct { fn number(parser: *Parser, comptime T: type) !T { const start = parser.offset; + if (parser.rest().len == 0) return error.ParsingNumberExpectsAtLeastOneCharacter; + const first_char = parser.rest()[0]; + + if (!(isDigit(first_char) or first_char == '-')) return error.ParsingNameFirstCharacterMustBeLetter; + parser.offset += 1; + for (parser.rest()) |c| { - if (ziglyph.isAsciiDigit(c)) { + if (isDigit(c)) { parser.offset += 1; continue; } @@ -804,6 +810,13 @@ fn isHexDigit(char: u8) bool { } } +fn isDigit(char: u8) bool { + switch (char) { + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' => return true, + else => return false, + } +} + const ParserError = error{ ExpectedMoreInput, DisallowedChar, @@ -913,6 +926,28 @@ test "parse predicates" { // } } +test "parse terms" { + const testing = std.testing; + + var arena_state = std.heap.ArenaAllocator.init(testing.allocator); + defer arena_state.deinit(); + const arena = arena_state.allocator(); + + { + var parser = Parser.init(arena, "1"); + const integer = try parser.number(i64); + + try testing.expectEqual(1, integer); + } + + { + var parser = Parser.init(arena, "-1"); + const integer = try parser.number(i64); + + try testing.expectEqual(-1, integer); + } +} + // test "parse rule body" { // const testing = std.testing; // const input: []const u8 = From d222baea56bfd3f13bb4a4f13f1fd4ca9c5a88fb Mon Sep 17 00:00:00 2001 From: Malcolm Still Date: Sat, 30 Mar 2024 20:23:28 +0000 Subject: [PATCH 25/52] Number parsing --- biscuit-parser/src/parser.zig | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/biscuit-parser/src/parser.zig b/biscuit-parser/src/parser.zig index b3a788f..eeb9e68 100644 --- a/biscuit-parser/src/parser.zig +++ b/biscuit-parser/src/parser.zig @@ -926,7 +926,7 @@ test "parse predicates" { // } } -test "parse terms" { +test "parse numbers" { const testing = std.testing; var arena_state = std.heap.ArenaAllocator.init(testing.allocator); @@ -940,12 +940,25 @@ test "parse terms" { try testing.expectEqual(1, integer); } + { + var parser = Parser.init(arena, "12345"); + const integer = try parser.number(i64); + + try testing.expectEqual(12345, integer); + } + { var parser = Parser.init(arena, "-1"); const integer = try parser.number(i64); try testing.expectEqual(-1, integer); } + + { + var parser = Parser.init(arena, "-"); + + try testing.expectError(error.InvalidCharacter, parser.number(i64)); + } } // test "parse rule body" { From 8fe6dabd2fa030088e320c7903bc83553a910204 Mon Sep 17 00:00:00 2001 From: Malcolm Still Date: Sat, 30 Mar 2024 20:27:17 +0000 Subject: [PATCH 26/52] Boolean test --- biscuit-parser/src/parser.zig | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/biscuit-parser/src/parser.zig b/biscuit-parser/src/parser.zig index eeb9e68..325e731 100644 --- a/biscuit-parser/src/parser.zig +++ b/biscuit-parser/src/parser.zig @@ -961,6 +961,28 @@ test "parse numbers" { } } +test "parse boolean" { + const testing = std.testing; + + var arena_state = std.heap.ArenaAllocator.init(testing.allocator); + defer arena_state.deinit(); + const arena = arena_state.allocator(); + + { + var parser = Parser.init(arena, "true"); + const boolean = try parser.boolean(); + + try testing.expectEqual(true, boolean); + } + + { + var parser = Parser.init(arena, "false"); + const boolean = try parser.boolean(); + + try testing.expectEqual(false, boolean); + } +} + // test "parse rule body" { // const testing = std.testing; // const input: []const u8 = From fb9a0137959e6e418ca8e99eea9f96a88a4a5bab Mon Sep 17 00:00:00 2001 From: Malcolm Still Date: Sat, 30 Mar 2024 20:44:48 +0000 Subject: [PATCH 27/52] Parse bytes --- biscuit-builder/src/term.zig | 4 +++ biscuit-parser/src/parser.zig | 65 +++++++++++++++++++++++++++++------ 2 files changed, 58 insertions(+), 11 deletions(-) diff --git a/biscuit-builder/src/term.zig b/biscuit-builder/src/term.zig index 11178c2..6a1e905 100644 --- a/biscuit-builder/src/term.zig +++ b/biscuit-builder/src/term.zig @@ -8,6 +8,7 @@ const TermTag = enum(u8) { integer, bool, date, + bytes, }; pub const Term = union(TermTag) { @@ -16,6 +17,7 @@ pub const Term = union(TermTag) { integer: i64, bool: bool, date: u64, + bytes: []const u8, pub fn deinit(_: Term) void {} @@ -26,6 +28,7 @@ pub const Term = union(TermTag) { .integer => |n| .{ .integer = n }, .bool => |b| .{ .bool = b }, .date => |d| .{ .date = d }, + .bytes => |b| .{ .bytes = b }, }; } @@ -36,6 +39,7 @@ pub const Term = union(TermTag) { .integer => |n| try writer.print("{}", .{n}), .bool => |b| if (b) try writer.print("true", .{}) else try writer.print("false", .{}), .date => |n| try writer.print("{}", .{n}), + .bytes => unreachable, } } }; diff --git a/biscuit-parser/src/parser.zig b/biscuit-parser/src/parser.zig index 325e731..80d2653 100644 --- a/biscuit-parser/src/parser.zig +++ b/biscuit-parser/src/parser.zig @@ -114,6 +114,16 @@ pub const Parser = struct { return .{ .bool = value }; } + bytes_blk: { + var term_parser = Parser.init(parser.allocator, rst); + + const value = term_parser.bytes() catch break :bytes_blk; + + parser.offset += term_parser.offset; + + return .{ .bytes = value }; + } + return error.NoFactTermFound; } @@ -348,19 +358,16 @@ pub const Parser = struct { return error.ExpectedBooleanTerm; } - fn hex(parser: *Parser) ![]const u8 { - const start = parser.offset; + fn bytes(parser: *Parser) ![]const u8 { + try parser.consume("hex:"); - for (parser.rest()) |c| { - if (isHexDigit(c)) { - parser.offset += 1; - continue; - } + const hex_string = try parser.hex(); - break; - } + if (!(hex_string.len % 2 == 0)) return error.ExpectedEvenNumberOfHexDigis; - return parser.input[start..parser.offset]; + const out = try parser.allocator.alloc(u8, hex_string.len / 2); + + return try std.fmt.hexToBytes(out, hex_string); } fn expression(parser: *Parser) ParserError!Expression { @@ -791,6 +798,21 @@ pub const Parser = struct { return parser.input[start..parser.offset]; } + fn hex(parser: *Parser) ![]const u8 { + const start = parser.offset; + + for (parser.rest()) |c| { + if (isHexDigit(c)) { + parser.offset += 1; + continue; + } + + break; + } + + return parser.input[start..parser.offset]; + } + fn skipWhiteSpace(parser: *Parser) void { for (parser.rest()) |c| { if (c == ' ' or c == '\t' or c == '\n') { @@ -805,7 +827,7 @@ pub const Parser = struct { fn isHexDigit(char: u8) bool { switch (char) { - 'a', 'b', 'c', 'd', 'e', 'f', 'A', 'B', 'C', 'D', 'E', 'F' => return true, + 'a', 'b', 'c', 'd', 'e', 'f', 'A', 'B', 'C', 'D', 'E', 'F', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' => return true, else => return false, } } @@ -983,6 +1005,27 @@ test "parse boolean" { } } +test "parse hex" { + const testing = std.testing; + + var arena_state = std.heap.ArenaAllocator.init(testing.allocator); + defer arena_state.deinit(); + const arena = arena_state.allocator(); + + { + var parser = Parser.init(arena, "hex:BeEf"); + const bytes = try parser.bytes(); + + try testing.expectEqualStrings("\xbe\xef", bytes); + } + + { + var parser = Parser.init(arena, "hex:BeE"); + + try testing.expectError(error.ExpectedEvenNumberOfHexDigis, parser.bytes()); + } +} + // test "parse rule body" { // const testing = std.testing; // const input: []const u8 = From a576a78d68fc4b526657fa332775416414f12f65 Mon Sep 17 00:00:00 2001 From: Malcolm Still Date: Sat, 30 Mar 2024 20:50:11 +0000 Subject: [PATCH 28/52] Test with all (apart from set) terms --- biscuit-parser/src/parser.zig | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/biscuit-parser/src/parser.zig b/biscuit-parser/src/parser.zig index 80d2653..e9ae8e0 100644 --- a/biscuit-parser/src/parser.zig +++ b/biscuit-parser/src/parser.zig @@ -859,6 +859,21 @@ test "parse predicates" { defer arena_state.deinit(); const arena = arena_state.allocator(); + { + var parser = Parser.init(arena, "read(-1, 1, \"hello world\", hex:abcd, true, false, $foo, 2024-03-30T20:48:00Z)"); + const predicate = try parser.predicate(.rule); + + try testing.expectEqualStrings("read", predicate.name); + try testing.expectEqual(-1, predicate.terms.items[0].integer); + try testing.expectEqual(1, predicate.terms.items[1].integer); + try testing.expectEqualStrings("hello world", predicate.terms.items[2].string); + try testing.expectEqualStrings("\xab\xcd", predicate.terms.items[3].bytes); + try testing.expectEqual(true, predicate.terms.items[4].bool); + try testing.expectEqual(false, predicate.terms.items[5].bool); + try testing.expectEqualStrings("foo", predicate.terms.items[6].variable); + try testing.expectEqual(1711831680, predicate.terms.items[7].date); + } + { var parser = Parser.init(arena, "read(true)"); const predicate = try parser.predicate(.fact); From 28c043ca190817c3fffbd139a8504580c85419a6 Mon Sep 17 00:00:00 2001 From: Malcolm Still Date: Sat, 30 Mar 2024 21:21:03 +0000 Subject: [PATCH 29/52] Set parsing --- biscuit-builder/src/root.zig | 1 + biscuit-builder/src/term.zig | 61 ++++++++++++++++++++++++++++----- biscuit-datalog/src/main.zig | 1 + biscuit-parser/src/parser.zig | 64 ++++++++++++++++++++++++++++------- 4 files changed, 106 insertions(+), 21 deletions(-) diff --git a/biscuit-builder/src/root.zig b/biscuit-builder/src/root.zig index 89c79ff..977df93 100644 --- a/biscuit-builder/src/root.zig +++ b/biscuit-builder/src/root.zig @@ -7,3 +7,4 @@ pub const Expression = @import("expression.zig").Expression; pub const Scope = @import("scope.zig").Scope; pub const Date = @import("date.zig").Date; pub const Policy = @import("policy.zig").Policy; +pub const Set = @import("biscuit-datalog").Set; diff --git a/biscuit-builder/src/term.zig b/biscuit-builder/src/term.zig index 6a1e905..27dde60 100644 --- a/biscuit-builder/src/term.zig +++ b/biscuit-builder/src/term.zig @@ -9,6 +9,7 @@ const TermTag = enum(u8) { bool, date, bytes, + set, }; pub const Term = union(TermTag) { @@ -18,18 +19,29 @@ pub const Term = union(TermTag) { bool: bool, date: u64, bytes: []const u8, + set: datalog.Set(Term), pub fn deinit(_: Term) void {} - pub fn toDatalog(term: Term, _: std.mem.Allocator, symbols: *datalog.SymbolTable) !datalog.Term { - return switch (term) { - .variable => |s| .{ .variable = @truncate(try symbols.insert(s)) }, // FIXME: assert symbol fits in u32 - .string => |s| .{ .string = try symbols.insert(s) }, - .integer => |n| .{ .integer = n }, - .bool => |b| .{ .bool = b }, - .date => |d| .{ .date = d }, - .bytes => |b| .{ .bytes = b }, - }; + pub fn toDatalog(term: Term, arena: std.mem.Allocator, symbols: *datalog.SymbolTable) !datalog.Term { + switch (term) { + .variable => |s| return .{ .variable = std.math.cast(u32, try symbols.insert(s)) orelse return error.FailedToCastInt }, + .string => |s| return .{ .string = try symbols.insert(s) }, + .integer => |n| return .{ .integer = n }, + .bool => |b| return .{ .bool = b }, + .date => |d| return .{ .date = d }, + .bytes => |b| return .{ .bytes = b }, + .set => |s| { + var datalog_set = datalog.Set(datalog.Term).init(arena); + + var it = s.iterator(); + while (it.next()) |t| { + try datalog_set.add(t); + } + + return .{ .set = datalog_set }; + }, + } } pub fn format(term: Term, comptime _: []const u8, _: std.fmt.FormatOptions, writer: anytype) !void { @@ -40,6 +52,37 @@ pub const Term = union(TermTag) { .bool => |b| if (b) try writer.print("true", .{}) else try writer.print("false", .{}), .date => |n| try writer.print("{}", .{n}), .bytes => unreachable, + .set => unreachable, + } + } + + pub fn eql(term: Term, other_term: Term) bool { + if (std.meta.activeTag(term) != std.meta.activeTag(other_term)) return false; + + return switch (term) { + .variable => |v| std.mem.eql(u8, v, other_term.variable), + .integer => |v| v == other_term.integer, + .string => |v| std.mem.eql(u8, v, other_term.string), + .bool => |v| v == other_term.bool, + .date => |v| v == other_term.date, + .bytes => |v| std.mem.eql(u8, v, other_term.bytes), + .set => |v| v.eql(other_term.set), + }; + } + + pub fn hash(term: Term, hasher: anytype) void { + // Hash the tag type + std.hash.autoHash(hasher, std.meta.activeTag(term)); + + // Hash the value + switch (term) { + .variable => |v| for (v) |b| std.hash.autoHash(hasher, b), + .integer => |v| std.hash.autoHash(hasher, v), + .string => |v| for (v) |b| std.hash.autoHash(hasher, b), + .bool => |v| std.hash.autoHash(hasher, v), + .date => |v| std.hash.autoHash(hasher, v), + .bytes => |v| for (v) |b| std.hash.autoHash(hasher, b), + .set => |v| v.hash(hasher), } } }; diff --git a/biscuit-datalog/src/main.zig b/biscuit-datalog/src/main.zig index 3d629ca..7fff113 100644 --- a/biscuit-datalog/src/main.zig +++ b/biscuit-datalog/src/main.zig @@ -15,6 +15,7 @@ pub const Check = @import("check.zig").Check; pub const Origin = @import("origin.zig").Origin; pub const TrustedOrigins = @import("trusted_origins.zig").TrustedOrigins; pub const world = @import("world.zig"); +pub const Set = @import("set.zig").Set; test { _ = @import("check.zig"); diff --git a/biscuit-parser/src/parser.zig b/biscuit-parser/src/parser.zig index e9ae8e0..6d89073 100644 --- a/biscuit-parser/src/parser.zig +++ b/biscuit-parser/src/parser.zig @@ -9,6 +9,7 @@ const Expression = @import("biscuit-builder").Expression; const Scope = @import("biscuit-builder").Scope; const Date = @import("biscuit-builder").Date; const Policy = @import("biscuit-builder").Policy; +const Set = @import("biscuit-builder").Set; const Ed25519 = std.crypto.sign.Ed25519; const log = std.log.scoped(.parser); @@ -41,8 +42,8 @@ pub const Parser = struct { parser.skipWhiteSpace(); try terms.append(try parser.term(switch (kind) { - .rule => .allow_variables, - .fact => .disallow_variables, + .rule => .allow, + .fact => .disallow, })); parser.skipWhiteSpace(); @@ -57,11 +58,11 @@ pub const Parser = struct { return .{ .name = predicate_name, .terms = terms }; } - fn term(parser: *Parser, variables: enum { allow_variables, disallow_variables }) !Term { + fn term(parser: *Parser, variables: AllowVariables) ParserError!Term { const rst = parser.rest(); - if (variables == .disallow_variables) { - try parser.reject('$'); // Variables are disallowed in a fact term + if (variables == .disallow) { + try parser.reject("$"); // Variables are disallowed in a fact term } else { variable_blk: { var term_parser = Parser.init(parser.allocator, rst); @@ -124,6 +125,16 @@ pub const Parser = struct { return .{ .bytes = value }; } + set_blk: { + var term_parser = Parser.init(parser.allocator, rst); + + const value = term_parser.set(variables) catch break :set_blk; + + parser.offset += term_parser.offset; + + return .{ .set = value }; + } + return error.NoFactTermFound; } @@ -352,7 +363,6 @@ pub const Parser = struct { fn boolean(parser: *Parser) !bool { if (parser.startsWithConsuming("true")) return true; - if (parser.startsWithConsuming("false")) return false; return error.ExpectedBooleanTerm; @@ -370,6 +380,27 @@ pub const Parser = struct { return try std.fmt.hexToBytes(out, hex_string); } + fn set(parser: *Parser, variables: AllowVariables) !Set(Term) { + var new_set = Set(Term).init(parser.allocator); + + try parser.consume("["); + + while (true) { + parser.skipWhiteSpace(); + + const trm = try parser.term(variables); + try new_set.add(trm); + + parser.skipWhiteSpace(); + + if (!parser.startsWithConsuming(",")) break; + } + + try parser.consume("]"); + + return new_set; + } + fn expression(parser: *Parser) ParserError!Expression { parser.skipWhiteSpace(); const e = try parser.expr(); @@ -614,7 +645,7 @@ pub const Parser = struct { } // Otherwise we expect term - const term1 = try parser.term(.allow_variables); + const term1 = try parser.term(.allow); return try Expression.value(term1); } @@ -635,7 +666,7 @@ pub const Parser = struct { } var e: Expression = undefined; - if (parser.term(.allow_variables)) |t1| { + if (parser.term(.allow)) |t1| { parser.skipWhiteSpace(); e = try Expression.value(t1); } else |_| { @@ -772,9 +803,8 @@ pub const Parser = struct { } /// Reject char. Does not consume the character - fn reject(parser: *Parser, char: u8) !void { - const peeked = parser.peek() orelse return error.ExpectedMoreInput; - if (peeked == char) return error.DisallowedChar; + fn reject(parser: *Parser, str: []const u8) !void { + if (parser.startsWith(str)) return error.DisallowedChar; } fn name(parser: *Parser) ![]const u8 { @@ -825,6 +855,11 @@ pub const Parser = struct { } }; +const AllowVariables = enum { + allow, + disallow, +}; + fn isHexDigit(char: u8) bool { switch (char) { 'a', 'b', 'c', 'd', 'e', 'f', 'A', 'B', 'C', 'D', 'E', 'F', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' => return true, @@ -860,7 +895,7 @@ test "parse predicates" { const arena = arena_state.allocator(); { - var parser = Parser.init(arena, "read(-1, 1, \"hello world\", hex:abcd, true, false, $foo, 2024-03-30T20:48:00Z)"); + var parser = Parser.init(arena, "read(-1, 1, \"hello world\", hex:abcd, true, false, $foo, 2024-03-30T20:48:00Z, [1, 2, 3])"); const predicate = try parser.predicate(.rule); try testing.expectEqualStrings("read", predicate.name); @@ -872,6 +907,11 @@ test "parse predicates" { try testing.expectEqual(false, predicate.terms.items[5].bool); try testing.expectEqualStrings("foo", predicate.terms.items[6].variable); try testing.expectEqual(1711831680, predicate.terms.items[7].date); + + const set = predicate.terms.items[8].set; + try testing.expect(set.contains(.{ .integer = 1 })); + try testing.expect(set.contains(.{ .integer = 2 })); + try testing.expect(set.contains(.{ .integer = 3 })); } { From 3835b601d383b6815ad8ff4432d06ddd06091576 Mon Sep 17 00:00:00 2001 From: Malcolm Still Date: Sat, 30 Mar 2024 21:34:31 +0000 Subject: [PATCH 30/52] More parser --- biscuit-parser/src/parser.zig | 19 +++---------------- 1 file changed, 3 insertions(+), 16 deletions(-) diff --git a/biscuit-parser/src/parser.zig b/biscuit-parser/src/parser.zig index 6d89073..61818e7 100644 --- a/biscuit-parser/src/parser.zig +++ b/biscuit-parser/src/parser.zig @@ -722,23 +722,10 @@ pub const Parser = struct { } fn scope(parser: *Parser, _: std.mem.Allocator) !Scope { - parser.skipWhiteSpace(); - - if (parser.startsWith("authority")) { - try parser.consume("authority"); - - return .{ .authority = {} }; - } - - if (parser.startsWith("previous")) { - try parser.consume("previous"); - - return .{ .previous = {} }; - } - - if (parser.startsWith("{")) { - try parser.consume("{"); + if (parser.startsWithConsuming("authority")) return .{ .authority = {} }; + if (parser.startsWithConsuming("previous")) return .{ .previous = {} }; + if (parser.startsWithConsuming("{")) { const parameter = try parser.name(); try parser.consume("}"); From 19163c813ce7f33e9f015507ddb2c6b7008b358f Mon Sep 17 00:00:00 2001 From: Malcolm Still Date: Sat, 30 Mar 2024 22:05:14 +0000 Subject: [PATCH 31/52] Spec compliant (?) name parsing --- biscuit-parser/src/parser.zig | 54 ++++++++++++++++++++++++++--------- 1 file changed, 40 insertions(+), 14 deletions(-) diff --git a/biscuit-parser/src/parser.zig b/biscuit-parser/src/parser.zig index 61818e7..56cfe68 100644 --- a/biscuit-parser/src/parser.zig +++ b/biscuit-parser/src/parser.zig @@ -794,17 +794,44 @@ pub const Parser = struct { if (parser.startsWith(str)) return error.DisallowedChar; } + fn nextCodepoint(parser: *Parser) !struct { len: u32, codepoint: u21 } { + const remaining = parser.rest(); + + if (remaining.len == 0) return error.NextCodePointExpectsAtLeastOneByte; + + const first_byte = remaining[0]; + + const byte_len = try std.unicode.utf8ByteSequenceLength(first_byte); + + const codepoint = switch (byte_len) { + 1 => try std.unicode.utf8Decode(remaining[0..1]), + 2 => try std.unicode.utf8Decode2(remaining[0..2]), + 3 => try std.unicode.utf8Decode3(remaining[0..3]), + 4 => try std.unicode.utf8Decode4(remaining[0..4]), + else => return error.IncorrectUtfDecodeLength, + }; + + return .{ .len = byte_len, .codepoint = codepoint }; + } + fn name(parser: *Parser) ![]const u8 { const start = parser.offset; if (parser.rest().len == 0) return error.ParsingNameExpectsAtLeastOneCharacter; - if (!ziglyph.isLetter(parser.rest()[0])) return error.ParsingNameFirstCharacterMustBeLetter; + const first_codepoint = try parser.nextCodepoint(); - parser.offset += 1; + if (!ziglyph.isLetter(first_codepoint.codepoint)) return error.ParsingNameFirstCharacterMustBeLetter; - for (parser.rest()) |c| { - if (ziglyph.isAlphaNum(c) or c == '_' or c == ':') { + parser.offset += first_codepoint.len; + + while (true) { + const next_codepoint = try parser.nextCodepoint(); + + if (ziglyph.isAlphaNum(next_codepoint.codepoint)) { + parser.offset += next_codepoint.len; + continue; + } else if (parser.startsWith("_") or parser.startsWith(":")) { parser.offset += 1; continue; } @@ -976,18 +1003,17 @@ test "parse predicates" { try testing.expectError(error.ParsingNameFirstCharacterMustBeLetter, parser.predicate(.fact)); } - // The specification states names can start with any UTF-8 letter. However, the rust implementation - // only seems to accept ASCII predicate names - // { - // const input = "こんにちは世界(true)"; - // var parser = Parser.init(arena, input); + { + // Names can be UTF-8 + const input = "こんにちは世界(true)"; + var parser = Parser.init(arena, input); - // const predicate = try parser.predicate(.fact); - // errdefer std.debug.print("Failed on input \"{s}\"", .{input}); + const predicate = try parser.predicate(.fact); + errdefer std.debug.print("Failed on input \"{s}\"", .{input}); - // try testing.expectEqualStrings("こんにちは世界", predicate.name); - // try testing.expectEqual(true, predicate.terms.items[0].bool); - // } + try testing.expectEqualStrings("こんにちは世界", predicate.name); + try testing.expectEqual(true, predicate.terms.items[0].bool); + } } test "parse numbers" { From e3d5e067aa2521d0b89f8aef79fdc4d6cd716da5 Mon Sep 17 00:00:00 2001 From: Malcolm Still Date: Sat, 30 Mar 2024 22:10:58 +0000 Subject: [PATCH 32/52] Clean up --- biscuit-parser/src/parser.zig | 46 ++++++++++++++++++----------------- 1 file changed, 24 insertions(+), 22 deletions(-) diff --git a/biscuit-parser/src/parser.zig b/biscuit-parser/src/parser.zig index 56cfe68..1fd0aca 100644 --- a/biscuit-parser/src/parser.zig +++ b/biscuit-parser/src/parser.zig @@ -794,39 +794,19 @@ pub const Parser = struct { if (parser.startsWith(str)) return error.DisallowedChar; } - fn nextCodepoint(parser: *Parser) !struct { len: u32, codepoint: u21 } { - const remaining = parser.rest(); - - if (remaining.len == 0) return error.NextCodePointExpectsAtLeastOneByte; - - const first_byte = remaining[0]; - - const byte_len = try std.unicode.utf8ByteSequenceLength(first_byte); - - const codepoint = switch (byte_len) { - 1 => try std.unicode.utf8Decode(remaining[0..1]), - 2 => try std.unicode.utf8Decode2(remaining[0..2]), - 3 => try std.unicode.utf8Decode3(remaining[0..3]), - 4 => try std.unicode.utf8Decode4(remaining[0..4]), - else => return error.IncorrectUtfDecodeLength, - }; - - return .{ .len = byte_len, .codepoint = codepoint }; - } - fn name(parser: *Parser) ![]const u8 { const start = parser.offset; if (parser.rest().len == 0) return error.ParsingNameExpectsAtLeastOneCharacter; - const first_codepoint = try parser.nextCodepoint(); + const first_codepoint = try nextCodepoint(parser.rest()); if (!ziglyph.isLetter(first_codepoint.codepoint)) return error.ParsingNameFirstCharacterMustBeLetter; parser.offset += first_codepoint.len; while (true) { - const next_codepoint = try parser.nextCodepoint(); + const next_codepoint = try nextCodepoint(parser.rest()); if (ziglyph.isAlphaNum(next_codepoint.codepoint)) { parser.offset += next_codepoint.len; @@ -888,6 +868,28 @@ fn isDigit(char: u8) bool { } } +/// Try to get the next UTF-8 codepoint from input. Returns the codepoint +/// and the number of bytes that codepoint takes up. +fn nextCodepoint(input: []const u8) !struct { codepoint: u21, len: u32 } { + if (input.len == 0) return error.NextCodePointExpectsAtLeastOneByte; + + const first_byte = input[0]; + + const byte_len = try std.unicode.utf8ByteSequenceLength(first_byte); + + if (input.len < byte_len) return error.NotEnoughInputForCodepoint; + + const codepoint = switch (byte_len) { + 1 => try std.unicode.utf8Decode(input[0..1]), + 2 => try std.unicode.utf8Decode2(input[0..2]), + 3 => try std.unicode.utf8Decode3(input[0..3]), + 4 => try std.unicode.utf8Decode4(input[0..4]), + else => return error.IncorrectUtfDecodeLength, + }; + + return .{ .len = byte_len, .codepoint = codepoint }; +} + const ParserError = error{ ExpectedMoreInput, DisallowedChar, From 94750457d49dc0a0a784b83ffd781de2f3a92f22 Mon Sep 17 00:00:00 2001 From: Malcolm Still Date: Sat, 30 Mar 2024 22:13:33 +0000 Subject: [PATCH 33/52] : _ test --- biscuit-parser/src/parser.zig | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/biscuit-parser/src/parser.zig b/biscuit-parser/src/parser.zig index 1fd0aca..625fff8 100644 --- a/biscuit-parser/src/parser.zig +++ b/biscuit-parser/src/parser.zig @@ -938,6 +938,15 @@ test "parse predicates" { try testing.expectEqual(true, predicate.terms.items[0].bool); } + { + // Names can contain : and _ + var parser = Parser.init(arena, "read:write_admin(true)"); + const predicate = try parser.predicate(.fact); + + try testing.expectEqualStrings("read:write_admin", predicate.name); + try testing.expectEqual(true, predicate.terms.items[0].bool); + } + { var parser = Parser.init(arena, "read(true, false)"); const predicate = try parser.predicate(.fact); @@ -1007,13 +1016,13 @@ test "parse predicates" { { // Names can be UTF-8 - const input = "こんにちは世界(true)"; + const input = "ビスケット(true)"; var parser = Parser.init(arena, input); const predicate = try parser.predicate(.fact); errdefer std.debug.print("Failed on input \"{s}\"", .{input}); - try testing.expectEqualStrings("こんにちは世界", predicate.name); + try testing.expectEqualStrings("ビスケット", predicate.name); try testing.expectEqual(true, predicate.terms.items[0].bool); } } From e8bd75b8f375c766ec2892ee2474d961128f5047 Mon Sep 17 00:00:00 2001 From: Malcolm Still Date: Sat, 30 Mar 2024 22:42:49 +0000 Subject: [PATCH 34/52] Fix variable name parsing + rule parsing tests --- biscuit-parser/src/parser.zig | 74 +++++++++++++++++++++++++++++++++-- 1 file changed, 71 insertions(+), 3 deletions(-) diff --git a/biscuit-parser/src/parser.zig b/biscuit-parser/src/parser.zig index 625fff8..a1c741e 100644 --- a/biscuit-parser/src/parser.zig +++ b/biscuit-parser/src/parser.zig @@ -199,12 +199,14 @@ pub const Parser = struct { } pub fn rule(parser: *Parser) !Rule { - const head = try parser.predicate(); + const head = try parser.predicate(.rule); parser.skipWhiteSpace(); try parser.consume("<-"); + parser.skipWhiteSpace(); + const body = try parser.ruleBody(); return .{ @@ -275,7 +277,7 @@ pub const Parser = struct { fn variable(parser: *Parser) ![]const u8 { try parser.consume("$"); - return try parser.name(); + return try parser.variableName(); } // FIXME: properly implement string parsing @@ -822,6 +824,34 @@ pub const Parser = struct { return parser.input[start..parser.offset]; } + fn variableName(parser: *Parser) ![]const u8 { + const start = parser.offset; + + if (parser.rest().len == 0) return error.ParsingNameExpectsAtLeastOneCharacter; + + const first_codepoint = try nextCodepoint(parser.rest()); + + if (!ziglyph.isAlphaNum(first_codepoint.codepoint)) return error.ParsingNameFirstCharacterMustBeLetter; + + parser.offset += first_codepoint.len; + + while (true) { + const next_codepoint = try nextCodepoint(parser.rest()); + + if (ziglyph.isAlphaNum(next_codepoint.codepoint)) { + parser.offset += next_codepoint.len; + continue; + } else if (parser.startsWith("_") or parser.startsWith(":")) { + parser.offset += 1; + continue; + } + + break; + } + + return parser.input[start..parser.offset]; + } + fn hex(parser: *Parser) ![]const u8 { const start = parser.offset; @@ -1020,7 +1050,6 @@ test "parse predicates" { var parser = Parser.init(arena, input); const predicate = try parser.predicate(.fact); - errdefer std.debug.print("Failed on input \"{s}\"", .{input}); try testing.expectEqualStrings("ビスケット", predicate.name); try testing.expectEqual(true, predicate.terms.items[0].bool); @@ -1105,6 +1134,45 @@ test "parse hex" { } } +test "parse rule" { + const testing = std.testing; + + var arena_state = std.heap.ArenaAllocator.init(testing.allocator); + defer arena_state.deinit(); + const arena = arena_state.allocator(); + + { + var parser = Parser.init(arena, "read($0, $1) <- operation($0), file($1)"); + const rule = try parser.rule(); + + try testing.expectEqualStrings("read", rule.head.name); + try testing.expectEqualStrings("0", rule.head.terms.items[0].variable); + try testing.expectEqualStrings("1", rule.head.terms.items[1].variable); + + try testing.expectEqualStrings("operation", rule.body.items[0].name); + try testing.expectEqualStrings("0", rule.body.items[0].terms.items[0].variable); + + try testing.expectEqualStrings("file", rule.body.items[1].name); + try testing.expectEqualStrings("1", rule.body.items[1].terms.items[0].variable); + } + + { + // Remove some spaces + var parser = Parser.init(arena, "read($0, $1)<-operation($0),file($1)"); + const rule = try parser.rule(); + + try testing.expectEqualStrings("read", rule.head.name); + try testing.expectEqualStrings("0", rule.head.terms.items[0].variable); + try testing.expectEqualStrings("1", rule.head.terms.items[1].variable); + + try testing.expectEqualStrings("operation", rule.body.items[0].name); + try testing.expectEqualStrings("0", rule.body.items[0].terms.items[0].variable); + + try testing.expectEqualStrings("file", rule.body.items[1].name); + try testing.expectEqualStrings("1", rule.body.items[1].terms.items[0].variable); + } +} + // test "parse rule body" { // const testing = std.testing; // const input: []const u8 = From 5c7dd791880c2aa6fa3a2b2ca2448f42081d47bd Mon Sep 17 00:00:00 2001 From: Malcolm Still Date: Sat, 30 Mar 2024 23:56:40 +0000 Subject: [PATCH 35/52] More parser tests --- biscuit-parser/src/parser.zig | 209 ++++++++++++++++++++++------------ 1 file changed, 137 insertions(+), 72 deletions(-) diff --git a/biscuit-parser/src/parser.zig b/biscuit-parser/src/parser.zig index a1c741e..62ee9c6 100644 --- a/biscuit-parser/src/parser.zig +++ b/biscuit-parser/src/parser.zig @@ -146,12 +146,15 @@ pub const Parser = struct { else return error.UnexpectedPolicyKind; + try parser.requiredWhiteSpace(); + const queries = try parser.checkBody(); return .{ .kind = kind, .queries = queries }; } pub fn check(parser: *Parser) !Check { + // Note the space after const kind: Check.Kind = if (parser.startsWithConsuming("check if")) .one else if (parser.startsWithConsuming("check all")) @@ -159,6 +162,8 @@ pub const Parser = struct { else return error.UnexpectedCheckKind; + try parser.requiredWhiteSpace(); + const queries = try parser.checkBody(); return .{ .kind = kind, .queries = queries }; @@ -184,6 +189,8 @@ pub const Parser = struct { try parser.consume("or"); + parser.skipWhiteSpace(); + const body = try parser.ruleBody(); try queries.append(.{ @@ -218,60 +225,76 @@ pub const Parser = struct { }; } - pub fn ruleBody(parser: *Parser) !struct { predicates: std.ArrayList(Predicate), expressions: std.ArrayList(Expression), scopes: std.ArrayList(Scope) } { + fn ruleBody(parser: *Parser) !struct { predicates: std.ArrayList(Predicate), expressions: std.ArrayList(Expression), scopes: std.ArrayList(Scope) } { var predicates = std.ArrayList(Predicate).init(parser.allocator); var expressions = std.ArrayList(Expression).init(parser.allocator); var scps = std.ArrayList(Scope).init(parser.allocator); + const required_rule_body = try parser.ruleBodyElement(); + + switch (required_rule_body) { + .predicate => |p| try predicates.append(p), + .expression => |e| try expressions.append(e), + } + while (true) { parser.skipWhiteSpace(); - // Try parsing a predicate - predicate_blk: { - var predicate_parser = Parser.init(parser.allocator, parser.rest()); - - const p = predicate_parser.predicate(.rule) catch break :predicate_blk; + if (!parser.startsWith(",")) break; - parser.offset += predicate_parser.offset; + try parser.consume(","); - try predicates.append(p); + parser.skipWhiteSpace(); - parser.skipWhiteSpace(); + const next_rule_body = try parser.ruleBodyElement(); - if (parser.startsWithConsuming(",")) continue; + switch (next_rule_body) { + .predicate => |p| try predicates.append(p), + .expression => |e| try expressions.append(e), } + } + + scopes_blk: { + var scope_parser = Parser.init(parser.allocator, parser.rest()); - // Otherwise try parsing an expression - expression_blk: { - var expression_parser = Parser.init(parser.allocator, parser.rest()); + const s = scope_parser.scopes(parser.allocator) catch break :scopes_blk; + + parser.offset += scope_parser.offset; + + scps = s; + } - const e = expression_parser.expression() catch break :expression_blk; + return .{ .predicates = predicates, .expressions = expressions, .scopes = scps }; + } - parser.offset += expression_parser.offset; + const BodyElementTag = enum { + predicate, + expression, + }; - try expressions.append(e); + fn ruleBodyElement(parser: *Parser) !union(BodyElementTag) { predicate: Predicate, expression: Expression } { + predicate_blk: { + var predicate_parser = Parser.init(parser.allocator, parser.rest()); - parser.skipWhiteSpace(); + const p = predicate_parser.predicate(.rule) catch break :predicate_blk; - if (parser.startsWithConsuming(",")) continue; - } + parser.offset += predicate_parser.offset; - // We haven't found a predicate or expression so we're done, - // other than potentially parsing a scope - break; + return .{ .predicate = p }; } - scopes_blk: { - var scope_parser = Parser.init(parser.allocator, parser.rest()); + // Otherwise try parsing an expression + expression_blk: { + var expression_parser = Parser.init(parser.allocator, parser.rest()); - const s = scope_parser.scopes(parser.allocator) catch break :scopes_blk; + const e = expression_parser.expression() catch break :expression_blk; - parser.offset += scope_parser.offset; + parser.offset += expression_parser.offset; - scps = s; + return .{ .expression = e }; } - return .{ .predicates = predicates, .expressions = expressions, .scopes = scps }; + return error.ExpectedPredicateOrExpression; } fn variable(parser: *Parser) ![]const u8 { @@ -700,6 +723,8 @@ pub const Parser = struct { } fn scopes(parser: *Parser, allocator: std.mem.Allocator) !std.ArrayList(Scope) { + try parser.requiredWhiteSpace(); + try parser.consume("trusting"); parser.skipWhiteSpace(); @@ -715,9 +740,7 @@ pub const Parser = struct { parser.skipWhiteSpace(); - if (!parser.startsWith(",")) break; - - try parser.consume(","); + if (!parser.startsWithConsuming(",")) break; } return scps; @@ -867,6 +890,14 @@ pub const Parser = struct { return parser.input[start..parser.offset]; } + /// Skip whitespace but the whitespace is required (i.e. we need at least one space, tab or newline) + fn requiredWhiteSpace(parser: *Parser) !void { + if (!(parser.startsWith(" ") or parser.startsWith("\t") or parser.startsWith("\n"))) return error.ExpectedWhiteSpace; + + parser.skipWhiteSpace(); + } + + /// Skip (optional) whitespace fn skipWhiteSpace(parser: *Parser) void { for (parser.rest()) |c| { if (c == ' ' or c == '\t' or c == '\n') { @@ -1171,64 +1202,98 @@ test "parse rule" { try testing.expectEqualStrings("file", rule.body.items[1].name); try testing.expectEqualStrings("1", rule.body.items[1].terms.items[0].variable); } + + { + // Remove some spaces + var parser = Parser.init(arena, "read($0, $1) <- operation($0), 1 < 3, file($1)"); + const rule = try parser.rule(); + + try testing.expectEqualStrings("read", rule.head.name); + try testing.expectEqualStrings("0", rule.head.terms.items[0].variable); + try testing.expectEqualStrings("1", rule.head.terms.items[1].variable); + + try testing.expectEqualStrings("operation", rule.body.items[0].name); + try testing.expectEqualStrings("0", rule.body.items[0].terms.items[0].variable); + + try testing.expectEqualStrings("file", rule.body.items[1].name); + try testing.expectEqualStrings("1", rule.body.items[1].terms.items[0].variable); + + try testing.expectEqualStrings("1 < 3", try std.fmt.allocPrint(arena, "{any}", .{rule.expressions.items[0]})); + } + + { + // We need at least one predicate or expression in the body + var parser = Parser.init(arena, "read($0, $1) <- "); + + try testing.expectError(error.ExpectedPredicateOrExpression, parser.rule()); + } } -// test "parse rule body" { -// const testing = std.testing; -// const input: []const u8 = -// \\query(false) <- read(true), write(false) -// ; +test "parse check" { + const testing = std.testing; -// var parser = Parser.init(testing.allocator, input); + var arena_state = std.heap.ArenaAllocator.init(testing.allocator); + defer arena_state.deinit(); + const arena = arena_state.allocator(); -// const r = try parser.rule(); -// defer r.deinit(); + { + var parser = Parser.init(arena, "check if right($0, $1), resource($0), operation($1), $0.contains(\"file\")"); + const check = try parser.check(); -// std.debug.print("{any}\n", .{r}); -// } + try testing.expectEqual(.one, check.kind); + try testing.expectEqual(1, check.queries.items.len); -// test "parse rule body with variables" { -// const testing = std.testing; -// const input: []const u8 = -// \\query($0) <- read($0), write(false) -// ; + try testing.expectEqualStrings("query", check.queries.items[0].head.name); + try testing.expectEqualStrings("right", check.queries.items[0].body.items[0].name); + try testing.expectEqualStrings("resource", check.queries.items[0].body.items[1].name); + try testing.expectEqualStrings("operation", check.queries.items[0].body.items[2].name); -// var parser = Parser.init(testing.allocator, input); + try testing.expectEqualStrings("$0.contains(\"file\")", try std.fmt.allocPrint(arena, "{any}", .{check.queries.items[0].expressions.items[0]})); + } -// const r = try parser.rule(); -// defer r.deinit(); + { + // Check with or + var parser = Parser.init(arena, "check if right($0, $1), resource($0), operation($1), $0.contains(\"file\") or admin(true)"); + const check = try parser.check(); -// std.debug.print("{any}\n", .{r}); -// } + try testing.expectEqual(.one, check.kind); + try testing.expectEqual(2, check.queries.items.len); -// test "parse check" { -// const testing = std.testing; -// const input: []const u8 = -// \\check if right($0, $1), resource($0), operation($1) -// ; + try testing.expectEqualStrings("query", check.queries.items[0].head.name); + try testing.expectEqualStrings("right", check.queries.items[0].body.items[0].name); + try testing.expectEqualStrings("resource", check.queries.items[0].body.items[1].name); + try testing.expectEqualStrings("operation", check.queries.items[0].body.items[2].name); -// var parser = Parser.init(testing.allocator, input); + try testing.expectEqualStrings("query", check.queries.items[1].head.name); + try testing.expectEqualStrings("admin", check.queries.items[1].body.items[0].name); + } -// const r = try parser.check(); -// defer r.deinit(); + { + // Check all + var parser = Parser.init(arena, "check all right($0, $1), resource($0), operation($1), $0.contains(\"file\") or admin(true)"); + const check = try parser.check(); -// std.debug.print("{any}\n", .{r}); -// } + try testing.expectEqual(.all, check.kind); + try testing.expectEqual(2, check.queries.items.len); -test "parse check with expression" { - const testing = std.testing; + try testing.expectEqualStrings("query", check.queries.items[0].head.name); + try testing.expectEqualStrings("right", check.queries.items[0].body.items[0].name); + try testing.expectEqualStrings("resource", check.queries.items[0].body.items[1].name); + try testing.expectEqualStrings("operation", check.queries.items[0].body.items[2].name); - var arena_state = std.heap.ArenaAllocator.init(testing.allocator); - defer arena_state.deinit(); + try testing.expectEqualStrings("query", check.queries.items[1].head.name); + try testing.expectEqualStrings("admin", check.queries.items[1].body.items[0].name); + } - const arena = arena_state.allocator(); + { + var parser = Parser.init(arena, "check if"); - const input: []const u8 = - \\check if right($0, $1), resource($0), operation($1), $0.contains("file") - ; + try testing.expectError(error.ExpectedWhiteSpace, parser.check()); + } - var parser = Parser.init(arena, input); + { + var parser = Parser.init(arena, "check if "); - const r = try parser.check(); - defer r.deinit(); + try testing.expectError(error.ExpectedPredicateOrExpression, parser.check()); + } } From f28d088e40d0d6dc6d521c9cd02d876aa2077600 Mon Sep 17 00:00:00 2001 From: Malcolm Still Date: Sun, 31 Mar 2024 00:02:49 +0000 Subject: [PATCH 36/52] Sets can be empty --- biscuit-parser/src/parser.zig | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/biscuit-parser/src/parser.zig b/biscuit-parser/src/parser.zig index 62ee9c6..2bf071c 100644 --- a/biscuit-parser/src/parser.zig +++ b/biscuit-parser/src/parser.zig @@ -413,7 +413,8 @@ pub const Parser = struct { while (true) { parser.skipWhiteSpace(); - const trm = try parser.term(variables); + // Try to parse a term. Since sets can be empty we break on catch; + const trm = parser.term(variables) catch break; try new_set.add(trm); parser.skipWhiteSpace(); @@ -972,7 +973,7 @@ test "parse predicates" { const arena = arena_state.allocator(); { - var parser = Parser.init(arena, "read(-1, 1, \"hello world\", hex:abcd, true, false, $foo, 2024-03-30T20:48:00Z, [1, 2, 3])"); + var parser = Parser.init(arena, "read(-1, 1, \"hello world\", hex:abcd, true, false, $foo, 2024-03-30T20:48:00Z, [1, 2, 3], [])"); const predicate = try parser.predicate(.rule); try testing.expectEqualStrings("read", predicate.name); @@ -989,6 +990,9 @@ test "parse predicates" { try testing.expect(set.contains(.{ .integer = 1 })); try testing.expect(set.contains(.{ .integer = 2 })); try testing.expect(set.contains(.{ .integer = 3 })); + + const empty_set = predicate.terms.items[9].set; + try testing.expectEqual(0, empty_set.count()); } { From c3f9b13b77b8755f675fcb364b21f5b8cb7a00bc Mon Sep 17 00:00:00 2001 From: Malcolm Still Date: Sun, 31 Mar 2024 00:08:45 +0000 Subject: [PATCH 37/52] fn expr -> fn expr0 --- biscuit-parser/src/parser.zig | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/biscuit-parser/src/parser.zig b/biscuit-parser/src/parser.zig index 2bf071c..2359d17 100644 --- a/biscuit-parser/src/parser.zig +++ b/biscuit-parser/src/parser.zig @@ -58,6 +58,9 @@ pub const Parser = struct { return .{ .name = predicate_name, .terms = terms }; } + /// Try to parse a term + /// + /// Does not consume `parser` input on failure. fn term(parser: *Parser, variables: AllowVariables) ParserError!Term { const rst = parser.rest(); @@ -272,6 +275,9 @@ pub const Parser = struct { expression, }; + /// Try to parse a rule body element (a predicate or an expression) + /// + /// Does not consume `parser` input on failure. fn ruleBodyElement(parser: *Parser) !union(BodyElementTag) { predicate: Predicate, expression: Expression } { predicate_blk: { var predicate_parser = Parser.init(parser.allocator, parser.rest()); @@ -429,12 +435,12 @@ pub const Parser = struct { fn expression(parser: *Parser) ParserError!Expression { parser.skipWhiteSpace(); - const e = try parser.expr(); + const e = try parser.expr0(); return e; } - fn expr(parser: *Parser) ParserError!Expression { + fn expr0(parser: *Parser) ParserError!Expression { var e = try parser.expr1(); while (true) { @@ -638,7 +644,7 @@ pub const Parser = struct { parser.skipWhiteSpace(); - const e2 = try parser.expr(); + const e2 = try parser.expr0(); parser.skipWhiteSpace(); @@ -682,7 +688,7 @@ pub const Parser = struct { if (parser.startsWithConsuming("!")) { parser.skipWhiteSpace(); - const e = try parser.expr(); + const e = try parser.expr0(); return try Expression.unary(parser.allocator, .negate, e); } @@ -714,7 +720,7 @@ pub const Parser = struct { parser.skipWhiteSpace(); - const e = try parser.expr(); + const e = try parser.expr0(); parser.skipWhiteSpace(); From 8bbb276c4eeda94cc0c69fdc50131b177af37f03 Mon Sep 17 00:00:00 2001 From: Malcolm Still Date: Sun, 31 Mar 2024 00:14:17 +0000 Subject: [PATCH 38/52] Prefer if (!parser.startsWithConsuming(",")) break; --- biscuit-parser/src/parser.zig | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/biscuit-parser/src/parser.zig b/biscuit-parser/src/parser.zig index 2359d17..5fff1ba 100644 --- a/biscuit-parser/src/parser.zig +++ b/biscuit-parser/src/parser.zig @@ -48,9 +48,7 @@ pub const Parser = struct { parser.skipWhiteSpace(); - if (parser.startsWithConsuming(",")) continue; - - break; + if (!parser.startsWithConsuming(",")) break; } try parser.consume(")"); From e01d8779ac963d6fb428354769700f3d3fa276d5 Mon Sep 17 00:00:00 2001 From: Malcolm Still Date: Sun, 31 Mar 2024 00:24:53 +0000 Subject: [PATCH 39/52] Consistent parsing of at least one of N --- biscuit-parser/src/parser.zig | 26 ++++++++++---------------- 1 file changed, 10 insertions(+), 16 deletions(-) diff --git a/biscuit-parser/src/parser.zig b/biscuit-parser/src/parser.zig index 5fff1ba..ec8b57c 100644 --- a/biscuit-parser/src/parser.zig +++ b/biscuit-parser/src/parser.zig @@ -170,28 +170,18 @@ pub const Parser = struct { return .{ .kind = kind, .queries = queries }; } + /// Parse check body + /// + /// E.g. given check if right($0, $1), resource($0), operation($1), $0.contains(\"file\") or admin(true) + /// this will (attempt to) parse `right($0, $1), resource($0), operation($1), $0.contains(\"file\") or admin(true)` + /// + /// Requires at least one rule body. fn checkBody(parser: *Parser) !std.ArrayList(Rule) { var queries = std.ArrayList(Rule).init(parser.allocator); - const required_body = try parser.ruleBody(); - - try queries.append(.{ - .head = .{ .name = "query", .terms = std.ArrayList(Term).init(parser.allocator) }, - .body = required_body.predicates, - .expressions = required_body.expressions, - .scopes = required_body.scopes, - .variables = null, - }); - while (true) { parser.skipWhiteSpace(); - if (!parser.startsWith("or")) break; - - try parser.consume("or"); - - parser.skipWhiteSpace(); - const body = try parser.ruleBody(); try queries.append(.{ @@ -201,6 +191,10 @@ pub const Parser = struct { .scopes = body.scopes, .variables = null, }); + + parser.skipWhiteSpace(); + + if (!parser.startsWithConsuming("or")) break; } return queries; From 986395093d5ded64a44569c97a8e7b090c9f058c Mon Sep 17 00:00:00 2001 From: Malcolm Still Date: Sun, 31 Mar 2024 00:27:26 +0000 Subject: [PATCH 40/52] Consistent parsing of at least one of N --- biscuit-parser/src/parser.zig | 21 ++++++--------------- 1 file changed, 6 insertions(+), 15 deletions(-) diff --git a/biscuit-parser/src/parser.zig b/biscuit-parser/src/parser.zig index ec8b57c..0d7741a 100644 --- a/biscuit-parser/src/parser.zig +++ b/biscuit-parser/src/parser.zig @@ -225,28 +225,19 @@ pub const Parser = struct { var expressions = std.ArrayList(Expression).init(parser.allocator); var scps = std.ArrayList(Scope).init(parser.allocator); - const required_rule_body = try parser.ruleBodyElement(); - - switch (required_rule_body) { - .predicate => |p| try predicates.append(p), - .expression => |e| try expressions.append(e), - } - while (true) { parser.skipWhiteSpace(); - if (!parser.startsWith(",")) break; - - try parser.consume(","); - - parser.skipWhiteSpace(); + const rule_body = try parser.ruleBodyElement(); - const next_rule_body = try parser.ruleBodyElement(); - - switch (next_rule_body) { + switch (rule_body) { .predicate => |p| try predicates.append(p), .expression => |e| try expressions.append(e), } + + parser.skipWhiteSpace(); + + if (!parser.startsWithConsuming(",")) break; } scopes_blk: { From 940496872317401ccd89321183595d41f3c8837f Mon Sep 17 00:00:00 2001 From: Malcolm Still Date: Sun, 31 Mar 2024 00:29:35 +0000 Subject: [PATCH 41/52] Policy parsing --- biscuit-parser/src/parser.zig | 38 +++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/biscuit-parser/src/parser.zig b/biscuit-parser/src/parser.zig index 0d7741a..720dab7 100644 --- a/biscuit-parser/src/parser.zig +++ b/biscuit-parser/src/parser.zig @@ -1290,3 +1290,41 @@ test "parse check" { try testing.expectError(error.ExpectedPredicateOrExpression, parser.check()); } } + +test "parse policy" { + const testing = std.testing; + + var arena_state = std.heap.ArenaAllocator.init(testing.allocator); + defer arena_state.deinit(); + const arena = arena_state.allocator(); + + { + var parser = Parser.init(arena, "allow if right($0, $1), resource($0), operation($1), $0.contains(\"file\")"); + const policy = try parser.policy(); + + try testing.expectEqual(.allow, policy.kind); + try testing.expectEqual(1, policy.queries.items.len); + + try testing.expectEqualStrings("query", policy.queries.items[0].head.name); + try testing.expectEqualStrings("right", policy.queries.items[0].body.items[0].name); + try testing.expectEqualStrings("resource", policy.queries.items[0].body.items[1].name); + try testing.expectEqualStrings("operation", policy.queries.items[0].body.items[2].name); + + try testing.expectEqualStrings("$0.contains(\"file\")", try std.fmt.allocPrint(arena, "{any}", .{policy.queries.items[0].expressions.items[0]})); + } + + { + var parser = Parser.init(arena, "deny if right($0, $1), resource($0), operation($1), $0.contains(\"file\")"); + const policy = try parser.policy(); + + try testing.expectEqual(.deny, policy.kind); + try testing.expectEqual(1, policy.queries.items.len); + + try testing.expectEqualStrings("query", policy.queries.items[0].head.name); + try testing.expectEqualStrings("right", policy.queries.items[0].body.items[0].name); + try testing.expectEqualStrings("resource", policy.queries.items[0].body.items[1].name); + try testing.expectEqualStrings("operation", policy.queries.items[0].body.items[2].name); + + try testing.expectEqualStrings("$0.contains(\"file\")", try std.fmt.allocPrint(arena, "{any}", .{policy.queries.items[0].expressions.items[0]})); + } +} From bdb6ae71b483f6a09277c45f87a730bed62d7fea Mon Sep 17 00:00:00 2001 From: Malcolm Still Date: Sun, 31 Mar 2024 00:46:58 +0000 Subject: [PATCH 42/52] parser.temporary() --- biscuit-parser/src/parser.zig | 38 ++++++++++++++++++++--------------- 1 file changed, 22 insertions(+), 16 deletions(-) diff --git a/biscuit-parser/src/parser.zig b/biscuit-parser/src/parser.zig index 720dab7..4581b83 100644 --- a/biscuit-parser/src/parser.zig +++ b/biscuit-parser/src/parser.zig @@ -23,6 +23,15 @@ pub const Parser = struct { return .{ .input = input, .allocator = allocator }; } + /// Return a new temporary parser with the current state of parent parser + /// for attempting to parse one of choice of things. + /// + /// E.g. when we parse a term we try parse each subtype of term with a temporary + /// parser. + pub fn temporary(parser: *Parser) Parser { + return Parser.init(parser.allocator, parser.rest()); + } + /// Try to parse fact /// /// E.g. read(1, "hello") will parse successfully, but read($foo, "hello") @@ -60,13 +69,11 @@ pub const Parser = struct { /// /// Does not consume `parser` input on failure. fn term(parser: *Parser, variables: AllowVariables) ParserError!Term { - const rst = parser.rest(); - if (variables == .disallow) { try parser.reject("$"); // Variables are disallowed in a fact term } else { variable_blk: { - var term_parser = Parser.init(parser.allocator, rst); + var term_parser = parser.temporary(); const value = term_parser.variable() catch break :variable_blk; @@ -77,7 +84,7 @@ pub const Parser = struct { } string_blk: { - var term_parser = Parser.init(parser.allocator, rst); + var term_parser = parser.temporary(); const value = term_parser.string() catch break :string_blk; @@ -87,7 +94,7 @@ pub const Parser = struct { } date_blk: { - var term_parser = Parser.init(parser.allocator, rst); + var term_parser = parser.temporary(); const value = term_parser.date() catch break :date_blk; @@ -97,7 +104,7 @@ pub const Parser = struct { } number_blk: { - var term_parser = Parser.init(parser.allocator, rst); + var term_parser = parser.temporary(); const value = term_parser.number(i64) catch break :number_blk; @@ -107,7 +114,7 @@ pub const Parser = struct { } bool_blk: { - var term_parser = Parser.init(parser.allocator, rst); + var term_parser = parser.temporary(); const value = term_parser.boolean() catch break :bool_blk; @@ -117,7 +124,7 @@ pub const Parser = struct { } bytes_blk: { - var term_parser = Parser.init(parser.allocator, rst); + var term_parser = parser.temporary(); const value = term_parser.bytes() catch break :bytes_blk; @@ -127,7 +134,7 @@ pub const Parser = struct { } set_blk: { - var term_parser = Parser.init(parser.allocator, rst); + var term_parser = parser.temporary(); const value = term_parser.set(variables) catch break :set_blk; @@ -155,7 +162,6 @@ pub const Parser = struct { } pub fn check(parser: *Parser) !Check { - // Note the space after const kind: Check.Kind = if (parser.startsWithConsuming("check if")) .one else if (parser.startsWithConsuming("check all")) @@ -241,7 +247,7 @@ pub const Parser = struct { } scopes_blk: { - var scope_parser = Parser.init(parser.allocator, parser.rest()); + var scope_parser = parser.temporary(); const s = scope_parser.scopes(parser.allocator) catch break :scopes_blk; @@ -263,7 +269,7 @@ pub const Parser = struct { /// Does not consume `parser` input on failure. fn ruleBodyElement(parser: *Parser) !union(BodyElementTag) { predicate: Predicate, expression: Expression } { predicate_blk: { - var predicate_parser = Parser.init(parser.allocator, parser.rest()); + var predicate_parser = parser.temporary(); const p = predicate_parser.predicate(.rule) catch break :predicate_blk; @@ -274,7 +280,7 @@ pub const Parser = struct { // Otherwise try parsing an expression expression_blk: { - var expression_parser = Parser.init(parser.allocator, parser.rest()); + var expression_parser = parser.temporary(); const e = expression_parser.expression() catch break :expression_blk; @@ -617,10 +623,10 @@ pub const Parser = struct { parser.skipWhiteSpace(); - if (!parser.startsWith(".")) return e1; - try parser.consume("."); + if (!parser.startsWithConsuming(".")) return e1; const op = try parser.binaryOp7(); + parser.skipWhiteSpace(); try parser.consume("("); @@ -650,7 +656,7 @@ pub const Parser = struct { fn exprTerm(parser: *Parser) ParserError!Expression { // Try to parse unary unary_blk: { - var unary_parser = Parser.init(parser.allocator, parser.rest()); + var unary_parser = parser.temporary(); const p = unary_parser.unary() catch break :unary_blk; From cca2bbd06a95ecca6f3bb0af9721b242b94303bd Mon Sep 17 00:00:00 2001 From: Malcolm Still Date: Sun, 31 Mar 2024 02:08:41 +0100 Subject: [PATCH 43/52] Expression parsing tests --- biscuit-builder/src/expression.zig | 2 +- biscuit-builder/src/term.zig | 18 +++++++++- biscuit-parser/src/parser.zig | 56 +++++++++++++++++++++--------- 3 files changed, 57 insertions(+), 19 deletions(-) diff --git a/biscuit-builder/src/expression.zig b/biscuit-builder/src/expression.zig index 6515038..cc475e5 100644 --- a/biscuit-builder/src/expression.zig +++ b/biscuit-builder/src/expression.zig @@ -159,7 +159,7 @@ pub const Expression = union(ExpressionType) { .value => |v| try writer.print("{any}", .{v}), .unary => |u| { switch (u.op) { - .negate => try writer.print("-{any}", .{u.expression}), + .negate => try writer.print("!{any}", .{u.expression}), .parens => try writer.print("({any})", .{u.expression}), .length => try writer.print("{any}.length()", .{u.expression}), } diff --git a/biscuit-builder/src/term.zig b/biscuit-builder/src/term.zig index 27dde60..e3dcb5d 100644 --- a/biscuit-builder/src/term.zig +++ b/biscuit-builder/src/term.zig @@ -52,7 +52,23 @@ pub const Term = union(TermTag) { .bool => |b| if (b) try writer.print("true", .{}) else try writer.print("false", .{}), .date => |n| try writer.print("{}", .{n}), .bytes => unreachable, - .set => unreachable, + .set => |s| { + try writer.print("[", .{}); + + const count = s.count(); + + var it = s.iterator(); + + var i: usize = 0; + while (it.next()) |t| { + defer i += 1; + try writer.print("{any}", .{t}); + + if (i < count - 1) try writer.print(", ", .{}); + } + + try writer.print("]", .{}); + }, } } diff --git a/biscuit-parser/src/parser.zig b/biscuit-parser/src/parser.zig index 4581b83..82c7bd2 100644 --- a/biscuit-parser/src/parser.zig +++ b/biscuit-parser/src/parser.zig @@ -422,19 +422,18 @@ pub const Parser = struct { return new_set; } + /// Parse an expression + /// + /// This is the top-level expression parsing function. Where + /// other parts of the code call `parser.expression` you know + /// they are parsing a "full" expression. + /// + /// The code uses the "precedence climbing" approach. fn expression(parser: *Parser) ParserError!Expression { - parser.skipWhiteSpace(); - const e = try parser.expr0(); - - return e; - } - - fn expr0(parser: *Parser) ParserError!Expression { var e = try parser.expr1(); while (true) { parser.skipWhiteSpace(); - if (parser.rest().len == 0) break; const op = parser.binaryOp0() catch break; @@ -460,7 +459,6 @@ pub const Parser = struct { while (true) { parser.skipWhiteSpace(); - if (parser.rest().len == 0) break; const op = parser.binaryOp1() catch break; @@ -490,7 +488,6 @@ pub const Parser = struct { while (true) { parser.skipWhiteSpace(); - if (parser.rest().len == 0) break; const op = parser.binaryOp2() catch break; @@ -516,7 +513,6 @@ pub const Parser = struct { while (true) { parser.skipWhiteSpace(); - if (parser.rest().len == 0) break; const op = parser.binaryOp3() catch break; @@ -541,7 +537,6 @@ pub const Parser = struct { while (true) { parser.skipWhiteSpace(); - if (parser.rest().len == 0) break; const op = parser.binaryOp4() catch break; @@ -569,7 +564,6 @@ pub const Parser = struct { while (true) { parser.skipWhiteSpace(); - if (parser.rest().len == 0) break; const op = parser.binaryOp5() catch break; @@ -597,7 +591,6 @@ pub const Parser = struct { while (true) { parser.skipWhiteSpace(); - if (parser.rest().len == 0) break; const op = parser.binaryOp6() catch break; @@ -633,7 +626,7 @@ pub const Parser = struct { parser.skipWhiteSpace(); - const e2 = try parser.expr0(); + const e2 = try parser.expression(); parser.skipWhiteSpace(); @@ -677,7 +670,7 @@ pub const Parser = struct { if (parser.startsWithConsuming("!")) { parser.skipWhiteSpace(); - const e = try parser.expr0(); + const e = try parser.expression(); return try Expression.unary(parser.allocator, .negate, e); } @@ -709,7 +702,7 @@ pub const Parser = struct { parser.skipWhiteSpace(); - const e = try parser.expr0(); + const e = try parser.expression(); parser.skipWhiteSpace(); @@ -1334,3 +1327,32 @@ test "parse policy" { try testing.expectEqualStrings("$0.contains(\"file\")", try std.fmt.allocPrint(arena, "{any}", .{policy.queries.items[0].expressions.items[0]})); } } + +test "parse expression" { + const testing = std.testing; + + var arena_state = std.heap.ArenaAllocator.init(testing.allocator); + defer arena_state.deinit(); + const arena = arena_state.allocator(); + + { + var parser = Parser.init(arena, "$0.contains(\"file\")"); + const expression = try parser.expression(); + + try testing.expectEqualStrings("$0.contains(\"file\")", try std.fmt.allocPrint(arena, "{any}", .{expression})); + } + + { + var parser = Parser.init(arena, "!(1 + 2)"); + const expression = try parser.expression(); + + try testing.expectEqualStrings("!(1 + 2)", try std.fmt.allocPrint(arena, "{any}", .{expression})); + } + + // { + // var parser = Parser.init(arena, "[1].intersection([2]).length().union([3])"); + // const expression = try parser.expression(); + + // try testing.expectEqualStrings("[1].intersection([2]).length().union([3])", try std.fmt.allocPrint(arena, "{any}", .{expression})); + // } +} From bd53fd7102cd0586abdff044dfe61d9fc9f3b4d2 Mon Sep 17 00:00:00 2001 From: Malcolm Still Date: Sun, 31 Mar 2024 02:11:20 +0100 Subject: [PATCH 44/52] Actually, group the binaryOp functions --- biscuit-parser/src/parser.zig | 124 +++++++++++++++++----------------- 1 file changed, 63 insertions(+), 61 deletions(-) diff --git a/biscuit-parser/src/parser.zig b/biscuit-parser/src/parser.zig index 82c7bd2..f109a31 100644 --- a/biscuit-parser/src/parser.zig +++ b/biscuit-parser/src/parser.zig @@ -447,13 +447,6 @@ pub const Parser = struct { return e; } - fn binaryOp0(parser: *Parser) ParserError!Expression.BinaryOp { - if (parser.startsWithConsuming("&&")) return .@"and"; - if (parser.startsWithConsuming("||")) return .@"or"; - - return error.UnexpectedOp; - } - fn expr1(parser: *Parser) ParserError!Expression { var e = try parser.expr2(); @@ -472,17 +465,6 @@ pub const Parser = struct { return e; } - fn binaryOp1(parser: *Parser) ParserError!Expression.BinaryOp { - if (parser.startsWithConsuming("<=")) return .less_or_equal; - if (parser.startsWithConsuming(">=")) return .greater_or_equal; - if (parser.startsWithConsuming("<")) return .less_than; - if (parser.startsWithConsuming(">")) return .greater_than; - if (parser.startsWithConsuming("==")) return .equal; - if (parser.startsWithConsuming("!=")) return .not_equal; - - return error.UnexpectedOp; - } - fn expr2(parser: *Parser) ParserError!Expression { var e = try parser.expr3(); @@ -501,13 +483,6 @@ pub const Parser = struct { return e; } - fn binaryOp2(parser: *Parser) ParserError!Expression.BinaryOp { - if (parser.startsWithConsuming("+")) return .add; - if (parser.startsWithConsuming("-")) return .sub; - - return error.UnexpectedOp; - } - fn expr3(parser: *Parser) ParserError!Expression { var e = try parser.expr4(); @@ -526,12 +501,6 @@ pub const Parser = struct { return e; } - fn binaryOp3(parser: *Parser) ParserError!Expression.BinaryOp { - if (parser.startsWithConsuming("^")) return .bitwise_xor; - - return error.UnexpectedOp; - } - fn expr4(parser: *Parser) ParserError!Expression { var e = try parser.expr5(); @@ -550,15 +519,6 @@ pub const Parser = struct { return e; } - fn binaryOp4(parser: *Parser) ParserError!Expression.BinaryOp { - if (parser.startsWith("|") and !parser.startsWith("||")) { - try parser.consume("|"); - return .bitwise_or; - } - - return error.UnexpectedOp; - } - fn expr5(parser: *Parser) ParserError!Expression { var e = try parser.expr6(); @@ -577,15 +537,6 @@ pub const Parser = struct { return e; } - fn binaryOp5(parser: *Parser) ParserError!Expression.BinaryOp { - if (parser.startsWith("&") and !parser.startsWith("&&")) { - try parser.consume("&"); - return .bitwise_and; - } - - return error.UnexpectedOp; - } - fn expr6(parser: *Parser) ParserError!Expression { var e = try parser.expr7(); @@ -604,13 +555,6 @@ pub const Parser = struct { return e; } - fn binaryOp6(parser: *Parser) ParserError!Expression.BinaryOp { - if (parser.startsWithConsuming("*")) return .mul; - if (parser.startsWithConsuming("/")) return .div; - - return error.UnexpectedOp; - } - fn expr7(parser: *Parser) ParserError!Expression { const e1 = try parser.exprTerm(); @@ -637,11 +581,69 @@ pub const Parser = struct { return try Expression.binary(parser.allocator, op, e1, e2); } + fn binaryOp0(parser: *Parser) ParserError!Expression.BinaryOp { + if (parser.startsWithConsuming("&&")) return .@"and"; + if (parser.startsWithConsuming("||")) return .@"or"; + + return error.UnexpectedOp; + } + + fn binaryOp1(parser: *Parser) ParserError!Expression.BinaryOp { + if (parser.startsWithConsuming("<=")) return .less_or_equal; + if (parser.startsWithConsuming(">=")) return .greater_or_equal; + if (parser.startsWithConsuming("<")) return .less_than; + if (parser.startsWithConsuming(">")) return .greater_than; + if (parser.startsWithConsuming("==")) return .equal; + if (parser.startsWithConsuming("!=")) return .not_equal; + + return error.UnexpectedOp; + } + + fn binaryOp2(parser: *Parser) ParserError!Expression.BinaryOp { + if (parser.startsWithConsuming("+")) return .add; + if (parser.startsWithConsuming("-")) return .sub; + + return error.UnexpectedOp; + } + + fn binaryOp3(parser: *Parser) ParserError!Expression.BinaryOp { + if (parser.startsWithConsuming("^")) return .bitwise_xor; + + return error.UnexpectedOp; + } + + fn binaryOp4(parser: *Parser) ParserError!Expression.BinaryOp { + if (parser.startsWith("|") and !parser.startsWith("||")) { + try parser.consume("|"); + return .bitwise_or; + } + + return error.UnexpectedOp; + } + + fn binaryOp5(parser: *Parser) ParserError!Expression.BinaryOp { + if (parser.startsWith("&") and !parser.startsWith("&&")) { + try parser.consume("&"); + return .bitwise_and; + } + + return error.UnexpectedOp; + } + + fn binaryOp6(parser: *Parser) ParserError!Expression.BinaryOp { + if (parser.startsWithConsuming("*")) return .mul; + if (parser.startsWithConsuming("/")) return .div; + + return error.UnexpectedOp; + } + fn binaryOp7(parser: *Parser) ParserError!Expression.BinaryOp { if (parser.startsWithConsuming("contains")) return .contains; if (parser.startsWithConsuming("starts_with")) return .prefix; if (parser.startsWithConsuming("ends_with")) return .suffix; if (parser.startsWithConsuming("matches")) return .regex; + if (parser.startsWithConsuming("intersection")) return .intersection; + if (parser.startsWithConsuming("union")) return .@"union"; return error.UnexpectedOp; } @@ -1349,10 +1351,10 @@ test "parse expression" { try testing.expectEqualStrings("!(1 + 2)", try std.fmt.allocPrint(arena, "{any}", .{expression})); } - // { - // var parser = Parser.init(arena, "[1].intersection([2]).length().union([3])"); - // const expression = try parser.expression(); + { + var parser = Parser.init(arena, "[1].intersection([2]).length().union([3])"); + const expression = try parser.expression(); - // try testing.expectEqualStrings("[1].intersection([2]).length().union([3])", try std.fmt.allocPrint(arena, "{any}", .{expression})); - // } + try testing.expectEqualStrings("[1].intersection([2]).length().union([3])", try std.fmt.allocPrint(arena, "{any}", .{expression})); + } } From 86ad555abfb83e432267cb5c1084f9259ccb1405 Mon Sep 17 00:00:00 2001 From: Malcolm Still Date: Sun, 31 Mar 2024 03:26:11 +0100 Subject: [PATCH 45/52] Fix precedence, fix chained methods --- biscuit-parser/src/parser.zig | 264 +++++++++++++++++++++++----------- 1 file changed, 179 insertions(+), 85 deletions(-) diff --git a/biscuit-parser/src/parser.zig b/biscuit-parser/src/parser.zig index f109a31..725677f 100644 --- a/biscuit-parser/src/parser.zig +++ b/biscuit-parser/src/parser.zig @@ -73,72 +73,72 @@ pub const Parser = struct { try parser.reject("$"); // Variables are disallowed in a fact term } else { variable_blk: { - var term_parser = parser.temporary(); + var tmp = parser.temporary(); - const value = term_parser.variable() catch break :variable_blk; + const value = tmp.variable() catch break :variable_blk; - parser.offset += term_parser.offset; + parser.offset += tmp.offset; return .{ .variable = value }; } } string_blk: { - var term_parser = parser.temporary(); + var tmp = parser.temporary(); - const value = term_parser.string() catch break :string_blk; + const value = tmp.string() catch break :string_blk; - parser.offset += term_parser.offset; + parser.offset += tmp.offset; return .{ .string = value }; } date_blk: { - var term_parser = parser.temporary(); + var tmp = parser.temporary(); - const value = term_parser.date() catch break :date_blk; + const value = tmp.date() catch break :date_blk; - parser.offset += term_parser.offset; + parser.offset += tmp.offset; return .{ .date = value }; } number_blk: { - var term_parser = parser.temporary(); + var tmp = parser.temporary(); - const value = term_parser.number(i64) catch break :number_blk; + const value = tmp.number(i64) catch break :number_blk; - parser.offset += term_parser.offset; + parser.offset += tmp.offset; return .{ .integer = value }; } bool_blk: { - var term_parser = parser.temporary(); + var tmp = parser.temporary(); - const value = term_parser.boolean() catch break :bool_blk; + const value = tmp.boolean() catch break :bool_blk; - parser.offset += term_parser.offset; + parser.offset += tmp.offset; return .{ .bool = value }; } bytes_blk: { - var term_parser = parser.temporary(); + var tmp = parser.temporary(); - const value = term_parser.bytes() catch break :bytes_blk; + const value = tmp.bytes() catch break :bytes_blk; - parser.offset += term_parser.offset; + parser.offset += tmp.offset; return .{ .bytes = value }; } set_blk: { - var term_parser = parser.temporary(); + var tmp = parser.temporary(); - const value = term_parser.set(variables) catch break :set_blk; + const value = tmp.set(variables) catch break :set_blk; - parser.offset += term_parser.offset; + parser.offset += tmp.offset; return .{ .set = value }; } @@ -247,11 +247,11 @@ pub const Parser = struct { } scopes_blk: { - var scope_parser = parser.temporary(); + var tmp = parser.temporary(); - const s = scope_parser.scopes(parser.allocator) catch break :scopes_blk; + const s = tmp.scopes(parser.allocator) catch break :scopes_blk; - parser.offset += scope_parser.offset; + parser.offset += tmp.offset; scps = s; } @@ -269,22 +269,22 @@ pub const Parser = struct { /// Does not consume `parser` input on failure. fn ruleBodyElement(parser: *Parser) !union(BodyElementTag) { predicate: Predicate, expression: Expression } { predicate_blk: { - var predicate_parser = parser.temporary(); + var tmp = parser.temporary(); - const p = predicate_parser.predicate(.rule) catch break :predicate_blk; + const p = tmp.predicate(.rule) catch break :predicate_blk; - parser.offset += predicate_parser.offset; + parser.offset += tmp.offset; return .{ .predicate = p }; } // Otherwise try parsing an expression expression_blk: { - var expression_parser = parser.temporary(); + var tmp = parser.temporary(); - const e = expression_parser.expression() catch break :expression_blk; + const e = tmp.expression() catch break :expression_blk; - parser.offset += expression_parser.offset; + parser.offset += tmp.offset; return .{ .expression = e }; } @@ -556,13 +556,84 @@ pub const Parser = struct { } fn expr7(parser: *Parser) ParserError!Expression { - const e1 = try parser.exprTerm(); + var e = try parser.expr8(); + + while (true) { + parser.skipWhiteSpace(); + + const op = parser.binaryOp7() catch break; + + parser.skipWhiteSpace(); + + const e2 = try parser.expr8(); + + e = try Expression.binary(parser.allocator, op, e, e2); + } + + return e; + } + + fn expr8(parser: *Parser) ParserError!Expression { + unary_negate_blk: { + var tmp = parser.temporary(); + + const e = tmp.unaryNegate() catch break :unary_negate_blk; + + parser.offset += tmp.offset; + + return e; + } + + expr9_blk: { + var tmp = parser.temporary(); + + const e = tmp.expr9() catch break :expr9_blk; + + parser.offset += tmp.offset; + + return e; + } + + return error.ExpectedUnaryNegateOrMethod; + } + + /// Parse a unary or binary method + fn expr9(parser: *Parser) ParserError!Expression { + var e1 = try parser.exprTerm(); parser.skipWhiteSpace(); if (!parser.startsWithConsuming(".")) return e1; - const op = try parser.binaryOp7(); + while (true) { + blk: { + var tmp = parser.temporary(); + + e1 = tmp.binaryMethod(e1) catch break :blk; + + parser.offset += tmp.offset; + + if (parser.startsWithConsuming(".")) continue; + } + + blk: { + var tmp = parser.temporary(); + + e1 = tmp.unaryMethod(e1) catch break :blk; + + parser.offset += tmp.offset; + + if (parser.startsWithConsuming(".")) continue; + } + + break; + } + + return e1; + } + + fn binaryMethod(parser: *Parser, e1: Expression) ParserError!Expression { + const op = try parser.binaryOp8(); parser.skipWhiteSpace(); @@ -581,14 +652,25 @@ pub const Parser = struct { return try Expression.binary(parser.allocator, op, e1, e2); } + fn unaryMethod(parser: *Parser, e1: Expression) ParserError!Expression { + try parser.consume("length()"); + + return try Expression.unary(parser.allocator, .length, e1); + } + fn binaryOp0(parser: *Parser) ParserError!Expression.BinaryOp { - if (parser.startsWithConsuming("&&")) return .@"and"; if (parser.startsWithConsuming("||")) return .@"or"; return error.UnexpectedOp; } fn binaryOp1(parser: *Parser) ParserError!Expression.BinaryOp { + if (parser.startsWithConsuming("&&")) return .@"and"; + + return error.UnexpectedOp; + } + + fn binaryOp2(parser: *Parser) ParserError!Expression.BinaryOp { if (parser.startsWithConsuming("<=")) return .less_or_equal; if (parser.startsWithConsuming(">=")) return .greater_or_equal; if (parser.startsWithConsuming("<")) return .less_than; @@ -599,13 +681,6 @@ pub const Parser = struct { return error.UnexpectedOp; } - fn binaryOp2(parser: *Parser) ParserError!Expression.BinaryOp { - if (parser.startsWithConsuming("+")) return .add; - if (parser.startsWithConsuming("-")) return .sub; - - return error.UnexpectedOp; - } - fn binaryOp3(parser: *Parser) ParserError!Expression.BinaryOp { if (parser.startsWithConsuming("^")) return .bitwise_xor; @@ -631,13 +706,20 @@ pub const Parser = struct { } fn binaryOp6(parser: *Parser) ParserError!Expression.BinaryOp { + if (parser.startsWithConsuming("+")) return .add; + if (parser.startsWithConsuming("-")) return .sub; + + return error.UnexpectedOp; + } + + fn binaryOp7(parser: *Parser) ParserError!Expression.BinaryOp { if (parser.startsWithConsuming("*")) return .mul; if (parser.startsWithConsuming("/")) return .div; return error.UnexpectedOp; } - fn binaryOp7(parser: *Parser) ParserError!Expression.BinaryOp { + fn binaryOp8(parser: *Parser) ParserError!Expression.BinaryOp { if (parser.startsWithConsuming("contains")) return .contains; if (parser.startsWithConsuming("starts_with")) return .prefix; if (parser.startsWithConsuming("ends_with")) return .suffix; @@ -650,12 +732,12 @@ pub const Parser = struct { fn exprTerm(parser: *Parser) ParserError!Expression { // Try to parse unary - unary_blk: { - var unary_parser = parser.temporary(); + unary_parens_blk: { + var tmp = parser.temporary(); - const p = unary_parser.unary() catch break :unary_blk; + const p = tmp.unaryParens() catch break :unary_parens_blk; - parser.offset += unary_parser.offset; + parser.offset += tmp.offset; return p; } @@ -666,37 +748,47 @@ pub const Parser = struct { return try Expression.value(term1); } - fn unary(parser: *Parser) ParserError!Expression { - parser.skipWhiteSpace(); + // fn unary(parser: *Parser) ParserError!Expression { + // parser.skipWhiteSpace(); - if (parser.startsWithConsuming("!")) { - parser.skipWhiteSpace(); + // if (parser.startsWithConsuming("!")) { + // parser.skipWhiteSpace(); - const e = try parser.expression(); + // const e = try parser.expression(); - return try Expression.unary(parser.allocator, .negate, e); - } + // return try Expression.unary(parser.allocator, .negate, e); + // } - if (parser.startsWith("(")) { - return try parser.unaryParens(); - } + // if (parser.startsWith("(")) { + // return try parser.unaryParens(); + // } - var e: Expression = undefined; - if (parser.term(.allow)) |t1| { - parser.skipWhiteSpace(); - e = try Expression.value(t1); - } else |_| { - e = try parser.unaryParens(); - parser.skipWhiteSpace(); - } + // var e: Expression = undefined; + // if (parser.term(.allow)) |t1| { + // parser.skipWhiteSpace(); + // e = try Expression.value(t1); + // } else |_| { + // e = try parser.unaryParens(); + // parser.skipWhiteSpace(); + // } - if (parser.consume(".length()")) |_| { - return try Expression.unary(parser.allocator, .length, e); - } else |_| { - return error.UnexpectedToken; - } + // if (parser.consume(".length()")) |_| { + // return try Expression.unary(parser.allocator, .length, e); + // } else |_| { + // return error.UnexpectedToken; + // } + + // return error.UnexpectedToken; + // } - return error.UnexpectedToken; + fn unaryNegate(parser: *Parser) ParserError!Expression { + try parser.consume("!"); + + parser.skipWhiteSpace(); + + const e = try parser.expression(); + + return try Expression.unary(parser.allocator, .negate, e); } fn unaryParens(parser: *Parser) ParserError!Expression { @@ -947,6 +1039,8 @@ const ParserError = error{ DisallowedChar, UnexpectedString, ExpectedChar, + ExpectedUnaryNegateOrMethod, + ExpectedUnaryOrBinaryMethod, NoFactTermFound, UnexpectedOp, MissingLeftParen, @@ -1337,24 +1431,24 @@ test "parse expression" { defer arena_state.deinit(); const arena = arena_state.allocator(); - { - var parser = Parser.init(arena, "$0.contains(\"file\")"); - const expression = try parser.expression(); - - try testing.expectEqualStrings("$0.contains(\"file\")", try std.fmt.allocPrint(arena, "{any}", .{expression})); - } - - { - var parser = Parser.init(arena, "!(1 + 2)"); - const expression = try parser.expression(); - - try testing.expectEqualStrings("!(1 + 2)", try std.fmt.allocPrint(arena, "{any}", .{expression})); - } + const inputs = [_][]const u8{ + "1", + "[2]", + "$0.contains(\"file\")", + "!(1 + 2)", + "1 ^ (4 + 6)", + "[1].intersection([2])", + "[1].intersection([2]).length().union([3])", + "1 + 2 * 3 / (4 + 5)", + "[1].length()", + "\"hello\".length()", + }; - { - var parser = Parser.init(arena, "[1].intersection([2]).length().union([3])"); + for (inputs) |input| { + var parser = Parser.init(arena, input); const expression = try parser.expression(); + std.debug.print("input = \"{s}\", output = \"{}\"\n", .{ input, expression }); - try testing.expectEqualStrings("[1].intersection([2]).length().union([3])", try std.fmt.allocPrint(arena, "{any}", .{expression})); + try testing.expectEqualStrings(input, try std.fmt.allocPrint(arena, "{any}", .{expression})); } } From 0f2ab5e978d3fc9c9d80187b46ff56aee94a42b7 Mon Sep 17 00:00:00 2001 From: Malcolm Still Date: Sun, 31 Mar 2024 03:28:20 +0100 Subject: [PATCH 46/52] Clean up --- biscuit-parser/src/parser.zig | 116 ++++++++++++---------------------- 1 file changed, 41 insertions(+), 75 deletions(-) diff --git a/biscuit-parser/src/parser.zig b/biscuit-parser/src/parser.zig index 725677f..9b1534e 100644 --- a/biscuit-parser/src/parser.zig +++ b/biscuit-parser/src/parser.zig @@ -632,6 +632,23 @@ pub const Parser = struct { return e1; } + fn exprTerm(parser: *Parser) ParserError!Expression { + blk: { + var tmp = parser.temporary(); + + const p = tmp.unaryParens() catch break :blk; + + parser.offset += tmp.offset; + + return p; + } + + // Otherwise we expect term + const term1 = try parser.term(.allow); + + return try Expression.value(term1); + } + fn binaryMethod(parser: *Parser, e1: Expression) ParserError!Expression { const op = try parser.binaryOp8(); @@ -658,6 +675,30 @@ pub const Parser = struct { return try Expression.unary(parser.allocator, .length, e1); } + fn unaryNegate(parser: *Parser) ParserError!Expression { + try parser.consume("!"); + + parser.skipWhiteSpace(); + + const e = try parser.expression(); + + return try Expression.unary(parser.allocator, .negate, e); + } + + fn unaryParens(parser: *Parser) ParserError!Expression { + try parser.consume("("); + + parser.skipWhiteSpace(); + + const e = try parser.expression(); + + parser.skipWhiteSpace(); + + try parser.consume(")"); + + return try Expression.unary(parser.allocator, .parens, e); + } + fn binaryOp0(parser: *Parser) ParserError!Expression.BinaryOp { if (parser.startsWithConsuming("||")) return .@"or"; @@ -730,81 +771,6 @@ pub const Parser = struct { return error.UnexpectedOp; } - fn exprTerm(parser: *Parser) ParserError!Expression { - // Try to parse unary - unary_parens_blk: { - var tmp = parser.temporary(); - - const p = tmp.unaryParens() catch break :unary_parens_blk; - - parser.offset += tmp.offset; - - return p; - } - - // Otherwise we expect term - const term1 = try parser.term(.allow); - - return try Expression.value(term1); - } - - // fn unary(parser: *Parser) ParserError!Expression { - // parser.skipWhiteSpace(); - - // if (parser.startsWithConsuming("!")) { - // parser.skipWhiteSpace(); - - // const e = try parser.expression(); - - // return try Expression.unary(parser.allocator, .negate, e); - // } - - // if (parser.startsWith("(")) { - // return try parser.unaryParens(); - // } - - // var e: Expression = undefined; - // if (parser.term(.allow)) |t1| { - // parser.skipWhiteSpace(); - // e = try Expression.value(t1); - // } else |_| { - // e = try parser.unaryParens(); - // parser.skipWhiteSpace(); - // } - - // if (parser.consume(".length()")) |_| { - // return try Expression.unary(parser.allocator, .length, e); - // } else |_| { - // return error.UnexpectedToken; - // } - - // return error.UnexpectedToken; - // } - - fn unaryNegate(parser: *Parser) ParserError!Expression { - try parser.consume("!"); - - parser.skipWhiteSpace(); - - const e = try parser.expression(); - - return try Expression.unary(parser.allocator, .negate, e); - } - - fn unaryParens(parser: *Parser) ParserError!Expression { - try parser.consume("("); - - parser.skipWhiteSpace(); - - const e = try parser.expression(); - - parser.skipWhiteSpace(); - - try parser.consume(")"); - - return try Expression.unary(parser.allocator, .parens, e); - } - fn scopes(parser: *Parser, allocator: std.mem.Allocator) !std.ArrayList(Scope) { try parser.requiredWhiteSpace(); From 13851088e65af086dc241888a8e315a73de8129f Mon Sep 17 00:00:00 2001 From: Malcolm Still Date: Sun, 31 Mar 2024 03:33:25 +0100 Subject: [PATCH 47/52] Clean up --- biscuit-parser/src/parser.zig | 49 +++++++++++++++++------------------ 1 file changed, 24 insertions(+), 25 deletions(-) diff --git a/biscuit-parser/src/parser.zig b/biscuit-parser/src/parser.zig index 9b1534e..f252224 100644 --- a/biscuit-parser/src/parser.zig +++ b/biscuit-parser/src/parser.zig @@ -72,10 +72,10 @@ pub const Parser = struct { if (variables == .disallow) { try parser.reject("$"); // Variables are disallowed in a fact term } else { - variable_blk: { + blk: { var tmp = parser.temporary(); - const value = tmp.variable() catch break :variable_blk; + const value = tmp.variable() catch break :blk; parser.offset += tmp.offset; @@ -83,60 +83,60 @@ pub const Parser = struct { } } - string_blk: { + blk: { var tmp = parser.temporary(); - const value = tmp.string() catch break :string_blk; + const value = tmp.string() catch break :blk; parser.offset += tmp.offset; return .{ .string = value }; } - date_blk: { + blk: { var tmp = parser.temporary(); - const value = tmp.date() catch break :date_blk; + const value = tmp.date() catch break :blk; parser.offset += tmp.offset; return .{ .date = value }; } - number_blk: { + blk: { var tmp = parser.temporary(); - const value = tmp.number(i64) catch break :number_blk; + const value = tmp.number(i64) catch break :blk; parser.offset += tmp.offset; return .{ .integer = value }; } - bool_blk: { + blk: { var tmp = parser.temporary(); - const value = tmp.boolean() catch break :bool_blk; + const value = tmp.boolean() catch break :blk; parser.offset += tmp.offset; return .{ .bool = value }; } - bytes_blk: { + blk: { var tmp = parser.temporary(); - const value = tmp.bytes() catch break :bytes_blk; + const value = tmp.bytes() catch break :blk; parser.offset += tmp.offset; return .{ .bytes = value }; } - set_blk: { + blk: { var tmp = parser.temporary(); - const value = tmp.set(variables) catch break :set_blk; + const value = tmp.set(variables) catch break :blk; parser.offset += tmp.offset; @@ -246,10 +246,10 @@ pub const Parser = struct { if (!parser.startsWithConsuming(",")) break; } - scopes_blk: { + blk: { var tmp = parser.temporary(); - const s = tmp.scopes(parser.allocator) catch break :scopes_blk; + const s = tmp.scopes(parser.allocator) catch break :blk; parser.offset += tmp.offset; @@ -268,10 +268,10 @@ pub const Parser = struct { /// /// Does not consume `parser` input on failure. fn ruleBodyElement(parser: *Parser) !union(BodyElementTag) { predicate: Predicate, expression: Expression } { - predicate_blk: { + blk: { var tmp = parser.temporary(); - const p = tmp.predicate(.rule) catch break :predicate_blk; + const p = tmp.predicate(.rule) catch break :blk; parser.offset += tmp.offset; @@ -279,10 +279,10 @@ pub const Parser = struct { } // Otherwise try parsing an expression - expression_blk: { + blk: { var tmp = parser.temporary(); - const e = tmp.expression() catch break :expression_blk; + const e = tmp.expression() catch break :blk; parser.offset += tmp.offset; @@ -574,20 +574,20 @@ pub const Parser = struct { } fn expr8(parser: *Parser) ParserError!Expression { - unary_negate_blk: { + blk: { var tmp = parser.temporary(); - const e = tmp.unaryNegate() catch break :unary_negate_blk; + const e = tmp.unaryNegate() catch break :blk; parser.offset += tmp.offset; return e; } - expr9_blk: { + blk: { var tmp = parser.temporary(); - const e = tmp.expr9() catch break :expr9_blk; + const e = tmp.expr9() catch break :blk; parser.offset += tmp.offset; @@ -1413,7 +1413,6 @@ test "parse expression" { for (inputs) |input| { var parser = Parser.init(arena, input); const expression = try parser.expression(); - std.debug.print("input = \"{s}\", output = \"{}\"\n", .{ input, expression }); try testing.expectEqualStrings(input, try std.fmt.allocPrint(arena, "{any}", .{expression})); } From ee7973c2dd2001698b6b4889572610583ce0c91a Mon Sep 17 00:00:00 2001 From: Malcolm Still Date: Sun, 31 Mar 2024 03:37:53 +0100 Subject: [PATCH 48/52] Clean up --- biscuit-builder/src/term.zig | 2 +- biscuit-parser/src/parser.zig | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/biscuit-builder/src/term.zig b/biscuit-builder/src/term.zig index e3dcb5d..d26abe1 100644 --- a/biscuit-builder/src/term.zig +++ b/biscuit-builder/src/term.zig @@ -51,7 +51,7 @@ pub const Term = union(TermTag) { .integer => |n| try writer.print("{}", .{n}), .bool => |b| if (b) try writer.print("true", .{}) else try writer.print("false", .{}), .date => |n| try writer.print("{}", .{n}), - .bytes => unreachable, + .bytes => |b| try writer.print("{x}", .{b}), .set => |s| { try writer.print("[", .{}); diff --git a/biscuit-parser/src/parser.zig b/biscuit-parser/src/parser.zig index f252224..a13541e 100644 --- a/biscuit-parser/src/parser.zig +++ b/biscuit-parser/src/parser.zig @@ -1023,7 +1023,7 @@ test "parse predicates" { const arena = arena_state.allocator(); { - var parser = Parser.init(arena, "read(-1, 1, \"hello world\", hex:abcd, true, false, $foo, 2024-03-30T20:48:00Z, [1, 2, 3], [])"); + var parser = Parser.init(arena, "read(-1, 1, \"hello world\", hex:abcd, true, false, $foo, 2024-03-30T20:48:00Z, [1, 2, 3], [], hex:)"); const predicate = try parser.predicate(.rule); try testing.expectEqualStrings("read", predicate.name); @@ -1043,6 +1043,8 @@ test "parse predicates" { const empty_set = predicate.terms.items[9].set; try testing.expectEqual(0, empty_set.count()); + + try testing.expectEqualStrings("", predicate.terms.items[10].bytes); } { From 1d1dbf3a73d1f3070b479ce19e8b41dfebbc0ab5 Mon Sep 17 00:00:00 2001 From: Malcolm Still Date: Sun, 31 Mar 2024 03:47:19 +0100 Subject: [PATCH 49/52] toDatalog --- biscuit-builder/src/term.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/biscuit-builder/src/term.zig b/biscuit-builder/src/term.zig index d26abe1..fb8e10e 100644 --- a/biscuit-builder/src/term.zig +++ b/biscuit-builder/src/term.zig @@ -36,7 +36,7 @@ pub const Term = union(TermTag) { var it = s.iterator(); while (it.next()) |t| { - try datalog_set.add(t); + try datalog_set.add(try t.toDatalog(arena, symbols)); } return .{ .set = datalog_set }; From faa440540fd14dc6d9bfff1838326ac202319fa7 Mon Sep 17 00:00:00 2001 From: Malcolm Still Date: Sun, 31 Mar 2024 03:52:22 +0100 Subject: [PATCH 50/52] Reinstates deinit in datalog structures - I'm in two minds - These are being used in tests but not in the actual code, because we're depending on arena allocation and so don't need to deinit - At the same time it would be nice not to do the arena setup in tests - To some extent it would be nice to know on the individual structures level that we know we can deinit - But it would also be nice _not_ to have the deinit functions to emphasize we're using arena --- biscuit-datalog/src/check.zig | 10 +++++----- biscuit-datalog/src/fact_set.zig | 14 +++++++------- biscuit-datalog/src/origin.zig | 6 ++++-- biscuit-datalog/src/rule_set.zig | 14 +++++++------- biscuit-datalog/src/set.zig | 4 ++-- biscuit-datalog/src/trusted_origins.zig | 4 ++-- 6 files changed, 27 insertions(+), 25 deletions(-) diff --git a/biscuit-datalog/src/check.zig b/biscuit-datalog/src/check.zig index be91c27..9815499 100644 --- a/biscuit-datalog/src/check.zig +++ b/biscuit-datalog/src/check.zig @@ -24,12 +24,12 @@ pub const Check = struct { return .{ .queries = rules, .kind = kind }; } - pub fn deinit(_: *Check) void { - // for (check.queries.items) |*query| { - // query.deinit(); - // } + pub fn deinit(check: *Check) void { + for (check.queries.items) |*query| { + query.deinit(); + } - // check.queries.deinit(); + check.queries.deinit(); } pub fn format(check: Check, comptime _: []const u8, _: std.fmt.FormatOptions, writer: anytype) !void { diff --git a/biscuit-datalog/src/fact_set.zig b/biscuit-datalog/src/fact_set.zig index e5fd040..a0fd9f4 100644 --- a/biscuit-datalog/src/fact_set.zig +++ b/biscuit-datalog/src/fact_set.zig @@ -38,15 +38,15 @@ pub const FactSet = struct { // value as a key, and we try to insert into hashmap that already contains that value, // we will leak the key if we don't detect the existing version and deallocate one of the // keys. - pub fn deinit(_: *FactSet) void { - // var it = fact_set.sets.iterator(); + pub fn deinit(fact_set: *FactSet) void { + var it = fact_set.sets.iterator(); - // while (it.next()) |origin_facts| { - // origin_facts.key_ptr.deinit(); // Okay, in practice this is also giving us incorrect alignment issues - // origin_facts.value_ptr.deinit(); - // } + while (it.next()) |origin_facts| { + origin_facts.key_ptr.deinit(); // Okay, in practice this is also giving us incorrect alignment issues + origin_facts.value_ptr.deinit(); + } - // fact_set.sets.deinit(); + fact_set.sets.deinit(); } pub const Iterator = struct { diff --git a/biscuit-datalog/src/origin.zig b/biscuit-datalog/src/origin.zig index 686ce9f..b9c3a37 100644 --- a/biscuit-datalog/src/origin.zig +++ b/biscuit-datalog/src/origin.zig @@ -23,8 +23,10 @@ pub const Origin = struct { return .{ .block_ids = block_ids }; } - pub fn deinit(_: *Origin) void { - // origin.block_ids.deinit(); + pub fn deinit(origin: *Origin) void { + if (@import("builtin").mode != .Debug) unreachable; // .deinit only used in tests + + origin.block_ids.deinit(); } pub fn format(origin: Origin, comptime _: []const u8, _: std.fmt.FormatOptions, writer: anytype) !void { diff --git a/biscuit-datalog/src/rule_set.zig b/biscuit-datalog/src/rule_set.zig index 61de157..0e156d2 100644 --- a/biscuit-datalog/src/rule_set.zig +++ b/biscuit-datalog/src/rule_set.zig @@ -15,15 +15,15 @@ pub const RuleSet = struct { }; } - pub fn deinit(_: *RuleSet) void { - // var it = rule_set.rules.iterator(); + pub fn deinit(rule_set: *RuleSet) void { + var it = rule_set.rules.iterator(); - // while (it.next()) |entry| { - // entry.key_ptr.deinit(); - // entry.value_ptr.deinit(); - // } + while (it.next()) |entry| { + entry.key_ptr.deinit(); + entry.value_ptr.deinit(); + } - // rule_set.rules.deinit(); + rule_set.rules.deinit(); } pub fn add(rule_set: *RuleSet, origin: u64, scope: TrustedOrigins, rule: Rule) !void { diff --git a/biscuit-datalog/src/set.zig b/biscuit-datalog/src/set.zig index 7c1b9a2..b8f10d3 100644 --- a/biscuit-datalog/src/set.zig +++ b/biscuit-datalog/src/set.zig @@ -45,8 +45,8 @@ pub fn Set(comptime K: type) type { }; } - pub fn deinit(_: *Self) void { - // set.inner.deinit(); + pub fn deinit(set: *Self) void { + set.inner.deinit(); } pub fn clone(set: *const Self) !Self { diff --git a/biscuit-datalog/src/trusted_origins.zig b/biscuit-datalog/src/trusted_origins.zig index 38c50b9..fea1f85 100644 --- a/biscuit-datalog/src/trusted_origins.zig +++ b/biscuit-datalog/src/trusted_origins.zig @@ -14,8 +14,8 @@ pub const TrustedOrigins = struct { return .{ .ids = InnerSet.init(allocator) }; } - pub fn deinit(_: *TrustedOrigins) void { - // trusted_origins.ids.deinit(); + pub fn deinit(trusted_origins: *TrustedOrigins) void { + trusted_origins.ids.deinit(); } pub fn clone(trusted_origins: *const TrustedOrigins) !TrustedOrigins { From c2d833ea3ae0a6931416cc26fd608713b94cf7fb Mon Sep 17 00:00:00 2001 From: Malcolm Still Date: Sun, 31 Mar 2024 04:30:47 +0100 Subject: [PATCH 51/52] FIx scope parsing --- biscuit-parser/src/parser.zig | 34 +++++++++++++++++++++++++--------- 1 file changed, 25 insertions(+), 9 deletions(-) diff --git a/biscuit-parser/src/parser.zig b/biscuit-parser/src/parser.zig index a13541e..ae000d9 100644 --- a/biscuit-parser/src/parser.zig +++ b/biscuit-parser/src/parser.zig @@ -154,7 +154,8 @@ pub const Parser = struct { else return error.UnexpectedPolicyKind; - try parser.requiredWhiteSpace(); + // FIXME: figure out if the space is required or not + // try parser.requiredWhiteSpace(); const queries = try parser.checkBody(); @@ -169,7 +170,8 @@ pub const Parser = struct { else return error.UnexpectedCheckKind; - try parser.requiredWhiteSpace(); + // FIXME: figure out if the space is required or not + // try parser.requiredWhiteSpace(); const queries = try parser.checkBody(); @@ -772,8 +774,6 @@ pub const Parser = struct { } fn scopes(parser: *Parser, allocator: std.mem.Allocator) !std.ArrayList(Scope) { - try parser.requiredWhiteSpace(); - try parser.consume("trusting"); parser.skipWhiteSpace(); @@ -940,11 +940,11 @@ pub const Parser = struct { } /// Skip whitespace but the whitespace is required (i.e. we need at least one space, tab or newline) - fn requiredWhiteSpace(parser: *Parser) !void { - if (!(parser.startsWith(" ") or parser.startsWith("\t") or parser.startsWith("\n"))) return error.ExpectedWhiteSpace; + // fn requiredWhiteSpace(parser: *Parser) !void { + // if (!(parser.startsWith(" ") or parser.startsWith("\t") or parser.startsWith("\n"))) return error.ExpectedWhiteSpace; - parser.skipWhiteSpace(); - } + // parser.skipWhiteSpace(); + // } /// Skip (optional) whitespace fn skipWhiteSpace(parser: *Parser) void { @@ -1344,7 +1344,7 @@ test "parse check" { { var parser = Parser.init(arena, "check if"); - try testing.expectError(error.ExpectedWhiteSpace, parser.check()); + try testing.expectError(error.ExpectedPredicateOrExpression, parser.check()); } { @@ -1352,6 +1352,22 @@ test "parse check" { try testing.expectError(error.ExpectedPredicateOrExpression, parser.check()); } + + { + const input = "check if query(1, 2) trusting ed25519/acdd6d5b53bfee478bf689f8e012fe7988bf755e3d7c5152947abc149bc20189, ed25519/a060270db7e9c9f06e8f9cc33a64e99f6596af12cb01c4b638df8afc7b642463"; + var parser = Parser.init(arena, input); + const check = try parser.check(); + + try testing.expectEqual(.one, check.kind); + try testing.expectEqual(1, check.queries.items.len); + + try testing.expectEqualStrings("query", check.queries.items[0].head.name); + try testing.expectEqualStrings("query", check.queries.items[0].body.items[0].name); + + try testing.expectEqual(2, check.queries.items[0].scopes.items.len); + try testing.expectEqualStrings("acdd6d5b53bfee478bf689f8e012fe7988bf755e3d7c5152947abc149bc20189", &std.fmt.bytesToHex(check.queries.items[0].scopes.items[0].public_key.toBytes(), .lower)); + try testing.expectEqualStrings("a060270db7e9c9f06e8f9cc33a64e99f6596af12cb01c4b638df8afc7b642463", &std.fmt.bytesToHex(check.queries.items[0].scopes.items[1].public_key.toBytes(), .lower)); + } } test "parse policy" { From 83b2d5f0d3fa83fe9fd96556498247b5e63f6f1f Mon Sep 17 00:00:00 2001 From: Malcolm Still Date: Sun, 31 Mar 2024 04:31:40 +0100 Subject: [PATCH 52/52] Rename deinit functions to testDeinit and comment out any deinit calls from other modules --- biscuit-datalog/src/check.zig | 4 ++-- biscuit-datalog/src/fact_set.zig | 8 ++++---- biscuit-datalog/src/origin.zig | 6 ++---- biscuit-datalog/src/rule_set.zig | 6 +++--- biscuit-datalog/src/trusted_origins.zig | 6 +++--- biscuit-datalog/src/world.zig | 2 +- biscuit/src/block.zig | 6 +++--- 7 files changed, 18 insertions(+), 20 deletions(-) diff --git a/biscuit-datalog/src/check.zig b/biscuit-datalog/src/check.zig index 9815499..1722732 100644 --- a/biscuit-datalog/src/check.zig +++ b/biscuit-datalog/src/check.zig @@ -24,9 +24,9 @@ pub const Check = struct { return .{ .queries = rules, .kind = kind }; } - pub fn deinit(check: *Check) void { + pub fn testDeinit(check: *Check) void { for (check.queries.items) |*query| { - query.deinit(); + query.testDeinit(); } check.queries.deinit(); diff --git a/biscuit-datalog/src/fact_set.zig b/biscuit-datalog/src/fact_set.zig index a0fd9f4..d478a47 100644 --- a/biscuit-datalog/src/fact_set.zig +++ b/biscuit-datalog/src/fact_set.zig @@ -38,11 +38,11 @@ pub const FactSet = struct { // value as a key, and we try to insert into hashmap that already contains that value, // we will leak the key if we don't detect the existing version and deallocate one of the // keys. - pub fn deinit(fact_set: *FactSet) void { + pub fn testDeinit(fact_set: *FactSet) void { var it = fact_set.sets.iterator(); while (it.next()) |origin_facts| { - origin_facts.key_ptr.deinit(); // Okay, in practice this is also giving us incorrect alignment issues + origin_facts.key_ptr.testDeinit(); // Okay, in practice this is also giving us incorrect alignment issues origin_facts.value_ptr.deinit(); } @@ -150,7 +150,7 @@ test "FactSet trustedIterator" { const Term = @import("term.zig").Term; var fs = FactSet.init(testing.allocator); - defer fs.deinit(); + defer fs.testDeinit(); var origin = Origin.init(testing.allocator); try origin.insert(0); @@ -184,7 +184,7 @@ test "FactSet trustedIterator" { // With a trusted iterator only trusting [0] we only expect a single fact { var trusted_origins = try TrustedOrigins.defaultOrigins(testing.allocator); - defer trusted_origins.deinit(); + defer trusted_origins.testDeinit(); var count: usize = 0; diff --git a/biscuit-datalog/src/origin.zig b/biscuit-datalog/src/origin.zig index b9c3a37..65130c4 100644 --- a/biscuit-datalog/src/origin.zig +++ b/biscuit-datalog/src/origin.zig @@ -23,9 +23,7 @@ pub const Origin = struct { return .{ .block_ids = block_ids }; } - pub fn deinit(origin: *Origin) void { - if (@import("builtin").mode != .Debug) unreachable; // .deinit only used in tests - + pub fn testDeinit(origin: *Origin) void { origin.block_ids.deinit(); } @@ -96,7 +94,7 @@ test "Origins" { const testing = std.testing; var origins = Origin.init(testing.allocator); - defer origins.deinit(); + defer origins.testDeinit(); try origins.insert(12); try origins.insert(13); diff --git a/biscuit-datalog/src/rule_set.zig b/biscuit-datalog/src/rule_set.zig index 0e156d2..491ac2f 100644 --- a/biscuit-datalog/src/rule_set.zig +++ b/biscuit-datalog/src/rule_set.zig @@ -15,11 +15,11 @@ pub const RuleSet = struct { }; } - pub fn deinit(rule_set: *RuleSet) void { + pub fn testDeinit(rule_set: *RuleSet) void { var it = rule_set.rules.iterator(); while (it.next()) |entry| { - entry.key_ptr.deinit(); + entry.key_ptr.testDeinit(); entry.value_ptr.deinit(); } @@ -44,7 +44,7 @@ test "RuleSet" { const test_log = std.log.scoped(.test_rule_set); var rs = RuleSet.init(testing.allocator); - defer rs.deinit(); + defer rs.testDeinit(); const default_origins = try TrustedOrigins.defaultOrigins(testing.allocator); const rule: Rule = undefined; diff --git a/biscuit-datalog/src/trusted_origins.zig b/biscuit-datalog/src/trusted_origins.zig index fea1f85..eff0067 100644 --- a/biscuit-datalog/src/trusted_origins.zig +++ b/biscuit-datalog/src/trusted_origins.zig @@ -14,7 +14,7 @@ pub const TrustedOrigins = struct { return .{ .ids = InnerSet.init(allocator) }; } - pub fn deinit(trusted_origins: *TrustedOrigins) void { + pub fn testDeinit(trusted_origins: *TrustedOrigins) void { trusted_origins.ids.deinit(); } @@ -124,10 +124,10 @@ test "Trusted origin" { const testing = std.testing; var to = try TrustedOrigins.defaultOrigins(testing.allocator); - defer to.deinit(); + defer to.testDeinit(); var o = Origin.init(testing.allocator); - defer o.deinit(); + defer o.testDeinit(); try o.insert(22); diff --git a/biscuit-datalog/src/world.zig b/biscuit-datalog/src/world.zig index ee925ae..57cc67d 100644 --- a/biscuit-datalog/src/world.zig +++ b/biscuit-datalog/src/world.zig @@ -47,7 +47,7 @@ pub const World = struct { const starting_fact_count = world.fact_set.count(); var new_fact_sets = FactSet.init(world.arena); - defer new_fact_sets.deinit(); + // defer new_fact_sets.deinit(); // Iterate over rules to generate new facts { diff --git a/biscuit/src/block.zig b/biscuit/src/block.zig index 567eec0..4e15b60 100644 --- a/biscuit/src/block.zig +++ b/biscuit/src/block.zig @@ -36,9 +36,9 @@ pub const Block = struct { } pub fn deinit(block: *Block) void { - for (block.checks.items) |*check| check.deinit(); - for (block.rules.items) |*rule| rule.deinit(); - for (block.facts.items) |*fact| fact.deinit(); + // for (block.checks.items) |*check| check.deinit(); + // for (block.rules.items) |*rule| rule.deinit(); + // for (block.facts.items) |*fact| fact.deinit(); block.checks.deinit(); block.rules.deinit();