Ensure group exists before continuing the handshake with Git
Diff
src/main.rs | 30 +++++++++++++++++++++++++++---
src/metadata.rs | 2 +-
src/git_command_handlers/fetch.rs | 4 ----
src/providers/gitlab.rs | 84 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------
src/providers/mod.rs | 12 ++++++++++--
5 files changed, 103 insertions(+), 29 deletions(-)
@@ -27,6 +27,7 @@
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 @@
codec: GitCodec,
gitlab: Arc<U>,
user: Option<User>,
group: Option<String>,
group: Option<Group>,
input_bytes: BytesMut,
output_bytes: BytesMut,
@@ -108,8 +109,8 @@
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 @@
async fn fetch_releases_by_crate(
&self,
group: &str,
) -> anyhow::Result<HashMap<(U::CratePath, String), Vec<Release>>> {
let user = self.user()?;
let group = self.group()?;
let mut res = HashMap::new();
@@ -208,7 +209,7 @@
packfile.insert(vec![], "config.json".to_string(), config_json)?;
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 @@
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:
@@ -1,4 +1,4 @@
use cargo_metadata::{Package};
use cargo_metadata::Package;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
@@ -32,10 +32,6 @@
handle.write(PktLine::Data(b"packfile\n"))?;
let packfile = PackFile::new(packfile_entries);
handle.write(PktLine::SidebandData(packfile))?;
@@ -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 @@
};
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,
@@ -102,16 +105,33 @@
#[async_trait]
impl super::PackageProvider for Gitlab {
type CratePath = Arc<GitlabCratePath>;
async fn fetch_group(self: Arc<Self>, group: &str, do_as: &User) -> anyhow::Result<Group> {
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::<GitlabGroupResponse>()
.await?
.into();
Ok(req)
}
async fn fetch_releases_for_group(
self: Arc<Self>,
group: &str,
group: &Group,
do_as: &User,
) -> anyhow::Result<Vec<(Self::CratePath, Release)>> {
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,11 +243,47 @@
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<reqwest::Response, anyhow::Error> {
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<String>,
error: Option<String>,
}
#[derive(Deserialize)]
pub struct GitlabGroupResponse {
id: u64,
name: String,
}
impl From<GitlabGroupResponse> for Group {
fn from(v: GitlabGroupResponse) -> Self {
Self {
id: v.id,
name: v.name,
}
}
}
@@ -21,9 +21,11 @@
type CratePath: std::fmt::Debug + Send + std::hash::Hash + Clone + Eq + PartialEq + Send + Sync;
async fn fetch_group(self: Arc<Self>, group: &str, do_as: &User) -> anyhow::Result<Group>;
async fn fetch_releases_for_group(
self: Arc<Self>,
group: &str,
group: &Group,
do_as: &User,
) -> anyhow::Result<Vec<(Self::CratePath, Release)>>;
@@ -33,13 +35,19 @@
version: &str,
) -> anyhow::Result<cargo_metadata::Metadata>;
fn cargo_dl_uri(&self, group: &str, token: &str) -> String;
fn cargo_dl_uri(&self, group: &Group, token: &str) -> String;
}
#[derive(Debug, Clone)]
pub struct User {
pub id: u64,
pub username: String,
}
#[derive(Debug, Clone)]
pub struct Group {
pub id: u64,
pub name: String,
}
#[derive(Debug)]