╭──────────╮ │← previous│ ╰──────────╯ ╭ changed ────────╮ │src/serve_ssh.zig│ ╰─────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────╮ │498 return .{ .service = service, .dir = try allocator.dupe(u8, dir_token) };│ │499 } │ │500 │ │500 -// true if `fingerprint` matches one of the named user's registered SSH keys │ │501 -fn isKeyAuthorized( │ │501 +const RepoAuthorization = enum { allowed, denied, not_found }; │ │502 + │ │503 +fn authorizeRepoKey( │ │504 io: std.Io, │ │505 allocator: std.mem.Allocator, │ │506 admin_repo_path: []const u8, │ ╰──────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────╮ │507 owner_name: []const u8, │ │508 + repo_name: []const u8, │ │509 + service: GitService, │ │510 fingerprint: *const [ssh.fingerprint_len]u8, │ │507 -) !bool { │ │511 +) !RepoAuthorization { │ │512 var arena = std.heap.ArenaAllocator.init(allocator);│ │513 defer arena.deinit(); │ │514 │ ╰─────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │511 - const user = (try evt.User.readByName(io, allocator, admin_repo_path, &arena, owner_name)) orelse return false; │ │515 + var admin = rp.Repo(.xit, evt.admin_repo_opts).open(io, allocator, .{ .path = admin_repo_path }) catch |err| switch (err) { │ │516 + error.RepoNotFound => return .not_found, │ │517 + else => |e| return e, │ │518 + }; │ │519 + defer admin.deinit(io, allocator); │ │520 │ │513 - return isKeyInAuthorizedKeys(user.event.ssh_keys, fingerprint); │ │521 + const moment = try evt.currentMoment(evt.admin_repo_opts, &admin); │ │522 + const repo = (try evt.Repo.readByOwnerAndName(evt.AdminDB, evt.admin_repo_opts.hash, moment, &arena, owner_name, repo_name)) orelse {│ │523 + if (service == .upload_pack) return .not_found; │ │524 + const owner = (try evt.User.readByName(io, allocator, admin_repo_path, &arena, owner_name)) orelse return .not_found; │ │525 + return if (isKeyInAuthorizedKeys(owner.event.ssh_keys, fingerprint)) .allowed else .denied; │ │526 + }; │ │527 + │ │528 + if ((service == .upload_pack and repo.repo.event.read_access == .public) or │ │529 + (service == .receive_pack and repo.repo.event.write_access == .public)) return .allowed; │ │530 + if (try keyBelongsToUser(moment, &arena, repo.repo.event.user_id, fingerprint)) return .allowed; │ │531 + │ │532 + const user_id_lists: []const []const u8 = switch (service) { │ │533 + .upload_pack => &.{ repo.repo.event.write_user_ids, repo.repo.event.read_user_ids }, │ │534 + .receive_pack => &.{repo.repo.event.write_user_ids}, │ │535 + }; │ │536 + for (user_id_lists) |user_ids| { │ │537 + var lines = std.mem.tokenizeScalar(u8, user_ids, '\n'); │ │538 + while (lines.next()) |id| { │ │539 + const user_id = try evt.parseEventId(id); │ │540 + if (try keyBelongsToUser(moment, &arena, &user_id, fingerprint)) return .allowed; │ │541 + } │ │542 + } │ │543 + return .denied; │ │544 +} │ │545 + │ │546 +fn keyBelongsToUser( │ │547 + moment: evt.AdminDB.HashMap(.read_only), │ │548 + arena: *std.heap.ArenaAllocator, │ │549 + user_id: []const u8, │ │550 + fingerprint: *const [ssh.fingerprint_len]u8, │ │551 +) !bool { │ │552 + const user = (try evt.User.readById(evt.AdminDB, evt.admin_repo_opts.hash, moment, arena, user_id)) orelse return false; │ │553 + return !user.removed and isKeyInAuthorizedKeys(user.event.ssh_keys, fingerprint); │ │554 } │ │555 │ │556 fn isKeyInAuthorizedKeys(ssh_keys: []const u8, fingerprint: *const [ssh.fingerprint_len]u8) bool { │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭ changed ──╮ │src/try.zig│ ╰───────────╯ ╭────────────────────────────────────────────────────────────────╮ │222 .user_id = &user_ids[r.user_index],│ │223 .name = r.name, │ │224 .description = r.description, │ │225 + .read_access = .public, │ │226 + .write_access = .public, │ │227 }, │ │228 }, │ │229 }; │ ╰────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │242 │ │243 try template_repo.addConfig(io, allocator, .{ .name = "user.name", .value = "haxy" }); │ │244 try template_repo.addConfig(io, allocator, .{ .name = "user.email", .value = "admin@example.test" }); │ │245 + try template_repo.addConfig(io, allocator, .{ .name = "receive.denycurrentbranch", .value = "updateinstead" });│ │246 │ │247 // a README plus a nested doc so the file tree has a directory to │ │248 // descend into │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯