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