From 32cb2c83d6a4ec10f43abb1d0a9b013dbcc64c33 Mon Sep 17 00:00:00 2001 From: dawn Date: Thu, 15 Oct 2026 16:27:15 +0300 Subject: [PATCH] knot2: check who can push before accepting a push Signed-off-by: dawn --- knot2/crates/knot-acl/src/lib.rs | 2 +- knot2/crates/knot-acl/src/repos.rs | 39 +++++++++++--- knot2/crates/knot-pack/src/lib.rs | 6 +-- knot2/crates/knot-receive/src/lib.rs | 13 +++-- knot2/crates/knot-ssh/src/exec.rs | 14 +++-- knot2/crates/knot-xrpc/src/branches.rs | 6 +-- knot2/crates/knot-xrpc/src/forks.rs | 16 +++--- knot2/crates/knot-xrpc/src/lfs.rs | 72 +++++++++++++------------- knot2/crates/knot-xrpc/src/lib.rs | 19 ++++--- knot2/crates/knot-xrpc/src/mint.rs | 2 +- knot2/crates/knot-xrpc/src/receive.rs | 37 +++++++------ knot2/crates/knot-xrpc/src/social.rs | 5 +- knot2/crates/knot-xrpc/src/tests.rs | 9 ++-- 13 files changed, 132 insertions(+), 108 deletions(-) diff --git a/knot2/crates/knot-acl/src/lib.rs b/knot2/crates/knot-acl/src/lib.rs index 6b1bffadc..e814bbf95 100644 --- a/knot2/crates/knot-acl/src/lib.rs +++ b/knot2/crates/knot-acl/src/lib.rs @@ -10,7 +10,7 @@ pub use knot_types::Decision; mod repos; -pub use repos::{ReadGrant, Repos, push_grant, read_grant, read_grant_by_path}; +pub use repos::{PushGrant, ReadGrant, Repos, push_grant, read_grant, read_grant_by_path}; pub trait Acl { fn is_admin(&self, who: &AccountDid) -> bool; diff --git a/knot2/crates/knot-acl/src/repos.rs b/knot2/crates/knot-acl/src/repos.rs index 8b24b85c4..111a0348d 100644 --- a/knot2/crates/knot-acl/src/repos.rs +++ b/knot2/crates/knot-acl/src/repos.rs @@ -1,8 +1,8 @@ use std::path::{Path, PathBuf}; use knot_git::{GitError, Layout, Repo}; -use knot_index::{CollaborationConsent, Index, Resolved}; -use knot_types::{ClonePath, KnotId, ObjectFormat, OwnerDid, RepoDid}; +use knot_index::{Index, Resolved}; +use knot_types::{AccountDid, ClonePath, KnotId, ObjectFormat, OwnerDid, RepoDid}; use crate::{Acl, can_push}; @@ -54,6 +54,12 @@ impl Repos { #[derive(Debug, Clone, PartialEq, Eq)] pub struct ReadGrant(RepoDid); +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct PushGrant { + read: ReadGrant, + pusher: AccountDid, +} + impl ReadGrant { // only for the knot's own background work and never for a request pub fn internal(repo: RepoDid) -> Self { @@ -69,6 +75,20 @@ impl ReadGrant { } } +impl PushGrant { + pub fn read(&self) -> &ReadGrant { + &self.read + } + + pub fn repo(&self) -> &RepoDid { + self.read.repo() + } + + pub fn pusher(&self) -> &AccountDid { + &self.pusher + } +} + pub fn read_grant(index: &Index, repo: RepoDid) -> Resolved> { index .owner_of(&repo) @@ -87,8 +107,15 @@ pub fn read_grant_by_path( } } -pub fn push_grant(acl: &impl Acl, consent: &CollaborationConsent<'_>) -> Option { - can_push(acl, consent) - .is_allowed() - .then(|| ReadGrant(consent.repo().clone())) +pub fn push_grant( + acl: &impl Acl, + index: &Index, + read: &ReadGrant, + pusher: &AccountDid, +) -> Option { + let consent = index.collaboration_consent(read.repo(), pusher); + can_push(acl, &consent).is_allowed().then(|| PushGrant { + read: read.clone(), + pusher: pusher.clone(), + }) } diff --git a/knot2/crates/knot-pack/src/lib.rs b/knot2/crates/knot-pack/src/lib.rs index cfd0b3ec7..4441d973d 100644 --- a/knot2/crates/knot-pack/src/lib.rs +++ b/knot2/crates/knot-pack/src/lib.rs @@ -330,7 +330,7 @@ pub use knot_edge::SocketPeer; pub trait ReceiveAdvertiser: Send + Sync + 'static { fn advertise( &self, - repo: RepoDid, + grant: ReadGrant, peer: knot_edge::SocketPeer, headers: HeaderMap, ) -> std::pin::Pin + Send + '_>>; @@ -575,9 +575,7 @@ async fn read_advertisement( )) } Some("git-receive-pack") => match &state.receive { - Some(advertiser) => Ok(advertiser - .advertise(grant.into_repo(), peer, headers.clone()) - .await), + Some(advertiser) => Ok(advertiser.advertise(grant, peer, headers.clone()).await), None => Err(PackError::PushOverSsh), }, _ => Err(PackError::UnsupportedService), diff --git a/knot2/crates/knot-receive/src/lib.rs b/knot2/crates/knot-receive/src/lib.rs index 2a95f3ef4..b13fd8785 100644 --- a/knot2/crates/knot-receive/src/lib.rs +++ b/knot2/crates/knot-receive/src/lib.rs @@ -41,7 +41,7 @@ use std::cell::RefCell; use std::sync::Arc; -use knot_acl::{ReadGrant, Repos}; +use knot_acl::{PushGrant, Repos}; use knot_atproto::Atproto; use knot_cob::CobHome; use knot_git::{RefUpdate, Repo}; @@ -75,11 +75,10 @@ pub trait RefSurface: Send + Sync { pub struct Push<'a, H: HttpTransport, C: Clock> { pub repos: &'a Repos, - pub grant: &'a ReadGrant, + pub grant: &'a PushGrant, pub received: ReceivedPack, pub limits: PackLimits, pub knot_actor: ActorId, - pub committer: AccountDid, pub index: &'a Index, pub atproto: &'a Atproto, pub resolve_slots: &'a ResolveSlots, @@ -98,7 +97,6 @@ pub async fn land(push: Push<'_, H, C>) -> Result(push: Push<'_, H, C>) -> Result Result<(Repo, ReceiveOutcome, Applied), PackError> { @@ -175,7 +174,7 @@ pub async fn land(push: Push<'_, H, C>) -> Result(push: Push<'_, H, C>) -> Result>() }) diff --git a/knot2/crates/knot-ssh/src/exec.rs b/knot2/crates/knot-ssh/src/exec.rs index d26909768..67671ca61 100644 --- a/knot2/crates/knot-ssh/src/exec.rs +++ b/knot2/crates/knot-ssh/src/exec.rs @@ -5,7 +5,7 @@ use std::sync::atomic::{AtomicBool, Ordering}; use std::time::Duration; use futures::StreamExt; -use knot_acl::{KnotAcl, ReadGrant, push_grant, read_grant, read_grant_by_path}; +use knot_acl::{KnotAcl, PushGrant, ReadGrant, push_grant, read_grant, read_grant_by_path}; use knot_index::Resolved; use knot_lfs::TransferOp; use knot_pack::{PackError, PackLimits, RepoLookup}; @@ -647,8 +647,8 @@ async fn serve_receive( return; } - let (committer, pushing) = match authorize_push(&state, &credential, &grant, peer).await { - PushAuth::Allowed(did, pushing) => (did, pushing), + let pushing = match authorize_push(&state, &credential, &grant, peer).await { + PushAuth::Allowed(pushing) => pushing, PushAuth::Refused { reason, message } => { tracing::warn!(repo = repo_did.as_str(), reason, "ssh push denied"); return fail(channel, &message).await; @@ -708,7 +708,6 @@ async fn serve_receive( received: body, limits: state.limits, knot_actor: state.knot_actor.clone(), - committer, index: &state.index, atproto: &state.atproto, resolve_slots: &state.slots.resolve, @@ -786,7 +785,7 @@ enum PusherLookup { } enum PushAuth { - Allowed(AccountDid, ReadGrant), + Allowed(PushGrant), Refused { reason: &'static str, message: String, @@ -807,10 +806,9 @@ async fn authorize_push( let repo = grant.repo(); match resolve_pusher(state, credential, repo, peer).await { PusherLookup::Matched(did) => { - let consent = state.index.collaboration_consent(repo, &did); let acl = KnotAcl::new(&state.admins, state.admission, &state.index); - match push_grant(&acl, &consent) { - Some(pushing) => PushAuth::Allowed(did, pushing), + match push_grant(&acl, &state.index, grant, &did) { + Some(pushing) => PushAuth::Allowed(pushing), None => PushAuth::Refused { reason: "unauthorized", message: state.catalog.ssh.push_denied.text(), diff --git a/knot2/crates/knot-xrpc/src/branches.rs b/knot2/crates/knot-xrpc/src/branches.rs index dd77c8f6e..8fbaf6b30 100644 --- a/knot2/crates/knot-xrpc/src/branches.rs +++ b/knot2/crates/knot-xrpc/src/branches.rs @@ -46,11 +46,11 @@ pub(crate) async fn delete_branch( let actor = state.authenticate_write(&headers, &method).await?; let input: DeleteBranchInput = decode(&body)?; let hosted = require_hosted(&state, input.repo)?; - let pushing = crate::authorize_push(&state, &actor, hosted.repo(), BRANCH_DENIED).await?; + let pushing = crate::authorize_push(&state, &actor, &hosted, BRANCH_DENIED).await?; let refname = input.branch.head_ref(); let repos = state.repos.clone(); - let target = pushing.clone(); + let target = pushing.read().clone(); let update = run_blocking(move || { let repo = repos.open(&target)?; if repo.default_branch().as_ref() == Some(&refname) { @@ -70,7 +70,7 @@ pub(crate) async fn delete_branch( }) .await?; - crate::refrecords::sync_after(&state, &pushing, update).await; + crate::refrecords::sync_after(&state, pushing.read(), update).await; Ok(ok_empty()) } diff --git a/knot2/crates/knot-xrpc/src/forks.rs b/knot2/crates/knot-xrpc/src/forks.rs index f67667edf..720697203 100644 --- a/knot2/crates/knot-xrpc/src/forks.rs +++ b/knot2/crates/knot-xrpc/src/forks.rs @@ -9,7 +9,7 @@ use http::HeaderMap; use serde::{Deserialize, Serialize}; use url::Url; -use knot_acl::ReadGrant; +use knot_acl::{PushGrant, ReadGrant}; use knot_git::{Filter, GitError, Haves, RefClass, RefUpdate, Repo, Staging, Wants}; use knot_pack::{FetchError, HaveOids, PackLimits, UpstreamRefs, WantOids}; use knot_runtime::{Clock, HttpTransport}; @@ -482,14 +482,14 @@ impl<'a> TargetRef<'a> { async fn pull_upstream_branch( state: &Arc>, - pushing: &ReadGrant, + pushing: &PushGrant, source: SourceRef<'_>, target: TargetRef<'_>, ) -> Result { let branch = source.get(); let target = target.get(); let repos = state.repos.clone(); - let opened = pushing.clone(); + let opened = pushing.read().clone(); let fork = run_blocking(move || { let repo = repos.open(&opened)?; load_fork_state(&repo) @@ -519,7 +519,7 @@ async fn pull_upstream_branch( .await?; let repos = state.repos.clone(); - let opened = pushing.clone(); + let opened = pushing.read().clone(); let haves = fork.haves.clone(); run_blocking(move || { let repo = repos.open(&opened)?; @@ -530,14 +530,14 @@ async fn pull_upstream_branch( let lfs_missing = crate::lfs::mirror_fork_objects( Arc::clone(state), upstream, - pushing.clone(), + pushing.read().clone(), WantOids::new(vec![tip]), HaveOids::new(haves), ) .await?; let repos = state.repos.clone(); - let opened = pushing.clone(); + let opened = pushing.read().clone(); let target = target.clone(); run_blocking(move || { let repo = repos.open(&opened)?; @@ -567,7 +567,7 @@ pub(crate) async fn fork_sync( let actor = state.authenticate_write(&headers, &method).await?; let input: ForkSyncInput = decode(&body)?; let hosted = require_hosted(&state, input.repo)?; - let pushing = crate::authorize_push(&state, &actor, hosted.repo(), FORK_DENIED).await?; + let pushing = crate::authorize_push(&state, &actor, &hosted, FORK_DENIED).await?; let branch = input.branch.head_ref(); let sync = pull_upstream_branch( &state, @@ -579,7 +579,7 @@ pub(crate) async fn fork_sync( if sync.old != Some(sync.new) && let Err(error) = crate::refrecords::seal_ref_move( &state, - pushing.clone(), + pushing.read().clone(), branch, sync.old, sync.new, diff --git a/knot2/crates/knot-xrpc/src/lfs.rs b/knot2/crates/knot-xrpc/src/lfs.rs index 9e3dac087..7350304a7 100644 --- a/knot2/crates/knot-xrpc/src/lfs.rs +++ b/knot2/crates/knot-xrpc/src/lfs.rs @@ -156,20 +156,17 @@ fn lfs_error(error: crate::XrpcError) -> Box { fn resolve_did( state: &XrpcState, segment: &crate::RepoDidSegment, -) -> Result> { - crate::resolve_repo_did(state, segment) - .map(ReadGrant::into_repo) - .map_err(lfs_error) +) -> Result> { + crate::resolve_repo_did(state, segment).map_err(lfs_error) } async fn resolve_named( state: &XrpcState, owner: &crate::OwnerSegment, name: &crate::RepoNameSegment, -) -> Result> { +) -> Result> { crate::resolve_repo_named(state, owner, name) .await - .map(ReadGrant::into_repo) .map_err(lfs_error) } @@ -203,13 +200,13 @@ async fn batch_named( headers: HeaderMap, body: Result, ) -> Response { - let repo = match resolve_named(&state, &did, &name).await { - Ok(repo) => repo, + let grant = match resolve_named(&state, &did, &name).await { + Ok(grant) => grant, Err(response) => return *response, }; serve_batch( &state, - repo, + grant, &format!("{}/{}", did.as_str(), name.as_str()), peer, &headers, @@ -225,21 +222,22 @@ async fn batch_did( headers: HeaderMap, body: Result, ) -> Response { - let repo = match resolve_did(&state, &did) { - Ok(repo) => repo, + let grant = match resolve_did(&state, &did) { + Ok(grant) => grant, Err(response) => return *response, }; - serve_batch(&state, repo, did.as_str(), peer, &headers, body).await + serve_batch(&state, grant, did.as_str(), peer, &headers, body).await } async fn serve_batch( state: &XrpcState, - repo: RepoDid, + grant: ReadGrant, path_prefix: &str, peer: SocketPeer, headers: &HeaderMap, body: Result, ) -> Response { + let repo = grant.repo(); let Some(lfs) = state.lfs.as_ref() else { return not_enabled(); }; @@ -283,8 +281,8 @@ async fn serve_batch( } let base = &state.knot_service_url; let objects: Result, Box> = match request.operation { - BatchOperation::Upload => match authorized_pusher(state, peer, headers, &repo).await { - Ok(_actor) => match probe_all(lfs, &repo, &request.objects).await { + BatchOperation::Upload => match authorized_pusher(state, peer, headers, &grant).await { + Ok(_actor) => match probe_all(lfs, repo, &request.objects).await { Ok(stored) => request .objects .iter() @@ -295,7 +293,7 @@ async fn serve_batch( }, Err(response) => return *response, }, - BatchOperation::Download => match probe_all(lfs, &repo, &request.objects).await { + BatchOperation::Download => match probe_all(lfs, repo, &request.objects).await { Ok(stored) => request .objects .iter() @@ -329,17 +327,17 @@ async fn authorized_pusher( state: &XrpcState, peer: SocketPeer, headers: &HeaderMap, - repo: &RepoDid, + read: &ReadGrant, ) -> Result> { crate::authenticate_and_authorize_push( state, peer, headers, - repo, + read, "you aren't authorized to push to this repository", ) .await - .map(|(actor, _)| actor) + .map(|pushing| pushing.pusher().clone()) .map_err(|error| Box::new(challenge(error))) } @@ -487,11 +485,11 @@ async fn object_named( Path(RepoObjectParams { did, name, oid }): Path, request: Request, ) -> Response { - let repo = match resolve_named(&state, &did, &name).await { - Ok(repo) => repo, + let grant = match resolve_named(&state, &did, &name).await { + Ok(grant) => grant, Err(response) => return *response, }; - serve_object(&state, repo, &oid, request).await + serve_object(&state, grant, &oid, request).await } async fn object_did( @@ -499,19 +497,20 @@ async fn object_did( Path(DidObjectParams { did, oid }): Path, request: Request, ) -> Response { - let repo = match resolve_did(&state, &did) { - Ok(repo) => repo, + let grant = match resolve_did(&state, &did) { + Ok(grant) => grant, Err(response) => return *response, }; - serve_object(&state, repo, &oid, request).await + serve_object(&state, grant, &oid, request).await } async fn serve_object( state: &XrpcState, - repo: RepoDid, + grant: ReadGrant, oid_raw: &OidSegment, request: Request, ) -> Response { + let repo = grant.repo(); let Some(lfs) = state.lfs.as_ref() else { return not_enabled(); }; @@ -590,11 +589,11 @@ async fn object_upload_named( peer: SocketPeer, request: Request, ) -> Response { - let repo = match resolve_named(&state, &did, &name).await { - Ok(repo) => repo, + let grant = match resolve_named(&state, &did, &name).await { + Ok(grant) => grant, Err(response) => return *response, }; - serve_object_upload(&state, repo, &oid, peer, request).await + serve_object_upload(&state, grant, &oid, peer, request).await } async fn object_upload_did( @@ -603,27 +602,28 @@ async fn object_upload_did( peer: SocketPeer, request: Request, ) -> Response { - let repo = match resolve_did(&state, &did) { - Ok(repo) => repo, + let grant = match resolve_did(&state, &did) { + Ok(grant) => grant, Err(response) => return *response, }; - serve_object_upload(&state, repo, &oid, peer, request).await + serve_object_upload(&state, grant, &oid, peer, request).await } async fn serve_object_upload( state: &XrpcState, - repo: RepoDid, + grant: ReadGrant, oid_raw: &OidSegment, peer: SocketPeer, request: Request, ) -> Response { + let repo = grant.repo(); let Some(lfs) = state.lfs.as_ref() else { return not_enabled(); }; let Ok(oid) = LfsOid::new(oid_raw.as_str()) else { return fail(StatusCode::NOT_FOUND, "object not found"); }; - if let Err(response) = authorized_pusher(state, peer, request.headers(), &repo).await { + if let Err(response) = authorized_pusher(state, peer, request.headers(), &grant).await { return *response; } let Some(size) = content_length(request.headers()) else { @@ -634,7 +634,7 @@ async fn serve_object_upload( }; let permit = match lfs.handle.admission.admit(size) { Ok(permit) => permit, - Err(error) => return store_fault(&repo, &oid, error), + Err(error) => return store_fault(repo, &oid, error), }; let store = Arc::clone(&lfs.handle.store); let target = repo.clone(); @@ -665,7 +665,7 @@ async fn serve_object_upload( ); StatusCode::OK.into_response() } - Ok(Err(error)) => store_fault(&repo, &oid, error), + Ok(Err(error)) => store_fault(repo, &oid, error), Err(_) => fail(StatusCode::INTERNAL_SERVER_ERROR, "upload task died"), } } diff --git a/knot2/crates/knot-xrpc/src/lib.rs b/knot2/crates/knot-xrpc/src/lib.rs index b0ad91cb3..7d1f7ad7d 100644 --- a/knot2/crates/knot-xrpc/src/lib.rs +++ b/knot2/crates/knot-xrpc/src/lib.rs @@ -68,7 +68,7 @@ use http::{HeaderMap, HeaderValue, StatusCode, header::AUTHORIZATION}; use serde::de::DeserializeOwned; use serde_json::json; -use knot_acl::{ReadGrant, Repos}; +use knot_acl::{PushGrant, ReadGrant, Repos}; use knot_atproto::{Atproto, AtprotoError, ServiceJwt}; use crate::legacy_admin::LegacyAdminSecret; @@ -658,13 +658,13 @@ pub(crate) async fn current_member( pub(crate) async fn authorize_push( state: &XrpcState, actor: &AccountDid, - repo: &RepoDid, + read: &ReadGrant, denied: &str, -) -> Result { - project_collaborators(state, repo).await?; - let consent = state.index.collaboration_consent(repo, actor); +) -> Result { + project_collaborators(state, read.repo()).await?; let acl = knot_acl::KnotAcl::new(&state.admins, state.admission, &state.index); - knot_acl::push_grant(&acl, &consent).ok_or_else(|| XrpcError::forbidden(denied)) + knot_acl::push_grant(&acl, &state.index, read, actor) + .ok_or_else(|| XrpcError::forbidden(denied)) } pub(crate) async fn authorize_create( @@ -688,15 +688,14 @@ pub(crate) async fn authenticate_and_authorize_push( state: &XrpcState, socket: SocketPeer, headers: &HeaderMap, - repo: &RepoDid, + read: &ReadGrant, denied: &str, -) -> Result<(AccountDid, ReadGrant), XrpcError> { +) -> Result { let peer = state.proxy_trust.client_peer(headers, socket.ip()); let guard = admit_pre_auth(state, peer)?; let actor = state.authenticate_push(headers).await?; guard.refund(); - let grant = authorize_push(state, &actor, repo, denied).await?; - Ok((actor, grant)) + authorize_push(state, &actor, read, denied).await } pub(crate) async fn run_blocking(task: F) -> Result diff --git a/knot2/crates/knot-xrpc/src/mint.rs b/knot2/crates/knot-xrpc/src/mint.rs index 9e3a49fbd..6e5917e52 100644 --- a/knot2/crates/knot-xrpc/src/mint.rs +++ b/knot2/crates/knot-xrpc/src/mint.rs @@ -51,7 +51,7 @@ pub(crate) async fn mint_token( let actor = state .authenticate_write_for_repo(&headers, &method, repo) .await?; - authorize_push(&state, &actor, repo, MINT_DENIED).await?; + authorize_push(&state, &actor, &hosted, MINT_DENIED).await?; let now = state.now(); let expires_at = RepoTokenExpiry::mint(exp, now) diff --git a/knot2/crates/knot-xrpc/src/receive.rs b/knot2/crates/knot-xrpc/src/receive.rs index b29f9f880..e1dca2c3a 100644 --- a/knot2/crates/knot-xrpc/src/receive.rs +++ b/knot2/crates/knot-xrpc/src/receive.rs @@ -10,12 +10,12 @@ use axum::http::{HeaderMap, HeaderValue, StatusCode, header}; use axum::response::{IntoResponse, Response}; use axum::routing::post; use futures::TryStreamExt; -use knot_acl::ReadGrant; +use knot_acl::{PushGrant, ReadGrant}; use knot_messages::{ErrorKey, HttpMessages}; use knot_pack::{PackError, PackReceiver, ReceivedPack, SocketPeer}; use knot_receive::{Push, land}; use knot_runtime::{Clock, HttpTransport}; -use knot_types::{AccountDid, ActorId, ObjectFormat, RepoDid}; +use knot_types::{ActorId, ObjectFormat}; use tokio_util::io::{StreamReader, SyncIoBridge}; use crate::{XrpcError, XrpcState, authenticate_and_authorize_push, run_blocking}; @@ -47,29 +47,29 @@ struct ReceiveGate { impl knot_pack::ReceiveAdvertiser for ReceiveGate { fn advertise( &self, - repo: RepoDid, + grant: ReadGrant, peer: SocketPeer, headers: HeaderMap, ) -> Pin + Send + '_>> { - Box::pin(async move { serve_advertisement(&self.state, repo, peer, &headers).await }) + Box::pin(async move { serve_advertisement(&self.state, grant, peer, &headers).await }) } } async fn serve_advertisement( state: &Arc>, - repo: RepoDid, + grant: ReadGrant, peer: SocketPeer, headers: &HeaderMap, ) -> Response { - let pushing = match authorized_pusher(state, peer, headers, &repo).await { - Ok((_, pushing)) => pushing, + let pushing = match authorized_pusher(state, peer, headers, &grant).await { + Ok(pushing) => pushing, Err(response) => return *response, }; let repos = state.repos.clone(); let missing = state.catalog.http.repo_not_found.text(); let advert = run_blocking(move || { let repo = repos - .open(&pushing) + .open(pushing.read()) .map_err(|_| XrpcError::not_found(missing))?; knot_pack::advertise_receive(&repo).map_err(map_pack) }) @@ -91,7 +91,7 @@ async fn receive_named( Ok(grant) => grant, Err(error) => return error.into_response(), }; - serve_receive(&state, grant.into_repo(), peer, &headers, body).await + serve_receive(&state, grant, peer, &headers, body).await } async fn receive_did( @@ -105,18 +105,18 @@ async fn receive_did( Ok(grant) => grant, Err(error) => return error.into_response(), }; - serve_receive(&state, grant.into_repo(), peer, &headers, body).await + serve_receive(&state, grant, peer, &headers, body).await } async fn serve_receive( state: &Arc>, - repo_did: RepoDid, + grant: ReadGrant, peer: SocketPeer, headers: &HeaderMap, body: Body, ) -> Response { - let (pusher, pushing) = match authorized_pusher(state, peer, headers, &repo_did).await { - Ok(authorized) => authorized, + let pushing = match authorized_pusher(state, peer, headers, &grant).await { + Ok(pushing) => pushing, Err(response) => return *response, }; @@ -127,7 +127,7 @@ async fn serve_receive( let format = { let repos = state.repos.clone(); - let target = pushing.clone(); + let target = pushing.read().clone(); let missing = state.catalog.http.repo_not_found.text(); match run_blocking(move || { repos @@ -176,7 +176,6 @@ async fn serve_receive( received, limits: state.pack_limits, knot_actor, - committer: pusher.clone(), index: &state.index, atproto: &state.atproto, resolve_slots: &state.slots.resolve, @@ -193,7 +192,7 @@ async fn serve_receive( match landed { Ok(framed) => git_response(RESULT, framed), Err(error) => { - tracing::warn!(repo = repo_did.as_str(), %error, "HTTP receive-pack failed"); + tracing::warn!(repo = grant.repo().as_str(), %error, "HTTP receive-pack failed"); error.into_response() } } @@ -233,10 +232,10 @@ async fn authorized_pusher( state: &Arc>, peer: SocketPeer, headers: &HeaderMap, - repo: &RepoDid, -) -> Result<(AccountDid, ReadGrant), Box> { + read: &ReadGrant, +) -> Result> { let denied = state.catalog.http.push_denied.text(); - authenticate_and_authorize_push(state, peer, headers, repo, &denied) + authenticate_and_authorize_push(state, peer, headers, read, &denied) .await .map_err(|error| Box::new(challenge(error))) } diff --git a/knot2/crates/knot-xrpc/src/social.rs b/knot2/crates/knot-xrpc/src/social.rs index 32c9db1fd..4811e1e54 100644 --- a/knot2/crates/knot-xrpc/src/social.rs +++ b/knot2/crates/knot-xrpc/src/social.rs @@ -3369,7 +3369,8 @@ pub(crate) async fn merge_pull( let input = decode::(&body)?; let address = RecordAddress::new(patch_collection(), input.patch); let session = session(&state, &headers, &method, input.repo.clone()).await?; - let pushing = crate::authorize_push(&state, &session.actor, &input.repo, MERGE_DENIED).await?; + let pushing = + crate::authorize_push(&state, &session.actor, &session.grant, MERGE_DENIED).await?; let moved_refs = Arc::new(Mutex::new(Vec::::new())); let sink = Arc::clone(&moved_refs); let outcome = on_repo(&state, session, move |ledger, cx| { @@ -3422,7 +3423,7 @@ pub(crate) async fn merge_pull( ); seal_moves( &state, - &pushing, + pushing.read(), &moves, "Post-receive after patch merge failed", ) diff --git a/knot2/crates/knot-xrpc/src/tests.rs b/knot2/crates/knot-xrpc/src/tests.rs index 040ed2126..c2814fccb 100644 --- a/knot2/crates/knot-xrpc/src/tests.rs +++ b/knot2/crates/knot-xrpc/src/tests.rs @@ -5668,9 +5668,12 @@ mod grant_lists { async fn allowed(&self) -> bool { let state = &self.world.state; match self.repo() { - Some(repo) => crate::authorize_push(state, &self.subject, repo, DENIED) - .await - .map(drop), + Some(repo) => { + let read = knot_acl::ReadGrant::internal(repo.clone()); + crate::authorize_push(state, &self.subject, &read, DENIED) + .await + .map(drop) + } None => crate::authorize_create(state, &self.subject, DENIED).await, } .is_ok() -- 2.54.0