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