╭ changed ────────╮ │src/serve_ssh.zig│ ╰─────────────────╯ ╭───────────╮ │← all files│ ╰───────────╯ ╭──────────╮ │← previous│ ╰──────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │554 service: GitService, │ │555 fingerprint: *const [ssh.fingerprint_len]u8, │ │556 ) !RepoAuthorization { │ │553 - var arena = std.heap.ArenaAllocator.init(allocator); │ │554 - defer arena.deinit(); │ │555 - │ │557 + const allocator = arena.child_allocator; │ │558 var admin = rp.Repo(.xit, evt.admin_repo_opts).open(io, allocator, .{ .path = admin_repo_path }) catch |err| switch (err) {│ │559 error.RepoNotFound => return .not_found, │ │560 else => |e| return e, │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │562 defer admin.deinit(io, allocator); │ │563 │ │564 const moment = try evt.currentMoment(evt.admin_repo_opts, &admin); │ │563 - const repo = (try evt.Repo.readByOwnerAndName(evt.AdminDB, evt.admin_repo_opts.hash, moment, &arena, owner_name, repo_name)) orelse {│ │565 + const repo = (try evt.Repo.readByOwnerAndName(evt.AdminDB, evt.admin_repo_opts.hash, moment, arena, owner_name, repo_name)) orelse { │ │566 if (service == .upload_pack) return .not_found; │ │565 - const owner = (try evt.User.readByName(io, allocator, admin_repo_path, &arena, owner_name)) orelse return .not_found; │ │566 - return if (isKeyInAuthorizedKeys(owner.event.ssh_keys, fingerprint)) .allowed else .denied; │ │567 + const owner = (try evt.User.readByName(io, allocator, admin_repo_path, arena, owner_name)) orelse return .not_found; │ │568 + return if (isKeyInAuthorizedKeys(owner.event.ssh_keys, fingerprint)) │ │569 + .{ .allowed = .{ .name = owner.event.name, .email = owner.event.email } } │ │570 + else │ │571 + .denied; │ │572 }; │ │573 │ │574 const min_role: evt.Repo.Role = switch (service) { │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │576 .receive_pack => .write, │ │577 }; │ │578 // skip the key lookups when the base role is enough │ │574 - if (evt.Repo.roleOf(repo.repo, null).atLeast(min_role)) return .allowed; │ │579 + if (evt.Repo.roleOf(repo.repo, null).atLeast(min_role)) return .{ .allowed = null }; │ │580 │ │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; │ │581 + const user = (try repoUserWithKey(moment, arena, repo.repo.event, fingerprint)) orelse return .denied;│ │582 + if (!evt.Repo.roleOf(repo.repo, user.id).atLeast(min_role)) return .denied; │ │583 + return .{ .allowed = user.author }; │ │584 } │ │585 │ │586 +const UserWithKey = struct { │ │587 + id: [evt.event_id_size]u8, │ │588 + author: evt.CommitAuthor, │ │589 +}; │ │590 + │ │591 // the repo's owner or collaborator holding the key, or null for anyone else │ │592 fn repoUserWithKey( │ │593 moment: evt.AdminDB.HashMap(.read_only), │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │594 arena: *std.heap.ArenaAllocator, │ │595 repo: evt.Repo, │ │596 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].*;│ │597 +) !?UserWithKey { │ │598 + if (try userWithKey(moment, arena, repo.user_id, fingerprint)) |user| return user; │ │599 │ │600 for ([_][]const u8{ repo.write_user_ids, repo.read_user_ids }) |user_ids| { │ │601 var lines = std.mem.tokenizeScalar(u8, user_ids, '\n'); │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────╮ │602 while (lines.next()) |id| { │ │603 const user_id = try evt.parseEventId(id); │ │594 - if (try keyBelongsToUser(moment, arena, &user_id, fingerprint)) return user_id;│ │604 + if (try userWithKey(moment, arena, &user_id, fingerprint)) |user| return user; │ │605 } │ │606 } │ │607 return null; │ ╰────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────╮ │608 } │ │609 │ │600 -fn keyBelongsToUser( │ │610 +fn userWithKey( │ │611 moment: evt.AdminDB.HashMap(.read_only),│ │612 arena: *std.heap.ArenaAllocator, │ │613 user_id: []const u8, │ ╰─────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │614 fingerprint: *const [ssh.fingerprint_len]u8, │ │605 -) !bool { │ │606 - const user = (try evt.User.readById(evt.AdminDB, evt.admin_repo_opts.hash, moment, arena, user_id)) orelse return false;│ │607 - return !user.removed and isKeyInAuthorizedKeys(user.event.ssh_keys, fingerprint); │ │615 +) !?UserWithKey { │ │616 + const user = (try evt.User.readById(evt.AdminDB, evt.admin_repo_opts.hash, moment, arena, user_id)) orelse return null; │ │617 + if (user.removed or !isKeyInAuthorizedKeys(user.event.ssh_keys, fingerprint)) return null; │ │618 + return .{ │ │619 + .id = user_id[0..evt.event_id_size].*, │ │620 + .author = .{ .name = user.event.name, .email = user.event.email }, │ │621 + }; │ │622 } │ │623 │ │624 fn isKeyInAuthorizedKeys(ssh_keys: []const u8, fingerprint: *const [ssh.fingerprint_len]u8) bool { │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯