]> Untitled Git - lemmy.git/blob - crates/apub/src/activities/deletion/mod.rs
Dont return error in case optional auth is invalid (#2879)
[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   websocket::UserOperationCrud,
39 };
40 use lemmy_db_schema::{
41   source::{
42     comment::{Comment, CommentUpdateForm},
43     community::{Community, CommunityUpdateForm},
44     person::Person,
45     post::{Post, PostUpdateForm},
46     private_message::{PrivateMessage, PrivateMessageUpdateForm},
47   },
48   traits::Crud,
49 };
50 use lemmy_utils::error::LemmyError;
51 use std::ops::Deref;
52 use url::Url;
53
54 pub mod delete;
55 pub mod delete_user;
56 pub mod undo_delete;
57
58 #[async_trait::async_trait]
59 impl SendActivity for DeletePost {
60   type Response = PostResponse;
61
62   async fn send_activity(
63     request: &Self,
64     response: &Self::Response,
65     context: &Data<LemmyContext>,
66   ) -> Result<(), LemmyError> {
67     let local_user_view = local_user_view_from_jwt(&request.auth, context).await?;
68     let community = Community::read(context.pool(), response.post_view.community.id).await?;
69     let deletable = DeletableObjects::Post(response.post_view.post.clone().into());
70     send_apub_delete_in_community(
71       local_user_view.person,
72       community,
73       deletable,
74       None,
75       request.deleted,
76       context,
77     )
78     .await
79   }
80 }
81
82 #[async_trait::async_trait]
83 impl SendActivity for RemovePost {
84   type Response = PostResponse;
85
86   async fn send_activity(
87     request: &Self,
88     response: &Self::Response,
89     context: &Data<LemmyContext>,
90   ) -> Result<(), LemmyError> {
91     let local_user_view = local_user_view_from_jwt(&request.auth, context).await?;
92     let community = Community::read(context.pool(), response.post_view.community.id).await?;
93     let deletable = DeletableObjects::Post(response.post_view.post.clone().into());
94     send_apub_delete_in_community(
95       local_user_view.person,
96       community,
97       deletable,
98       request.reason.clone().or_else(|| Some(String::new())),
99       request.removed,
100       context,
101     )
102     .await
103   }
104 }
105
106 #[async_trait::async_trait]
107 impl SendActivity for DeleteComment {
108   type Response = CommentResponse;
109
110   async fn send_activity(
111     request: &Self,
112     response: &Self::Response,
113     context: &Data<LemmyContext>,
114   ) -> Result<(), LemmyError> {
115     let community_id = response.comment_view.community.id;
116     let community = Community::read(context.pool(), community_id).await?;
117     let person = Person::read(context.pool(), response.comment_view.creator.id).await?;
118     let deletable = DeletableObjects::Comment(response.comment_view.comment.clone().into());
119     send_apub_delete_in_community(person, community, deletable, None, request.deleted, context)
120       .await
121   }
122 }
123
124 #[async_trait::async_trait]
125 impl SendActivity for RemoveComment {
126   type Response = CommentResponse;
127
128   async fn send_activity(
129     request: &Self,
130     response: &Self::Response,
131     context: &Data<LemmyContext>,
132   ) -> Result<(), LemmyError> {
133     let local_user_view = local_user_view_from_jwt(&request.auth, context).await?;
134     let comment = Comment::read(context.pool(), request.comment_id).await?;
135     let community = Community::read(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(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(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(context.pool(), recipient_id).await?.into();
257
258   let deletable = DeletableObjects::PrivateMessage(pm.into());
259   let inbox = vec![recipient.shared_inbox_or_inbox()];
260   if deleted {
261     let delete = Delete::new(actor, deletable, recipient.id(), None, None, context)?;
262     send_lemmy_activity(context, delete, actor, inbox, true).await?;
263   } else {
264     let undo = UndoDelete::new(actor, deletable, recipient.id(), None, None, context)?;
265     send_lemmy_activity(context, undo, actor, inbox, true).await?;
266   };
267   Ok(())
268 }
269
270 pub enum DeletableObjects {
271   Community(ApubCommunity),
272   Comment(ApubComment),
273   Post(ApubPost),
274   PrivateMessage(ApubPrivateMessage),
275 }
276
277 impl DeletableObjects {
278   #[tracing::instrument(skip_all)]
279   pub(crate) async fn read_from_db(
280     ap_id: &Url,
281     context: &Data<LemmyContext>,
282   ) -> Result<DeletableObjects, LemmyError> {
283     if let Some(c) = ApubCommunity::read_from_id(ap_id.clone(), context).await? {
284       return Ok(DeletableObjects::Community(c));
285     }
286     if let Some(p) = ApubPost::read_from_id(ap_id.clone(), context).await? {
287       return Ok(DeletableObjects::Post(p));
288     }
289     if let Some(c) = ApubComment::read_from_id(ap_id.clone(), context).await? {
290       return Ok(DeletableObjects::Comment(c));
291     }
292     if let Some(p) = ApubPrivateMessage::read_from_id(ap_id.clone(), context).await? {
293       return Ok(DeletableObjects::PrivateMessage(p));
294     }
295     Err(diesel::NotFound.into())
296   }
297
298   pub(crate) fn id(&self) -> Url {
299     match self {
300       DeletableObjects::Community(c) => c.id(),
301       DeletableObjects::Comment(c) => c.ap_id.clone().into(),
302       DeletableObjects::Post(p) => p.ap_id.clone().into(),
303       DeletableObjects::PrivateMessage(p) => p.ap_id.clone().into(),
304     }
305   }
306 }
307
308 #[tracing::instrument(skip_all)]
309 pub(in crate::activities) async fn verify_delete_activity(
310   activity: &Delete,
311   is_mod_action: bool,
312   context: &Data<LemmyContext>,
313 ) -> Result<(), LemmyError> {
314   let object = DeletableObjects::read_from_db(activity.object.id(), context).await?;
315   match object {
316     DeletableObjects::Community(community) => {
317       verify_is_public(&activity.to, &[])?;
318       if community.local {
319         // can only do this check for local community, in remote case it would try to fetch the
320         // deleted community (which fails)
321         verify_person_in_community(&activity.actor, &community, context).await?;
322       }
323       // community deletion is always a mod (or admin) action
324       verify_mod_action(&activity.actor, activity.object.id(), community.id, context).await?;
325     }
326     DeletableObjects::Post(p) => {
327       verify_is_public(&activity.to, &[])?;
328       verify_delete_post_or_comment(
329         &activity.actor,
330         &p.ap_id.clone().into(),
331         &activity.community(context).await?,
332         is_mod_action,
333         context,
334       )
335       .await?;
336     }
337     DeletableObjects::Comment(c) => {
338       verify_is_public(&activity.to, &[])?;
339       verify_delete_post_or_comment(
340         &activity.actor,
341         &c.ap_id.clone().into(),
342         &activity.community(context).await?,
343         is_mod_action,
344         context,
345       )
346       .await?;
347     }
348     DeletableObjects::PrivateMessage(_) => {
349       verify_person(&activity.actor, context).await?;
350       verify_domains_match(activity.actor.inner(), activity.object.id())?;
351     }
352   }
353   Ok(())
354 }
355
356 #[tracing::instrument(skip_all)]
357 async fn verify_delete_post_or_comment(
358   actor: &ObjectId<ApubPerson>,
359   object_id: &Url,
360   community: &ApubCommunity,
361   is_mod_action: bool,
362   context: &Data<LemmyContext>,
363 ) -> Result<(), LemmyError> {
364   verify_person_in_community(actor, community, context).await?;
365   if is_mod_action {
366     verify_mod_action(actor, object_id, community.id, context).await?;
367   } else {
368     // domain of post ap_id and post.creator ap_id are identical, so we just check the former
369     verify_domains_match(actor.inner(), object_id)?;
370   }
371   Ok(())
372 }
373
374 /// Write deletion or restoring of an object to the database, and send websocket message.
375 #[tracing::instrument(skip_all)]
376 async fn receive_delete_action(
377   object: &Url,
378   actor: &ObjectId<ApubPerson>,
379   deleted: bool,
380   context: &Data<LemmyContext>,
381 ) -> Result<(), LemmyError> {
382   match DeletableObjects::read_from_db(object, context).await? {
383     DeletableObjects::Community(community) => {
384       if community.local {
385         let mod_: Person = actor.dereference(context).await?.deref().clone();
386         let object = DeletableObjects::Community(community.clone());
387         let c: Community = community.deref().deref().clone();
388         send_apub_delete_in_community(mod_, c, object, None, true, context).await?;
389       }
390
391       let community = Community::update(
392         context.pool(),
393         community.id,
394         &CommunityUpdateForm::builder()
395           .deleted(Some(deleted))
396           .build(),
397       )
398       .await?;
399       context
400         .send_community_ws_message(
401           &UserOperationCrud::DeleteCommunity,
402           community.id,
403           None,
404           None,
405         )
406         .await?;
407     }
408     DeletableObjects::Post(post) => {
409       if deleted != post.deleted {
410         let deleted_post = Post::update(
411           context.pool(),
412           post.id,
413           &PostUpdateForm::builder().deleted(Some(deleted)).build(),
414         )
415         .await?;
416         context
417           .send_post_ws_message(&UserOperationCrud::DeletePost, deleted_post.id, None, None)
418           .await?;
419       }
420     }
421     DeletableObjects::Comment(comment) => {
422       if deleted != comment.deleted {
423         let deleted_comment = Comment::update(
424           context.pool(),
425           comment.id,
426           &CommentUpdateForm::builder().deleted(Some(deleted)).build(),
427         )
428         .await?;
429         context
430           .send_comment_ws_message_simple(&UserOperationCrud::DeleteComment, deleted_comment.id)
431           .await?;
432       }
433     }
434     DeletableObjects::PrivateMessage(pm) => {
435       let deleted_private_message = PrivateMessage::update(
436         context.pool(),
437         pm.id,
438         &PrivateMessageUpdateForm::builder()
439           .deleted(Some(deleted))
440           .build(),
441       )
442       .await?;
443
444       context
445         .send_pm_ws_message(
446           &UserOperationCrud::DeletePrivateMessage,
447           deleted_private_message.id,
448           None,
449         )
450         .await?;
451     }
452   }
453   Ok(())
454 }