use std::collections::{
HashMap,
HashSet,
};
use anyhow::Context as _;
use async_trait::async_trait;
use tracing::debug;
use yapma_common::http::{
self,
Response as _,
};
use super::{
gpg::{
self,
Deserializable as _,
KeyTrait as _,
},
resolver::Resolver,
ssh,
};
pub struct Github<'a> {
username: &'a str,
}
impl<'a> Github<'a> {
pub const fn new(username: &'a str) -> Self {
Self { username }
}
}
#[async_trait]
impl<'a> Resolver for Github<'a> {
type Error = anyhow::Error;
#[tracing::instrument(skip_all, fields(platform = "github"))]
async fn resolve_ssh<C: http::Client>(
&self,
client: &C,
) -> Result<HashSet<ssh::PublicKey>, Self::Error> {
let url = format!("https://github.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 url = format!("https://api.github.com/users/{}/gpg_keys", self.username);
debug!("fetch: {url}");
#[derive(serde::Deserialize)]
struct Key {
raw_key: String,
}
let keys = client
.get(url)
.await?
.json::<Vec<Key>>()
.await?
.iter()
.map(|Key { raw_key }| {
let (key, _) = gpg::SignedPublicKey::from_string(raw_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)
}
}