2 extern crate lazy_static;
5 pub mod activity_queue;
12 pub mod private_message;
15 use crate::extensions::{
16 group_extensions::GroupExtension,
17 page_extension::PageExtension,
18 signatures::{PublicKey, PublicKeyExtension},
20 use activitystreams::{
22 actor::{ApActor, Group, Person},
25 object::{Page, Tombstone},
28 use activitystreams_ext::{Ext1, Ext2};
29 use actix_web::{body::Body, HttpResponse};
30 use anyhow::{anyhow, Context};
31 use chrono::NaiveDateTime;
32 use lemmy_db::{activity::do_insert_activity, user::User_, DbPool};
33 use lemmy_structs::{blocking, WebFingerResponse};
35 apub::get_apub_protocol_string,
37 request::{retry, RecvError},
39 utils::{convert_datetime, MentionData},
42 use lemmy_websocket::LemmyContext;
46 use url::{ParseError, Url};
48 type GroupExt = Ext2<ApActor<Group>, GroupExtension, PublicKeyExtension>;
49 type PersonExt = Ext1<ApActor<Person>, PublicKeyExtension>;
50 type PageExt = Ext1<Page, PageExtension>;
52 pub static APUB_JSON_CONTENT_TYPE: &str = "application/activity+json";
54 /// Convert the data to json and turn it into an HTTP Response with the correct ActivityPub
56 fn create_apub_response<T>(data: &T) -> HttpResponse<Body>
61 .content_type(APUB_JSON_CONTENT_TYPE)
65 fn create_apub_tombstone_response<T>(data: &T) -> HttpResponse<Body>
70 .content_type(APUB_JSON_CONTENT_TYPE)
74 // Checks if the ID has a valid format, correct scheme, and is in the allowed instance list.
75 fn check_is_apub_id_valid(apub_id: &Url) -> Result<(), LemmyError> {
76 let settings = Settings::get();
77 let domain = apub_id.domain().context(location_info!())?.to_string();
78 let local_instance = settings
81 .collect::<Vec<&str>>()
83 .context(location_info!())?
86 if !settings.federation.enabled {
87 return if domain == local_instance {
92 "Trying to connect with {}, but federation is disabled",
100 if apub_id.scheme() != get_apub_protocol_string() {
101 return Err(anyhow!("invalid apub id scheme: {:?}", apub_id.scheme()).into());
104 let mut allowed_instances = Settings::get().get_allowed_instances();
105 let blocked_instances = Settings::get().get_blocked_instances();
107 if !allowed_instances.is_empty() {
108 // need to allow this explicitly because apub activities might contain objects from our local
109 // instance. split is needed to remove the port in our federation test setup.
110 allowed_instances.push(local_instance);
112 if allowed_instances.contains(&domain) {
115 Err(anyhow!("{} not in federation allowlist", domain).into())
117 } else if !blocked_instances.is_empty() {
118 if blocked_instances.contains(&domain) {
119 Err(anyhow!("{} is in federation blocklist", domain).into())
124 panic!("Invalid config, both allowed_instances and blocked_instances are specified");
128 #[async_trait::async_trait(?Send)]
131 async fn to_apub(&self, pool: &DbPool) -> Result<Self::Response, LemmyError>;
132 fn to_tombstone(&self) -> Result<Tombstone, LemmyError>;
135 /// Updated is actually the deletion time
136 fn create_tombstone<T>(
139 updated: Option<NaiveDateTime>,
141 ) -> Result<Tombstone, LemmyError>
146 if let Some(updated) = updated {
147 let mut tombstone = Tombstone::new();
148 tombstone.set_id(object_id.parse()?);
149 tombstone.set_former_type(former_type.to_string());
150 tombstone.set_deleted(convert_datetime(updated));
153 Err(anyhow!("Cant convert to tombstone because updated time was None.").into())
156 Err(anyhow!("Cant convert object to tombstone if it wasnt deleted").into())
160 #[async_trait::async_trait(?Send)]
163 /// Converts an object from ActivityPub type to Lemmy internal type.
165 /// * `apub` The object to read from
166 /// * `context` LemmyContext which holds DB pool, HTTP client etc
167 /// * `expected_domain` If present, ensure that the apub object comes from the same domain as
171 apub: &Self::ApubType,
172 context: &LemmyContext,
173 expected_domain: Option<Url>,
174 ) -> Result<Self, LemmyError>
179 #[async_trait::async_trait(?Send)]
180 pub trait ApubObjectType {
181 async fn send_create(&self, creator: &User_, context: &LemmyContext) -> Result<(), LemmyError>;
182 async fn send_update(&self, creator: &User_, context: &LemmyContext) -> Result<(), LemmyError>;
183 async fn send_delete(&self, creator: &User_, context: &LemmyContext) -> Result<(), LemmyError>;
184 async fn send_undo_delete(
187 context: &LemmyContext,
188 ) -> Result<(), LemmyError>;
189 async fn send_remove(&self, mod_: &User_, context: &LemmyContext) -> Result<(), LemmyError>;
190 async fn send_undo_remove(&self, mod_: &User_, context: &LemmyContext) -> Result<(), LemmyError>;
193 pub(in crate) fn check_actor_domain<T, Kind>(
195 expected_domain: Option<Url>,
196 ) -> Result<String, LemmyError>
198 T: Base + AsBase<Kind>,
200 let actor_id = if let Some(url) = expected_domain {
201 let domain = url.domain().context(location_info!())?;
202 apub.id(domain)?.context(location_info!())?
204 let actor_id = apub.id_unchecked().context(location_info!())?;
205 check_is_apub_id_valid(&actor_id)?;
208 Ok(actor_id.to_string())
211 #[async_trait::async_trait(?Send)]
212 pub trait ApubLikeableType {
213 async fn send_like(&self, creator: &User_, context: &LemmyContext) -> Result<(), LemmyError>;
214 async fn send_dislike(&self, creator: &User_, context: &LemmyContext) -> Result<(), LemmyError>;
215 async fn send_undo_like(&self, creator: &User_, context: &LemmyContext)
216 -> Result<(), LemmyError>;
219 #[async_trait::async_trait(?Send)]
220 pub trait ActorType {
221 fn actor_id_str(&self) -> String;
223 // TODO: every actor should have a public key, so this shouldnt be an option (needs to be fixed in db)
224 fn public_key(&self) -> Option<String>;
225 fn private_key(&self) -> Option<String>;
227 /// numeric id in the database, used for insert_activity
228 fn user_id(&self) -> i32;
230 // These two have default impls, since currently a community can't follow anything,
231 // and a user can't be followed (yet)
232 #[allow(unused_variables)]
233 async fn send_follow(
235 follow_actor_id: &Url,
236 context: &LemmyContext,
237 ) -> Result<(), LemmyError>;
238 async fn send_unfollow(
240 follow_actor_id: &Url,
241 context: &LemmyContext,
242 ) -> Result<(), LemmyError>;
244 #[allow(unused_variables)]
245 async fn send_accept_follow(
248 context: &LemmyContext,
249 ) -> Result<(), LemmyError>;
251 async fn send_delete(&self, creator: &User_, context: &LemmyContext) -> Result<(), LemmyError>;
252 async fn send_undo_delete(
255 context: &LemmyContext,
256 ) -> Result<(), LemmyError>;
258 async fn send_remove(&self, mod_: &User_, context: &LemmyContext) -> Result<(), LemmyError>;
259 async fn send_undo_remove(&self, mod_: &User_, context: &LemmyContext) -> Result<(), LemmyError>;
261 /// For a given community, returns the inboxes of all followers.
262 async fn get_follower_inboxes(&self, pool: &DbPool) -> Result<Vec<Url>, LemmyError>;
264 fn actor_id(&self) -> Result<Url, ParseError> {
265 Url::parse(&self.actor_id_str())
268 // TODO move these to the db rows
269 fn get_inbox_url(&self) -> Result<Url, ParseError> {
270 Url::parse(&format!("{}/inbox", &self.actor_id_str()))
273 fn get_shared_inbox_url(&self) -> Result<Url, LemmyError> {
274 let actor_id = self.actor_id()?;
278 &actor_id.host_str().context(location_info!())?,
279 if let Some(port) = actor_id.port() {
285 Ok(Url::parse(&url)?)
288 fn get_outbox_url(&self) -> Result<Url, ParseError> {
289 Url::parse(&format!("{}/outbox", &self.actor_id_str()))
292 fn get_followers_url(&self) -> Result<Url, ParseError> {
293 Url::parse(&format!("{}/followers", &self.actor_id_str()))
296 fn get_following_url(&self) -> String {
297 format!("{}/following", &self.actor_id_str())
300 fn get_liked_url(&self) -> String {
301 format!("{}/liked", &self.actor_id_str())
304 fn get_public_key_ext(&self) -> Result<PublicKeyExtension, LemmyError> {
307 id: format!("{}#main-key", self.actor_id_str()),
308 owner: self.actor_id_str(),
309 public_key_pem: self.public_key().context(location_info!())?,
316 pub async fn fetch_webfinger_url(
317 mention: &MentionData,
319 ) -> Result<Url, LemmyError> {
320 let fetch_url = format!(
321 "{}://{}/.well-known/webfinger?resource=acct:{}@{}",
322 get_apub_protocol_string(),
327 debug!("Fetching webfinger url: {}", &fetch_url);
329 let response = retry(|| client.get(&fetch_url).send()).await?;
331 let res: WebFingerResponse = response
334 .map_err(|e| RecvError(e.to_string()))?;
339 .find(|l| l.type_.eq(&Some("application/activity+json".to_string())))
340 .ok_or_else(|| anyhow!("No application/activity+json link found."))?;
344 .map(|u| Url::parse(&u))
346 .ok_or_else(|| anyhow!("No href found.").into())
349 pub async fn insert_activity<T>(
354 ) -> Result<(), LemmyError>
356 T: Serialize + std::fmt::Debug + Send + 'static,
358 blocking(pool, move |conn| {
359 do_insert_activity(conn, user_id, &data, local)