From e57bc3cc9788eec12c300acb7cb86ae158d448cd Mon Sep 17 00:00:00 2001 From: Jordan Doyle Date: Sat, 12 Mar 2022 17:10:11 +0000 Subject: [PATCH] Ensure group exists before continuing the handshake with Git --- src/git_command_handlers/fetch.rs | 4 ---- src/main.rs | 30 ++++++++++++++++++++++-------- src/metadata.rs | 2 +- src/providers/gitlab.rs | 84 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------------- src/providers/mod.rs | 12 ++++++++++-- 5 files changed, 103 insertions(+), 29 deletions(-) diff --git a/src/git_command_handlers/fetch.rs b/src/git_command_handlers/fetch.rs index 0034e9f..d157996 100644 --- a/src/git_command_handlers/fetch.rs +++ b/src/git_command_handlers/fetch.rs @@ -32,10 +32,6 @@ pub fn handle( // magic header handle.write(PktLine::Data(b"packfile\n"))?; - // send a welcome message - // handle.write(PktLine::SidebandMsg(b"Hello from gitlab-cargo-shim!\n"))?; - // handle.flush(session, channel); - // send the complete packfile let packfile = PackFile::new(packfile_entries); handle.write(PktLine::SidebandData(packfile))?; diff --git a/src/main.rs b/src/main.rs index ade64fc..66472ff 100644 --- a/src/main.rs +++ b/src/main.rs @@ -27,6 +27,7 @@ use thrussh::{ use thrussh_keys::key::PublicKey; use tokio_util::{codec::Decoder, codec::Encoder as CodecEncoder}; use tracing::error; +use crate::providers::Group; const AGENT: &str = concat!( "agent=", @@ -94,7 +95,7 @@ pub struct Handler { codec: GitCodec, gitlab: Arc, user: Option, - group: Option, + group: Option, // fetcher_future: Option>>>, input_bytes: BytesMut, output_bytes: BytesMut, @@ -108,8 +109,8 @@ impl Handler { self.user.as_ref().ok_or(anyhow::anyhow!("no user set")) } - fn group(&self) -> anyhow::Result<&str> { - self.group.as_deref().ok_or(anyhow::anyhow!("no group set")) + fn group(&self) -> anyhow::Result<&Group> { + self.group.as_ref().ok_or(anyhow::anyhow!("no group set")) } fn write(&mut self, packet: PktLine<'_>) -> Result<(), anyhow::Error> { @@ -125,9 +126,9 @@ impl Handler { async fn fetch_releases_by_crate( &self, - group: &str, ) -> anyhow::Result>> { let user = self.user()?; + let group = self.group()?; let mut res = HashMap::new(); @@ -208,7 +209,7 @@ impl Handler { packfile.insert(vec![], "config.json".to_string(), config_json)?; // fetch the releases for every project within the given group - let releases_by_crate = self.fetch_releases_by_crate(group).await?; + let releases_by_crate = self.fetch_releases_by_crate().await?; let mut buffer = BytesMut::new(); @@ -414,11 +415,24 @@ impl<'a, U: UserProvider + PackageProvider + Send + Sync + 'static> thrussh::ser // parse the requested group from the given path (the argument // given to `git-upload-pack`) if let Some(group) = args.next().filter(|v| v.as_str() != "/") { + let user = self.user()?; let group = group .trim_start_matches('/') - .trim_end_matches('/') - .to_string(); - self.group = Some(group); + .trim_end_matches('/'); + + match self.gitlab.clone().fetch_group(group, user).await { + Ok(v) => self.group = Some(v), + Err(e) => { + session.extended_data(channel, 1, CryptoVec::from_slice(format!(indoc::indoc! {" + \r\nGitLab returned an error when attempting to query for group `{}` as `{}`: + + {} + + The group might not exist or you may not have permission to view it.\r\n + "}, group, user.username, e).as_bytes())); + session.close(channel); + } + } } else { session.extended_data(channel, 1, CryptoVec::from_slice(indoc::indoc! {b" \r\nNo group was given in the path part of the SSH URI. A GitLab group should be defined in your .cargo/config.toml as follows: diff --git a/src/metadata.rs b/src/metadata.rs index 2a75669..9b35137 100644 --- a/src/metadata.rs +++ b/src/metadata.rs @@ -1,4 +1,4 @@ -use cargo_metadata::{Package}; +use cargo_metadata::Package; use serde::{Deserialize, Serialize}; use std::collections::HashMap; diff --git a/src/providers/gitlab.rs b/src/providers/gitlab.rs index 3d39ab8..0019dbe 100644 --- a/src/providers/gitlab.rs +++ b/src/providers/gitlab.rs @@ -1,9 +1,10 @@ -use crate::providers::{Release, User}; +use crate::providers::{Group, Release, User}; use async_trait::async_trait; use futures::{stream::FuturesUnordered, StreamExt, TryStreamExt}; use percent_encoding::{utf8_percent_encode, NON_ALPHANUMERIC}; use reqwest::header; use serde::{Deserialize, Serialize}; +use std::borrow::Cow; use std::sync::Arc; const GITLAB_API_ENDPOINT: &str = "http://127.0.0.1:3000"; @@ -43,14 +44,16 @@ impl super::UserProvider for Gitlab { }; if username == "gitlab-ci-token" { - let res: GitlabJobResponse = self - .client - .get(format!("{}/job", self.base_url)) - .header("JOB-TOKEN", password) - .send() - .await? - .json() - .await?; + let res: GitlabJobResponse = handle_error( + self.client + .get(format!("{}/job", self.base_url)) + .header("JOB-TOKEN", password) + .send() + .await?, + ) + .await? + .json() + .await?; Ok(Some(User { id: res.user.id, @@ -103,15 +106,32 @@ impl super::UserProvider for Gitlab { impl super::PackageProvider for Gitlab { type CratePath = Arc; + async fn fetch_group(self: Arc, group: &str, do_as: &User) -> anyhow::Result { + let uri = format!( + "{}/groups/{}?sudo={}", + self.base_url, + utf8_percent_encode(group, NON_ALPHANUMERIC), + do_as.id + ); + + let req = handle_error(self.client.get(uri).send().await?) + .await? + .json::() + .await? + .into(); + + Ok(req) + } + async fn fetch_releases_for_group( self: Arc, - group: &str, + group: &Group, do_as: &User, ) -> anyhow::Result> { let mut next_uri = Some(format!( "{}/groups/{}/packages?per_page=100&pagination=keyset&sort=asc&sudo={}", self.base_url, - utf8_percent_encode(group, NON_ALPHANUMERIC), + utf8_percent_encode(&group.name, NON_ALPHANUMERIC), do_as.id )); @@ -223,14 +243,50 @@ impl super::PackageProvider for Gitlab { Ok(self.client.get(uri).send().await?.json().await?) } - fn cargo_dl_uri(&self, group: &str, token: &str) -> String { + fn cargo_dl_uri(&self, group: &Group, token: &str) -> String { format!( - "{}/groups/{group}/packages/generic/{{sha256-checksum}}/{{crate}}-{{version}}.crate?private_token={token}", - self.base_url + "{}/groups/{}/packages/generic/{{sha256-checksum}}/{{crate}}-{{version}}.crate?private_token={token}", + self.base_url, + group.id, ) } } +async fn handle_error(resp: reqwest::Response) -> Result { + if resp.status().is_success() { + Ok(resp) + } else { + let resp: GitlabErrorResponse = resp.json().await?; + Err(anyhow::Error::msg( + resp.message + .or(resp.error) + .map(Cow::Owned) + .unwrap_or_else(|| Cow::Borrowed("unknown error")), + )) + } +} + +#[derive(Deserialize)] +pub struct GitlabErrorResponse { + message: Option, + error: Option, +} + +#[derive(Deserialize)] +pub struct GitlabGroupResponse { + id: u64, + name: String, +} + +impl From for Group { + fn from(v: GitlabGroupResponse) -> Self { + Self { + id: v.id, + name: v.name, + } + } +} + #[derive(Clone, Debug, Hash, PartialEq, Eq)] pub struct GitlabCratePath { project: String, diff --git a/src/providers/mod.rs b/src/providers/mod.rs index ffa2bed..4aebf12 100644 --- a/src/providers/mod.rs +++ b/src/providers/mod.rs @@ -21,9 +21,11 @@ pub trait PackageProvider { /// figure out the path of a package. type CratePath: std::fmt::Debug + Send + std::hash::Hash + Clone + Eq + PartialEq + Send + Sync; + async fn fetch_group(self: Arc, group: &str, do_as: &User) -> anyhow::Result; + async fn fetch_releases_for_group( self: Arc, - group: &str, + group: &Group, do_as: &User, ) -> anyhow::Result>; @@ -33,7 +35,7 @@ pub trait PackageProvider { version: &str, ) -> anyhow::Result; - fn cargo_dl_uri(&self, group: &str, token: &str) -> String; + fn cargo_dl_uri(&self, group: &Group, token: &str) -> String; } #[derive(Debug, Clone)] @@ -42,6 +44,12 @@ pub struct User { pub username: String, } +#[derive(Debug, Clone)] +pub struct Group { + pub id: u64, + pub name: String, +} + #[derive(Debug)] pub struct Release { pub name: String, -- libgit2 1.7.2