]> Untitled Git - lemmy.git/blob - lemmy_apub/src/inbox/shared_inbox.rs
4457a1a6df12ff546b25cf3837e712f5cb7467a1
[lemmy.git] / lemmy_apub / src / inbox / shared_inbox.rs
1 use crate::{
2   activities::receive::{
3     comment::{
4       receive_create_comment,
5       receive_delete_comment,
6       receive_dislike_comment,
7       receive_like_comment,
8       receive_remove_comment,
9       receive_update_comment,
10     },
11     comment_undo::{
12       receive_undo_delete_comment,
13       receive_undo_dislike_comment,
14       receive_undo_like_comment,
15       receive_undo_remove_comment,
16     },
17     community::{
18       receive_delete_community,
19       receive_remove_community,
20       receive_undo_delete_community,
21       receive_undo_remove_community,
22     },
23     find_by_id,
24     post::{
25       receive_create_post,
26       receive_delete_post,
27       receive_dislike_post,
28       receive_like_post,
29       receive_remove_post,
30       receive_update_post,
31     },
32     post_undo::{
33       receive_undo_delete_post,
34       receive_undo_dislike_post,
35       receive_undo_like_post,
36       receive_undo_remove_post,
37     },
38     receive_unhandled_activity,
39     verify_activity_domains_valid,
40     FindResults,
41   },
42   check_is_apub_id_valid,
43   extensions::signatures::verify_signature,
44   fetcher::get_or_fetch_and_upsert_actor,
45   inbox::{get_activity_id, is_activity_already_known},
46   insert_activity,
47   ActorType,
48 };
49 use activitystreams::{
50   activity::{ActorAndObject, Announce, Create, Delete, Dislike, Like, Remove, Undo, Update},
51   base::AnyBase,
52   prelude::*,
53 };
54 use actix_web::{web, HttpRequest, HttpResponse};
55 use anyhow::{anyhow, Context};
56 use lemmy_db::{site::Site, Crud};
57 use lemmy_structs::blocking;
58 use lemmy_utils::{location_info, LemmyError};
59 use lemmy_websocket::LemmyContext;
60 use log::debug;
61 use serde::{Deserialize, Serialize};
62 use std::fmt::Debug;
63 use url::Url;
64
65 /// Allowed activity types for shared inbox.
66 #[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd, Deserialize, Serialize)]
67 #[serde(rename_all = "PascalCase")]
68 pub enum ValidTypes {
69   Create,
70   Update,
71   Like,
72   Dislike,
73   Delete,
74   Undo,
75   Remove,
76   Announce,
77 }
78
79 // TODO: this isnt entirely correct, cause some of these receive are not ActorAndObject,
80 //       but it might still work due to the anybase conversion
81 pub type AcceptedActivities = ActorAndObject<ValidTypes>;
82
83 /// Handler for all incoming requests to shared inbox.
84 pub async fn shared_inbox(
85   request: HttpRequest,
86   input: web::Json<AcceptedActivities>,
87   context: web::Data<LemmyContext>,
88 ) -> Result<HttpResponse, LemmyError> {
89   let activity = input.into_inner();
90
91   let actor_id = activity
92     .actor()?
93     .to_owned()
94     .single_xsd_any_uri()
95     .context(location_info!())?;
96   debug!(
97     "Shared inbox received activity {:?} from {}",
98     &activity.id_unchecked(),
99     &actor_id
100   );
101
102   check_is_apub_id_valid(&actor_id)?;
103
104   let request_counter = &mut 0;
105   let actor = get_or_fetch_and_upsert_actor(&actor_id, &context, request_counter).await?;
106   verify_signature(&request, actor.as_ref())?;
107
108   let activity_id = get_activity_id(&activity, &actor_id)?;
109   if is_activity_already_known(context.pool(), &activity_id).await? {
110     return Ok(HttpResponse::Ok().finish());
111   }
112
113   let any_base = activity.clone().into_any_base()?;
114   let kind = activity.kind().context(location_info!())?;
115   let res = match kind {
116     ValidTypes::Announce => {
117       receive_announce(&context, any_base, actor.as_ref(), request_counter).await
118     }
119     ValidTypes::Create => receive_create(&context, any_base, actor_id, request_counter).await,
120     ValidTypes::Update => receive_update(&context, any_base, actor_id, request_counter).await,
121     ValidTypes::Like => receive_like(&context, any_base, actor_id, request_counter).await,
122     ValidTypes::Dislike => receive_dislike(&context, any_base, actor_id, request_counter).await,
123     ValidTypes::Remove => receive_remove(&context, any_base, actor_id).await,
124     ValidTypes::Delete => receive_delete(&context, any_base, actor_id, request_counter).await,
125     ValidTypes::Undo => receive_undo(&context, any_base, actor_id, request_counter).await,
126   };
127
128   insert_activity(
129     &activity_id,
130     actor.user_id(),
131     activity.clone(),
132     false,
133     context.pool(),
134   )
135   .await?;
136   res
137 }
138
139 /// Takes an announce and passes the inner activity to the appropriate handler.
140 async fn receive_announce(
141   context: &LemmyContext,
142   activity: AnyBase,
143   actor: &dyn ActorType,
144   request_counter: &mut i32,
145 ) -> Result<HttpResponse, LemmyError> {
146   let announce = Announce::from_any_base(activity)?.context(location_info!())?;
147   verify_activity_domains_valid(&announce, actor.actor_id()?, false)?;
148
149   let kind = announce.object().as_single_kind_str();
150   let object = announce
151     .object()
152     .to_owned()
153     .one()
154     .context(location_info!())?;
155
156   let inner_id = object.id().context(location_info!())?.to_owned();
157   check_is_apub_id_valid(&inner_id)?;
158   if is_activity_already_known(context.pool(), &inner_id).await? {
159     return Ok(HttpResponse::Ok().finish());
160   }
161
162   match kind {
163     Some("Create") => receive_create(context, object, inner_id, request_counter).await,
164     Some("Update") => receive_update(context, object, inner_id, request_counter).await,
165     Some("Like") => receive_like(context, object, inner_id, request_counter).await,
166     Some("Dislike") => receive_dislike(context, object, inner_id, request_counter).await,
167     Some("Delete") => receive_delete(context, object, inner_id, request_counter).await,
168     Some("Remove") => receive_remove(context, object, inner_id).await,
169     Some("Undo") => receive_undo(context, object, inner_id, request_counter).await,
170     _ => receive_unhandled_activity(announce),
171   }
172 }
173
174 async fn receive_create(
175   context: &LemmyContext,
176   activity: AnyBase,
177   expected_domain: Url,
178   request_counter: &mut i32,
179 ) -> Result<HttpResponse, LemmyError> {
180   let create = Create::from_any_base(activity)?.context(location_info!())?;
181   verify_activity_domains_valid(&create, expected_domain, true)?;
182
183   match create.object().as_single_kind_str() {
184     Some("Page") => receive_create_post(create, context, request_counter).await,
185     Some("Note") => receive_create_comment(create, context, request_counter).await,
186     _ => receive_unhandled_activity(create),
187   }
188 }
189
190 async fn receive_update(
191   context: &LemmyContext,
192   activity: AnyBase,
193   expected_domain: Url,
194   request_counter: &mut i32,
195 ) -> Result<HttpResponse, LemmyError> {
196   let update = Update::from_any_base(activity)?.context(location_info!())?;
197   verify_activity_domains_valid(&update, expected_domain, true)?;
198
199   match update.object().as_single_kind_str() {
200     Some("Page") => receive_update_post(update, context, request_counter).await,
201     Some("Note") => receive_update_comment(update, context, request_counter).await,
202     _ => receive_unhandled_activity(update),
203   }
204 }
205
206 async fn receive_like(
207   context: &LemmyContext,
208   activity: AnyBase,
209   expected_domain: Url,
210   request_counter: &mut i32,
211 ) -> Result<HttpResponse, LemmyError> {
212   let like = Like::from_any_base(activity)?.context(location_info!())?;
213   verify_activity_domains_valid(&like, expected_domain, false)?;
214
215   match like.object().as_single_kind_str() {
216     Some("Page") => receive_like_post(like, context, request_counter).await,
217     Some("Note") => receive_like_comment(like, context, request_counter).await,
218     _ => receive_unhandled_activity(like),
219   }
220 }
221
222 async fn receive_dislike(
223   context: &LemmyContext,
224   activity: AnyBase,
225   expected_domain: Url,
226   request_counter: &mut i32,
227 ) -> Result<HttpResponse, LemmyError> {
228   let enable_downvotes = blocking(context.pool(), move |conn| {
229     Site::read(conn, 1).map(|s| s.enable_downvotes)
230   })
231   .await??;
232   if !enable_downvotes {
233     return Ok(HttpResponse::Ok().finish());
234   }
235
236   let dislike = Dislike::from_any_base(activity)?.context(location_info!())?;
237   verify_activity_domains_valid(&dislike, expected_domain, false)?;
238
239   match dislike.object().as_single_kind_str() {
240     Some("Page") => receive_dislike_post(dislike, context, request_counter).await,
241     Some("Note") => receive_dislike_comment(dislike, context, request_counter).await,
242     _ => receive_unhandled_activity(dislike),
243   }
244 }
245
246 pub async fn receive_delete(
247   context: &LemmyContext,
248   activity: AnyBase,
249   expected_domain: Url,
250   request_counter: &mut i32,
251 ) -> Result<HttpResponse, LemmyError> {
252   let delete = Delete::from_any_base(activity)?.context(location_info!())?;
253   verify_activity_domains_valid(&delete, expected_domain, true)?;
254
255   let object = delete
256     .object()
257     .to_owned()
258     .single_xsd_any_uri()
259     .context(location_info!())?;
260
261   match find_by_id(context, object).await {
262     Ok(FindResults::Post(p)) => receive_delete_post(context, delete, p, request_counter).await,
263     Ok(FindResults::Comment(c)) => {
264       receive_delete_comment(context, delete, c, request_counter).await
265     }
266     Ok(FindResults::Community(c)) => {
267       receive_delete_community(context, delete, c, request_counter).await
268     }
269     // if we dont have the object, no need to do anything
270     Err(_) => Ok(HttpResponse::Ok().finish()),
271   }
272 }
273
274 async fn receive_remove(
275   context: &LemmyContext,
276   activity: AnyBase,
277   expected_domain: Url,
278 ) -> Result<HttpResponse, LemmyError> {
279   let remove = Remove::from_any_base(activity)?.context(location_info!())?;
280   verify_activity_domains_valid(&remove, expected_domain, false)?;
281
282   let cc = remove
283     .cc()
284     .map(|c| c.as_many())
285     .flatten()
286     .context(location_info!())?;
287   let community_id = cc
288     .first()
289     .map(|c| c.as_xsd_any_uri())
290     .flatten()
291     .context(location_info!())?;
292
293   let object = remove
294     .object()
295     .to_owned()
296     .single_xsd_any_uri()
297     .context(location_info!())?;
298
299   // Ensure that remove activity comes from the same domain as the community
300   remove.id(community_id.domain().context(location_info!())?)?;
301
302   match find_by_id(context, object).await {
303     Ok(FindResults::Post(p)) => receive_remove_post(context, remove, p).await,
304     Ok(FindResults::Comment(c)) => receive_remove_comment(context, remove, c).await,
305     Ok(FindResults::Community(c)) => receive_remove_community(context, remove, c).await,
306     // if we dont have the object, no need to do anything
307     Err(_) => Ok(HttpResponse::Ok().finish()),
308   }
309 }
310
311 async fn receive_undo(
312   context: &LemmyContext,
313   activity: AnyBase,
314   expected_domain: Url,
315   request_counter: &mut i32,
316 ) -> Result<HttpResponse, LemmyError> {
317   let undo = Undo::from_any_base(activity)?.context(location_info!())?;
318   verify_activity_domains_valid(&undo, expected_domain.to_owned(), true)?;
319
320   match undo.object().as_single_kind_str() {
321     Some("Delete") => receive_undo_delete(context, undo, expected_domain, request_counter).await,
322     Some("Remove") => receive_undo_remove(context, undo, expected_domain, request_counter).await,
323     Some("Like") => receive_undo_like(context, undo, expected_domain, request_counter).await,
324     Some("Dislike") => receive_undo_dislike(context, undo, expected_domain, request_counter).await,
325     _ => receive_unhandled_activity(undo),
326   }
327 }
328
329 async fn receive_undo_delete(
330   context: &LemmyContext,
331   undo: Undo,
332   expected_domain: Url,
333   request_counter: &mut i32,
334 ) -> Result<HttpResponse, LemmyError> {
335   let delete = Delete::from_any_base(undo.object().to_owned().one().context(location_info!())?)?
336     .context(location_info!())?;
337   verify_activity_domains_valid(&delete, expected_domain, true)?;
338
339   let object = delete
340     .object()
341     .to_owned()
342     .single_xsd_any_uri()
343     .context(location_info!())?;
344   match find_by_id(context, object).await {
345     Ok(FindResults::Post(p)) => receive_undo_delete_post(context, undo, p, request_counter).await,
346     Ok(FindResults::Comment(c)) => {
347       receive_undo_delete_comment(context, undo, c, request_counter).await
348     }
349     Ok(FindResults::Community(c)) => {
350       receive_undo_delete_community(context, undo, c, request_counter).await
351     }
352     // if we dont have the object, no need to do anything
353     Err(_) => Ok(HttpResponse::Ok().finish()),
354   }
355 }
356
357 async fn receive_undo_remove(
358   context: &LemmyContext,
359   undo: Undo,
360   expected_domain: Url,
361   request_counter: &mut i32,
362 ) -> Result<HttpResponse, LemmyError> {
363   let remove = Remove::from_any_base(undo.object().to_owned().one().context(location_info!())?)?
364     .context(location_info!())?;
365   verify_activity_domains_valid(&remove, expected_domain, false)?;
366
367   let object = remove
368     .object()
369     .to_owned()
370     .single_xsd_any_uri()
371     .context(location_info!())?;
372   match find_by_id(context, object).await {
373     Ok(FindResults::Post(p)) => receive_undo_remove_post(context, undo, p, request_counter).await,
374     Ok(FindResults::Comment(c)) => {
375       receive_undo_remove_comment(context, undo, c, request_counter).await
376     }
377     Ok(FindResults::Community(c)) => {
378       receive_undo_remove_community(context, undo, c, request_counter).await
379     }
380     // if we dont have the object, no need to do anything
381     Err(_) => Ok(HttpResponse::Ok().finish()),
382   }
383 }
384
385 async fn receive_undo_like(
386   context: &LemmyContext,
387   undo: Undo,
388   expected_domain: Url,
389   request_counter: &mut i32,
390 ) -> Result<HttpResponse, LemmyError> {
391   let like = Like::from_any_base(undo.object().to_owned().one().context(location_info!())?)?
392     .context(location_info!())?;
393   verify_activity_domains_valid(&like, expected_domain, false)?;
394
395   let type_ = like
396     .object()
397     .as_single_kind_str()
398     .context(location_info!())?;
399   match type_ {
400     "Note" => receive_undo_like_comment(undo, &like, context, request_counter).await,
401     "Page" => receive_undo_like_post(undo, &like, context, request_counter).await,
402     d => Err(anyhow!("Undo Delete type {} not supported", d).into()),
403   }
404 }
405
406 async fn receive_undo_dislike(
407   context: &LemmyContext,
408   undo: Undo,
409   expected_domain: Url,
410   request_counter: &mut i32,
411 ) -> Result<HttpResponse, LemmyError> {
412   let dislike = Dislike::from_any_base(undo.object().to_owned().one().context(location_info!())?)?
413     .context(location_info!())?;
414   verify_activity_domains_valid(&dislike, expected_domain, false)?;
415
416   let type_ = dislike
417     .object()
418     .as_single_kind_str()
419     .context(location_info!())?;
420   match type_ {
421     "Note" => receive_undo_dislike_comment(undo, &dislike, context, request_counter).await,
422     "Page" => receive_undo_dislike_post(undo, &dislike, context, request_counter).await,
423     d => Err(anyhow!("Undo Delete type {} not supported", d).into()),
424   }
425 }