╭ changed ─────────╮ │src/event/Repo.zig│ ╰──────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │32 public, │ │33 }; │ │34 │ │35 +// what a user may do in a repo, ordered so a higher role includes the lower ones │ │36 +pub const Role = enum { │ │37 + none, │ │38 + read, │ │39 + write, │ │40 + owner, │ │41 + │ │42 + pub fn atLeast(self: Role, min: Role) bool { │ │43 + return @intFromEnum(self) >= @intFromEnum(min); │ │44 + } │ │45 +}; │ │46 + │ │47 pub const name_max_len = 32; │ │48 + │ │49 +// the role `user_id_maybe` holds in the repo. null is a logged-out user, who │ │50 +// gets the base role the access fields give everyone. │ │51 +pub fn roleOf(record: Record, user_id_maybe: ?[evt.event_id_size]u8) Role { │ │52 + const event = record.event; │ │53 + const base: Role = if (event.write_access == .public) .write else if (event.read_access == .public) .read else .none;│ │54 + const user_id = user_id_maybe orelse return base; │ │55 + if (std.mem.eql(u8, event.user_id, &user_id)) return .owner; │ │56 │ │57 + const user_id_hex = std.fmt.bytesToHex(user_id, .lower); │ │58 + if (base == .write or containsUserId(event.write_user_ids, &user_id_hex)) return .write; │ │59 + if (base == .read or containsUserId(event.read_user_ids, &user_id_hex)) return .read; │ │60 + return .none; │ │61 +} │ │62 + │ │63 +fn containsUserId(user_ids: []const u8, user_id_hex: []const u8) bool { │ │64 + var lines = std.mem.tokenizeScalar(u8, user_ids, '\n'); │ │65 + while (lines.next()) |id| { │ │66 + if (std.mem.eql(u8, id, user_id_hex)) return true; │ │67 + } │ │68 + return false; │ │69 +} │ │70 + │ │71 // the moment keys `evt.merge` reads and writes for this kind │ │72 pub const merge_policy: evt.MergePolicy = .target_wins; │ │73 pub const record_map_key = "event-id->repo"; │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭ changed ────────╮ │src/serve_ssh.zig│ ╰─────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │566 return if (isKeyInAuthorizedKeys(owner.event.ssh_keys, fingerprint)) .allowed else .denied; │ │567 }; │ │568 │ │569 - if ((service == .upload_pack and repo.repo.event.read_access == .public) or │ │570 - (service == .receive_pack and repo.repo.event.write_access == .public)) return .allowed; │ │571 - if (try keyBelongsToUser(moment, &arena, repo.repo.event.user_id, fingerprint)) return .allowed; │ │569 + const min_role: evt.Repo.Role = switch (service) { │ │570 + .upload_pack => .read, │ │571 + .receive_pack => .write, │ │572 + }; │ │573 + // skip the key lookups when the base role is enough │ │574 + if (evt.Repo.roleOf(repo.repo, null).atLeast(min_role)) return .allowed; │ │575 + │ │576 + const user_id_maybe = try repoUserWithKey(moment, &arena, repo.repo.event, fingerprint); │ │577 + return if (evt.Repo.roleOf(repo.repo, user_id_maybe).atLeast(min_role)) .allowed else .denied; │ │578 +} │ │579 + │ │580 +// the repo's owner or collaborator holding the key, or null for anyone else │ │581 +fn repoUserWithKey( │ │582 + moment: evt.AdminDB.HashMap(.read_only), │ │583 + arena: *std.heap.ArenaAllocator, │ │584 + repo: evt.Repo, │ │585 + fingerprint: *const [ssh.fingerprint_len]u8, │ │586 +) !?[evt.event_id_size]u8 { │ │587 + // a user found by this id has a full-size id │ │588 + if (try keyBelongsToUser(moment, arena, repo.user_id, fingerprint)) return repo.user_id[0..evt.event_id_size].*;│ │589 │ │573 - const user_id_lists: []const []const u8 = switch (service) { │ │574 - .upload_pack => &.{ repo.repo.event.write_user_ids, repo.repo.event.read_user_ids }, │ │575 - .receive_pack => &.{repo.repo.event.write_user_ids}, │ │576 - }; │ │577 - for (user_id_lists) |user_ids| { │ │590 + for ([_][]const u8{ repo.write_user_ids, repo.read_user_ids }) |user_ids| { │ │591 var lines = std.mem.tokenizeScalar(u8, user_ids, '\n'); │ │592 while (lines.next()) |id| { │ │593 const user_id = try evt.parseEventId(id); │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────╮ │581 - if (try keyBelongsToUser(moment, &arena, &user_id, fingerprint)) return .allowed;│ │594 + if (try keyBelongsToUser(moment, arena, &user_id, fingerprint)) return user_id; │ │595 } │ │596 } │ │584 - return .denied; │ │597 + return null; │ │598 } │ │599 │ │600 fn keyBelongsToUser( │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────╯