]> Untitled Git - lemmy.git/blob - crates/apub/src/activities/block/block_user.rs
3e574232d24e1fc0f770720dfc748d2cd5ba4cc4
[lemmy.git] / crates / apub / src / activities / block / block_user.rs
1 use crate::{
2   activities::{
3     block::{generate_cc, SiteOrCommunity},
4     community::{announce::GetCommunity, send_activity_in_community},
5     generate_activity_id,
6     send_lemmy_activity,
7     verify_is_public,
8     verify_mod_action,
9     verify_person_in_community,
10   },
11   activity_lists::AnnouncableActivities,
12   check_apub_id_valid,
13   fetch_local_site_data,
14   local_instance,
15   objects::{community::ApubCommunity, instance::remote_instance_inboxes, person::ApubPerson},
16   protocol::activities::block::block_user::BlockUser,
17   ActorType,
18 };
19 use activitypub_federation::{
20   core::object_id::ObjectId,
21   data::Data,
22   traits::{ActivityHandler, Actor},
23   utils::verify_domains_match,
24 };
25 use activitystreams_kinds::{activity::BlockType, public};
26 use anyhow::anyhow;
27 use chrono::NaiveDateTime;
28 use lemmy_api_common::utils::{blocking, remove_user_data, remove_user_data_in_community};
29 use lemmy_db_schema::{
30   source::{
31     community::{
32       CommunityFollower,
33       CommunityFollowerForm,
34       CommunityPersonBan,
35       CommunityPersonBanForm,
36     },
37     moderator::{ModBan, ModBanForm, ModBanFromCommunity, ModBanFromCommunityForm},
38     person::{Person, PersonUpdateForm},
39   },
40   traits::{Bannable, Crud, Followable},
41 };
42 use lemmy_utils::{error::LemmyError, utils::convert_datetime};
43 use lemmy_websocket::LemmyContext;
44 use url::Url;
45
46 impl BlockUser {
47   pub(in crate::activities::block) async fn new(
48     target: &SiteOrCommunity,
49     user: &ApubPerson,
50     mod_: &ApubPerson,
51     remove_data: Option<bool>,
52     reason: Option<String>,
53     expires: Option<NaiveDateTime>,
54     context: &LemmyContext,
55   ) -> Result<BlockUser, LemmyError> {
56     Ok(BlockUser {
57       actor: ObjectId::new(mod_.actor_id()),
58       to: vec![public()],
59       object: ObjectId::new(user.actor_id()),
60       cc: generate_cc(target, context.pool()).await?,
61       target: target.id(),
62       kind: BlockType::Block,
63       remove_data,
64       summary: reason,
65       id: generate_activity_id(
66         BlockType::Block,
67         &context.settings().get_protocol_and_hostname(),
68       )?,
69       expires: expires.map(convert_datetime),
70       unparsed: Default::default(),
71     })
72   }
73
74   #[tracing::instrument(skip_all)]
75   pub async fn send(
76     target: &SiteOrCommunity,
77     user: &ApubPerson,
78     mod_: &ApubPerson,
79     remove_data: bool,
80     reason: Option<String>,
81     expires: Option<NaiveDateTime>,
82     context: &LemmyContext,
83   ) -> Result<(), LemmyError> {
84     let block = BlockUser::new(
85       target,
86       user,
87       mod_,
88       Some(remove_data),
89       reason,
90       expires,
91       context,
92     )
93     .await?;
94
95     match target {
96       SiteOrCommunity::Site(_) => {
97         let inboxes = remote_instance_inboxes(context.pool()).await?;
98         send_lemmy_activity(context, block, mod_, inboxes, false).await
99       }
100       SiteOrCommunity::Community(c) => {
101         let activity = AnnouncableActivities::BlockUser(block);
102         let inboxes = vec![user.shared_inbox_or_inbox()];
103         send_activity_in_community(activity, mod_, c, inboxes, context).await
104       }
105     }
106   }
107 }
108
109 #[async_trait::async_trait(?Send)]
110 impl ActivityHandler for BlockUser {
111   type DataType = LemmyContext;
112   type Error = LemmyError;
113
114   fn id(&self) -> &Url {
115     &self.id
116   }
117
118   fn actor(&self) -> &Url {
119     self.actor.inner()
120   }
121
122   #[tracing::instrument(skip_all)]
123   async fn verify(
124     &self,
125     context: &Data<LemmyContext>,
126     request_counter: &mut i32,
127   ) -> Result<(), LemmyError> {
128     let local_site_data = blocking(context.pool(), fetch_local_site_data).await??;
129     check_apub_id_valid(self.id(), &local_site_data, context.settings())
130       .map_err(LemmyError::from_message)?;
131
132     verify_is_public(&self.to, &self.cc)?;
133     match self
134       .target
135       .dereference(context, local_instance(context), request_counter)
136       .await?
137     {
138       SiteOrCommunity::Site(site) => {
139         let domain = self.object.inner().domain().expect("url needs domain");
140         if context.settings().hostname == domain {
141           return Err(
142             anyhow!("Site bans from remote instance can't affect user's home instance").into(),
143           );
144         }
145         // site ban can only target a user who is on the same instance as the actor (admin)
146         verify_domains_match(&site.actor_id(), self.actor.inner())?;
147         verify_domains_match(&site.actor_id(), self.object.inner())?;
148       }
149       SiteOrCommunity::Community(community) => {
150         verify_person_in_community(&self.actor, &community, context, request_counter).await?;
151         verify_mod_action(
152           &self.actor,
153           self.object.inner(),
154           community.id,
155           context,
156           request_counter,
157         )
158         .await?;
159       }
160     }
161     Ok(())
162   }
163
164   #[tracing::instrument(skip_all)]
165   async fn receive(
166     self,
167     context: &Data<LemmyContext>,
168     request_counter: &mut i32,
169   ) -> Result<(), LemmyError> {
170     let expires = self.expires.map(|u| u.naive_local());
171     let mod_person = self
172       .actor
173       .dereference(context, local_instance(context), request_counter)
174       .await?;
175     let blocked_person = self
176       .object
177       .dereference(context, local_instance(context), request_counter)
178       .await?;
179     let target = self
180       .target
181       .dereference(context, local_instance(context), request_counter)
182       .await?;
183     match target {
184       SiteOrCommunity::Site(_site) => {
185         let blocked_person = blocking(context.pool(), move |conn| {
186           Person::update(
187             conn,
188             blocked_person.id,
189             &PersonUpdateForm::builder()
190               .banned(Some(true))
191               .ban_expires(Some(expires))
192               .build(),
193           )
194         })
195         .await??;
196         if self.remove_data.unwrap_or(false) {
197           remove_user_data(
198             blocked_person.id,
199             context.pool(),
200             context.settings(),
201             context.client(),
202           )
203           .await?;
204         }
205
206         // write mod log
207         let form = ModBanForm {
208           mod_person_id: mod_person.id,
209           other_person_id: blocked_person.id,
210           reason: self.summary,
211           banned: Some(true),
212           expires,
213         };
214         blocking(context.pool(), move |conn| ModBan::create(conn, &form)).await??;
215       }
216       SiteOrCommunity::Community(community) => {
217         let community_user_ban_form = CommunityPersonBanForm {
218           community_id: community.id,
219           person_id: blocked_person.id,
220           expires: Some(expires),
221         };
222         blocking(context.pool(), move |conn| {
223           CommunityPersonBan::ban(conn, &community_user_ban_form)
224         })
225         .await??;
226
227         // Also unsubscribe them from the community, if they are subscribed
228         let community_follower_form = CommunityFollowerForm {
229           community_id: community.id,
230           person_id: blocked_person.id,
231           pending: false,
232         };
233         blocking(context.pool(), move |conn: &mut _| {
234           CommunityFollower::unfollow(conn, &community_follower_form)
235         })
236         .await?
237         .ok();
238
239         if self.remove_data.unwrap_or(false) {
240           remove_user_data_in_community(community.id, blocked_person.id, context.pool()).await?;
241         }
242
243         // write to mod log
244         let form = ModBanFromCommunityForm {
245           mod_person_id: mod_person.id,
246           other_person_id: blocked_person.id,
247           community_id: community.id,
248           reason: self.summary,
249           banned: Some(true),
250           expires,
251         };
252         blocking(context.pool(), move |conn| {
253           ModBanFromCommunity::create(conn, &form)
254         })
255         .await??;
256       }
257     }
258
259     Ok(())
260   }
261 }
262
263 #[async_trait::async_trait(?Send)]
264 impl GetCommunity for BlockUser {
265   #[tracing::instrument(skip_all)]
266   async fn get_community(
267     &self,
268     context: &LemmyContext,
269     request_counter: &mut i32,
270   ) -> Result<ApubCommunity, LemmyError> {
271     let target = self
272       .target
273       .dereference(context, local_instance(context), request_counter)
274       .await?;
275     match target {
276       SiteOrCommunity::Community(c) => Ok(c),
277       SiteOrCommunity::Site(_) => Err(anyhow!("Calling get_community() on site activity").into()),
278     }
279   }
280 }