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