]> Untitled Git - lemmy.git/blob - lemmy_apub/src/inbox/community_inbox.rs
In activity table, remove `user_id` and add `sensitive` (#127)
[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 res = match kind {
92     ValidTypes::Follow => handle_follow(any_base, user, community, &context).await,
93     ValidTypes::Undo => handle_undo_follow(any_base, user, community, &context).await,
94   };
95
96   insert_activity(&activity_id, activity.clone(), false, true, context.pool()).await?;
97   res
98 }
99
100 /// Handle a follow request from a remote user, adding the user as follower and returning an
101 /// Accept activity.
102 async fn handle_follow(
103   activity: AnyBase,
104   user: User_,
105   community: Community,
106   context: &LemmyContext,
107 ) -> Result<HttpResponse, LemmyError> {
108   let follow = Follow::from_any_base(activity)?.context(location_info!())?;
109   verify_activity_domains_valid(&follow, user.actor_id()?, false)?;
110
111   let community_follower_form = CommunityFollowerForm {
112     community_id: community.id,
113     user_id: user.id,
114   };
115
116   // This will fail if they're already a follower, but ignore the error.
117   blocking(&context.pool(), move |conn| {
118     CommunityFollower::follow(&conn, &community_follower_form).ok()
119   })
120   .await?;
121
122   community.send_accept_follow(follow, context).await?;
123
124   Ok(HttpResponse::Ok().finish())
125 }
126
127 /// Handle `Undo/Follow` from a user, removing the user from followers list.
128 async fn handle_undo_follow(
129   activity: AnyBase,
130   user: User_,
131   community: Community,
132   context: &LemmyContext,
133 ) -> Result<HttpResponse, LemmyError> {
134   let undo = Undo::from_any_base(activity)?.context(location_info!())?;
135   verify_activity_domains_valid(&undo, user.actor_id()?, true)?;
136
137   let object = undo.object().to_owned().one().context(location_info!())?;
138   let follow = Follow::from_any_base(object)?.context(location_info!())?;
139   verify_activity_domains_valid(&follow, user.actor_id()?, false)?;
140
141   let community_follower_form = CommunityFollowerForm {
142     community_id: community.id,
143     user_id: user.id,
144   };
145
146   // This will fail if they aren't a follower, but ignore the error.
147   blocking(&context.pool(), move |conn| {
148     CommunityFollower::unfollow(&conn, &community_follower_form).ok()
149   })
150   .await?;
151
152   Ok(HttpResponse::Ok().finish())
153 }