]> Untitled Git - lemmy.git/blob - crates/apub/src/activities/deletion/mod.rs
Implement federated user following (fixes #752) (#2577)
[lemmy.git] / crates / apub / src / activities / deletion / mod.rs
1 use crate::{
2   activities::{
3     community::{announce::GetCommunity, 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::activities::deletion::{delete::Delete, undo_delete::UndoDelete},
20   ActorType,
21 };
22 use activitypub_federation::{
23   core::object_id::ObjectId,
24   traits::{Actor, ApubObject},
25   utils::verify_domains_match,
26 };
27 use activitystreams_kinds::public;
28 use lemmy_db_schema::{
29   source::{
30     comment::{Comment, CommentUpdateForm},
31     community::{Community, CommunityUpdateForm},
32     person::Person,
33     post::{Post, PostUpdateForm},
34     private_message::{PrivateMessage, PrivateMessageUpdateForm},
35   },
36   traits::Crud,
37 };
38 use lemmy_utils::error::LemmyError;
39 use lemmy_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   LemmyContext,
47   UserOperationCrud,
48 };
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 /// Parameter `reason` being set indicates that this is a removal by a mod. If its unset, this
57 /// action was done by a normal user.
58 #[tracing::instrument(skip_all)]
59 pub async fn send_apub_delete_in_community(
60   actor: Person,
61   community: Community,
62   object: DeletableObjects,
63   reason: Option<String>,
64   deleted: bool,
65   context: &LemmyContext,
66 ) -> Result<(), LemmyError> {
67   let actor = ApubPerson::from(actor);
68   let is_mod_action = reason.is_some();
69   let activity = if deleted {
70     let delete = Delete::new(&actor, object, public(), Some(&community), reason, context)?;
71     AnnouncableActivities::Delete(delete)
72   } else {
73     let undo = UndoDelete::new(&actor, object, public(), Some(&community), reason, context)?;
74     AnnouncableActivities::UndoDelete(undo)
75   };
76   send_activity_in_community(
77     activity,
78     &actor,
79     &community.into(),
80     vec![],
81     is_mod_action,
82     context,
83   )
84   .await
85 }
86
87 #[tracing::instrument(skip_all)]
88 pub async fn send_apub_delete_private_message(
89   actor: &ApubPerson,
90   pm: PrivateMessage,
91   deleted: bool,
92   context: &LemmyContext,
93 ) -> Result<(), LemmyError> {
94   let recipient_id = pm.recipient_id;
95   let recipient: ApubPerson = Person::read(context.pool(), recipient_id).await?.into();
96
97   let deletable = DeletableObjects::PrivateMessage(Box::new(pm.into()));
98   let inbox = vec![recipient.shared_inbox_or_inbox()];
99   if deleted {
100     let delete = Delete::new(actor, deletable, recipient.actor_id(), None, None, context)?;
101     send_lemmy_activity(context, delete, actor, inbox, true).await?;
102   } else {
103     let undo = UndoDelete::new(actor, deletable, recipient.actor_id(), None, None, context)?;
104     send_lemmy_activity(context, undo, actor, inbox, true).await?;
105   };
106   Ok(())
107 }
108
109 pub enum DeletableObjects {
110   Community(Box<ApubCommunity>),
111   Comment(Box<ApubComment>),
112   Post(Box<ApubPost>),
113   PrivateMessage(Box<ApubPrivateMessage>),
114 }
115
116 impl DeletableObjects {
117   #[tracing::instrument(skip_all)]
118   pub(crate) async fn read_from_db(
119     ap_id: &Url,
120     context: &LemmyContext,
121   ) -> Result<DeletableObjects, LemmyError> {
122     if let Some(c) = ApubCommunity::read_from_apub_id(ap_id.clone(), context).await? {
123       return Ok(DeletableObjects::Community(Box::new(c)));
124     }
125     if let Some(p) = ApubPost::read_from_apub_id(ap_id.clone(), context).await? {
126       return Ok(DeletableObjects::Post(Box::new(p)));
127     }
128     if let Some(c) = ApubComment::read_from_apub_id(ap_id.clone(), context).await? {
129       return Ok(DeletableObjects::Comment(Box::new(c)));
130     }
131     if let Some(p) = ApubPrivateMessage::read_from_apub_id(ap_id.clone(), context).await? {
132       return Ok(DeletableObjects::PrivateMessage(Box::new(p)));
133     }
134     Err(diesel::NotFound.into())
135   }
136
137   pub(crate) fn id(&self) -> Url {
138     match self {
139       DeletableObjects::Community(c) => c.actor_id(),
140       DeletableObjects::Comment(c) => c.ap_id.clone().into(),
141       DeletableObjects::Post(p) => p.ap_id.clone().into(),
142       DeletableObjects::PrivateMessage(p) => p.ap_id.clone().into(),
143     }
144   }
145 }
146
147 #[tracing::instrument(skip_all)]
148 pub(in crate::activities) async fn verify_delete_activity(
149   activity: &Delete,
150   is_mod_action: bool,
151   context: &LemmyContext,
152   request_counter: &mut i32,
153 ) -> Result<(), LemmyError> {
154   let object = DeletableObjects::read_from_db(activity.object.id(), context).await?;
155   match object {
156     DeletableObjects::Community(community) => {
157       verify_is_public(&activity.to, &[])?;
158       if community.local {
159         // can only do this check for local community, in remote case it would try to fetch the
160         // deleted community (which fails)
161         verify_person_in_community(&activity.actor, &community, context, request_counter).await?;
162       }
163       // community deletion is always a mod (or admin) action
164       verify_mod_action(
165         &activity.actor,
166         activity.object.id(),
167         community.id,
168         context,
169         request_counter,
170       )
171       .await?;
172     }
173     DeletableObjects::Post(p) => {
174       verify_is_public(&activity.to, &[])?;
175       verify_delete_post_or_comment(
176         &activity.actor,
177         &p.ap_id.clone().into(),
178         &activity.get_community(context, request_counter).await?,
179         is_mod_action,
180         context,
181         request_counter,
182       )
183       .await?;
184     }
185     DeletableObjects::Comment(c) => {
186       verify_is_public(&activity.to, &[])?;
187       verify_delete_post_or_comment(
188         &activity.actor,
189         &c.ap_id.clone().into(),
190         &activity.get_community(context, request_counter).await?,
191         is_mod_action,
192         context,
193         request_counter,
194       )
195       .await?;
196     }
197     DeletableObjects::PrivateMessage(_) => {
198       verify_person(&activity.actor, context, request_counter).await?;
199       verify_domains_match(activity.actor.inner(), activity.object.id())?;
200     }
201   }
202   Ok(())
203 }
204
205 #[tracing::instrument(skip_all)]
206 async fn verify_delete_post_or_comment(
207   actor: &ObjectId<ApubPerson>,
208   object_id: &Url,
209   community: &ApubCommunity,
210   is_mod_action: bool,
211   context: &LemmyContext,
212   request_counter: &mut i32,
213 ) -> Result<(), LemmyError> {
214   verify_person_in_community(actor, community, context, request_counter).await?;
215   if is_mod_action {
216     verify_mod_action(actor, object_id, community.id, context, request_counter).await?;
217   } else {
218     // domain of post ap_id and post.creator ap_id are identical, so we just check the former
219     verify_domains_match(actor.inner(), object_id)?;
220   }
221   Ok(())
222 }
223
224 /// Write deletion or restoring of an object to the database, and send websocket message.
225 #[tracing::instrument(skip_all)]
226 async fn receive_delete_action(
227   object: &Url,
228   actor: &ObjectId<ApubPerson>,
229   deleted: bool,
230   context: &LemmyContext,
231   request_counter: &mut i32,
232 ) -> Result<(), LemmyError> {
233   match DeletableObjects::read_from_db(object, context).await? {
234     DeletableObjects::Community(community) => {
235       if community.local {
236         let mod_: Person = actor
237           .dereference(context, local_instance(context).await, request_counter)
238           .await?
239           .deref()
240           .clone();
241         let object = DeletableObjects::Community(community.clone());
242         let c: Community = community.deref().deref().clone();
243         send_apub_delete_in_community(mod_, c, object, None, true, context).await?;
244       }
245
246       let community = Community::update(
247         context.pool(),
248         community.id,
249         &CommunityUpdateForm::builder()
250           .deleted(Some(deleted))
251           .build(),
252       )
253       .await?;
254       send_community_ws_message(
255         community.id,
256         UserOperationCrud::DeleteCommunity,
257         None,
258         None,
259         context,
260       )
261       .await?;
262     }
263     DeletableObjects::Post(post) => {
264       if deleted != post.deleted {
265         let deleted_post = Post::update(
266           context.pool(),
267           post.id,
268           &PostUpdateForm::builder().deleted(Some(deleted)).build(),
269         )
270         .await?;
271         send_post_ws_message(
272           deleted_post.id,
273           UserOperationCrud::DeletePost,
274           None,
275           None,
276           context,
277         )
278         .await?;
279       }
280     }
281     DeletableObjects::Comment(comment) => {
282       if deleted != comment.deleted {
283         let deleted_comment = Comment::update(
284           context.pool(),
285           comment.id,
286           &CommentUpdateForm::builder().deleted(Some(deleted)).build(),
287         )
288         .await?;
289         send_comment_ws_message_simple(
290           deleted_comment.id,
291           UserOperationCrud::DeleteComment,
292           context,
293         )
294         .await?;
295       }
296     }
297     DeletableObjects::PrivateMessage(pm) => {
298       let deleted_private_message = PrivateMessage::update(
299         context.pool(),
300         pm.id,
301         &PrivateMessageUpdateForm::builder()
302           .deleted(Some(deleted))
303           .build(),
304       )
305       .await?;
306
307       send_pm_ws_message(
308         deleted_private_message.id,
309         UserOperationCrud::DeletePrivateMessage,
310         None,
311         context,
312       )
313       .await?;
314     }
315   }
316   Ok(())
317 }