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