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