╭──────────╮ │← previous│ ╰──────────╯ ╭ changed ────────╮ │src/serve_ssh.zig│ ╰─────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────╮ │543 return .{ .service = service, .dir = try allocator.dupe(u8, dir_token) }; │ │544 } │ │545 │ │542 -const RepoAuthorization = enum { allowed, denied, not_found }; │ │546 +const RepoAuthorization = union(enum) { allowed: ?evt.CommitAuthor, denied, not_found };│ │547 │ │548 fn authorizeRepoKey( │ │549 io: std.Io, │ ╰─────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────╮ │546 - allocator: std.mem.Allocator, │ │550 + arena: *std.heap.ArenaAllocator,│ │551 admin_repo_path: []const u8, │ │552 owner_name: []const u8, │ │553 repo_name: []const u8, │ ╰─────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │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 { │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭ changed ───────╮ │src/test/net.zig│ ╰────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │1765 var head_buffer: [rf.MAX_REF_CONTENT_SIZE]u8 = undefined; │ │1766 const head = try rf.readHead(.xit, opts, state.readOnly(), ctx.io, &head_buffer) orelse return error.HeadNotFound; │ │1767 ctx.result.* = try xit.object.writeCommit(.xit, opts, state, ctx.io, ctx.allocator, ctx.metadata, &tree, head.ref); │ │1768 - try xit.undo.writeMessage(opts, state, .{ .commit = ctx.metadata }); │ │1768 + try xit.undo.write(opts, state, std.Io.Timestamp.now(ctx.io, .real).toSeconds(), .{ .commit = try .init(ctx.metadata.message, ctx.result.*, opts.max_read_size) });│ │1769 } │ │1770 }; │ │1771 try repo.core.db_file.lock(io, .exclusive); │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯