]> Untitled Git - lemmy.git/blob - lemmy_apub/src/inbox/user_inbox.rs
9173fec42167bfe3f690ff966a85db15c6b1c68f
[lemmy.git] / lemmy_apub / src / inbox / user_inbox.rs
1 use crate::{
2   activities::receive::{
3     community::{
4       receive_delete_community,
5       receive_remove_community,
6       receive_undo_delete_community,
7       receive_undo_remove_community,
8     },
9     private_message::{
10       receive_create_private_message,
11       receive_delete_private_message,
12       receive_undo_delete_private_message,
13       receive_update_private_message,
14     },
15     receive_unhandled_activity,
16     verify_activity_domains_valid,
17   },
18   check_is_apub_id_valid,
19   fetcher::get_or_fetch_and_upsert_community,
20   inbox::{
21     get_activity_id,
22     get_activity_to_and_cc,
23     inbox_verify_http_signature,
24     is_activity_already_known,
25     is_addressed_to_public,
26     receive_for_community::{
27       receive_create_for_community,
28       receive_delete_for_community,
29       receive_dislike_for_community,
30       receive_like_for_community,
31       receive_remove_for_community,
32       receive_undo_for_community,
33       receive_update_for_community,
34     },
35   },
36   insert_activity,
37   ActorType,
38 };
39 use activitystreams::{
40   activity::{Accept, ActorAndObject, Announce, Delete, Follow, Undo},
41   base::AnyBase,
42   prelude::*,
43 };
44 use actix_web::{web, HttpRequest, HttpResponse};
45 use anyhow::{anyhow, Context};
46 use diesel::NotFound;
47 use lemmy_db::{
48   community::{Community, CommunityFollower, CommunityFollowerForm},
49   private_message::PrivateMessage,
50   user::User_,
51   Followable,
52 };
53 use lemmy_structs::blocking;
54 use lemmy_utils::{location_info, LemmyError};
55 use lemmy_websocket::LemmyContext;
56 use log::debug;
57 use serde::{Deserialize, Serialize};
58 use std::fmt::Debug;
59 use url::Url;
60
61 /// Allowed activities for user inbox.
62 #[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd, Deserialize, Serialize)]
63 #[serde(rename_all = "PascalCase")]
64 pub enum UserValidTypes {
65   Accept,   // community accepted our follow request
66   Create,   // create private message
67   Update,   // edit private message
68   Delete,   // private message or community deleted by creator
69   Undo,     // private message or community restored
70   Remove,   // community removed by admin
71   Announce, // post, comment or vote in community
72 }
73
74 pub type UserAcceptedActivities = ActorAndObject<UserValidTypes>;
75
76 /// Handler for all incoming activities to user inboxes.
77 pub async fn user_inbox(
78   request: HttpRequest,
79   input: web::Json<UserAcceptedActivities>,
80   path: web::Path<String>,
81   context: web::Data<LemmyContext>,
82 ) -> Result<HttpResponse, LemmyError> {
83   let activity = input.into_inner();
84   // First of all check the http signature
85   let request_counter = &mut 0;
86   let actor = inbox_verify_http_signature(&activity, &context, request, request_counter).await?;
87
88   // Do nothing if we received the same activity before
89   let activity_id = get_activity_id(&activity, &actor.actor_id()?)?;
90   if is_activity_already_known(context.pool(), &activity_id).await? {
91     return Ok(HttpResponse::Ok().finish());
92   }
93
94   // Check if the activity is actually meant for us
95   let username = path.into_inner();
96   let user = blocking(&context.pool(), move |conn| {
97     User_::read_from_name(&conn, &username)
98   })
99   .await??;
100   let to_and_cc = get_activity_to_and_cc(&activity)?;
101   if !to_and_cc.contains(&&user.actor_id()?) {
102     return Err(anyhow!("Activity delivered to wrong user").into());
103   }
104
105   insert_activity(&activity_id, activity.clone(), false, true, context.pool()).await?;
106
107   debug!(
108     "User {} received activity {:?} from {}",
109     user.name,
110     &activity.id_unchecked(),
111     &actor.actor_id_str()
112   );
113
114   user_receive_message(
115     activity.clone(),
116     Some(user.clone()),
117     actor.as_ref(),
118     &context,
119     request_counter,
120   )
121   .await
122 }
123
124 /// Receives Accept/Follow, Announce, private messages and community (undo) remove, (undo) delete
125 pub(crate) async fn user_receive_message(
126   activity: UserAcceptedActivities,
127   to_user: Option<User_>,
128   actor: &dyn ActorType,
129   context: &LemmyContext,
130   request_counter: &mut i32,
131 ) -> Result<HttpResponse, LemmyError> {
132   // TODO: must be addressed to one or more local users, or to followers of a remote community
133
134   // TODO: if it is addressed to community followers, check that at least one local user is following it
135
136   let any_base = activity.clone().into_any_base()?;
137   let kind = activity.kind().context(location_info!())?;
138   let actor_url = actor.actor_id()?;
139   match kind {
140     UserValidTypes::Accept => {
141       receive_accept(&context, any_base, actor, to_user.unwrap(), request_counter).await?;
142     }
143     UserValidTypes::Announce => {
144       receive_announce(&context, any_base, actor, request_counter).await?
145     }
146     UserValidTypes::Create => {
147       receive_create_private_message(&context, any_base, actor_url, request_counter).await?
148     }
149     UserValidTypes::Update => {
150       receive_update_private_message(&context, any_base, actor_url, request_counter).await?
151     }
152     UserValidTypes::Delete => {
153       receive_delete(context, any_base, &actor_url, request_counter).await?
154     }
155     UserValidTypes::Undo => receive_undo(context, any_base, &actor_url, request_counter).await?,
156     UserValidTypes::Remove => receive_remove_community(&context, any_base, &actor_url).await?,
157   };
158
159   // TODO: would be logical to move websocket notification code here
160
161   Ok(HttpResponse::Ok().finish())
162 }
163
164 /// Handle accepted follows.
165 async fn receive_accept(
166   context: &LemmyContext,
167   activity: AnyBase,
168   actor: &dyn ActorType,
169   user: User_,
170   request_counter: &mut i32,
171 ) -> Result<(), LemmyError> {
172   let accept = Accept::from_any_base(activity)?.context(location_info!())?;
173   verify_activity_domains_valid(&accept, &actor.actor_id()?, false)?;
174
175   // TODO: we should check that we actually sent this activity, because the remote instance
176   //       could just put a fake Follow
177   let object = accept.object().to_owned().one().context(location_info!())?;
178   let follow = Follow::from_any_base(object)?.context(location_info!())?;
179   verify_activity_domains_valid(&follow, &user.actor_id()?, false)?;
180
181   let community_uri = accept
182     .actor()?
183     .to_owned()
184     .single_xsd_any_uri()
185     .context(location_info!())?;
186
187   let community =
188     get_or_fetch_and_upsert_community(&community_uri, context, request_counter).await?;
189
190   // Now you need to add this to the community follower
191   let community_follower_form = CommunityFollowerForm {
192     community_id: community.id,
193     user_id: user.id,
194   };
195
196   // This will fail if they're already a follower
197   blocking(&context.pool(), move |conn| {
198     CommunityFollower::follow(conn, &community_follower_form).ok()
199   })
200   .await?;
201
202   Ok(())
203 }
204
205 /// Takes an announce and passes the inner activity to the appropriate handler.
206 async fn receive_announce(
207   context: &LemmyContext,
208   activity: AnyBase,
209   actor: &dyn ActorType,
210   request_counter: &mut i32,
211 ) -> Result<(), LemmyError> {
212   let announce = Announce::from_any_base(activity)?.context(location_info!())?;
213   verify_activity_domains_valid(&announce, &actor.actor_id()?, false)?;
214   is_addressed_to_public(&announce)?;
215
216   let kind = announce.object().as_single_kind_str();
217   let inner_activity = announce
218     .object()
219     .to_owned()
220     .one()
221     .context(location_info!())?;
222
223   let inner_id = inner_activity.id().context(location_info!())?.to_owned();
224   check_is_apub_id_valid(&inner_id)?;
225   if is_activity_already_known(context.pool(), &inner_id).await? {
226     return Ok(());
227   }
228
229   dbg!(&kind);
230   match kind {
231     Some("Create") => {
232       receive_create_for_community(context, inner_activity, &inner_id, request_counter).await
233     }
234     Some("Update") => {
235       receive_update_for_community(context, inner_activity, &inner_id, request_counter).await
236     }
237     Some("Like") => {
238       receive_like_for_community(context, inner_activity, &inner_id, request_counter).await
239     }
240     Some("Dislike") => {
241       receive_dislike_for_community(context, inner_activity, &inner_id, request_counter).await
242     }
243     Some("Delete") => receive_delete_for_community(context, inner_activity, &inner_id).await,
244     Some("Remove") => receive_remove_for_community(context, inner_activity, &inner_id).await,
245     Some("Undo") => {
246       receive_undo_for_community(context, inner_activity, &inner_id, request_counter).await
247     }
248     _ => receive_unhandled_activity(inner_activity),
249   }
250 }
251
252 async fn receive_delete(
253   context: &LemmyContext,
254   any_base: AnyBase,
255   expected_domain: &Url,
256   request_counter: &mut i32,
257 ) -> Result<(), LemmyError> {
258   use CommunityOrPrivateMessage::*;
259
260   let delete = Delete::from_any_base(any_base.clone())?.context(location_info!())?;
261   verify_activity_domains_valid(&delete, expected_domain, true)?;
262   let object_uri = delete
263     .object()
264     .to_owned()
265     .single_xsd_any_uri()
266     .context(location_info!())?;
267
268   match find_community_or_private_message_by_id(context, object_uri).await? {
269     Community(c) => receive_delete_community(context, c).await,
270     PrivateMessage(p) => receive_delete_private_message(context, delete, p, request_counter).await,
271   }
272 }
273
274 async fn receive_undo(
275   context: &LemmyContext,
276   any_base: AnyBase,
277   expected_domain: &Url,
278   request_counter: &mut i32,
279 ) -> Result<(), LemmyError> {
280   use CommunityOrPrivateMessage::*;
281   let undo = Undo::from_any_base(any_base)?.context(location_info!())?;
282   verify_activity_domains_valid(&undo, expected_domain, true)?;
283
284   let inner_activity = undo.object().to_owned().one().context(location_info!())?;
285   let kind = inner_activity.kind_str();
286   match kind {
287     Some("Delete") => {
288       let delete = Delete::from_any_base(inner_activity)?.context(location_info!())?;
289       verify_activity_domains_valid(&delete, expected_domain, true)?;
290       let object_uri = delete
291         .object()
292         .to_owned()
293         .single_xsd_any_uri()
294         .context(location_info!())?;
295       match find_community_or_private_message_by_id(context, object_uri).await? {
296         Community(c) => receive_undo_delete_community(context, undo, c, expected_domain).await,
297         PrivateMessage(p) => {
298           receive_undo_delete_private_message(context, undo, expected_domain, p, request_counter)
299             .await
300         }
301       }
302     }
303     Some("Remove") => receive_undo_remove_community(context, undo, expected_domain).await,
304     _ => receive_unhandled_activity(undo),
305   }
306 }
307 enum CommunityOrPrivateMessage {
308   Community(Community),
309   PrivateMessage(PrivateMessage),
310 }
311
312 async fn find_community_or_private_message_by_id(
313   context: &LemmyContext,
314   apub_id: Url,
315 ) -> Result<CommunityOrPrivateMessage, LemmyError> {
316   let ap_id = apub_id.to_string();
317   let community = blocking(context.pool(), move |conn| {
318     Community::read_from_actor_id(conn, &ap_id)
319   })
320   .await?;
321   if let Ok(c) = community {
322     return Ok(CommunityOrPrivateMessage::Community(c));
323   }
324
325   let ap_id = apub_id.to_string();
326   let private_message = blocking(context.pool(), move |conn| {
327     PrivateMessage::read_from_apub_id(conn, &ap_id)
328   })
329   .await?;
330   if let Ok(p) = private_message {
331     return Ok(CommunityOrPrivateMessage::PrivateMessage(p));
332   }
333
334   return Err(NotFound.into());
335 }