╭ changed ────────╮ │src/serve_ssh.zig│ ╰─────────────────╯ ╭───────────╮ │← all files│ ╰───────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │409 defer any_repo.deinit(io, allocator); │ │410 │ │411 switch (any_repo) { │ │412 - inline else => |*repo| switch (parsed.service) { │ │413 - .upload_pack => { │ │412 + inline else => |*repo| switch (allowed) { │ │413 + .fetch => { │ │414 var sideband = progress.Sideband{ .writer = &writer.interface, .sess = sess }; │ │415 try repo.uploadPack(io, allocator, &reader.interface, &writer.interface, .{ .protocol_version = protocol_version }, &sideband);│ │416 }, │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │417 - .receive_pack => try push.receivePackAndConsume(repo.self_repo_opts, io, allocator, repo, &reader.interface, &writer.interface, .{ .protocol_version = protocol_version, .protected_branches = allowed.protected_branches }, allowed.author, handler.server, handler.err, sess),│ │417 + .push => |pusher| try push.receivePackAndConsume(repo.self_repo_opts, io, allocator, repo, &reader.interface, &writer.interface, .{ .protocol_version = protocol_version, .protected_branches = pusher.protected_branches }, pusher.author, handler.server, handler.err, sess), │ │418 }, │ │419 } │ │420 } │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │549 │ │550 const RepoAuthorization = union(enum) { allowed: Allowed, create: RepoCreator, denied, not_found };│ │551 │ │552 -// a key allowed into an existing repo │ │553 -const Allowed = struct { │ │554 - // null when the repo's base role let the key in │ │555 - author: ?evt.CommitAuthor, │ │552 +// what a key may do in an existing repo │ │553 +const Allowed = union(enum) { │ │554 + fetch, │ │555 + push: Pusher, │ │556 +}; │ │557 + │ │558 +// the key holder a push acts as │ │559 +const Pusher = struct { │ │560 + author: evt.CommitAuthor, │ │561 protected_branches: []const []const u8, │ │562 }; │ │563 │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │597 .upload_pack => .read, │ │598 .receive_pack => .write, │ │599 }; │ │595 - const protected_branches = try repo.repo.event.protectedBranchNames(arena.allocator()); │ │596 - // skip the key lookups when the base role is enough │ │597 - if (repo.role) |base| { │ │598 - if (base.atLeast(min_role)) return .{ .allowed = .{ .author = null, .protected_branches = protected_branches } };│ │599 - } │ │600 + // any key may fetch a public repo; a push always needs a key holder │ │601 + if (service == .upload_pack and repo.role != null) return .{ .allowed = .fetch }; │ │602 │ │603 // a key can be shared, so a holder whose role falls short doesn't end the search │ │604 const owner_id = try evt.parseEventId(&repo.repo.event.user_id); │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │605 for (try evt.User.readIdsByFingerprint(evt.AdminDB, evt.admin_repo_opts.hash, moment, arena, fingerprint)) |holder_id| { │ │606 const seen = (try evt.readRepoById(io, allocator, arena, users_dir, &owner_id, &repo.event_id, holder_id)) orelse return .not_found; │ │607 const role = seen.role orelse continue; │ │606 - if (role.atLeast(min_role)) return .{ .allowed = .{ .author = try commitAuthor(moment, arena, &holder_id), .protected_branches = protected_branches } };│ │608 + if (!role.atLeast(min_role)) continue; │ │609 + return .{ .allowed = switch (service) { │ │610 + .upload_pack => .fetch, │ │611 + .receive_pack => .{ .push = .{ │ │612 + .author = try commitAuthor(moment, arena, &holder_id), │ │613 + .protected_branches = try repo.repo.event.protectedBranchNames(arena.allocator()), │ │614 + } }, │ │615 + } }; │ │616 } │ │617 return .denied; │ │618 } │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯