use std::collections::{
HashMap,
HashSet,
};
use anyhow::Context as _;
use async_trait::async_trait;
use tracing::debug;
use url::Url;
use yapma_common::http::{
self,
Response as _,
};
use super::{
gpg::{
self,
Deserializable as _,
KeyTrait as _,
},
resolver::Resolver,
ssh,
};
pub struct Gitlab<'a> {
username: &'a str,
}
impl<'a> Gitlab<'a> {
pub const fn new(username: &'a str) -> Self {
Self { username }
}
}
#[async_trait]
impl<'a> Resolver for Gitlab<'a> {
type Error = anyhow::Error;
#[tracing::instrument(skip_all, fields(platform = "gitlab"))]
async fn resolve_ssh<C: http::Client>(
&self,
client: &C,
) -> Result<HashSet<ssh::PublicKey>, Self::Error> {
let url = format!("https://gitlab.com/{}.keys", self.username);
debug!("fetch: {url}");
let keys = client
.get(url)
.await?
.text()
.await?
.lines()
.map(ssh::PublicKey::from_openssh)
.collect::<Result<_, _>>()?;
Ok(keys)
}
#[tracing::instrument(skip_all, fields(platform = "gitlab"))]
async fn resolve_gpg<C: http::Client>(
&self,
client: &C,
) -> Result<HashMap<gpg::Fingerprint, gpg::SignedPublicKey>, Self::Error> {
let mut url = Url::parse("https://gitlab.com/api/v4/users").unwrap();
#[derive(serde::Deserialize)]
struct User {
id: u64,
}
url.query_pairs_mut().append_pair("username", self.username);
debug!("resolve user: {url}");
let user = {
let mut user = client.get(url.as_str()).await?.json::<Vec<User>>().await?;
user.pop()
.with_context(|| format!("username `{}` not found at gitlab", self.username))?
};
#[derive(serde::Deserialize)]
struct Key {
key: String,
}
url.set_query(None);
url.path_segments_mut()
.expect("cannot be base cannot be")
.push(&user.id.to_string())
.push("gpg_keys");
debug!("fetch: {url}");
let keys = client
.get(url)
.await?
.json::<Vec<Key>>()
.await?
.iter()
.map(|Key { key }| {
let (key, _) = gpg::SignedPublicKey::from_string(key.as_str())?;
let fp = key.fingerprint();
key.verify()
.with_context(|| format!("key {} failed to verify", hex::encode_upper(&fp)))?;
Ok((fp, key))
})
.collect::<Result<HashMap<_, _>, anyhow::Error>>()?;
Ok(keys)
}
}