218 lines
6.8 KiB
Rust
218 lines
6.8 KiB
Rust
use std::{io::Cursor, sync::Arc};
|
|
|
|
use magnetar_common::{
|
|
config::MagnetarNetworkingProtocol,
|
|
util::{FediverseTag, FediverseTagDisplay, FediverseTagParseError},
|
|
};
|
|
use magnetar_core::web_model::{acct::Acct, content_type::ContentJrdJson};
|
|
use magnetar_host_meta::{Xrd, XrdXml};
|
|
use magnetar_webfinger::webfinger::{WebFinger, WebFingerRel, WebFingerSubject};
|
|
use thiserror::Error;
|
|
use tracing::trace;
|
|
use url::Url;
|
|
|
|
use crate::{
|
|
client::federation_client::{FederationClient, FederationClientError},
|
|
FederationLookupService, HostMetaResolverService, HostUnmapped, MappedUser, UnmappedUser,
|
|
WebFingerResolverService,
|
|
};
|
|
|
|
pub struct HostMetaResolverProviderDefault {
|
|
client: Arc<FederationClient>,
|
|
protocol: MagnetarNetworkingProtocol,
|
|
}
|
|
|
|
#[async_trait::async_trait]
|
|
impl HostMetaResolverService for HostMetaResolverProviderDefault {
|
|
type Error = FederationClientError;
|
|
|
|
async fn resolve(&self, HostUnmapped(host): &HostUnmapped) -> Result<Xrd, Self::Error> {
|
|
let host_meta_xml = self
|
|
.client
|
|
.get(Url::parse(&format!(
|
|
"{}://{}/.well-known/host-meta",
|
|
self.protocol.as_ref(),
|
|
host
|
|
))?)
|
|
.send()
|
|
.await?;
|
|
|
|
let XrdXml::Xrd(xrd) = quick_xml::de::from_reader(Cursor::new(host_meta_xml))?;
|
|
|
|
Ok(xrd)
|
|
}
|
|
}
|
|
|
|
pub struct WebFingerResolverProviderDefault {
|
|
client: Arc<FederationClient>,
|
|
}
|
|
|
|
#[async_trait::async_trait]
|
|
impl WebFingerResolverService for WebFingerResolverProviderDefault {
|
|
type Error = FederationClientError;
|
|
|
|
async fn resolve_url(&self, url: &str) -> Result<WebFinger, Self::Error> {
|
|
let host_meta_xml = self
|
|
.client
|
|
.get(Url::parse(url)?)
|
|
.content_type(ContentJrdJson)
|
|
.send()
|
|
.await?;
|
|
let webfinger = serde_json::from_reader(Cursor::new(host_meta_xml))?;
|
|
Ok(webfinger)
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Error)]
|
|
pub enum FederationLookupErrror {
|
|
#[error("Federation client error: {0}")]
|
|
FederationClientError(#[from] FederationClientError),
|
|
#[error("Fediverse tag parse error: {0}")]
|
|
FediverseTagParseError(#[from] FediverseTagParseError),
|
|
#[error("URL parse error: {0}")]
|
|
UrlParseError(#[from] url::ParseError),
|
|
#[error("Missing ActivityStreams URL in WebFinger")]
|
|
MissingApUrl,
|
|
#[error("Missing Acct URI in WebFinger")]
|
|
MissingAcctUri,
|
|
}
|
|
|
|
#[derive(Clone)]
|
|
pub struct FederationLookupServiceProviderDefault {
|
|
host_meta_resolver: Arc<dyn HostMetaResolverService<Error = FederationClientError>>,
|
|
webfinger_resolver: Arc<dyn WebFingerResolverService<Error = FederationClientError>>,
|
|
protocol: MagnetarNetworkingProtocol,
|
|
}
|
|
|
|
impl FederationLookupServiceProviderDefault {
|
|
pub fn new(
|
|
host_meta_resolver: Arc<dyn HostMetaResolverService<Error = FederationClientError>>,
|
|
webfinger_resolver: Arc<dyn WebFingerResolverService<Error = FederationClientError>>,
|
|
protocol: MagnetarNetworkingProtocol,
|
|
) -> Self {
|
|
Self {
|
|
host_meta_resolver,
|
|
webfinger_resolver,
|
|
protocol,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[async_trait::async_trait]
|
|
impl FederationLookupService for FederationLookupServiceProviderDefault {
|
|
type Error = FederationLookupErrror;
|
|
|
|
#[tracing::instrument(level = "trace", skip(self))]
|
|
async fn map_fedi_tag(
|
|
&self,
|
|
user: &UnmappedUser,
|
|
) -> Result<MappedUser, FederationLookupErrror> {
|
|
trace!("Fetching flow initiated");
|
|
let host_meta = self.host_meta_resolver.resolve(&user.host).await;
|
|
let webfinger_template = match &host_meta {
|
|
Ok(h) => {
|
|
trace!("host-meta found: {:?}", h);
|
|
h.get_webfinger_template().map(str::to_owned)
|
|
}
|
|
Err(e) => {
|
|
trace!("host-meta fetch failed: {}", e);
|
|
None
|
|
}
|
|
}
|
|
.unwrap_or_else(|| {
|
|
Xrd::default_host_meta(self.protocol.as_ref(), &user.host.as_ref().to_string())
|
|
.get_webfinger_template()
|
|
.expect("default WebFinger template")
|
|
.to_owned()
|
|
});
|
|
|
|
let webfinger = self
|
|
.webfinger_resolver
|
|
.resolve(
|
|
&webfinger_template,
|
|
Acct::from(FediverseTag::from(user)).as_ref(),
|
|
)
|
|
.await?;
|
|
|
|
trace!("Webfinger fetched: {:?}", webfinger);
|
|
|
|
let real_tag = match &webfinger.subject {
|
|
WebFingerSubject::Acct(acct) => Some(acct.clone()),
|
|
_ => webfinger
|
|
.aliases
|
|
.iter()
|
|
.flatten()
|
|
.find_map(|alias| match alias {
|
|
WebFingerSubject::Acct(acct) => Some(acct.clone()),
|
|
_ => None,
|
|
}),
|
|
}
|
|
.ok_or(FederationLookupErrror::MissingAcctUri)?;
|
|
|
|
let ap_url = webfinger
|
|
.links
|
|
.iter()
|
|
.find_map(|link| match link {
|
|
WebFingerRel::RelSelf { href, .. } | WebFingerRel::RelSelfAlt { href, .. } => {
|
|
Some(href)
|
|
}
|
|
_ => None,
|
|
})
|
|
.ok_or(FederationLookupErrror::MissingApUrl)?
|
|
.parse()?;
|
|
|
|
Ok(MappedUser {
|
|
host_meta: host_meta.ok(),
|
|
webfinger,
|
|
tag_mapped: FediverseTag::try_from(&FediverseTagDisplay::try_from(&real_tag)?)?,
|
|
ap_url,
|
|
})
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod test {
|
|
use std::sync::Arc;
|
|
|
|
use magnetar_common::{config::MagnetarNetworkingProtocol, util::FediverseTag};
|
|
use tracing::{info, Level};
|
|
|
|
use crate::{
|
|
client::federation_client::FederationClient, FederationLookupService, HostUnmapped,
|
|
UnmappedUser,
|
|
};
|
|
|
|
use super::{
|
|
FederationLookupServiceProviderDefault, HostMetaResolverProviderDefault,
|
|
WebFingerResolverProviderDefault,
|
|
};
|
|
|
|
#[tokio::test]
|
|
async fn should_resolve() {
|
|
tracing_subscriber::fmt()
|
|
.with_max_level(Level::TRACE)
|
|
.init();
|
|
|
|
let client = Arc::new(FederationClient::new(true, 64000, 20).unwrap());
|
|
|
|
let federation_lookup = FederationLookupServiceProviderDefault::new(
|
|
Arc::new(HostMetaResolverProviderDefault {
|
|
protocol: MagnetarNetworkingProtocol::Https,
|
|
client: client.clone(),
|
|
}),
|
|
Arc::new(WebFingerResolverProviderDefault { client }),
|
|
MagnetarNetworkingProtocol::Https,
|
|
);
|
|
let tag: FediverseTag = "@natty@astolfo.social".parse().unwrap();
|
|
info!("Resolving: {}", tag);
|
|
let resolved = federation_lookup
|
|
.map_fedi_tag(&UnmappedUser {
|
|
name: tag.name,
|
|
host: HostUnmapped(tag.host.unwrap()),
|
|
})
|
|
.await
|
|
.unwrap();
|
|
info!("Resolved: {:#?}", resolved);
|
|
}
|
|
}
|