]> Untitled Git - lemmy.git/blob - crates/apub/src/activities/deletion/mod.rs
Implement separate mod activities for feature, lock post (#2716)
[lemmy.git] / crates / apub / src / activities / deletion / mod.rs
1 use crate::{
2   activities::{
3     community::send_activity_in_community,
4     send_lemmy_activity,
5     verify_is_public,
6     verify_mod_action,
7     verify_person,
8     verify_person_in_community,
9   },
10   activity_lists::AnnouncableActivities,
11   local_instance,
12   objects::{
13     comment::ApubComment,
14     community::ApubCommunity,
15     person::ApubPerson,
16     post::ApubPost,
17     private_message::ApubPrivateMessage,
18   },
19   protocol::{
20     activities::deletion::{delete::Delete, undo_delete::UndoDelete},
21     InCommunity,
22   },
23   ActorType,
24   SendActivity,
25 };
26 use activitypub_federation::{
27   core::object_id::ObjectId,
28   traits::{Actor, ApubObject},
29   utils::verify_domains_match,
30 };
31 use activitystreams_kinds::public;
32 use lemmy_api_common::{
33   comment::{CommentResponse, DeleteComment, RemoveComment},
34   community::{CommunityResponse, DeleteCommunity, RemoveCommunity},
35   context::LemmyContext,
36   post::{DeletePost, PostResponse, RemovePost},
37   private_message::{DeletePrivateMessage, PrivateMessageResponse},
38   utils::get_local_user_view_from_jwt,
39   websocket::{
40     send::{
41       send_comment_ws_message_simple,
42       send_community_ws_message,
43       send_pm_ws_message,
44       send_post_ws_message,
45     },
46     UserOperationCrud,
47   },
48 };
49 use lemmy_db_schema::{
50   source::{
51     comment::{Comment, CommentUpdateForm},
52     community::{Community, CommunityUpdateForm},
53     person::Person,
54     post::{Post, PostUpdateForm},
55     private_message::{PrivateMessage, PrivateMessageUpdateForm},
56   },
57   traits::Crud,
58 };
59 use lemmy_utils::error::LemmyError;
60 use std::ops::Deref;
61 use url::Url;
62
63 pub mod delete;
64 pub mod delete_user;
65 pub mod undo_delete;
66
67 #[async_trait::async_trait(?Send)]
68 impl SendActivity for DeletePost {
69   type Response = PostResponse;
70
71   async fn send_activity(
72     request: &Self,
73     response: &Self::Response,
74     context: &LemmyContext,
75   ) -> Result<(), LemmyError> {
76     let local_user_view =
77       get_local_user_view_from_jwt(&request.auth, context.pool(), context.secret()).await?;
78     let community = Community::read(context.pool(), response.post_view.community.id).await?;
79     let deletable = DeletableObjects::Post(response.post_view.post.clone().into());
80     send_apub_delete_in_community(
81       local_user_view.person,
82       community,
83       deletable,
84       None,
85       request.deleted,
86       context,
87     )
88     .await
89   }
90 }
91
92 #[async_trait::async_trait(?Send)]
93 impl SendActivity for RemovePost {
94   type Response = PostResponse;
95
96   async fn send_activity(
97     request: &Self,
98     response: &Self::Response,
99     context: &LemmyContext,
100   ) -> Result<(), LemmyError> {
101     let local_user_view =
102       get_local_user_view_from_jwt(&request.auth, context.pool(), context.secret()).await?;
103     let community = Community::read(context.pool(), response.post_view.community.id).await?;
104     let deletable = DeletableObjects::Post(response.post_view.post.clone().into());
105     send_apub_delete_in_community(
106       local_user_view.person,
107       community,
108       deletable,
109       request.reason.clone().or_else(|| Some(String::new())),
110       request.removed,
111       context,
112     )
113     .await
114   }
115 }
116
117 #[async_trait::async_trait(?Send)]
118 impl SendActivity for DeleteComment {
119   type Response = CommentResponse;
120
121   async fn send_activity(
122     request: &Self,
123     response: &Self::Response,
124     context: &LemmyContext,
125   ) -> Result<(), LemmyError> {
126     let community_id = response.comment_view.community.id;
127     let community = Community::read(context.pool(), community_id).await?;
128     let person = Person::read(context.pool(), response.comment_view.creator.id).await?;
129     let deletable = DeletableObjects::Comment(response.comment_view.comment.clone().into());
130     send_apub_delete_in_community(person, community, deletable, None, request.deleted, context)
131       .await
132   }
133 }
134
135 #[async_trait::async_trait(?Send)]
136 impl SendActivity for RemoveComment {
137   type Response = CommentResponse;
138
139   async fn send_activity(
140     request: &Self,
141     response: &Self::Response,
142     context: &LemmyContext,
143   ) -> Result<(), LemmyError> {
144     let local_user_view =
145       get_local_user_view_from_jwt(&request.auth, context.pool(), context.secret()).await?;
146     let comment = Comment::read(context.pool(), request.comment_id).await?;
147     let community = Community::read(context.pool(), response.comment_view.community.id).await?;
148     let deletable = DeletableObjects::Comment(comment.into());
149     send_apub_delete_in_community(
150       local_user_view.person,
151       community,
152       deletable,
153       request.reason.clone().or_else(|| Some(String::new())),
154       request.removed,
155       context,
156     )
157     .await
158   }
159 }
160
161 #[async_trait::async_trait(?Send)]
162 impl SendActivity for DeletePrivateMessage {
163   type Response = PrivateMessageResponse;
164
165   async fn send_activity(
166     request: &Self,
167     response: &Self::Response,
168     context: &LemmyContext,
169   ) -> Result<(), LemmyError> {
170     let local_user_view =
171       get_local_user_view_from_jwt(&request.auth, context.pool(), context.secret()).await?;
172     send_apub_delete_private_message(
173       &local_user_view.person.into(),
174       response.private_message_view.private_message.clone(),
175       request.deleted,
176       context,
177     )
178     .await
179   }
180 }
181
182 #[async_trait::async_trait(?Send)]
183 impl SendActivity for DeleteCommunity {
184   type Response = CommunityResponse;
185
186   async fn send_activity(
187     request: &Self,
188     _response: &Self::Response,
189     context: &LemmyContext,
190   ) -> Result<(), LemmyError> {
191     let local_user_view =
192       get_local_user_view_from_jwt(&request.auth, context.pool(), context.secret()).await?;
193     let community = Community::read(context.pool(), request.community_id).await?;
194     let deletable = DeletableObjects::Community(community.clone().into());
195     send_apub_delete_in_community(
196       local_user_view.person,
197       community,
198       deletable,
199       None,
200       request.deleted,
201       context,
202     )
203     .await
204   }
205 }
206
207 #[async_trait::async_trait(?Send)]
208 impl SendActivity for RemoveCommunity {
209   type Response = CommunityResponse;
210
211   async fn send_activity(
212     request: &Self,
213     _response: &Self::Response,
214     context: &LemmyContext,
215   ) -> Result<(), LemmyError> {
216     let local_user_view =
217       get_local_user_view_from_jwt(&request.auth, context.pool(), context.secret()).await?;
218     let community = Community::read(context.pool(), request.community_id).await?;
219     let deletable = DeletableObjects::Community(community.clone().into());
220     send_apub_delete_in_community(
221       local_user_view.person,
222       community,
223       deletable,
224       request.reason.clone().or_else(|| Some(String::new())),
225       request.removed,
226       context,
227     )
228     .await
229   }
230 }
231
232 /// Parameter `reason` being set indicates that this is a removal by a mod. If its unset, this
233 /// action was done by a normal user.
234 #[tracing::instrument(skip_all)]
235 async fn send_apub_delete_in_community(
236   actor: Person,
237   community: Community,
238   object: DeletableObjects,
239   reason: Option<String>,
240   deleted: bool,
241   context: &LemmyContext,
242 ) -> Result<(), LemmyError> {
243   let actor = ApubPerson::from(actor);
244   let is_mod_action = reason.is_some();
245   let activity = if deleted {
246     let delete = Delete::new(&actor, object, public(), Some(&community), reason, context)?;
247     AnnouncableActivities::Delete(delete)
248   } else {
249     let undo = UndoDelete::new(&actor, object, public(), Some(&community), reason, context)?;
250     AnnouncableActivities::UndoDelete(undo)
251   };
252   send_activity_in_community(
253     activity,
254     &actor,
255     &community.into(),
256     vec![],
257     is_mod_action,
258     context,
259   )
260   .await
261 }
262
263 #[tracing::instrument(skip_all)]
264 async fn send_apub_delete_private_message(
265   actor: &ApubPerson,
266   pm: PrivateMessage,
267   deleted: bool,
268   context: &LemmyContext,
269 ) -> Result<(), LemmyError> {
270   let recipient_id = pm.recipient_id;
271   let recipient: ApubPerson = Person::read(context.pool(), recipient_id).await?.into();
272
273   let deletable = DeletableObjects::PrivateMessage(pm.into());
274   let inbox = vec![recipient.shared_inbox_or_inbox()];
275   if deleted {
276     let delete = Delete::new(actor, deletable, recipient.actor_id(), None, None, context)?;
277     send_lemmy_activity(context, delete, actor, inbox, true).await?;
278   } else {
279     let undo = UndoDelete::new(actor, deletable, recipient.actor_id(), None, None, context)?;
280     send_lemmy_activity(context, undo, actor, inbox, true).await?;
281   };
282   Ok(())
283 }
284
285 pub enum DeletableObjects {
286   Community(ApubCommunity),
287   Comment(ApubComment),
288   Post(ApubPost),
289   PrivateMessage(ApubPrivateMessage),
290 }
291
292 impl DeletableObjects {
293   #[tracing::instrument(skip_all)]
294   pub(crate) async fn read_from_db(
295     ap_id: &Url,
296     context: &LemmyContext,
297   ) -> Result<DeletableObjects, LemmyError> {
298     if let Some(c) = ApubCommunity::read_from_apub_id(ap_id.clone(), context).await? {
299       return Ok(DeletableObjects::Community(c));
300     }
301     if let Some(p) = ApubPost::read_from_apub_id(ap_id.clone(), context).await? {
302       return Ok(DeletableObjects::Post(p));
303     }
304     if let Some(c) = ApubComment::read_from_apub_id(ap_id.clone(), context).await? {
305       return Ok(DeletableObjects::Comment(c));
306     }
307     if let Some(p) = ApubPrivateMessage::read_from_apub_id(ap_id.clone(), context).await? {
308       return Ok(DeletableObjects::PrivateMessage(p));
309     }
310     Err(diesel::NotFound.into())
311   }
312
313   pub(crate) fn id(&self) -> Url {
314     match self {
315       DeletableObjects::Community(c) => c.actor_id(),
316       DeletableObjects::Comment(c) => c.ap_id.clone().into(),
317       DeletableObjects::Post(p) => p.ap_id.clone().into(),
318       DeletableObjects::PrivateMessage(p) => p.ap_id.clone().into(),
319     }
320   }
321 }
322
323 #[tracing::instrument(skip_all)]
324 pub(in crate::activities) async fn verify_delete_activity(
325   activity: &Delete,
326   is_mod_action: bool,
327   context: &LemmyContext,
328   request_counter: &mut i32,
329 ) -> Result<(), LemmyError> {
330   let object = DeletableObjects::read_from_db(activity.object.id(), context).await?;
331   match object {
332     DeletableObjects::Community(community) => {
333       verify_is_public(&activity.to, &[])?;
334       if community.local {
335         // can only do this check for local community, in remote case it would try to fetch the
336         // deleted community (which fails)
337         verify_person_in_community(&activity.actor, &community, context, request_counter).await?;
338       }
339       // community deletion is always a mod (or admin) action
340       verify_mod_action(
341         &activity.actor,
342         activity.object.id(),
343         community.id,
344         context,
345         request_counter,
346       )
347       .await?;
348     }
349     DeletableObjects::Post(p) => {
350       verify_is_public(&activity.to, &[])?;
351       verify_delete_post_or_comment(
352         &activity.actor,
353         &p.ap_id.clone().into(),
354         &activity.community(context, request_counter).await?,
355         is_mod_action,
356         context,
357         request_counter,
358       )
359       .await?;
360     }
361     DeletableObjects::Comment(c) => {
362       verify_is_public(&activity.to, &[])?;
363       verify_delete_post_or_comment(
364         &activity.actor,
365         &c.ap_id.clone().into(),
366         &activity.community(context, request_counter).await?,
367         is_mod_action,
368         context,
369         request_counter,
370       )
371       .await?;
372     }
373     DeletableObjects::PrivateMessage(_) => {
374       verify_person(&activity.actor, context, request_counter).await?;
375       verify_domains_match(activity.actor.inner(), activity.object.id())?;
376     }
377   }
378   Ok(())
379 }
380
381 #[tracing::instrument(skip_all)]
382 async fn verify_delete_post_or_comment(
383   actor: &ObjectId<ApubPerson>,
384   object_id: &Url,
385   community: &ApubCommunity,
386   is_mod_action: bool,
387   context: &LemmyContext,
388   request_counter: &mut i32,
389 ) -> Result<(), LemmyError> {
390   verify_person_in_community(actor, community, context, request_counter).await?;
391   if is_mod_action {
392     verify_mod_action(actor, object_id, community.id, context, request_counter).await?;
393   } else {
394     // domain of post ap_id and post.creator ap_id are identical, so we just check the former
395     verify_domains_match(actor.inner(), object_id)?;
396   }
397   Ok(())
398 }
399
400 /// Write deletion or restoring of an object to the database, and send websocket message.
401 #[tracing::instrument(skip_all)]
402 async fn receive_delete_action(
403   object: &Url,
404   actor: &ObjectId<ApubPerson>,
405   deleted: bool,
406   context: &LemmyContext,
407   request_counter: &mut i32,
408 ) -> Result<(), LemmyError> {
409   match DeletableObjects::read_from_db(object, context).await? {
410     DeletableObjects::Community(community) => {
411       if community.local {
412         let mod_: Person = actor
413           .dereference(context, local_instance(context).await, request_counter)
414           .await?
415           .deref()
416           .clone();
417         let object = DeletableObjects::Community(community.clone());
418         let c: Community = community.deref().deref().clone();
419         send_apub_delete_in_community(mod_, c, object, None, true, context).await?;
420       }
421
422       let community = Community::update(
423         context.pool(),
424         community.id,
425         &CommunityUpdateForm::builder()
426           .deleted(Some(deleted))
427           .build(),
428       )
429       .await?;
430       send_community_ws_message(
431         community.id,
432         UserOperationCrud::DeleteCommunity,
433         None,
434         None,
435         context,
436       )
437       .await?;
438     }
439     DeletableObjects::Post(post) => {
440       if deleted != post.deleted {
441         let deleted_post = Post::update(
442           context.pool(),
443           post.id,
444           &PostUpdateForm::builder().deleted(Some(deleted)).build(),
445         )
446         .await?;
447         send_post_ws_message(
448           deleted_post.id,
449           UserOperationCrud::DeletePost,
450           None,
451           None,
452           context,
453         )
454         .await?;
455       }
456     }
457     DeletableObjects::Comment(comment) => {
458       if deleted != comment.deleted {
459         let deleted_comment = Comment::update(
460           context.pool(),
461           comment.id,
462           &CommentUpdateForm::builder().deleted(Some(deleted)).build(),
463         )
464         .await?;
465         send_comment_ws_message_simple(
466           deleted_comment.id,
467           UserOperationCrud::DeleteComment,
468           context,
469         )
470         .await?;
471       }
472     }
473     DeletableObjects::PrivateMessage(pm) => {
474       let deleted_private_message = PrivateMessage::update(
475         context.pool(),
476         pm.id,
477         &PrivateMessageUpdateForm::builder()
478           .deleted(Some(deleted))
479           .build(),
480       )
481       .await?;
482
483       send_pm_ws_message(
484         deleted_private_message.id,
485         UserOperationCrud::DeletePrivateMessage,
486         None,
487         context,
488       )
489       .await?;
490     }
491   }
492   Ok(())
493 }