╭ changed ────────╮ │src/serve_ssh.zig│ ╰─────────────────╯ ╭───────────╮ │← all files│ ╰───────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │386 } │ │387 │ │388 const owner_repo = evt.parseOwnerRepoPath(repo_identity) orelse return writeError(sess, "repo path must be <owner>:<repo>"); │ │389 - var author_arena = std.heap.ArenaAllocator.init(allocator); │ │390 - defer author_arena.deinit(); │ │391 - const author = switch (try authorizeRepoKey(io, &author_arena, handler.admin_repo_path, handler.server.users_dir, owner_repo.owner, owner_repo.name, parsed.service, &sess.fingerprint)) { │ │392 - .allowed => |user| user, │ │389 + var allowed_arena = std.heap.ArenaAllocator.init(allocator); │ │390 + defer allowed_arena.deinit(); │ │391 + const allowed = switch (try authorizeRepoKey(io, &allowed_arena, handler.admin_repo_path, handler.server.users_dir, owner_repo.owner, owner_repo.name, parsed.service, &sess.fingerprint)) {│ │392 + .allowed => |value| value, │ │393 .create => |creator| return runCreatingPush(handler, sess, &creator, owner_repo.name, protocol_version, &reader.interface, &writer.interface), │ │394 .denied => return switch (parsed.service) { │ │395 .upload_pack => writeError(sess, "unauthorized: this SSH key cannot read this repo"), │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │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 }, author, handler.server, handler.err, sess), │ │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),│ │418 }, │ │419 } │ │420 } │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │547 return .{ .service = service, .dir = try allocator.dupe(u8, dir_token) }; │ │548 } │ │549 │ │550 -const RepoAuthorization = union(enum) { allowed: ?evt.CommitAuthor, create: RepoCreator, denied, not_found };│ │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, │ │556 + protected_branches: []const []const u8, │ │557 +}; │ │558 │ │559 // the owner of a repo a push may create │ │560 const RepoCreator = struct { │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │592 .upload_pack => .read, │ │593 .receive_pack => .write, │ │594 }; │ │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| { │ │590 - if (base.atLeast(min_role)) return .{ .allowed = null }; │ │598 + if (base.atLeast(min_role)) return .{ .allowed = .{ .author = null, .protected_branches = protected_branches } };│ │599 } │ │600 │ │601 // a key can be shared, so a holder whose role falls short doesn't end the search │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │603 for (try evt.User.readIdsByFingerprint(evt.AdminDB, evt.admin_repo_opts.hash, moment, arena, fingerprint)) |holder_id| { │ │604 const seen = (try evt.readRepoById(io, allocator, arena, users_dir, &owner_id, &repo.event_id, holder_id)) orelse return .not_found; │ │605 const role = seen.role orelse continue; │ │598 - if (role.atLeast(min_role)) return .{ .allowed = try commitAuthor(moment, arena, &holder_id) }; │ │606 + if (role.atLeast(min_role)) return .{ .allowed = .{ .author = try commitAuthor(moment, arena, &holder_id), .protected_branches = protected_branches } };│ │607 } │ │608 return .denied; │ │609 } │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯