use apub_core::{
activitypub::{Activity, DeliverableObject},
ingest::{Authority, Ingest},
repo::Repo,
session::Session,
};
use url::Url;
pub trait InboxType {
fn is_shared(&self) -> bool;
}
#[derive(Clone, Debug)]
pub struct ValidateAuthority<I> {
ingest: I,
}
#[derive(Clone, Debug)]
pub struct ValidateInbox<I> {
ingest: I,
}
#[derive(Clone, Debug)]
pub struct ValidateHosts<I> {
ingest: I,
}
pub fn validate_authority<I>(ingest: I) -> ValidateAuthority<I> {
ValidateAuthority { ingest }
}
pub fn validate_inbox<I>(ingest: I) -> ValidateInbox<I> {
ValidateInbox { ingest }
}
pub fn validate_hosts<I>(ingest: I) -> ValidateHosts<I> {
ValidateHosts { ingest }
}
#[derive(Clone, Debug)]
pub struct AuthorityError {
authority: Authority,
actor: Url,
}
#[derive(Clone, Debug)]
pub struct InboxError;
#[derive(Clone, Debug)]
pub struct HostError;
#[async_trait::async_trait(?Send)]
impl<A, I> Ingest<A> for ValidateAuthority<I>
where
A: Activity + 'static,
I: Ingest<A>,
I::Error: From<AuthorityError>,
{
type Local = I::Local;
type ActorId = I::ActorId;
type Error = I::Error;
fn local_repo(&self) -> &Self::Local {
self.ingest.local_repo()
}
fn is_local(&self, url: &Url) -> bool {
self.ingest.is_local(url)
}
async fn ingest<R: Repo, S: Session>(
&self,
authority: Authority,
actor_id: Self::ActorId,
activity: &A,
remote_repo: R,
session: S,
) -> Result<(), Self::Error>
where
Self::Error: From<R::Error>,
{
let activity_actor = activity.actor_id();
let valid = match &authority {
Authority::Server(url) => {
url.host() == activity_actor.host() && url.port() == activity_actor.port()
}
Authority::Actor(actor) => actor == activity_actor,
Authority::None => false,
};
if !valid {
return Err(I::Error::from(AuthorityError {
authority,
actor: activity_actor.clone(),
}));
}
self.ingest
.ingest(authority, actor_id, activity, remote_repo, session)
.await
}
}
#[async_trait::async_trait(?Send)]
impl<A, I> Ingest<A> for ValidateInbox<I>
where
A: DeliverableObject + 'static,
I: Ingest<A>,
I::Error: From<InboxError>,
I::ActorId: InboxType,
{
type Local = I::Local;
type ActorId = I::ActorId;
type Error = I::Error;
fn local_repo(&self) -> &Self::Local {
self.ingest.local_repo()
}
fn is_local(&self, url: &Url) -> bool {
self.ingest.is_local(url)
}
async fn ingest<R: Repo, S: Session>(
&self,
authority: Authority,
actor_id: Self::ActorId,
activity: &A,
remote_repo: R,
session: S,
) -> Result<(), Self::Error>
where
Self::Error: From<R::Error>,
{
if !actor_id.is_shared() && activity.is_public() {
return Err(I::Error::from(InboxError));
}
if actor_id.is_shared() && !activity.is_public() {
return Err(I::Error::from(InboxError));
}
self.ingest
.ingest(authority, actor_id, activity, remote_repo, session)
.await
}
}
#[async_trait::async_trait(?Send)]
impl<A, I> Ingest<A> for ValidateHosts<I>
where
A: Activity + 'static,
I: Ingest<A>,
I::Error: From<HostError>,
{
type Local = I::Local;
type ActorId = I::ActorId;
type Error = I::Error;
fn local_repo(&self) -> &Self::Local {
self.ingest.local_repo()
}
fn is_local(&self, url: &Url) -> bool {
self.ingest.is_local(url)
}
async fn ingest<R: Repo, S: Session>(
&self,
authority: Authority,
actor_id: Self::ActorId,
activity: &A,
remote_repo: R,
session: S,
) -> Result<(), Self::Error>
where
Self::Error: From<R::Error>,
{
if activity.id().host() != activity.actor_id().host()
|| activity.id().port() != activity.actor_id().port()
{
return Err(I::Error::from(HostError));
}
self.ingest
.ingest(authority, actor_id, activity, remote_repo, session)
.await
}
}
impl std::fmt::Display for AuthorityError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"Authority '{}' not permitted to act on behalf of actor '{}'",
self.authority, self.actor
)
}
}
impl std::error::Error for AuthorityError {}
impl std::fmt::Display for InboxError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Object delivered to invalid inbox")
}
}
impl std::error::Error for InboxError {}
impl std::fmt::Display for HostError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Activity and Actor host do not match")
}
}
impl std::error::Error for HostError {}