]> Untitled Git - lemmy.git/blob - lemmy_apub/src/inbox/mod.rs
Merge branch 'main' into move_views_to_diesel
[lemmy.git] / lemmy_apub / src / inbox / mod.rs
1 use crate::{
2   check_is_apub_id_valid,
3   extensions::signatures::verify_signature,
4   fetcher::get_or_fetch_and_upsert_actor,
5   ActorType,
6 };
7 use activitystreams::{
8   activity::ActorAndObjectRefExt,
9   base::{AsBase, BaseExt, Extends},
10   object::{AsObject, ObjectExt},
11   public,
12 };
13 use actix_web::HttpRequest;
14 use anyhow::{anyhow, Context};
15 use lemmy_db::{activity::Activity, community::Community, user::User_, ApubObject, DbPool};
16 use lemmy_structs::blocking;
17 use lemmy_utils::{location_info, settings::Settings, LemmyError};
18 use lemmy_websocket::LemmyContext;
19 use serde::{export::fmt::Debug, Serialize};
20 use url::Url;
21
22 pub mod community_inbox;
23 mod receive_for_community;
24 pub mod shared_inbox;
25 pub mod user_inbox;
26
27 pub(crate) fn get_activity_id<T, Kind>(activity: &T, creator_uri: &Url) -> Result<Url, LemmyError>
28 where
29   T: BaseExt<Kind> + Extends<Kind> + Debug,
30   Kind: Serialize,
31   <T as Extends<Kind>>::Error: From<serde_json::Error> + Send + Sync + 'static,
32 {
33   let creator_domain = creator_uri.host_str().context(location_info!())?;
34   let activity_id = activity.id(creator_domain)?;
35   Ok(activity_id.context(location_info!())?.to_owned())
36 }
37
38 pub(crate) async fn is_activity_already_known(
39   pool: &DbPool,
40   activity_id: &Url,
41 ) -> Result<bool, LemmyError> {
42   let activity_id = activity_id.to_string();
43   let existing = blocking(pool, move |conn| {
44     Activity::read_from_apub_id(&conn, &activity_id)
45   })
46   .await?;
47   match existing {
48     Ok(_) => Ok(true),
49     Err(_) => Ok(false),
50   }
51 }
52
53 pub(crate) fn get_activity_to_and_cc<T, Kind>(activity: &T) -> Vec<Url>
54 where
55   T: AsBase<Kind> + AsObject<Kind> + ActorAndObjectRefExt,
56 {
57   let mut to_and_cc = vec![];
58   if let Some(to) = activity.to() {
59     let to = to.to_owned().unwrap_to_vec();
60     let mut to = to
61       .iter()
62       .map(|t| t.as_xsd_any_uri())
63       .flatten()
64       .map(|t| t.to_owned())
65       .collect();
66     to_and_cc.append(&mut to);
67   }
68   if let Some(cc) = activity.cc() {
69     let cc = cc.to_owned().unwrap_to_vec();
70     let mut cc = cc
71       .iter()
72       .map(|c| c.as_xsd_any_uri())
73       .flatten()
74       .map(|c| c.to_owned())
75       .collect();
76     to_and_cc.append(&mut cc);
77   }
78   to_and_cc
79 }
80
81 pub(crate) fn is_addressed_to_public<T, Kind>(activity: &T) -> Result<(), LemmyError>
82 where
83   T: AsBase<Kind> + AsObject<Kind> + ActorAndObjectRefExt,
84 {
85   let to_and_cc = get_activity_to_and_cc(activity);
86   if to_and_cc.contains(&public()) {
87     Ok(())
88   } else {
89     Err(anyhow!("Activity is not addressed to public").into())
90   }
91 }
92
93 pub(crate) async fn inbox_verify_http_signature<T, Kind>(
94   activity: &T,
95   context: &LemmyContext,
96   request: HttpRequest,
97   request_counter: &mut i32,
98 ) -> Result<Box<dyn ActorType>, LemmyError>
99 where
100   T: AsObject<Kind> + ActorAndObjectRefExt + Extends<Kind> + AsBase<Kind>,
101   Kind: Serialize,
102   <T as Extends<Kind>>::Error: From<serde_json::Error> + Send + Sync + 'static,
103 {
104   let actor_id = activity
105     .actor()?
106     .to_owned()
107     .single_xsd_any_uri()
108     .context(location_info!())?;
109   check_is_apub_id_valid(&actor_id)?;
110   let actor = get_or_fetch_and_upsert_actor(&actor_id, &context, request_counter).await?;
111   verify_signature(&request, actor.as_ref())?;
112   Ok(actor)
113 }
114
115 /// Returns true if `to_and_cc` contains at least one local user.
116 pub(crate) async fn is_addressed_to_local_user(
117   to_and_cc: &[Url],
118   pool: &DbPool,
119 ) -> Result<bool, LemmyError> {
120   for url in to_and_cc {
121     let url = url.to_string();
122     let user = blocking(&pool, move |conn| User_::read_from_apub_id(&conn, &url)).await?;
123     if let Ok(u) = user {
124       if u.local {
125         return Ok(true);
126       }
127     }
128   }
129   Ok(false)
130 }
131
132 /// If `to_and_cc` contains the followers collection of a remote community, returns this community
133 /// (like `https://example.com/c/main/followers`)
134 pub(crate) async fn is_addressed_to_community_followers(
135   to_and_cc: &[Url],
136   pool: &DbPool,
137 ) -> Result<Option<Community>, LemmyError> {
138   for url in to_and_cc {
139     let url = url.to_string();
140     // TODO: extremely hacky, we should just store the followers url for each community in the db
141     if url.ends_with("/followers") {
142       let community_url = url.replace("/followers", "");
143       let community = blocking(&pool, move |conn| {
144         Community::read_from_apub_id(&conn, &community_url)
145       })
146       .await??;
147       if !community.local {
148         return Ok(Some(community));
149       }
150     }
151   }
152   Ok(None)
153 }
154
155 pub(in crate::inbox) fn assert_activity_not_local<T, Kind>(activity: &T) -> Result<(), LemmyError>
156 where
157   T: BaseExt<Kind> + Debug,
158 {
159   let id = activity.id_unchecked().context(location_info!())?;
160   let activity_domain = id.domain().context(location_info!())?;
161
162   if activity_domain == Settings::get().hostname {
163     return Err(
164       anyhow!(
165         "Error: received activity which was sent by local instance: {:?}",
166         activity
167       )
168       .into(),
169     );
170   }
171   Ok(())
172 }