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