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