apub_ingest/
lib.rs

1use apub_core::{
2    activitypub::{Activity, DeliverableObject},
3    ingest::{Authority, Ingest},
4    repo::Repo,
5    session::Session,
6};
7use url::Url;
8
9pub trait InboxType {
10    fn is_shared(&self) -> bool;
11}
12
13/// Rejects if the Authority does not have permission to act on behalf of Actor
14#[derive(Clone, Debug)]
15pub struct ValidateAuthority<I> {
16    ingest: I,
17}
18
19/// Rejects if a public message was not sent to a shared inbox
20/// Rejects if a private message was sent to a shared inbox
21#[derive(Clone, Debug)]
22pub struct ValidateInbox<I> {
23    ingest: I,
24}
25
26/// Rejects if Activity's host does not match Actor's host
27#[derive(Clone, Debug)]
28pub struct ValidateHosts<I> {
29    ingest: I,
30}
31
32/// Rejects if the Authority does not have permission to act on behalf of Actor
33pub fn validate_authority<I>(ingest: I) -> ValidateAuthority<I> {
34    ValidateAuthority { ingest }
35}
36
37/// Rejects if a public message was not sent to a shared inbox
38/// Rejects if a private message was sent to a shared inbox
39pub fn validate_inbox<I>(ingest: I) -> ValidateInbox<I> {
40    ValidateInbox { ingest }
41}
42
43/// Rejects if Activity's host does not match Actor's host
44pub fn validate_hosts<I>(ingest: I) -> ValidateHosts<I> {
45    ValidateHosts { ingest }
46}
47
48#[derive(Clone, Debug)]
49pub struct AuthorityError {
50    authority: Authority,
51    actor: Url,
52}
53
54#[derive(Clone, Debug)]
55pub struct InboxError;
56
57#[derive(Clone, Debug)]
58pub struct HostError;
59
60#[async_trait::async_trait(?Send)]
61impl<A, I> Ingest<A> for ValidateAuthority<I>
62where
63    A: Activity + 'static,
64    I: Ingest<A>,
65    I::Error: From<AuthorityError>,
66{
67    type Local = I::Local;
68    type ActorId = I::ActorId;
69    type Error = I::Error;
70
71    fn local_repo(&self) -> &Self::Local {
72        self.ingest.local_repo()
73    }
74
75    fn is_local(&self, url: &Url) -> bool {
76        self.ingest.is_local(url)
77    }
78
79    async fn ingest<R: Repo, S: Session>(
80        &self,
81        authority: Authority,
82        actor_id: Self::ActorId,
83        activity: &A,
84        remote_repo: R,
85        session: S,
86    ) -> Result<(), Self::Error>
87    where
88        Self::Error: From<R::Error>,
89    {
90        let activity_actor = activity.actor_id();
91
92        let valid = match &authority {
93            Authority::Server(url) => {
94                url.host() == activity_actor.host() && url.port() == activity_actor.port()
95            }
96            Authority::Actor(actor) => actor == activity_actor,
97            Authority::None => false,
98        };
99
100        if !valid {
101            return Err(I::Error::from(AuthorityError {
102                authority,
103                actor: activity_actor.clone(),
104            }));
105        }
106
107        self.ingest
108            .ingest(authority, actor_id, activity, remote_repo, session)
109            .await
110    }
111}
112
113#[async_trait::async_trait(?Send)]
114impl<A, I> Ingest<A> for ValidateInbox<I>
115where
116    A: DeliverableObject + 'static,
117    I: Ingest<A>,
118    I::Error: From<InboxError>,
119    I::ActorId: InboxType,
120{
121    type Local = I::Local;
122    type ActorId = I::ActorId;
123    type Error = I::Error;
124
125    fn local_repo(&self) -> &Self::Local {
126        self.ingest.local_repo()
127    }
128
129    fn is_local(&self, url: &Url) -> bool {
130        self.ingest.is_local(url)
131    }
132
133    async fn ingest<R: Repo, S: Session>(
134        &self,
135        authority: Authority,
136        actor_id: Self::ActorId,
137        activity: &A,
138        remote_repo: R,
139        session: S,
140    ) -> Result<(), Self::Error>
141    where
142        Self::Error: From<R::Error>,
143    {
144        // delivered a public activity to a user inbox
145        if !actor_id.is_shared() && activity.is_public() {
146            return Err(I::Error::from(InboxError));
147        }
148
149        // delivered a private activity to a shared inbox
150        if actor_id.is_shared() && !activity.is_public() {
151            return Err(I::Error::from(InboxError));
152        }
153
154        self.ingest
155            .ingest(authority, actor_id, activity, remote_repo, session)
156            .await
157    }
158}
159
160#[async_trait::async_trait(?Send)]
161impl<A, I> Ingest<A> for ValidateHosts<I>
162where
163    A: Activity + 'static,
164    I: Ingest<A>,
165    I::Error: From<HostError>,
166{
167    type Local = I::Local;
168    type ActorId = I::ActorId;
169    type Error = I::Error;
170
171    fn local_repo(&self) -> &Self::Local {
172        self.ingest.local_repo()
173    }
174
175    fn is_local(&self, url: &Url) -> bool {
176        self.ingest.is_local(url)
177    }
178    async fn ingest<R: Repo, S: Session>(
179        &self,
180        authority: Authority,
181        actor_id: Self::ActorId,
182        activity: &A,
183        remote_repo: R,
184        session: S,
185    ) -> Result<(), Self::Error>
186    where
187        Self::Error: From<R::Error>,
188    {
189        if activity.id().host() != activity.actor_id().host()
190            || activity.id().port() != activity.actor_id().port()
191        {
192            return Err(I::Error::from(HostError));
193        }
194
195        self.ingest
196            .ingest(authority, actor_id, activity, remote_repo, session)
197            .await
198    }
199}
200
201impl std::fmt::Display for AuthorityError {
202    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
203        write!(
204            f,
205            "Authority '{}' not permitted to act on behalf of actor '{}'",
206            self.authority, self.actor
207        )
208    }
209}
210impl std::error::Error for AuthorityError {}
211
212impl std::fmt::Display for InboxError {
213    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
214        write!(f, "Object delivered to invalid inbox")
215    }
216}
217impl std::error::Error for InboxError {}
218
219impl std::fmt::Display for HostError {
220    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
221        write!(f, "Activity and Actor host do not match")
222    }
223}
224impl std::error::Error for HostError {}