]> Untitled Git - lemmy.git/blob - lemmy_apub/src/inbox/shared_inbox.rs
In activity table, remove `user_id` and add `sensitive` (#127)
[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(&activity_id, activity.clone(), false, true, context.pool()).await?;
129   res
130 }
131
132 /// Takes an announce and passes the inner activity to the appropriate handler.
133 async fn receive_announce(
134   context: &LemmyContext,
135   activity: AnyBase,
136   actor: &dyn ActorType,
137   request_counter: &mut i32,
138 ) -> Result<HttpResponse, LemmyError> {
139   let announce = Announce::from_any_base(activity)?.context(location_info!())?;
140   verify_activity_domains_valid(&announce, actor.actor_id()?, false)?;
141
142   let kind = announce.object().as_single_kind_str();
143   let object = announce
144     .object()
145     .to_owned()
146     .one()
147     .context(location_info!())?;
148
149   let inner_id = object.id().context(location_info!())?.to_owned();
150   check_is_apub_id_valid(&inner_id)?;
151   if is_activity_already_known(context.pool(), &inner_id).await? {
152     return Ok(HttpResponse::Ok().finish());
153   }
154
155   match kind {
156     Some("Create") => receive_create(context, object, inner_id, request_counter).await,
157     Some("Update") => receive_update(context, object, inner_id, request_counter).await,
158     Some("Like") => receive_like(context, object, inner_id, request_counter).await,
159     Some("Dislike") => receive_dislike(context, object, inner_id, request_counter).await,
160     Some("Delete") => receive_delete(context, object, inner_id, request_counter).await,
161     Some("Remove") => receive_remove(context, object, inner_id).await,
162     Some("Undo") => receive_undo(context, object, inner_id, request_counter).await,
163     _ => receive_unhandled_activity(announce),
164   }
165 }
166
167 async fn receive_create(
168   context: &LemmyContext,
169   activity: AnyBase,
170   expected_domain: Url,
171   request_counter: &mut i32,
172 ) -> Result<HttpResponse, LemmyError> {
173   let create = Create::from_any_base(activity)?.context(location_info!())?;
174   verify_activity_domains_valid(&create, expected_domain, true)?;
175
176   match create.object().as_single_kind_str() {
177     Some("Page") => receive_create_post(create, context, request_counter).await,
178     Some("Note") => receive_create_comment(create, context, request_counter).await,
179     _ => receive_unhandled_activity(create),
180   }
181 }
182
183 async fn receive_update(
184   context: &LemmyContext,
185   activity: AnyBase,
186   expected_domain: Url,
187   request_counter: &mut i32,
188 ) -> Result<HttpResponse, LemmyError> {
189   let update = Update::from_any_base(activity)?.context(location_info!())?;
190   verify_activity_domains_valid(&update, expected_domain, true)?;
191
192   match update.object().as_single_kind_str() {
193     Some("Page") => receive_update_post(update, context, request_counter).await,
194     Some("Note") => receive_update_comment(update, context, request_counter).await,
195     _ => receive_unhandled_activity(update),
196   }
197 }
198
199 async fn receive_like(
200   context: &LemmyContext,
201   activity: AnyBase,
202   expected_domain: Url,
203   request_counter: &mut i32,
204 ) -> Result<HttpResponse, LemmyError> {
205   let like = Like::from_any_base(activity)?.context(location_info!())?;
206   verify_activity_domains_valid(&like, expected_domain, false)?;
207
208   match like.object().as_single_kind_str() {
209     Some("Page") => receive_like_post(like, context, request_counter).await,
210     Some("Note") => receive_like_comment(like, context, request_counter).await,
211     _ => receive_unhandled_activity(like),
212   }
213 }
214
215 async fn receive_dislike(
216   context: &LemmyContext,
217   activity: AnyBase,
218   expected_domain: Url,
219   request_counter: &mut i32,
220 ) -> Result<HttpResponse, LemmyError> {
221   let enable_downvotes = blocking(context.pool(), move |conn| {
222     Site::read(conn, 1).map(|s| s.enable_downvotes)
223   })
224   .await??;
225   if !enable_downvotes {
226     return Ok(HttpResponse::Ok().finish());
227   }
228
229   let dislike = Dislike::from_any_base(activity)?.context(location_info!())?;
230   verify_activity_domains_valid(&dislike, expected_domain, false)?;
231
232   match dislike.object().as_single_kind_str() {
233     Some("Page") => receive_dislike_post(dislike, context, request_counter).await,
234     Some("Note") => receive_dislike_comment(dislike, context, request_counter).await,
235     _ => receive_unhandled_activity(dislike),
236   }
237 }
238
239 pub async fn receive_delete(
240   context: &LemmyContext,
241   activity: AnyBase,
242   expected_domain: Url,
243   request_counter: &mut i32,
244 ) -> Result<HttpResponse, LemmyError> {
245   let delete = Delete::from_any_base(activity)?.context(location_info!())?;
246   verify_activity_domains_valid(&delete, expected_domain, true)?;
247
248   let object = delete
249     .object()
250     .to_owned()
251     .single_xsd_any_uri()
252     .context(location_info!())?;
253
254   match find_by_id(context, object).await {
255     Ok(FindResults::Post(p)) => receive_delete_post(context, delete, p, request_counter).await,
256     Ok(FindResults::Comment(c)) => {
257       receive_delete_comment(context, delete, c, request_counter).await
258     }
259     Ok(FindResults::Community(c)) => {
260       receive_delete_community(context, delete, c, request_counter).await
261     }
262     // if we dont have the object, no need to do anything
263     Err(_) => Ok(HttpResponse::Ok().finish()),
264   }
265 }
266
267 async fn receive_remove(
268   context: &LemmyContext,
269   activity: AnyBase,
270   expected_domain: Url,
271 ) -> Result<HttpResponse, LemmyError> {
272   let remove = Remove::from_any_base(activity)?.context(location_info!())?;
273   verify_activity_domains_valid(&remove, expected_domain, false)?;
274
275   let cc = remove
276     .cc()
277     .map(|c| c.as_many())
278     .flatten()
279     .context(location_info!())?;
280   let community_id = cc
281     .first()
282     .map(|c| c.as_xsd_any_uri())
283     .flatten()
284     .context(location_info!())?;
285
286   let object = remove
287     .object()
288     .to_owned()
289     .single_xsd_any_uri()
290     .context(location_info!())?;
291
292   // Ensure that remove activity comes from the same domain as the community
293   remove.id(community_id.domain().context(location_info!())?)?;
294
295   match find_by_id(context, object).await {
296     Ok(FindResults::Post(p)) => receive_remove_post(context, remove, p).await,
297     Ok(FindResults::Comment(c)) => receive_remove_comment(context, remove, c).await,
298     Ok(FindResults::Community(c)) => receive_remove_community(context, remove, c).await,
299     // if we dont have the object, no need to do anything
300     Err(_) => Ok(HttpResponse::Ok().finish()),
301   }
302 }
303
304 async fn receive_undo(
305   context: &LemmyContext,
306   activity: AnyBase,
307   expected_domain: Url,
308   request_counter: &mut i32,
309 ) -> Result<HttpResponse, LemmyError> {
310   let undo = Undo::from_any_base(activity)?.context(location_info!())?;
311   verify_activity_domains_valid(&undo, expected_domain.to_owned(), true)?;
312
313   match undo.object().as_single_kind_str() {
314     Some("Delete") => receive_undo_delete(context, undo, expected_domain, request_counter).await,
315     Some("Remove") => receive_undo_remove(context, undo, expected_domain, request_counter).await,
316     Some("Like") => receive_undo_like(context, undo, expected_domain, request_counter).await,
317     Some("Dislike") => receive_undo_dislike(context, undo, expected_domain, request_counter).await,
318     _ => receive_unhandled_activity(undo),
319   }
320 }
321
322 async fn receive_undo_delete(
323   context: &LemmyContext,
324   undo: Undo,
325   expected_domain: Url,
326   request_counter: &mut i32,
327 ) -> Result<HttpResponse, LemmyError> {
328   let delete = Delete::from_any_base(undo.object().to_owned().one().context(location_info!())?)?
329     .context(location_info!())?;
330   verify_activity_domains_valid(&delete, expected_domain, true)?;
331
332   let object = delete
333     .object()
334     .to_owned()
335     .single_xsd_any_uri()
336     .context(location_info!())?;
337   match find_by_id(context, object).await {
338     Ok(FindResults::Post(p)) => receive_undo_delete_post(context, undo, p, request_counter).await,
339     Ok(FindResults::Comment(c)) => {
340       receive_undo_delete_comment(context, undo, c, request_counter).await
341     }
342     Ok(FindResults::Community(c)) => {
343       receive_undo_delete_community(context, undo, c, request_counter).await
344     }
345     // if we dont have the object, no need to do anything
346     Err(_) => Ok(HttpResponse::Ok().finish()),
347   }
348 }
349
350 async fn receive_undo_remove(
351   context: &LemmyContext,
352   undo: Undo,
353   expected_domain: Url,
354   request_counter: &mut i32,
355 ) -> Result<HttpResponse, LemmyError> {
356   let remove = Remove::from_any_base(undo.object().to_owned().one().context(location_info!())?)?
357     .context(location_info!())?;
358   verify_activity_domains_valid(&remove, expected_domain, false)?;
359
360   let object = remove
361     .object()
362     .to_owned()
363     .single_xsd_any_uri()
364     .context(location_info!())?;
365   match find_by_id(context, object).await {
366     Ok(FindResults::Post(p)) => receive_undo_remove_post(context, undo, p, request_counter).await,
367     Ok(FindResults::Comment(c)) => {
368       receive_undo_remove_comment(context, undo, c, request_counter).await
369     }
370     Ok(FindResults::Community(c)) => {
371       receive_undo_remove_community(context, undo, c, request_counter).await
372     }
373     // if we dont have the object, no need to do anything
374     Err(_) => Ok(HttpResponse::Ok().finish()),
375   }
376 }
377
378 async fn receive_undo_like(
379   context: &LemmyContext,
380   undo: Undo,
381   expected_domain: Url,
382   request_counter: &mut i32,
383 ) -> Result<HttpResponse, LemmyError> {
384   let like = Like::from_any_base(undo.object().to_owned().one().context(location_info!())?)?
385     .context(location_info!())?;
386   verify_activity_domains_valid(&like, expected_domain, false)?;
387
388   let type_ = like
389     .object()
390     .as_single_kind_str()
391     .context(location_info!())?;
392   match type_ {
393     "Note" => receive_undo_like_comment(undo, &like, context, request_counter).await,
394     "Page" => receive_undo_like_post(undo, &like, context, request_counter).await,
395     d => Err(anyhow!("Undo Delete type {} not supported", d).into()),
396   }
397 }
398
399 async fn receive_undo_dislike(
400   context: &LemmyContext,
401   undo: Undo,
402   expected_domain: Url,
403   request_counter: &mut i32,
404 ) -> Result<HttpResponse, LemmyError> {
405   let dislike = Dislike::from_any_base(undo.object().to_owned().one().context(location_info!())?)?
406     .context(location_info!())?;
407   verify_activity_domains_valid(&dislike, expected_domain, false)?;
408
409   let type_ = dislike
410     .object()
411     .as_single_kind_str()
412     .context(location_info!())?;
413   match type_ {
414     "Note" => receive_undo_dislike_comment(undo, &dislike, context, request_counter).await,
415     "Page" => receive_undo_dislike_post(undo, &dislike, context, request_counter).await,
416     d => Err(anyhow!("Undo Delete type {} not supported", d).into()),
417   }
418 }