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