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