╭─────────────╮ │scroll to top│ ╰─────────────╯ ╭ Grant.zig ────────────────────────────────────────────────────╮ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ╰───────────────────────────────────────────────────────────────╯
╭ find file ─ ─ ─ ─ ─ ─ ─ ─ ─ ─╮ ╭────╮ ╭ clone ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─╮ │ │ │http│ ssh │ │ ╰ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─╯ ╰────╯ ╰ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─╯
2 const evt = @import("../event.zig"); 3 const xit = @import("xit"); 4 const hash = xit.hash; 5 6 // what the grant gives access to, held in the same user repo 7 target_id: [evt.event_id_size * 2]u8, 8 // who it gives access to 9 user_id: [evt.event_id_size * 2]u8, 10 role: evt.Repo.Role, 11 12 // what the db stores: the event's data plus the commit-derived fields 13 pub const Record = struct { 14 event: Self, 15 removed: bool = false, 16 created_order: u64 = 0, 17 updated_order: u64 = 0, 18 }; 19 20 const Self = @This(); 21 22 // the moment keys `evt.merge` reads and writes for this kind 23 pub const merge_policy: evt.MergePolicy = .target_wins; 24 pub const record_map_key = "event-id->grant"; 25 pub const all_id_set_key = "grant-id-set"; 26 pub const target_to_grant_id_set_key = "target->grant-id-set"; 27 28 // the id of the one grant a user can hold on a target 29 pub fn idOf(target_id: *const [evt.event_id_size]u8, user_id: *const [evt.event_id_size]u8) [evt.event_id_size]u8 { 30 var hasher = std.crypto.hash.sha2.Sha256.init(.{}); 31 // the kind name keeps derived ids of different kinds apart 32 hasher.update(@tagName(evt.EventKind.grant)); 33 hasher.update(target_id); 34 hasher.update(user_id); 35 var digest: [std.crypto.hash.sha2.Sha256.digest_length]u8 = undefined; 36 hasher.final(&digest); 37 return digest[0..evt.event_id_size].*; 38 } 39 40 // only what the event says about itself is checked. a grant naming a missing 41 // target or user is inert. 42 fn validate(event_id: *const [evt.event_id_size]u8, record: Record) !void { 43 const target_id = try evt.parseEventId(&record.event.target_id); 44 const user_id = try evt.parseEventId(&record.event.user_id); 45 if (!std.mem.eql(u8, event_id, &idOf(&target_id, &user_id))) return error.InvalidGrantId; 46 } 47 48 pub fn consume( 49 comptime DB: type, 50 comptime hash_kind: hash.HashKind, 51 haxy_moment: DB.HashMap(.read_write), 52 event_id: *const [evt.event_id_size]u8, 53 record_maybe: ?Record, 54 arena: *std.heap.ArenaAllocator, 55 _: ?[]const u8, 56 ) !void { 57 const grant_key = hash.hashInt(hash_kind, event_id); 58 const records = try DB.HashMap(.read_write).init(try haxy_moment.putCursor(hash.hashInt(hash_kind, record_map_key))); 59 const by_target = try DB.HashMap(.read_write).init(try haxy_moment.putCursor(hash.hashInt(hash_kind, target_to_grant_id_set_key))); 60 61 var existing_maybe: ?Record = null; 62 const existing_cursor_maybe = try records.getCursor(grant_key); 63 if (existing_cursor_maybe) |cursor| { 64 existing_maybe = try evt.read(Record, DB, hash_kind, arena, try DB.HashMap(.read_only).init(cursor)); 65 } 66 67 var record = record_maybe orelse try evt.removedRecord(Record, DB, hash_kind, haxy_moment.readOnly(), existing_maybe); 68 69 if (!record.removed) try validate(event_id, record); 70 71 if (existing_maybe) |existing| { 72 // updates preserve the original creation metadata 73 record.created_order = existing.created_order; 74 } 75 76 const grant_cursor = try records.putCursor(grant_key); 77 try evt.upsert(Record, DB, hash_kind, try DB.HashMap(.read_write).init(grant_cursor), record); 78 try evt.indexEvent(DB, hash_kind, haxy_moment, event_id, .grant, existing_maybe, record); 79 80 const order_key = evt.orderKeyDesc(record.created_order, event_id); 81 82 // the id set retains removed records so merges can carry removals 83 if (existing_cursor_maybe == null) { 84 const ids = try DB.SortedSet(.read_write).init(try haxy_moment.putCursor(hash.hashInt(hash_kind, all_id_set_key))); 85 try ids.put(&order_key); 86 } 87 88 // the id fixes the target, so a grant never moves between sets 89 const target_id = try evt.parseEventId(&record.event.target_id); 90 const target_grants = try DB.SortedSet(.read_write).init(try by_target.putCursor(hash.hashInt(hash_kind, &target_id))); 91 if (record.removed) { 92 _ = try target_grants.remove(&order_key); 93 } else { 94 try target_grants.put(&order_key); 95 } 96 } 97 98 // the role a user was granted on a target, or null without an active grant 99 pub fn readRole( 100 comptime DB: type, 101 comptime hash_kind: hash.HashKind, 102 haxy_moment: DB.HashMap(.read_only), 103 arena: *std.heap.ArenaAllocator, 104 target_id: *const [evt.event_id_size]u8, 105 user_id: *const [evt.event_id_size]u8, 106 ) !?evt.Repo.Role { 107 const Granted = struct { event: struct { role: evt.Repo.Role }, removed: bool }; 108 const grant = (try evt.readRecordSubset(Self, Granted, DB, hash_kind, haxy_moment, arena, &idOf(target_id, user_id))) orelse return null; 109 return if (grant.removed) null else grant.event.role; 110 } 111 112 // a target's active grants, newest first 113 pub fn readByTarget( 114 comptime DB: type, 115 comptime hash_kind: hash.HashKind, 116 haxy_moment: DB.HashMap(.read_only), 117 arena: *std.heap.ArenaAllocator, 118 target_id: *const [evt.event_id_size]u8, 119 ) ![]const Self { 120 var grants: std.ArrayList(Self) = .empty; 121 const by_target_cursor = try haxy_moment.getCursor(hash.hashInt(hash_kind, target_to_grant_id_set_key)) orelse return grants.items; 122 const by_target = try DB.HashMap(.read_only).init(by_target_cursor); 123 const target_grants_cursor = try by_target.getCursor(hash.hashInt(hash_kind, target_id)) orelse return grants.items; 124 const target_grants = try DB.SortedSet(.read_only).init(target_grants_cursor); 125 126 var iter = try target_grants.iteratorFromIndex(0); 127 while (try iter.next()) |kv_cursor| { 128 const grant_id = try evt.readOrderKeyId(DB, kv_cursor); 129 const grant = (try evt.readRecordSubset(Self, struct { event: Self }, DB, hash_kind, haxy_moment, arena, &grant_id)) orelse continue; 130 try grants.append(arena.allocator(), grant.event); 131 } 132 return grants.items; 133 } 134