···11+use std::ops;22+33+use axum::{extract::OptionalFromRequestParts, http::request::Parts, response::IntoResponse};44+use reqwest::header::ToStrError;55+66+/// Extract the "Git-Protocol" header from a request.77+#[derive(Debug)]88+pub struct GitProtocol(pub String);99+1010+impl GitProtocol {1111+ pub fn as_str(&self) -> &str {1212+ &self.01313+ }1414+}1515+1616+impl ops::Deref for GitProtocol {1717+ type Target = str;1818+ fn deref(&self) -> &Self::Target {1919+ &self.02020+ }2121+}2222+2323+impl<S> OptionalFromRequestParts<S> for GitProtocol2424+where2525+ S: Send + Sync,2626+{2727+ type Rejection = GitProtocolRejection;2828+2929+ async fn from_request_parts(parts: &mut Parts, _: &S) -> Result<Option<Self>, Self::Rejection> {3030+ let header_value = parts3131+ .headers3232+ .get("Git-Protocol")3333+ .map(|header_value| header_value.to_str())3434+ .transpose()3535+ .map_err(GitProtocolRejection)?;3636+3737+ Ok(header_value.map(|value| Self(value.to_string())))3838+ }3939+}4040+4141+#[derive(Debug)]4242+pub struct GitProtocolRejection(ToStrError);4343+4444+impl IntoResponse for GitProtocolRejection {4545+ fn into_response(self) -> axum::response::Response {4646+ todo!()4747+ }4848+}
+1
crates/gordian-knot/src/lib.rs
···55use tokio::{net::TcpListener, task::JoinSet};66use tokio_util::sync::CancellationToken;7788+pub mod command;89pub mod extractors;910pub mod model;1011pub mod private;
+3-142
crates/gordian-knot/src/model.rs
···66pub mod repository;7788use core::ops;99-use std::{borrow::Cow, ffi::OsString, net::SocketAddr, sync::Arc};99+use std::{borrow::Cow, net::SocketAddr, sync::Arc};10101111-use axum::{1212- extract::{FromRef, FromRequestParts, OptionalFromRequestParts},1313- http::request::Parts,1414-};1111+use axum::extract::FromRef;1512use futures_util::future::BoxFuture;1616-use git_service::{state::GitServiceState, util::SetOptionEnv as _};1713use gordian_auth::jwt;1814use gordian_identity::{HttpClient, Resolver};1915use gordian_lexicon::sh_tangled::knot::Member;2016use gordian_types::Tid;2117use time::OffsetDateTime;2222-use tokio::process::Command;23182419use crate::{2525- extractors::request_id::RequestId,2626- model::{config::KnotConfiguration, repository::TangledRepository},2727- private,2828- public::git::{Error, GitAuthorization},2020+ model::config::KnotConfiguration,2921 services::{3022 authorization::{AuthorizationClaimsStore, AuthorizationClaimsStoreError},3123 database::DataStore,···117125 now: i64,118126 ) -> BoxFuture<'_, Result<(), AuthorizationClaimsStoreError>> {119127 self.inner.store_claims(claims, now)120120- }121121-}122122-123123-impl GitServiceState for Knot {124124- type Rejection = Error;125125-126126- async fn init_upload_archive(&self, parts: &mut Parts) -> Result<Command, Self::Rejection> {127127- let request_id = RequestId::from_request_parts(parts, self).await.unwrap();128128- let repository = TangledRepository::from_git_request(parts, self).await?;129129- let mut command = repository.git();130130- command131131- .option_env("X_REQUEST_ID", request_id)132132- .args(["upload-archive"])133133- .arg(repository.path());134134-135135- Ok(command.into())136136- }137137-138138- async fn init_upload_pack_advertisement(139139- &self,140140- parts: &mut Parts,141141- ) -> Result<tokio::process::Command, Self::Rejection> {142142- let request_id = RequestId::from_request_parts(parts, self).await.unwrap();143143- let repository = TangledRepository::from_git_request(parts, self).await?;144144- let mut command = repository.git();145145- command146146- .option_env("X_REQUEST_ID", request_id)147147- .args([148148- "upload-pack",149149- "--http-backend-info-refs",150150- "--stateless-rpc",151151- "--strict",152152- "--timeout=10",153153- ])154154- .arg(repository.path());155155-156156- Ok(command.into())157157- }158158-159159- async fn init_upload_pack(160160- &self,161161- parts: &mut Parts,162162- ) -> Result<tokio::process::Command, Self::Rejection> {163163- let request_id = RequestId::from_request_parts(parts, self).await.unwrap();164164- let repository = TangledRepository::from_git_request(parts, self).await?;165165- let mut command = repository.git();166166- command167167- .option_env("X_REQUEST_ID", request_id)168168- .args(["upload-pack", "--strict", "--stateless-rpc"])169169- .arg(repository.path());170170-171171- Ok(command.into())172172- }173173-174174- async fn init_receive_pack_advertisement(175175- &self,176176- parts: &mut Parts,177177- ) -> Result<tokio::process::Command, Self::Rejection> {178178- let GitAuthorization(auth) = GitAuthorization::from_request_parts(parts, self).await?;179179- let request_id = RequestId::from_request_parts(parts, self).await.unwrap();180180- let repository = TangledRepository::from_git_request(parts, self).await?;181181-182182- if !self.can_push(repository.repository_key(), &auth.iss).await {183183- tracing::error!(did = %auth.iss, "push denied");184184- return Err(Error::forbidden(185185- self,186186- format!(187187- "'{}' does not have permission to push to this repository",188188- auth.iss189189- ),190190- ))?;191191- }192192-193193- let nonce_seed = self.generate_push_seed(repository.repository_key());194194- let mut command = repository.git();195195- command196196- .env(private::ENV_USER_DID, auth.iss.as_str())197197- .option_env("X_REQUEST_ID", request_id)198198- .args([199199- "-c",200200- &nonce_seed,201201- "receive-pack",202202- "--http-backend-info-refs",203203- "--stateless-rpc",204204- ])205205- .arg(repository.path());206206-207207- Ok(command.into())208208- }209209-210210- async fn init_receive_pack(211211- &self,212212- parts: &mut Parts,213213- ) -> Result<tokio::process::Command, Self::Rejection> {214214- let GitAuthorization(auth) = GitAuthorization::from_request_parts(parts, self).await?;215215- let request_id = RequestId::from_request_parts(parts, self).await.unwrap();216216- let repository = TangledRepository::from_git_request(parts, self).await?;217217-218218- if !self.can_push(repository.repository_key(), &auth.iss).await {219219- tracing::error!(did = %auth.iss, "push denied");220220- return Err(Error::forbidden(221221- self,222222- format!(223223- "'{}' does not have permission to push to this repository",224224- auth.iss225225- ),226226- ))?;227227- }228228-229229- let allowed_signers_path = std::env::current_dir()230230- .unwrap()231231- .join("allowed_signers")232232- .join(auth.iss.as_str());233233-234234- let mut allowed_signers_option = OsString::with_capacity(235235- "gpg.ssh.allowedSignersFile=".len() + allowed_signers_path.as_os_str().len(),236236- );237237- allowed_signers_option.push("gpg.ssh.allowedSignersFile=");238238- allowed_signers_option.push(&allowed_signers_path);239239-240240- let nonce_seed = self.generate_push_seed(repository.repository_key());241241- let mut command = repository.git();242242- command243243- .env(private::ENV_USER_DID, auth.iss.as_str())244244- .option_env("X_REQUEST_ID", request_id)245245- .args(["-c", &nonce_seed, "-c"])246246- .arg(&allowed_signers_option)247247- .args(["receive-pack", "--stateless-rpc"])248248- .arg(repository.path());249249-250250- Ok(command.into())251128 }252129}