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