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