]> Untitled Git - lemmy.git/blob - lemmy_apub/src/inbox/community_inbox.rs
ffc5f6d71a767598b45a236b933226a6fce0d469
[lemmy.git] / lemmy_apub / src / inbox / community_inbox.rs
1 use crate::{
2   activities::receive::verify_activity_domains_valid,
3   check_is_apub_id_valid,
4   extensions::signatures::verify_signature,
5   fetcher::get_or_fetch_and_upsert_user,
6   inbox::{get_activity_id, is_activity_already_known},
7   insert_activity,
8   ActorType,
9 };
10 use activitystreams::{
11   activity::{ActorAndObject, Follow, Undo},
12   base::AnyBase,
13   prelude::*,
14 };
15 use actix_web::{web, HttpRequest, HttpResponse};
16 use anyhow::{anyhow, Context};
17 use lemmy_db::{
18   community::{Community, CommunityFollower, CommunityFollowerForm},
19   user::User_,
20   Followable,
21 };
22 use lemmy_structs::blocking;
23 use lemmy_utils::{location_info, LemmyError};
24 use lemmy_websocket::LemmyContext;
25 use log::info;
26 use serde::{Deserialize, Serialize};
27 use std::fmt::Debug;
28
29 /// Allowed activities for community inbox.
30 #[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd, Deserialize, Serialize)]
31 #[serde(rename_all = "PascalCase")]
32 pub enum ValidTypes {
33   Follow,
34   Undo,
35 }
36
37 pub type AcceptedActivities = ActorAndObject<ValidTypes>;
38
39 /// Handler for all incoming receive to community inboxes.
40 pub async fn community_inbox(
41   request: HttpRequest,
42   input: web::Json<AcceptedActivities>,
43   path: web::Path<String>,
44   context: web::Data<LemmyContext>,
45 ) -> Result<HttpResponse, LemmyError> {
46   let activity = input.into_inner();
47
48   let path = path.into_inner();
49   let community = blocking(&context.pool(), move |conn| {
50     Community::read_from_name(&conn, &path)
51   })
52   .await??;
53
54   let to = activity
55     .to()
56     .context(location_info!())?
57     .to_owned()
58     .single_xsd_any_uri();
59   if Some(community.actor_id()?) != to {
60     return Err(anyhow!("Activity delivered to wrong community").into());
61   }
62
63   info!(
64     "Community {} received activity {:?}",
65     &community.name, &activity
66   );
67   let user_uri = activity
68     .actor()?
69     .as_single_xsd_any_uri()
70     .context(location_info!())?;
71   info!(
72     "Community {} inbox received activity {:?} from {}",
73     community.name,
74     &activity.id_unchecked(),
75     &user_uri
76   );
77   check_is_apub_id_valid(user_uri)?;
78
79   let request_counter = &mut 0;
80   let user = get_or_fetch_and_upsert_user(&user_uri, &context, request_counter).await?;
81
82   verify_signature(&request, &user)?;
83
84   let activity_id = get_activity_id(&activity, user_uri)?;
85   if is_activity_already_known(context.pool(), &activity_id).await? {
86     return Ok(HttpResponse::Ok().finish());
87   }
88
89   let any_base = activity.clone().into_any_base()?;
90   let kind = activity.kind().context(location_info!())?;
91   let user_id = user.id;
92   let res = match kind {
93     ValidTypes::Follow => handle_follow(any_base, user, community, &context).await,
94     ValidTypes::Undo => handle_undo_follow(any_base, user, community, &context).await,
95   };
96
97   insert_activity(
98     &activity_id,
99     user_id,
100     activity.clone(),
101     false,
102     context.pool(),
103   )
104   .await?;
105   res
106 }
107
108 /// Handle a follow request from a remote user, adding the user as follower and returning an
109 /// Accept activity.
110 async fn handle_follow(
111   activity: AnyBase,
112   user: User_,
113   community: Community,
114   context: &LemmyContext,
115 ) -> Result<HttpResponse, LemmyError> {
116   let follow = Follow::from_any_base(activity)?.context(location_info!())?;
117   verify_activity_domains_valid(&follow, user.actor_id()?, false)?;
118
119   let community_follower_form = CommunityFollowerForm {
120     community_id: community.id,
121     user_id: user.id,
122   };
123
124   // This will fail if they're already a follower, but ignore the error.
125   blocking(&context.pool(), move |conn| {
126     CommunityFollower::follow(&conn, &community_follower_form).ok()
127   })
128   .await?;
129
130   community.send_accept_follow(follow, context).await?;
131
132   Ok(HttpResponse::Ok().finish())
133 }
134
135 /// Handle `Undo/Follow` from a user, removing the user from followers list.
136 async fn handle_undo_follow(
137   activity: AnyBase,
138   user: User_,
139   community: Community,
140   context: &LemmyContext,
141 ) -> Result<HttpResponse, LemmyError> {
142   let undo = Undo::from_any_base(activity)?.context(location_info!())?;
143   verify_activity_domains_valid(&undo, user.actor_id()?, true)?;
144
145   let object = undo.object().to_owned().one().context(location_info!())?;
146   let follow = Follow::from_any_base(object)?.context(location_info!())?;
147   verify_activity_domains_valid(&follow, user.actor_id()?, false)?;
148
149   let community_follower_form = CommunityFollowerForm {
150     community_id: community.id,
151     user_id: user.id,
152   };
153
154   // This will fail if they aren't a follower, but ignore the error.
155   blocking(&context.pool(), move |conn| {
156     CommunityFollower::unfollow(&conn, &community_follower_form).ok()
157   })
158   .await?;
159
160   Ok(HttpResponse::Ok().finish())
161 }