]> Untitled Git - lemmy.git/blob - crates/apub/src/inbox/community_inbox.rs
Refactor activitypub code
[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::source::{
35   community::{Community, CommunityFollower, CommunityFollowerForm},
36   user::User_,
37 };
38 use lemmy_db_views_actor::community_user_ban_view::CommunityUserBanView;
39 use lemmy_utils::{location_info, LemmyError};
40 use lemmy_websocket::LemmyContext;
41 use log::info;
42 use serde::{Deserialize, Serialize};
43 use std::fmt::Debug;
44 use url::Url;
45
46 /// Allowed activities for community inbox.
47 #[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd, Deserialize, Serialize)]
48 #[serde(rename_all = "PascalCase")]
49 pub enum CommunityValidTypes {
50   Follow,  // follow request from a user
51   Undo,    // unfollow from a user
52   Create,  // create post or comment
53   Update,  // update post or comment
54   Like,    // upvote post or comment
55   Dislike, // downvote post or comment
56   Delete,  // post or comment deleted by creator
57   Remove,  // post or comment removed by mod or admin, or mod removed from community
58   Add,     // mod added to community
59 }
60
61 pub type CommunityAcceptedActivities = ActorAndObject<CommunityValidTypes>;
62
63 /// Handler for all incoming receive to community inboxes.
64 pub async fn community_inbox(
65   request: HttpRequest,
66   input: web::Json<CommunityAcceptedActivities>,
67   path: web::Path<String>,
68   context: web::Data<LemmyContext>,
69 ) -> Result<HttpResponse, LemmyError> {
70   let activity = input.into_inner();
71   // First of all check the http signature
72   let request_counter = &mut 0;
73   let actor = inbox_verify_http_signature(&activity, &context, request, request_counter).await?;
74
75   // Do nothing if we received the same activity before
76   let activity_id = get_activity_id(&activity, &actor.actor_id())?;
77   if is_activity_already_known(context.pool(), &activity_id).await? {
78     return Ok(HttpResponse::Ok().finish());
79   }
80
81   // Check if the activity is actually meant for us
82   let path = path.into_inner();
83   let community = blocking(&context.pool(), move |conn| {
84     Community::read_from_name(&conn, &path)
85   })
86   .await??;
87   let to_and_cc = get_activity_to_and_cc(&activity);
88   if !to_and_cc.contains(&&community.actor_id()) {
89     return Err(anyhow!("Activity delivered to wrong community").into());
90   }
91
92   assert_activity_not_local(&activity)?;
93   insert_activity(&activity_id, activity.clone(), false, true, context.pool()).await?;
94
95   info!(
96     "Community {} received activity {:?} from {}",
97     community.name,
98     &activity.id_unchecked(),
99     &actor.actor_id()
100   );
101
102   community_receive_message(
103     activity.clone(),
104     community.clone(),
105     actor.as_ref(),
106     &context,
107     request_counter,
108   )
109   .await
110 }
111
112 /// Receives Follow, Undo/Follow, post actions, comment actions (including votes)
113 pub(crate) async fn community_receive_message(
114   activity: CommunityAcceptedActivities,
115   to_community: Community,
116   actor: &dyn ActorType,
117   context: &LemmyContext,
118   request_counter: &mut i32,
119 ) -> Result<HttpResponse, LemmyError> {
120   // Only users can send activities to the community, so we can get the actor as user
121   // unconditionally.
122   let actor_id = actor.actor_id();
123   let user = blocking(&context.pool(), move |conn| {
124     User_::read_from_apub_id(&conn, &actor_id.into())
125   })
126   .await??;
127   check_community_or_site_ban(&user, to_community.id, context.pool()).await?;
128
129   let any_base = activity.clone().into_any_base()?;
130   let actor_url = actor.actor_id();
131   let activity_kind = activity.kind().context(location_info!())?;
132   let do_announce = match activity_kind {
133     CommunityValidTypes::Follow => {
134       handle_follow(any_base.clone(), user, &to_community, &context).await?;
135       false
136     }
137     CommunityValidTypes::Undo => {
138       handle_undo(
139         context,
140         activity.clone(),
141         actor_url,
142         &to_community,
143         request_counter,
144       )
145       .await?
146     }
147     CommunityValidTypes::Create => {
148       receive_create_for_community(context, any_base.clone(), &actor_url, request_counter).await?;
149       true
150     }
151     CommunityValidTypes::Update => {
152       receive_update_for_community(context, any_base.clone(), None, &actor_url, request_counter)
153         .await?;
154       true
155     }
156     CommunityValidTypes::Like => {
157       receive_like_for_community(context, any_base.clone(), &actor_url, request_counter).await?;
158       true
159     }
160     CommunityValidTypes::Dislike => {
161       receive_dislike_for_community(context, any_base.clone(), &actor_url, request_counter).await?;
162       true
163     }
164     CommunityValidTypes::Delete => {
165       receive_delete_for_community(context, any_base.clone(), None, &actor_url).await?;
166       true
167     }
168     CommunityValidTypes::Add => {
169       receive_add_for_community(context, any_base.clone(), None, request_counter).await?;
170       true
171     }
172     CommunityValidTypes::Remove => {
173       receive_remove_for_community(context, any_base.clone(), None, request_counter).await?;
174       true
175     }
176   };
177
178   if do_announce {
179     // Check again that the activity is public, just to be sure
180     verify_is_addressed_to_public(&activity)?;
181     to_community
182       .send_announce(activity.into_any_base()?, context)
183       .await?;
184   }
185
186   Ok(HttpResponse::Ok().finish())
187 }
188
189 /// Handle a follow request from a remote user, adding the user as follower and returning an
190 /// Accept activity.
191 async fn handle_follow(
192   activity: AnyBase,
193   user: User_,
194   community: &Community,
195   context: &LemmyContext,
196 ) -> Result<HttpResponse, LemmyError> {
197   let follow = Follow::from_any_base(activity)?.context(location_info!())?;
198   verify_activity_domains_valid(&follow, &user.actor_id(), false)?;
199
200   let community_follower_form = CommunityFollowerForm {
201     community_id: community.id,
202     user_id: user.id,
203     pending: false,
204   };
205
206   // This will fail if they're already a follower, but ignore the error.
207   blocking(&context.pool(), move |conn| {
208     CommunityFollower::follow(&conn, &community_follower_form).ok()
209   })
210   .await?;
211
212   community.send_accept_follow(follow, context).await?;
213
214   Ok(HttpResponse::Ok().finish())
215 }
216
217 async fn handle_undo(
218   context: &LemmyContext,
219   activity: CommunityAcceptedActivities,
220   actor_url: Url,
221   to_community: &Community,
222   request_counter: &mut i32,
223 ) -> Result<bool, LemmyError> {
224   let inner_kind = activity
225     .object()
226     .is_single_kind(&FollowType::Follow.to_string());
227   let any_base = activity.into_any_base()?;
228   if inner_kind {
229     handle_undo_follow(any_base, actor_url, to_community, &context).await?;
230     Ok(false)
231   } else {
232     receive_undo_for_community(context, any_base, None, &actor_url, request_counter).await?;
233     Ok(true)
234   }
235 }
236
237 /// Handle `Undo/Follow` from a user, removing the user from followers list.
238 async fn handle_undo_follow(
239   activity: AnyBase,
240   user_url: Url,
241   community: &Community,
242   context: &LemmyContext,
243 ) -> Result<(), LemmyError> {
244   let undo = Undo::from_any_base(activity)?.context(location_info!())?;
245   verify_activity_domains_valid(&undo, &user_url, true)?;
246
247   let object = undo.object().to_owned().one().context(location_info!())?;
248   let follow = Follow::from_any_base(object)?.context(location_info!())?;
249   verify_activity_domains_valid(&follow, &user_url, false)?;
250
251   let user = blocking(&context.pool(), move |conn| {
252     User_::read_from_apub_id(&conn, &user_url.into())
253   })
254   .await??;
255   let community_follower_form = CommunityFollowerForm {
256     community_id: community.id,
257     user_id: user.id,
258     pending: false,
259   };
260
261   // This will fail if they aren't a follower, but ignore the error.
262   blocking(&context.pool(), move |conn| {
263     CommunityFollower::unfollow(&conn, &community_follower_form).ok()
264   })
265   .await?;
266
267   Ok(())
268 }
269
270 pub(crate) async fn check_community_or_site_ban(
271   user: &User_,
272   community_id: i32,
273   pool: &DbPool,
274 ) -> Result<(), LemmyError> {
275   if user.banned {
276     return Err(anyhow!("User is banned from site").into());
277   }
278   let user_id = user.id;
279   let is_banned = move |conn: &'_ _| CommunityUserBanView::get(conn, user_id, community_id).is_ok();
280   if blocking(pool, is_banned).await? {
281     return Err(anyhow!("User is banned from community").into());
282   }
283
284   Ok(())
285 }