Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Origins + more test suite passing #7

Merged
merged 47 commits into from
Mar 29, 2024
Merged
Changes from 1 commit
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
190fd93
WIP
malcolmstill Mar 19, 2024
ec48aff
Working test001
malcolmstill Mar 19, 2024
bb0942f
Initial parser
malcolmstill Mar 19, 2024
525d888
Fix parameter name
malcolmstill Mar 20, 2024
40880e4
More illuminating name
malcolmstill Mar 21, 2024
d876d4c
WIP parser
malcolmstill Mar 23, 2024
10432f2
WIP parser
malcolmstill Mar 23, 2024
e7dc323
WIP parser
malcolmstill Mar 23, 2024
7e07bf5
Test 010 passes
malcolmstill Mar 23, 2024
185e678
Test 012 passes
malcolmstill Mar 23, 2024
521bbb9
Test 022 works
malcolmstill Mar 23, 2024
04da1de
Convert bytes and set
malcolmstill Mar 23, 2024
fad0623
Some RFC 3339 support
malcolmstill Mar 24, 2024
7acf870
WIP date
malcolmstill Mar 24, 2024
9d44302
WIP expressions
malcolmstill Mar 24, 2024
59011f7
WIP expression parser
malcolmstill Mar 24, 2024
f5c7dc1
Working expression parse
malcolmstill Mar 24, 2024
b17b962
Fix
malcolmstill Mar 24, 2024
9faa7d8
WIP
malcolmstill Mar 24, 2024
e36ccc6
WIP
malcolmstill Mar 25, 2024
0df1e74
WIP
malcolmstill Mar 25, 2024
e19e375
RuleSet / FactSet
malcolmstill Mar 25, 2024
3d061f8
Trusted facts compiles but does not work
malcolmstill Mar 25, 2024
2524056
Still not working
malcolmstill Mar 26, 2024
9f9fa9d
WIP
malcolmstill Mar 26, 2024
1959667
Fixed iterator (need to be pointers to FactSet, not FactSet by value)…
malcolmstill Mar 26, 2024
ec38967
WIP
malcolmstill Mar 26, 2024
2fd0b47
WIP
malcolmstill Mar 26, 2024
bb37f8c
I have a feeling we shouldn't think about TrustedOrigins as containin…
malcolmstill Mar 26, 2024
283109b
Fix Term.convert
malcolmstill Mar 26, 2024
710f96c
Rule.findMatch: evalute expressions where rule has no body predicates
malcolmstill Mar 26, 2024
9cb5ba4
Parse external key out of block
malcolmstill Mar 26, 2024
d1d7f3b
Verify external signature
malcolmstill Mar 26, 2024
a92c5b0
Test 027 passes
malcolmstill Mar 26, 2024
25ea0f0
More scope stuff
malcolmstill Mar 27, 2024
0aa6dd0
Test 024 third pary works
malcolmstill Mar 28, 2024
fd7aed5
Fix test 025
malcolmstill Mar 28, 2024
3b29527
Partial fix fo r test 026
malcolmstill Mar 28, 2024
286621b
Implement policies / builder expression conversion
malcolmstill Mar 28, 2024
b029049
Fix parser + working test 026...this required some additional mapping…
malcolmstill Mar 28, 2024
5ca2107
Depend on zig-regex + working test 014
malcolmstill Mar 28, 2024
270e63c
Concat
malcolmstill Mar 28, 2024
460f826
Update the samples...eveything now works apart from test 018
malcolmstill Mar 28, 2024
a5a06c0
Latest samples.json + required sample.zig changes
malcolmstill Mar 28, 2024
26028a7
Yay, test018 passes...all the tests pass!
malcolmstill Mar 28, 2024
f8bb0f3
Fix double free
malcolmstill Mar 28, 2024
58cfd30
Let's get all module tests running again
malcolmstill Mar 28, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Trusted facts compiles but does not work
malcolmstill committed Mar 25, 2024
commit 3d061f8fada98f1b433d4a300974ca37c2ca09ba
34 changes: 24 additions & 10 deletions biscuit-datalog/src/combinator.zig
Original file line number Diff line number Diff line change
@@ -4,7 +4,9 @@ const mem = std.mem;
const Fact = @import("fact.zig").Fact;
const Predicate = @import("predicate.zig").Predicate;
const Term = @import("term.zig").Term;
const Set = @import("set.zig").Set;
const FactSet = @import("fact_set.zig").FactSet;
const Origin = @import("origin.zig").Origin;
const TrustedOrigins = @import("origin.zig").TrustedOrigins;
const Expression = @import("expression.zig").Expression;
const MatchedVariables = @import("matched_variables.zig").MatchedVariables;
const SymbolTable = @import("symbol_table.zig").SymbolTable;
@@ -56,11 +58,12 @@ pub const Combinator = struct {
predicates: []Predicate, // List of the predicates so we can generate new Combinators
expressions: []Expression,
current_bindings: ?std.AutoHashMap(u64, Term) = null,
facts: *const Set(Fact),
fact_iterator: Set(Fact).Iterator,
facts: *const FactSet,
trusted_fact_iterator: FactSet.TrustedIterator,
symbols: SymbolTable,
trusted_origins: TrustedOrigins,

pub fn init(id: usize, allocator: mem.Allocator, variables: MatchedVariables, predicates: []Predicate, expressions: []Expression, all_facts: *const Set(Fact), symbols: SymbolTable) !*Combinator {
pub fn init(id: usize, allocator: mem.Allocator, variables: MatchedVariables, predicates: []Predicate, expressions: []Expression, all_facts: *const FactSet, symbols: SymbolTable, trusted_origins: TrustedOrigins) !*Combinator {
std.debug.print("Init combinator[{}]: predicates = {any}\n", .{ id, predicates });
const c = try allocator.create(Combinator);

@@ -73,7 +76,8 @@ pub const Combinator = struct {
.expressions = expressions,
.variables = variables,
.symbols = symbols,
.fact_iterator = all_facts.iterator(),
.trusted_fact_iterator = all_facts.trustedIterator(trusted_origins),
.trusted_origins = trusted_origins,
};

return c;
@@ -84,21 +88,30 @@ pub const Combinator = struct {
combinator.allocator.destroy(combinator);
}

// QUESTION: is the return value guaranteed to be complete? I.e. each variable has been matched with some non-variable term?
/// next returns the next _valid_ combination of variable bindings
pub fn next(combinator: *Combinator) !?MatchedVariables {
pub fn next(combinator: *Combinator) !?struct { Origin, MatchedVariables } {
blk: while (true) {
std.debug.print("next[{}]\n", .{combinator.id});
// Return from next combinator until expended
if (combinator.next_combinator) |c| {
if (try c.next()) |vars| {
return vars;
if (try c.next()) |origin_vars| {
return origin_vars;
} else {
c.deinit();
combinator.next_combinator = null;
continue;
}
}

const fact = combinator.fact_iterator.next() orelse return null;
// Lookup the next (trusted) fact
const origin_fact = combinator.trusted_fact_iterator.next() orelse {
std.debug.print("combinator[{}] trusted fact iterator exhausted\n", .{combinator.id});
return null;
};
const origin = origin_fact.origin.*;
const fact = origin_fact.fact.*;

// Only consider facts that match the current predicate
if (!fact.matchPredicate(combinator.predicates[0])) continue;
std.debug.print("combinator[{}]: fact = {any}\n", .{ combinator.id, fact });
@@ -140,7 +153,7 @@ pub const Combinator = struct {
}
}

return vars;
return .{ origin, vars };
} else {
if (combinator.next_combinator) |c| c.deinit();

@@ -152,6 +165,7 @@ pub const Combinator = struct {
combinator.expressions,
combinator.facts,
combinator.symbols,
combinator.trusted_origins,
);
}
}
128 changes: 119 additions & 9 deletions biscuit-datalog/src/fact_set.zig
Original file line number Diff line number Diff line change
@@ -2,39 +2,149 @@ const std = @import("std");
const Fact = @import("fact.zig").Fact;
const Set = @import("set.zig").Set;
const Origin = @import("origin.zig").Origin;
const TrustedOrigins = @import("origin.zig").TrustedOrigins;

pub const FactSet = struct {
facts: std.AutoHashMap(Origin, Set(Fact)),
sets: std.AutoHashMap(Origin, Set(Fact)),
allocator: std.mem.Allocator,

pub fn init(allocator: std.mem.Allocator) FactSet {
return .{
.facts = std.AutoHashMap(Origin, Set(Fact)).init(allocator),
.sets = std.AutoHashMap(Origin, Set(Fact)).init(allocator),
.allocator = allocator,
};
}

pub fn deinit(fact_set: *FactSet) void {
var it = fact_set.facts.iterator();
var it = fact_set.sets.iterator();

while (it.next()) |entry| {
entry.key_ptr.deinit();
entry.value_ptr.deinit();
while (it.next()) |origin_facts| {
origin_facts.key_ptr.deinit();
origin_facts.value_ptr.deinit();
}

fact_set.facts.deinit();
fact_set.sets.deinit();
}

pub const Iterator = struct {
set_it: std.AutoHashMap(Origin, Set(Fact)).Iterator,
origin_fact_it: ?struct { origin: *Origin, fact_it: Set(Fact).Iterator } = null,

pub fn next(it: *Iterator) ?struct { origin: *Origin, fact: *Fact } {
while (true) {
if (it.origin_fact_it) |origin_fact_it| {
//
const origin = origin_fact_it.origin;
var fact_it = origin_fact_it.fact_it;

const fact = fact_it.next() orelse {
it.origin_fact_it = null;
continue;
};

return .{ .origin = origin, .fact = fact };
} else {
const origin_set = it.set_it.next() orelse return null;

it.origin_fact_it = .{ .origin = origin_set.key_ptr, .fact_it = origin_set.value_ptr.iterator() };

continue;
}

unreachable;
}
}
};

pub fn iterator(fact_set: FactSet) Iterator {
return .{ .set_it = fact_set.sets.iterator() };
}

pub const TrustedIterator = struct {
trusted_origins: TrustedOrigins,
set_it: std.AutoHashMap(Origin, Set(Fact)).Iterator,
origin_fact_it: ?struct { origin: *Origin, fact_it: Set(Fact).Iterator } = null,

pub fn next(it: *TrustedIterator) ?struct { origin: *Origin, fact: *Fact } {
while (true) {
std.debug.print("start\n", .{});
if (it.origin_fact_it) |origin_fact_it| {
const origin = origin_fact_it.origin;
var fact_it = origin_fact_it.fact_it;

std.debug.print("Reading next fact for origin {any}\n", .{origin});
const fact = fact_it.next() orelse {
std.debug.print("no more facts in {any}\n", .{origin});
it.origin_fact_it = null;

std.debug.print("continue ultra\n", .{});
continue;
};

std.debug.print("Gotta be here?\n", .{});
return .{ .origin = origin, .fact = fact };
} else {
std.debug.assert(it.origin_fact_it == null);

const origin_set = it.set_it.next() orelse return null;
const origin = origin_set.key_ptr;

// If we don't trust the origin of this set, we start the loop again
if (!it.trusted_origins.containsAll(origin.*)) {
std.debug.print("continue foxbat\n", .{});
continue;
}
defer std.debug.assert(it.origin_fact_it != null);

std.debug.print("here\n", .{});
it.origin_fact_it = .{
.origin = origin,
.fact_it = origin_set.value_ptr.iterator(),
};

std.debug.print("continue omega\n", .{});
continue;
}

unreachable;
}
}
};

/// Return an iterator over facts that match the trusted origin.
pub fn trustedIterator(fact_set: FactSet, trusted_origins: TrustedOrigins) TrustedIterator {
std.debug.print("Making trusted iterator\n", .{});
return .{ .set_it = fact_set.sets.iterator(), .trusted_origins = trusted_origins };
}

/// Return the total number of facts in the fact set
pub fn count(fact_set: *FactSet) usize {
var n: usize = 0;

var it = fact_set.sets.valueIterator();
while (it.next()) |facts| {
n += facts.count();
}

return n;
}

pub fn add(fact_set: *FactSet, origin: Origin, fact: Fact) !void {
if (fact_set.facts.getEntry(origin)) |entry| {
if (fact_set.sets.getEntry(origin)) |entry| {
try entry.value_ptr.add(fact);
} else {
var set = Set(Fact).init(fact_set.allocator);
try set.add(fact);

try fact_set.facts.put(origin, set);
try fact_set.sets.put(origin, set);
}
}

pub fn contains(fact_set: *const FactSet, origin: Origin, fact: Fact) bool {
const set = fact_set.sets.get(origin) orelse return false;

return set.contains(fact);
}
};

test "FactSet" {
2 changes: 2 additions & 0 deletions biscuit-datalog/src/main.zig
Original file line number Diff line number Diff line change
@@ -11,6 +11,8 @@ pub const symbol_table = @import("symbol_table.zig");
pub const SymbolTable = @import("symbol_table.zig").SymbolTable;
pub const Term = @import("term.zig").Term;
pub const Check = @import("check.zig").Check;
pub const Origin = @import("origin.zig").Origin;
pub const TrustedOrigins = @import("origin.zig").TrustedOrigins;
pub const world = @import("world.zig");

test {
2 changes: 1 addition & 1 deletion biscuit-datalog/src/matched_variables.zig
Original file line number Diff line number Diff line change
@@ -31,7 +31,7 @@ const Term = @import("term.zig").Term;
pub const MatchedVariables = struct {
variables: std.AutoHashMap(u32, ?Term),

pub fn init(allocator: mem.Allocator, rule: *Rule) !MatchedVariables {
pub fn init(allocator: mem.Allocator, rule: *const Rule) !MatchedVariables {
var variables = std.AutoHashMap(u32, ?Term).init(allocator);

// Add all variables in predicates in the rule's body to variable set
67 changes: 56 additions & 11 deletions biscuit-datalog/src/origin.zig
Original file line number Diff line number Diff line change
@@ -6,14 +6,43 @@ const Scope = @import("scope.zig").Scope;
pub const Origin = struct {
block_ids: std.AutoHashMap(usize, void),

// Authorizer id is maximum int storable in u64
pub const AuthorizerId = std.math.maxInt(u64);

pub fn init(allocator: mem.Allocator) Origin {
return .{ .block_ids = std.AutoHashMap(usize, void).init(allocator) };
}

// pub fn initWithId(allocator: mem.Allocator, block_id: usize) !Origin {
// var block_ids = std.AutoHashMap(usize, void).init(allocator);

// try block_ids.put(block_id, {});

// return .{ .block_ids = block_ids };
// }

pub fn deinit(origin: *Origin) void {
origin.block_ids.deinit();
}

pub fn format(origin: Origin, comptime _: []const u8, _: std.fmt.FormatOptions, writer: anytype) !void {
var it = origin.block_ids.keyIterator();

try writer.print("[", .{});
while (it.next()) |block_id| {
try writer.print("{}", .{block_id.*});
}
try writer.print("]", .{});
}

pub fn clone(origin: *const Origin) !Origin {
return .{ .block_ids = try origin.block_ids.clone() };
}

// pub fn authorizer(allocator: mem.Allocator) !Origin {
// return try Origin.initWithId(allocator, AuthorizerId);
// }

pub fn insert(origin: *Origin, block_id: usize) !void {
try origin.block_ids.put(block_id, {});
}
@@ -39,6 +68,10 @@ pub const TrustedOrigins = struct {
trusted_origins.origin.block_ids.deinit();
}

pub fn clone(trusted_origins: *const TrustedOrigins) !TrustedOrigins {
return .{ .origin = try trusted_origins.origin.clone() };
}

/// Return a TrustedOrigins default of trusting the authority block (0)
/// and the authorizer (max int).
pub fn defaultOrigins(allocator: mem.Allocator) !TrustedOrigins {
@@ -57,31 +90,30 @@ pub const TrustedOrigins = struct {
default_origins: TrustedOrigins,
current_block: usize,
public_key_to_block_id: std.AutoHashMap(usize, std.ArrayList(usize)),
) TrustedOrigins {
const max_int = std.math.maxInt(usize);
) !TrustedOrigins {
_ = public_key_to_block_id;

if (rule_scopes.len == 0) {
var origins = default_origins.clone();
var trusted_origins = try default_origins.clone();

try origins.insert(current_block);
try origins.insert(max_int);
try trusted_origins.origin.insert(current_block);
try trusted_origins.origin.insert(Origin.AuthorizerId);

return origins;
return trusted_origins;
}

var trusted_origins = TrustedOrigins.init(allocator);
trusted_origins.origin.insert(max_int);
trusted_origins.origin.insert(current_block);
try trusted_origins.origin.insert(Origin.AuthorizerId);
try trusted_origins.origin.insert(current_block);

for (rule_scopes) |scope| {
switch (scope) {
.authority => trusted_origins.origin.insert(0),
.authority => try trusted_origins.origin.insert(0),
.previous => {
if (current_block == max_int) continue;
if (current_block == Origin.AuthorizerId) continue;

for (0..current_block + 1) |i| {
try trusted_origins.origins.insert(i);
try trusted_origins.origin.insert(i);
}
},
.public_key => |public_key_id| {
@@ -94,4 +126,17 @@ pub const TrustedOrigins = struct {

return trusted_origins;
}

/// Check that TrustedOrigins contain _all_ origin ids in fact_origin
pub fn containsAll(trusted_origins: TrustedOrigins, fact_origin: Origin) bool {
var origin_it = fact_origin.block_ids.keyIterator();

while (origin_it.next()) |origin_id| {
if (trusted_origins.origin.block_ids.contains(origin_id.*)) continue;

return false;
}

return true;
}
};
22 changes: 15 additions & 7 deletions biscuit-datalog/src/rule.zig
Original file line number Diff line number Diff line change
@@ -4,13 +4,15 @@ const meta = std.meta;
const schema = @import("biscuit-schema");
const Set = @import("set.zig").Set;
const Fact = @import("fact.zig").Fact;
const FactSet = @import("fact_set.zig").FactSet;
const Predicate = @import("predicate.zig").Predicate;
const Term = @import("term.zig").Term;
const SymbolTable = @import("symbol_table.zig").SymbolTable;
const MatchedVariables = @import("matched_variables.zig").MatchedVariables;
const Combinator = @import("combinator.zig").Combinator;
const Scope = @import("scope.zig").Scope;
const Expression = @import("expression.zig").Expression;
const TrustedOrigins = @import("origin.zig").TrustedOrigins;

pub const Rule = struct {
head: Predicate,
@@ -111,7 +113,7 @@ pub const Rule = struct {
/// ```
///
/// ...and we add it to the set of facts (the set will take care of deduplication)
pub fn apply(rule: *Rule, allocator: mem.Allocator, facts: *const Set(Fact), new_facts: *Set(Fact), symbols: SymbolTable) !void {
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();

@@ -120,10 +122,13 @@ pub const Rule = struct {

// TODO: if body is empty stuff

var it = try Combinator.init(0, allocator, matched_variables, rule.body.items, rule.expressions.items, facts, symbols);
var it = try Combinator.init(0, allocator, matched_variables, rule.body.items, rule.expressions.items, facts, symbols, trusted_origins);
defer it.deinit();

blk: while (try it.next()) |*bindings| {
blk: while (try it.next()) |*origin_bindings| {
const origin = origin_bindings[0];
const bindings = origin_bindings[1];

// TODO: Describe why clonedWithAllocator? More generally, describe in comment the overall
// lifetimes / memory allocation approach during evaluation.
var predicate = try rule.head.cloneWithAllocator(allocator);
@@ -142,14 +147,17 @@ pub const Rule = struct {
const fact = Fact.init(predicate);
std.debug.print("adding new fact = {any}\n", .{fact});

var new_origin = try origin.clone();
try new_origin.insert(origin_id);

// 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(fact)) continue;
if (new_facts.contains(new_origin, fact)) continue;

try new_facts.add(try fact.clone());
try new_facts.add(new_origin, try fact.clone());
}
}

@@ -158,7 +166,7 @@ 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 Set(Fact), symbols: SymbolTable) !bool {
pub fn findMatch(rule: *Rule, allocator: mem.Allocator, facts: *const FactSet, symbols: SymbolTable, trusted_origins: TrustedOrigins) !bool {
std.debug.print("\nrule.findMatch on {any}\n", .{rule});
var arena = std.heap.ArenaAllocator.init(allocator);
defer arena.deinit();
@@ -171,7 +179,7 @@ pub const Rule = struct {
// }
// }

var it = try Combinator.init(0, allocator, matched_variables, rule.body.items, rule.expressions.items, facts, symbols);
var it = try Combinator.init(0, allocator, matched_variables, rule.body.items, rule.expressions.items, facts, symbols, trusted_origins);
defer it.deinit();

return try it.next() != null;
85 changes: 52 additions & 33 deletions biscuit-datalog/src/world.zig
Original file line number Diff line number Diff line change
@@ -1,16 +1,19 @@
const std = @import("std");
const mem = std.mem;
const Fact = @import("fact.zig").Fact;
const Origin = @import("origin.zig").Origin;
const Rule = @import("rule.zig").Rule;

const Set = @import("set.zig").Set;
const FactSet = @import("fact_set.zig").FactSet;
const RuleSet = @import("rule_set.zig").RuleSet;
const TrustedOrigins = @import("origin.zig").TrustedOrigins;
const RunLimits = @import("run_limits.zig").RunLimits;
const SymbolTable = @import("symbol_table.zig").SymbolTable;

pub const World = struct {
allocator: mem.Allocator,
facts: Set(Fact),
rules: std.ArrayList(Rule),
fact_set: FactSet,
rule_set: RuleSet,
symbols: std.ArrayList([]const u8),

/// init world
@@ -26,20 +29,16 @@ pub const World = struct {
pub fn init(allocator: mem.Allocator) World {
return .{
.allocator = allocator,
.facts = Set(Fact).init(allocator),
.rules = std.ArrayList(Rule).init(allocator),
.fact_set = FactSet.init(allocator),
.rule_set = RuleSet.init(allocator),
.symbols = std.ArrayList([]const u8).init(allocator),
};
}

pub fn deinit(world: *World) void {
var it = world.facts.iterator();
while (it.next()) |fact| {
fact.deinit();
}
world.symbols.deinit();
world.rules.deinit();
world.facts.deinit();
world.rule_set.deinit();
world.fact_set.deinit();
}

pub fn run(world: *World, symbols: SymbolTable) !void {
@@ -49,49 +48,69 @@ pub const World = struct {
pub fn runWithLimits(world: *World, symbols: SymbolTable, limits: RunLimits) !void {
std.debug.print("runWithLimits\n", .{});
for (0..limits.max_iterations) |_| {
const starting_fact_count = world.facts.count();
const starting_fact_count = world.fact_set.count();

var new_facts = Set(Fact).init(world.allocator);
var new_fact_sets = FactSet.init(world.allocator);
defer {
var it = new_facts.iterator();
while (it.next()) |fact| fact.deinit();
new_facts.deinit();
var it = new_fact_sets.iterator();
while (it.next()) |origin_fact| {
origin_fact.origin.deinit();
origin_fact.fact.deinit();
}
new_fact_sets.deinit();
}

for (world.rules.items) |*rule| {
try rule.apply(world.allocator, &world.facts, &new_facts, symbols);
// Iterate over rules to generate new facts
{
var it = world.rule_set.rules.iterator();

while (it.next()) |origin_set| {
const trusted_origins = origin_set.key_ptr.*;
const set = origin_set.value_ptr;

for (set.items) |*origin_rule| {
const rule = origin_rule.rule;
const origin_id = origin_rule.origin;

try rule.apply(world.allocator, origin_id, &world.fact_set, &new_fact_sets, symbols, trusted_origins);
}
}
}

var it = new_facts.iterator();
while (it.next()) |fact| {
if (world.facts.contains(fact.*)) continue;
try world.facts.add(try fact.cloneWithAllocator(world.allocator));
var it = new_fact_sets.iterator();
while (it.next()) |origin_fact| {
const origin = origin_fact.origin.*;
const fact = origin_fact.fact.*;
if (world.fact_set.contains(origin, fact)) continue;

try world.fact_set.add(origin, try fact.cloneWithAllocator(world.allocator));
}

std.debug.print("starting_fact_count = {}, world.facts.count() = {}\n", .{ starting_fact_count, world.facts.count() });
std.debug.print("starting_fact_count = {}, world.facts.count() = {}\n", .{ starting_fact_count, world.fact_set.count() });
// If we haven't generated any new facts, we're done.
if (starting_fact_count == world.facts.count()) {
if (starting_fact_count == world.fact_set.count()) {
std.debug.print("No new facts!\n", .{});
return;
}

if (world.facts.count() > limits.max_facts) return error.TooManyFacts;
if (world.fact_set.count() > limits.max_facts) return error.TooManyFacts;
}

return error.TooManyIterations;
}

pub fn addFact(world: *World, fact: Fact) !void {
std.debug.print("world: adding fact = {any}\n", .{fact});
try world.facts.add(fact);
/// Add fact with origin to world
pub fn addFact(world: *World, origin: Origin, fact: Fact) !void {
std.debug.print("world: adding fact = {any} ({any}) \n", .{ fact, origin });
try world.fact_set.add(origin, fact);
}

pub fn addRule(world: *World, rule: Rule) !void {
std.debug.print("world: adding rule = {any}\n", .{rule});
try world.rules.append(rule);
pub fn addRule(world: *World, origin_id: usize, scope: TrustedOrigins, rule: Rule) !void {
std.debug.print("world: adding rule = {any} ({}, {any})\n", .{ rule, origin_id, scope });
try world.rule_set.add(origin_id, scope, rule);
}

pub fn queryMatch(world: *World, rule: *Rule, symbols: SymbolTable) !bool {
return rule.findMatch(world.allocator, &world.facts, symbols);
pub fn queryMatch(world: *World, rule: *Rule, symbols: SymbolTable, trusted_origins: TrustedOrigins) !bool {
return rule.findMatch(world.allocator, &world.fact_set, symbols, trusted_origins);
}
};
145 changes: 137 additions & 8 deletions biscuit/src/authorizer.zig
Original file line number Diff line number Diff line change
@@ -2,8 +2,11 @@ const std = @import("std");
const mem = std.mem;
const Biscuit = @import("biscuit.zig").Biscuit;
const World = @import("biscuit-datalog").world.World;
const Origin = @import("biscuit-datalog").Origin;
const TrustedOrigins = @import("biscuit-datalog").TrustedOrigins;
const Check = @import("biscuit-datalog").check.Check;
const SymbolTable = @import("biscuit-datalog").symbol_table.SymbolTable;
const Scope = @import("biscuit-datalog").Scope;
const Parser = @import("biscuit-parser").Parser;
const builder = @import("biscuit-builder");

@@ -13,6 +16,8 @@ pub const Authorizer = struct {
biscuit: ?Biscuit,
world: World,
symbols: SymbolTable,
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 {
return .{
@@ -21,19 +26,42 @@ pub const Authorizer = struct {
.biscuit = biscuit,
.world = World.init(allocator),
.symbols = SymbolTable.init("authorizer", allocator),
.public_key_to_block_id = std.AutoHashMap(usize, std.ArrayList(usize)).init(allocator),
.scopes = std.ArrayList(Scope).init(allocator),
};
}

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();

{
var it = authorizer.public_key_to_block_id.iterator();
while (it.next()) |entry| {
entry.value_ptr.deinit();
}

authorizer.public_key_to_block_id.deinit();
}
}

pub fn authorizerTrustedOrigins(authorizer: *Authorizer) !TrustedOrigins {
return try TrustedOrigins.fromScopes(
authorizer.allocator,
authorizer.scopes.items,
try TrustedOrigins.defaultOrigins(authorizer.allocator),
Origin.AuthorizerId,
authorizer.public_key_to_block_id,
);
}

/// 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);
@@ -42,9 +70,13 @@ pub const Authorizer = struct {

std.debug.print("fact = {any}\n", .{fact});

try authorizer.world.addFact(try fact.convert(authorizer.allocator, &authorizer.symbols));
var origin = Origin.init(authorizer.allocator);
try origin.insert(Origin.AuthorizerId);

try authorizer.world.addFact(origin, try fact.convert(authorizer.allocator, &authorizer.symbols));
}

/// Add check from string to authorizer
pub fn addCheck(authorizer: *Authorizer, input: []const u8) !void {
var parser = Parser.init(authorizer.allocator, input);

@@ -77,12 +109,69 @@ 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| {
for (biscuit.authority.facts.items) |fact| {
try authorizer.world.addFact(try fact.convert(&biscuit.authority.symbols, &authorizer.symbols));
for (biscuit.authority.facts.items) |authority_fact| {
const fact = try authority_fact.convert(&biscuit.authority.symbols, &authorizer.symbols);
var origin = Origin.init(authorizer.allocator);
try origin.insert(0);

try authorizer.world.addFact(origin, fact);
}

for (biscuit.authority.rules.items) |rule| {
try authorizer.world.addRule(try rule.convert(&biscuit.authority.symbols, &authorizer.symbols));
const authority_trusted_origins = try TrustedOrigins.fromScopes(
authorizer.allocator,
biscuit.authority.scopes.items,
try TrustedOrigins.defaultOrigins(authorizer.allocator),
0,
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.convert(&biscuit.authority.symbols, &authorizer.symbols);

// A authority block's rule trusts
const rule_trusted_origins = try TrustedOrigins.fromScopes(
authorizer.allocator,
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.convert(&biscuit.symbols, &authorizer.symbols);

var origin = Origin.init(authorizer.allocator);
try origin.insert(block_id);

try authorizer.world.addFact(origin, fact);
}

const block_trusted_origins = try TrustedOrigins.fromScopes(
authorizer.allocator,
block.scopes.items,
try TrustedOrigins.defaultOrigins(authorizer.allocator),
block_id,
authorizer.public_key_to_block_id,
);

for (block.rules.items) |block_rule| {
const rule = try block_rule.convert(&biscuit.authority.symbols, &authorizer.symbols);

const block_rule_trusted_origins = try TrustedOrigins.fromScopes(
authorizer.allocator,
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);
}
}
}

@@ -95,7 +184,15 @@ pub const Authorizer = struct {
const check = try c.convert(authorizer.allocator, &authorizer.symbols);

for (check.queries.items, 0..) |*query, check_id| {
const is_match = try authorizer.world.queryMatch(query, authorizer.symbols);
const rule_trusted_origins = try TrustedOrigins.fromScopes(
authorizer.allocator,
query.scopes.items,
try authorizer.authorizerTrustedOrigins(),
Origin.AuthorizerId,
authorizer.public_key_to_block_id,
);

const is_match = try authorizer.world.queryMatch(query, authorizer.symbols, rule_trusted_origins);

if (!is_match) try errors.append(.{ .failed_authority_check = .{ .check_id = check_id } });
std.debug.print("match {any} = {}\n", .{ query, is_match });
@@ -104,12 +201,28 @@ pub const Authorizer = struct {

// 4. Run checks in the biscuit's authority block
if (authorizer.biscuit) |biscuit| {
const authority_trusted_origins = try TrustedOrigins.fromScopes(
authorizer.allocator,
biscuit.authority.scopes.items,
try TrustedOrigins.defaultOrigins(authorizer.allocator),
0,
authorizer.public_key_to_block_id,
);

for (biscuit.authority.checks.items) |c| {
const check = try c.convert(&biscuit.symbols, &authorizer.symbols);
std.debug.print("{any}\n", .{check});

for (check.queries.items, 0..) |*query, check_id| {
const is_match = try authorizer.world.queryMatch(query, authorizer.symbols);
const rule_trusted_origins = try TrustedOrigins.fromScopes(
authorizer.allocator,
query.scopes.items,
authority_trusted_origins,
0,
authorizer.public_key_to_block_id,
);

const is_match = try authorizer.world.queryMatch(query, authorizer.symbols, rule_trusted_origins);

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 });
@@ -122,6 +235,14 @@ pub const Authorizer = struct {
// 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.allocator,
block.scopes.items,
try TrustedOrigins.defaultOrigins(authorizer.allocator),
block_id,
authorizer.public_key_to_block_id,
);

std.debug.print("block = {any}\n", .{block});

for (block.checks.items, 0..) |c, check_id| {
@@ -130,7 +251,15 @@ pub const Authorizer = struct {
std.debug.print("check = {any}\n", .{check});

for (check.queries.items) |*query| {
const is_match = try authorizer.world.queryMatch(query, authorizer.symbols);
const rule_trusted_origins = try TrustedOrigins.fromScopes(
authorizer.allocator,
query.scopes.items,
block_trusted_origins,
block_id,
authorizer.public_key_to_block_id,
);

const is_match = try authorizer.world.queryMatch(query, authorizer.symbols, rule_trusted_origins);

if (!is_match) try errors.append(.{ .failed_block_check = .{ .block_id = block_id, .check_id = check_id } });

28 changes: 16 additions & 12 deletions biscuit/src/block.zig
Original file line number Diff line number Diff line change
@@ -4,6 +4,7 @@ const schema = @import("biscuit-schema");
const Fact = @import("biscuit-datalog").fact.Fact;
const Rule = @import("biscuit-datalog").rule.Rule;
const Check = @import("biscuit-datalog").check.Check;
const Scope = @import("biscuit-datalog").Scope;
const SymbolTable = @import("biscuit-datalog").symbol_table.SymbolTable;
const MIN_SCHEMA_VERSION = format.serialized_biscuit.MIN_SCHEMA_VERSION;
const MAX_SCHEMA_VERSION = format.serialized_biscuit.MAX_SCHEMA_VERSION;
@@ -15,6 +16,7 @@ pub const Block = struct {
facts: std.ArrayList(Fact),
rules: std.ArrayList(Rule),
checks: std.ArrayList(Check),
scopes: std.ArrayList(Scope),

pub fn init(allocator: std.mem.Allocator) Block {
return .{
@@ -24,16 +26,29 @@ pub const Block = struct {
.facts = std.ArrayList(Fact).init(allocator),
.rules = std.ArrayList(Rule).init(allocator),
.checks = std.ArrayList(Check).init(allocator),
.scopes = std.ArrayList(Scope).init(allocator),
};
}

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();

block.checks.deinit();
block.rules.deinit();
block.facts.deinit();
block.scopes.deinit();
block.symbols.deinit();
}

/// Given a blocks contents as bytes, derserialize into runtime block
pub fn fromBytes(allocator: std.mem.Allocator, data: []const u8, symbols: *SymbolTable) !Block {
std.debug.print("Block.fromBytes\n", .{});
const decoded_block = try schema.decodeBlock(allocator, data);
defer decoded_block.deinit();

var block = init(allocator);
var block = Block.init(allocator);
errdefer block.deinit();

const version = decoded_block.version orelse return error.ExpectedVersion;
@@ -62,17 +77,6 @@ pub const Block = struct {
return block;
}

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();

block.checks.deinit();
block.rules.deinit();
block.facts.deinit();
block.symbols.deinit();
}

pub fn format(block: Block, comptime _: []const u8, _: std.fmt.FormatOptions, writer: anytype) !void {
try writer.print("block:\n", .{});
try writer.print(" version: {}\n", .{block.version});