]> Untitled Git - lemmy.git/blob - crates/apub/src/activities/deletion/mod.rs
Support mastodon deletes
[lemmy.git] / crates / apub / src / activities / deletion / mod.rs
1 use crate::{
2   activities::{verify_mod_action, verify_person_in_community},
3   objects::{comment::ApubComment, community::ApubCommunity, person::ApubPerson, post::ApubPost},
4   protocol::{
5     activities::deletion::{delete::Delete, undo_delete::UndoDelete},
6     objects::tombstone::Tombstone,
7   },
8 };
9 use lemmy_api_common::blocking;
10 use lemmy_apub_lib::{object_id::ObjectId, traits::ApubObject, verify::verify_domains_match};
11 use lemmy_db_schema::source::{comment::Comment, community::Community, post::Post};
12 use lemmy_utils::LemmyError;
13 use lemmy_websocket::{
14   send::{send_comment_ws_message_simple, send_community_ws_message, send_post_ws_message},
15   LemmyContext,
16   UserOperationCrud,
17 };
18 use url::Url;
19
20 pub mod delete;
21 pub mod undo_delete;
22
23 pub async fn send_apub_delete(
24   actor: &ApubPerson,
25   community: &ApubCommunity,
26   object: DeletableObjects,
27   deleted: bool,
28   context: &LemmyContext,
29 ) -> Result<(), LemmyError> {
30   if deleted {
31     Delete::send(actor, community, object, None, context).await
32   } else {
33     UndoDelete::send(actor, community, object, None, context).await
34   }
35 }
36
37 // TODO: remove reason is actually optional in lemmy. we set an empty string in that case, but its
38 //       ugly
39 pub async fn send_apub_remove(
40   actor: &ApubPerson,
41   community: &ApubCommunity,
42   object: DeletableObjects,
43   reason: String,
44   removed: bool,
45   context: &LemmyContext,
46 ) -> Result<(), LemmyError> {
47   if removed {
48     Delete::send(actor, community, object, Some(reason), context).await
49   } else {
50     UndoDelete::send(actor, community, object, Some(reason), context).await
51   }
52 }
53
54 pub enum DeletableObjects {
55   Community(Box<ApubCommunity>),
56   Comment(Box<ApubComment>),
57   Post(Box<ApubPost>),
58 }
59
60 impl DeletableObjects {
61   pub(crate) async fn read_from_db(
62     ap_id: &Url,
63     context: &LemmyContext,
64   ) -> Result<DeletableObjects, LemmyError> {
65     if let Some(c) = ApubCommunity::read_from_apub_id(ap_id.clone(), context).await? {
66       return Ok(DeletableObjects::Community(Box::new(c)));
67     }
68     if let Some(p) = ApubPost::read_from_apub_id(ap_id.clone(), context).await? {
69       return Ok(DeletableObjects::Post(Box::new(p)));
70     }
71     if let Some(c) = ApubComment::read_from_apub_id(ap_id.clone(), context).await? {
72       return Ok(DeletableObjects::Comment(Box::new(c)));
73     }
74     Err(diesel::NotFound.into())
75   }
76
77   pub(crate) fn to_tombstone(&self) -> Result<Tombstone, LemmyError> {
78     match self {
79       DeletableObjects::Community(c) => c.to_tombstone(),
80       DeletableObjects::Comment(c) => c.to_tombstone(),
81       DeletableObjects::Post(p) => p.to_tombstone(),
82     }
83   }
84 }
85
86 pub(in crate::activities) async fn verify_delete_activity(
87   object: &Url,
88   actor: &ObjectId<ApubPerson>,
89   community: &ApubCommunity,
90   is_mod_action: bool,
91   context: &LemmyContext,
92   request_counter: &mut i32,
93 ) -> Result<(), LemmyError> {
94   let object = DeletableObjects::read_from_db(object, context).await?;
95   match object {
96     DeletableObjects::Community(community) => {
97       if community.local {
98         // can only do this check for local community, in remote case it would try to fetch the
99         // deleted community (which fails)
100         verify_person_in_community(actor, &community, context, request_counter).await?;
101       }
102       // community deletion is always a mod (or admin) action
103       verify_mod_action(actor, &community, context, request_counter).await?;
104     }
105     DeletableObjects::Post(p) => {
106       verify_delete_activity_post_or_comment(
107         actor,
108         &p.ap_id.clone().into(),
109         community,
110         is_mod_action,
111         context,
112         request_counter,
113       )
114       .await?;
115     }
116     DeletableObjects::Comment(c) => {
117       verify_delete_activity_post_or_comment(
118         actor,
119         &c.ap_id.clone().into(),
120         community,
121         is_mod_action,
122         context,
123         request_counter,
124       )
125       .await?;
126     }
127   }
128   Ok(())
129 }
130
131 async fn verify_delete_activity_post_or_comment(
132   actor: &ObjectId<ApubPerson>,
133   object_id: &Url,
134   community: &ApubCommunity,
135   is_mod_action: bool,
136   context: &LemmyContext,
137   request_counter: &mut i32,
138 ) -> Result<(), LemmyError> {
139   verify_person_in_community(actor, community, context, request_counter).await?;
140   if is_mod_action {
141     verify_mod_action(actor, community, context, request_counter).await?;
142   } else {
143     // domain of post ap_id and post.creator ap_id are identical, so we just check the former
144     verify_domains_match(actor.inner(), object_id)?;
145   }
146   Ok(())
147 }
148
149 /// Write deletion or restoring of an object to the database, and send websocket message.
150 /// TODO: we should do something similar for receive_remove_action(), but its much more complicated
151 ///       because of the mod log
152 async fn receive_delete_action(
153   object: &Url,
154   actor: &ObjectId<ApubPerson>,
155   deleted: bool,
156   context: &LemmyContext,
157   request_counter: &mut i32,
158 ) -> Result<(), LemmyError> {
159   match DeletableObjects::read_from_db(object, context).await? {
160     DeletableObjects::Community(community) => {
161       if community.local {
162         let mod_ = actor.dereference(context, request_counter).await?;
163         let object = DeletableObjects::Community(community.clone());
164         send_apub_delete(&mod_, &community.clone(), object, true, context).await?;
165       }
166
167       let community = blocking(context.pool(), move |conn| {
168         Community::update_deleted(conn, community.id, deleted)
169       })
170       .await??;
171       send_community_ws_message(
172         community.id,
173         UserOperationCrud::DeleteCommunity,
174         None,
175         None,
176         context,
177       )
178       .await?;
179     }
180     DeletableObjects::Post(post) => {
181       if deleted != post.deleted {
182         let deleted_post = blocking(context.pool(), move |conn| {
183           Post::update_deleted(conn, post.id, deleted)
184         })
185         .await??;
186         send_post_ws_message(
187           deleted_post.id,
188           UserOperationCrud::DeletePost,
189           None,
190           None,
191           context,
192         )
193         .await?;
194       }
195     }
196     DeletableObjects::Comment(comment) => {
197       if deleted != comment.deleted {
198         let deleted_comment = blocking(context.pool(), move |conn| {
199           Comment::update_deleted(conn, comment.id, deleted)
200         })
201         .await??;
202         send_comment_ws_message_simple(
203           deleted_comment.id,
204           UserOperationCrud::DeleteComment,
205           context,
206         )
207         .await?;
208       }
209     }
210   }
211   Ok(())
212 }