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