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