]> Untitled Git - lemmy.git/blob - crates/apub_receive/src/inbox/community_inbox.rs
Running clippy --fix (#1647)
[lemmy.git] / crates / apub_receive / src / inbox / community_inbox.rs
1 use crate::{
2   activities::receive::verify_activity_domains_valid,
3   inbox::{
4     assert_activity_not_local,
5     get_activity_id,
6     inbox_verify_http_signature,
7     is_activity_already_known,
8     receive_for_community::{
9       receive_add_for_community,
10       receive_block_user_for_community,
11       receive_create_for_community,
12       receive_delete_for_community,
13       receive_dislike_for_community,
14       receive_like_for_community,
15       receive_remove_for_community,
16       receive_undo_for_community,
17       receive_update_for_community,
18     },
19     verify_is_addressed_to_public,
20   },
21 };
22 use activitystreams::{
23   activity::{kind::FollowType, ActorAndObject, Follow, Undo},
24   base::AnyBase,
25   prelude::*,
26 };
27 use actix_web::{web, HttpRequest, HttpResponse};
28 use anyhow::{anyhow, Context};
29 use lemmy_api_common::blocking;
30 use lemmy_apub::{
31   check_community_or_site_ban,
32   get_activity_to_and_cc,
33   insert_activity,
34   ActorType,
35   CommunityType,
36 };
37 use lemmy_db_queries::{source::community::Community_, ApubObject, Followable};
38 use lemmy_db_schema::source::{
39   community::{Community, CommunityFollower, CommunityFollowerForm},
40   person::Person,
41 };
42 use lemmy_utils::{location_info, LemmyError};
43 use lemmy_websocket::LemmyContext;
44 use log::info;
45 use serde::{Deserialize, Serialize};
46 use std::fmt::Debug;
47 use url::Url;
48
49 /// Allowed activities for community inbox.
50 #[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd, Deserialize, Serialize)]
51 #[serde(rename_all = "PascalCase")]
52 pub enum CommunityValidTypes {
53   Follow,  // follow request from a person
54   Undo,    // unfollow from a person
55   Create,  // create post or comment
56   Update,  // update post or comment
57   Like,    // upvote post or comment
58   Dislike, // downvote post or comment
59   Delete,  // post or comment deleted by creator
60   Remove,  // post or comment removed by mod or admin, or mod removed from community
61   Add,     // mod added to community
62   Block,   // user blocked by community
63 }
64
65 pub type CommunityAcceptedActivities = ActorAndObject<CommunityValidTypes>;
66
67 /// Handler for all incoming receive to community inboxes.
68 pub async fn community_inbox(
69   request: HttpRequest,
70   input: web::Json<CommunityAcceptedActivities>,
71   path: web::Path<String>,
72   context: web::Data<LemmyContext>,
73 ) -> Result<HttpResponse, LemmyError> {
74   let activity = input.into_inner();
75   // First of all check the http signature
76   let request_counter = &mut 0;
77   let actor = inbox_verify_http_signature(&activity, &context, request, request_counter).await?;
78
79   // Do nothing if we received the same activity before
80   let activity_id = get_activity_id(&activity, &actor.actor_id())?;
81   if is_activity_already_known(context.pool(), &activity_id).await? {
82     return Ok(HttpResponse::Ok().finish());
83   }
84
85   // Check if the activity is actually meant for us
86   let path = path.into_inner();
87   let community = blocking(context.pool(), move |conn| {
88     Community::read_from_name(conn, &path)
89   })
90   .await??;
91   let to_and_cc = get_activity_to_and_cc(&activity);
92   if !to_and_cc.contains(&community.actor_id()) {
93     return Err(anyhow!("Activity delivered to wrong community").into());
94   }
95
96   assert_activity_not_local(&activity)?;
97   insert_activity(&activity_id, activity.clone(), false, true, context.pool()).await?;
98
99   community_receive_message(
100     activity.clone(),
101     community.clone(),
102     actor.as_ref(),
103     &context,
104     request_counter,
105   )
106   .await
107 }
108
109 /// Receives Follow, Undo/Follow, post actions, comment actions (including votes)
110 pub(crate) async fn community_receive_message(
111   activity: CommunityAcceptedActivities,
112   to_community: Community,
113   actor: &dyn ActorType,
114   context: &LemmyContext,
115   request_counter: &mut i32,
116 ) -> Result<HttpResponse, LemmyError> {
117   // Only persons can send activities to the community, so we can get the actor as person
118   // unconditionally.
119   let actor_id = actor.actor_id();
120   let person = blocking(context.pool(), move |conn| {
121     Person::read_from_apub_id(conn, &actor_id.into())
122   })
123   .await??;
124   check_community_or_site_ban(&person, to_community.id, context.pool()).await?;
125
126   info!(
127     "Community {} received activity {} from {}",
128     to_community.name,
129     &activity
130       .id_unchecked()
131       .context(location_info!())?
132       .to_string(),
133     &person.actor_id().to_string()
134   );
135
136   let any_base = activity.clone().into_any_base()?;
137   let actor_url = actor.actor_id();
138   let activity_kind = activity.kind().context(location_info!())?;
139   let do_announce = match activity_kind {
140     CommunityValidTypes::Follow => {
141       Box::pin(handle_follow(
142         any_base.clone(),
143         person,
144         &to_community,
145         context,
146       ))
147       .await?;
148       false
149     }
150     CommunityValidTypes::Undo => {
151       Box::pin(handle_undo(
152         context,
153         activity.clone(),
154         actor_url,
155         &to_community,
156         request_counter,
157       ))
158       .await?
159     }
160     CommunityValidTypes::Create => {
161       Box::pin(receive_create_for_community(
162         context,
163         any_base.clone(),
164         &actor_url,
165         request_counter,
166       ))
167       .await?;
168       true
169     }
170     CommunityValidTypes::Update => {
171       Box::pin(receive_update_for_community(
172         context,
173         any_base.clone(),
174         None,
175         &actor_url,
176         request_counter,
177       ))
178       .await?;
179       true
180     }
181     CommunityValidTypes::Like => {
182       Box::pin(receive_like_for_community(
183         context,
184         any_base.clone(),
185         &actor_url,
186         request_counter,
187       ))
188       .await?;
189       true
190     }
191     CommunityValidTypes::Dislike => {
192       Box::pin(receive_dislike_for_community(
193         context,
194         any_base.clone(),
195         &actor_url,
196         request_counter,
197       ))
198       .await?;
199       true
200     }
201     CommunityValidTypes::Delete => {
202       Box::pin(receive_delete_for_community(
203         context,
204         any_base.clone(),
205         None,
206         &actor_url,
207         request_counter,
208       ))
209       .await?;
210       true
211     }
212     CommunityValidTypes::Add => {
213       Box::pin(receive_add_for_community(
214         context,
215         any_base.clone(),
216         None,
217         request_counter,
218       ))
219       .await?;
220       true
221     }
222     CommunityValidTypes::Remove => {
223       Box::pin(receive_remove_for_community(
224         context,
225         any_base.clone(),
226         None,
227         request_counter,
228       ))
229       .await?;
230       true
231     }
232     CommunityValidTypes::Block => {
233       Box::pin(receive_block_user_for_community(
234         context,
235         any_base.clone(),
236         None,
237         request_counter,
238       ))
239       .await?;
240       true
241     }
242   };
243
244   if do_announce {
245     // Check again that the activity is public, just to be sure
246     verify_is_addressed_to_public(&activity)?;
247     let mut object_actor = activity.object().clone().single_xsd_any_uri();
248     // If activity is something like Undo/Block, we need to access activity.object.object
249     if object_actor.is_none() {
250       object_actor = activity
251         .object()
252         .as_one()
253         .map(|a| ActorAndObject::from_any_base(a.to_owned()).ok())
254         .flatten()
255         .flatten()
256         .map(|a: ActorAndObject<CommunityValidTypes>| a.object().to_owned().single_xsd_any_uri())
257         .flatten();
258     }
259     to_community
260       .send_announce(activity.into_any_base()?, object_actor, context)
261       .await?;
262   }
263
264   Ok(HttpResponse::Ok().finish())
265 }
266
267 /// Handle a follow request from a remote person, adding the person as follower and returning an
268 /// Accept activity.
269 async fn handle_follow(
270   activity: AnyBase,
271   person: Person,
272   community: &Community,
273   context: &LemmyContext,
274 ) -> Result<HttpResponse, LemmyError> {
275   let follow = Follow::from_any_base(activity)?.context(location_info!())?;
276   verify_activity_domains_valid(&follow, &person.actor_id(), false)?;
277
278   let community_follower_form = CommunityFollowerForm {
279     community_id: community.id,
280     person_id: person.id,
281     pending: false,
282   };
283
284   // This will fail if they're already a follower, but ignore the error.
285   blocking(context.pool(), move |conn| {
286     CommunityFollower::follow(conn, &community_follower_form).ok()
287   })
288   .await?;
289
290   community.send_accept_follow(follow, context).await?;
291
292   Ok(HttpResponse::Ok().finish())
293 }
294
295 async fn handle_undo(
296   context: &LemmyContext,
297   activity: CommunityAcceptedActivities,
298   actor_url: Url,
299   to_community: &Community,
300   request_counter: &mut i32,
301 ) -> Result<bool, LemmyError> {
302   let inner_kind = activity
303     .object()
304     .is_single_kind(&FollowType::Follow.to_string());
305   let any_base = activity.into_any_base()?;
306   if inner_kind {
307     handle_undo_follow(any_base, actor_url, to_community, context).await?;
308     Ok(false)
309   } else {
310     receive_undo_for_community(context, any_base, None, &actor_url, request_counter).await?;
311     Ok(true)
312   }
313 }
314
315 /// Handle `Undo/Follow` from a person, removing the person from followers list.
316 async fn handle_undo_follow(
317   activity: AnyBase,
318   person_url: Url,
319   community: &Community,
320   context: &LemmyContext,
321 ) -> Result<(), LemmyError> {
322   let undo = Undo::from_any_base(activity)?.context(location_info!())?;
323   verify_activity_domains_valid(&undo, &person_url, true)?;
324
325   let object = undo.object().to_owned().one().context(location_info!())?;
326   let follow = Follow::from_any_base(object)?.context(location_info!())?;
327   verify_activity_domains_valid(&follow, &person_url, false)?;
328
329   let person = blocking(context.pool(), move |conn| {
330     Person::read_from_apub_id(conn, &person_url.into())
331   })
332   .await??;
333   let community_follower_form = CommunityFollowerForm {
334     community_id: community.id,
335     person_id: person.id,
336     pending: false,
337   };
338
339   // This will fail if they aren't a follower, but ignore the error.
340   blocking(context.pool(), move |conn| {
341     CommunityFollower::unfollow(conn, &community_follower_form).ok()
342   })
343   .await?;
344
345   Ok(())
346 }