4 receive_create_comment,
5 receive_delete_comment,
6 receive_dislike_comment,
8 receive_remove_comment,
9 receive_update_comment,
12 receive_undo_delete_comment,
13 receive_undo_dislike_comment,
14 receive_undo_like_comment,
15 receive_undo_remove_comment,
18 receive_delete_community,
19 receive_remove_community,
20 receive_undo_delete_community,
21 receive_undo_remove_community,
33 receive_undo_delete_post,
34 receive_undo_dislike_post,
35 receive_undo_like_post,
36 receive_undo_remove_post,
38 receive_unhandled_activity,
39 verify_activity_domains_valid,
42 check_is_apub_id_valid,
43 extensions::signatures::verify_signature,
44 fetcher::get_or_fetch_and_upsert_actor,
45 inbox::{get_activity_id, is_activity_already_known},
49 use activitystreams::{
50 activity::{ActorAndObject, Announce, Create, Delete, Dislike, Like, Remove, Undo, Update},
54 use actix_web::{web, HttpRequest, HttpResponse};
55 use anyhow::{anyhow, Context};
56 use lemmy_db::{site::Site, Crud};
57 use lemmy_structs::blocking;
58 use lemmy_utils::{location_info, LemmyError};
59 use lemmy_websocket::LemmyContext;
61 use serde::{Deserialize, Serialize};
65 /// Allowed activity types for shared inbox.
66 #[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd, Deserialize, Serialize)]
67 #[serde(rename_all = "PascalCase")]
79 // TODO: this isnt entirely correct, cause some of these receive are not ActorAndObject,
80 // but it might still work due to the anybase conversion
81 pub type AcceptedActivities = ActorAndObject<ValidTypes>;
83 /// Handler for all incoming requests to shared inbox.
84 pub async fn shared_inbox(
86 input: web::Json<AcceptedActivities>,
87 context: web::Data<LemmyContext>,
88 ) -> Result<HttpResponse, LemmyError> {
89 let activity = input.into_inner();
91 let actor_id = activity
95 .context(location_info!())?;
97 "Shared inbox received activity {:?} from {}",
98 &activity.id_unchecked(),
102 check_is_apub_id_valid(&actor_id)?;
104 let request_counter = &mut 0;
105 let actor = get_or_fetch_and_upsert_actor(&actor_id, &context, request_counter).await?;
106 verify_signature(&request, actor.as_ref())?;
108 let activity_id = get_activity_id(&activity, &actor_id)?;
109 if is_activity_already_known(context.pool(), &activity_id).await? {
110 return Ok(HttpResponse::Ok().finish());
113 let any_base = activity.clone().into_any_base()?;
114 let kind = activity.kind().context(location_info!())?;
115 let res = match kind {
116 ValidTypes::Announce => {
117 receive_announce(&context, any_base, actor.as_ref(), request_counter).await
119 ValidTypes::Create => receive_create(&context, any_base, actor_id, request_counter).await,
120 ValidTypes::Update => receive_update(&context, any_base, actor_id, request_counter).await,
121 ValidTypes::Like => receive_like(&context, any_base, actor_id, request_counter).await,
122 ValidTypes::Dislike => receive_dislike(&context, any_base, actor_id, request_counter).await,
123 ValidTypes::Remove => receive_remove(&context, any_base, actor_id).await,
124 ValidTypes::Delete => receive_delete(&context, any_base, actor_id, request_counter).await,
125 ValidTypes::Undo => receive_undo(&context, any_base, actor_id, request_counter).await,
128 insert_activity(&activity_id, activity.clone(), false, true, context.pool()).await?;
132 /// Takes an announce and passes the inner activity to the appropriate handler.
133 async fn receive_announce(
134 context: &LemmyContext,
136 actor: &dyn ActorType,
137 request_counter: &mut i32,
138 ) -> Result<HttpResponse, LemmyError> {
139 let announce = Announce::from_any_base(activity)?.context(location_info!())?;
140 verify_activity_domains_valid(&announce, actor.actor_id()?, false)?;
142 let kind = announce.object().as_single_kind_str();
143 let object = announce
147 .context(location_info!())?;
149 let inner_id = object.id().context(location_info!())?.to_owned();
150 check_is_apub_id_valid(&inner_id)?;
151 if is_activity_already_known(context.pool(), &inner_id).await? {
152 return Ok(HttpResponse::Ok().finish());
156 Some("Create") => receive_create(context, object, inner_id, request_counter).await,
157 Some("Update") => receive_update(context, object, inner_id, request_counter).await,
158 Some("Like") => receive_like(context, object, inner_id, request_counter).await,
159 Some("Dislike") => receive_dislike(context, object, inner_id, request_counter).await,
160 Some("Delete") => receive_delete(context, object, inner_id, request_counter).await,
161 Some("Remove") => receive_remove(context, object, inner_id).await,
162 Some("Undo") => receive_undo(context, object, inner_id, request_counter).await,
163 _ => receive_unhandled_activity(announce),
167 async fn receive_create(
168 context: &LemmyContext,
170 expected_domain: Url,
171 request_counter: &mut i32,
172 ) -> Result<HttpResponse, LemmyError> {
173 let create = Create::from_any_base(activity)?.context(location_info!())?;
174 verify_activity_domains_valid(&create, expected_domain, true)?;
176 match create.object().as_single_kind_str() {
177 Some("Page") => receive_create_post(create, context, request_counter).await,
178 Some("Note") => receive_create_comment(create, context, request_counter).await,
179 _ => receive_unhandled_activity(create),
183 async fn receive_update(
184 context: &LemmyContext,
186 expected_domain: Url,
187 request_counter: &mut i32,
188 ) -> Result<HttpResponse, LemmyError> {
189 let update = Update::from_any_base(activity)?.context(location_info!())?;
190 verify_activity_domains_valid(&update, expected_domain, true)?;
192 match update.object().as_single_kind_str() {
193 Some("Page") => receive_update_post(update, context, request_counter).await,
194 Some("Note") => receive_update_comment(update, context, request_counter).await,
195 _ => receive_unhandled_activity(update),
199 async fn receive_like(
200 context: &LemmyContext,
202 expected_domain: Url,
203 request_counter: &mut i32,
204 ) -> Result<HttpResponse, LemmyError> {
205 let like = Like::from_any_base(activity)?.context(location_info!())?;
206 verify_activity_domains_valid(&like, expected_domain, false)?;
208 match like.object().as_single_kind_str() {
209 Some("Page") => receive_like_post(like, context, request_counter).await,
210 Some("Note") => receive_like_comment(like, context, request_counter).await,
211 _ => receive_unhandled_activity(like),
215 async fn receive_dislike(
216 context: &LemmyContext,
218 expected_domain: Url,
219 request_counter: &mut i32,
220 ) -> Result<HttpResponse, LemmyError> {
221 let enable_downvotes = blocking(context.pool(), move |conn| {
222 Site::read(conn, 1).map(|s| s.enable_downvotes)
225 if !enable_downvotes {
226 return Ok(HttpResponse::Ok().finish());
229 let dislike = Dislike::from_any_base(activity)?.context(location_info!())?;
230 verify_activity_domains_valid(&dislike, expected_domain, false)?;
232 match dislike.object().as_single_kind_str() {
233 Some("Page") => receive_dislike_post(dislike, context, request_counter).await,
234 Some("Note") => receive_dislike_comment(dislike, context, request_counter).await,
235 _ => receive_unhandled_activity(dislike),
239 pub async fn receive_delete(
240 context: &LemmyContext,
242 expected_domain: Url,
243 request_counter: &mut i32,
244 ) -> Result<HttpResponse, LemmyError> {
245 let delete = Delete::from_any_base(activity)?.context(location_info!())?;
246 verify_activity_domains_valid(&delete, expected_domain, true)?;
251 .single_xsd_any_uri()
252 .context(location_info!())?;
254 match find_by_id(context, object).await {
255 Ok(FindResults::Post(p)) => receive_delete_post(context, delete, p, request_counter).await,
256 Ok(FindResults::Comment(c)) => {
257 receive_delete_comment(context, delete, c, request_counter).await
259 Ok(FindResults::Community(c)) => {
260 receive_delete_community(context, delete, c, request_counter).await
262 // if we dont have the object, no need to do anything
263 Err(_) => Ok(HttpResponse::Ok().finish()),
267 async fn receive_remove(
268 context: &LemmyContext,
270 expected_domain: Url,
271 ) -> Result<HttpResponse, LemmyError> {
272 let remove = Remove::from_any_base(activity)?.context(location_info!())?;
273 verify_activity_domains_valid(&remove, expected_domain, false)?;
277 .map(|c| c.as_many())
279 .context(location_info!())?;
280 let community_id = cc
282 .map(|c| c.as_xsd_any_uri())
284 .context(location_info!())?;
289 .single_xsd_any_uri()
290 .context(location_info!())?;
292 // Ensure that remove activity comes from the same domain as the community
293 remove.id(community_id.domain().context(location_info!())?)?;
295 match find_by_id(context, object).await {
296 Ok(FindResults::Post(p)) => receive_remove_post(context, remove, p).await,
297 Ok(FindResults::Comment(c)) => receive_remove_comment(context, remove, c).await,
298 Ok(FindResults::Community(c)) => receive_remove_community(context, remove, c).await,
299 // if we dont have the object, no need to do anything
300 Err(_) => Ok(HttpResponse::Ok().finish()),
304 async fn receive_undo(
305 context: &LemmyContext,
307 expected_domain: Url,
308 request_counter: &mut i32,
309 ) -> Result<HttpResponse, LemmyError> {
310 let undo = Undo::from_any_base(activity)?.context(location_info!())?;
311 verify_activity_domains_valid(&undo, expected_domain.to_owned(), true)?;
313 match undo.object().as_single_kind_str() {
314 Some("Delete") => receive_undo_delete(context, undo, expected_domain, request_counter).await,
315 Some("Remove") => receive_undo_remove(context, undo, expected_domain, request_counter).await,
316 Some("Like") => receive_undo_like(context, undo, expected_domain, request_counter).await,
317 Some("Dislike") => receive_undo_dislike(context, undo, expected_domain, request_counter).await,
318 _ => receive_unhandled_activity(undo),
322 async fn receive_undo_delete(
323 context: &LemmyContext,
325 expected_domain: Url,
326 request_counter: &mut i32,
327 ) -> Result<HttpResponse, LemmyError> {
328 let delete = Delete::from_any_base(undo.object().to_owned().one().context(location_info!())?)?
329 .context(location_info!())?;
330 verify_activity_domains_valid(&delete, expected_domain, true)?;
335 .single_xsd_any_uri()
336 .context(location_info!())?;
337 match find_by_id(context, object).await {
338 Ok(FindResults::Post(p)) => receive_undo_delete_post(context, undo, p, request_counter).await,
339 Ok(FindResults::Comment(c)) => {
340 receive_undo_delete_comment(context, undo, c, request_counter).await
342 Ok(FindResults::Community(c)) => {
343 receive_undo_delete_community(context, undo, c, request_counter).await
345 // if we dont have the object, no need to do anything
346 Err(_) => Ok(HttpResponse::Ok().finish()),
350 async fn receive_undo_remove(
351 context: &LemmyContext,
353 expected_domain: Url,
354 request_counter: &mut i32,
355 ) -> Result<HttpResponse, LemmyError> {
356 let remove = Remove::from_any_base(undo.object().to_owned().one().context(location_info!())?)?
357 .context(location_info!())?;
358 verify_activity_domains_valid(&remove, expected_domain, false)?;
363 .single_xsd_any_uri()
364 .context(location_info!())?;
365 match find_by_id(context, object).await {
366 Ok(FindResults::Post(p)) => receive_undo_remove_post(context, undo, p, request_counter).await,
367 Ok(FindResults::Comment(c)) => {
368 receive_undo_remove_comment(context, undo, c, request_counter).await
370 Ok(FindResults::Community(c)) => {
371 receive_undo_remove_community(context, undo, c, request_counter).await
373 // if we dont have the object, no need to do anything
374 Err(_) => Ok(HttpResponse::Ok().finish()),
378 async fn receive_undo_like(
379 context: &LemmyContext,
381 expected_domain: Url,
382 request_counter: &mut i32,
383 ) -> Result<HttpResponse, LemmyError> {
384 let like = Like::from_any_base(undo.object().to_owned().one().context(location_info!())?)?
385 .context(location_info!())?;
386 verify_activity_domains_valid(&like, expected_domain, false)?;
390 .as_single_kind_str()
391 .context(location_info!())?;
393 "Note" => receive_undo_like_comment(undo, &like, context, request_counter).await,
394 "Page" => receive_undo_like_post(undo, &like, context, request_counter).await,
395 d => Err(anyhow!("Undo Delete type {} not supported", d).into()),
399 async fn receive_undo_dislike(
400 context: &LemmyContext,
402 expected_domain: Url,
403 request_counter: &mut i32,
404 ) -> Result<HttpResponse, LemmyError> {
405 let dislike = Dislike::from_any_base(undo.object().to_owned().one().context(location_info!())?)?
406 .context(location_info!())?;
407 verify_activity_domains_valid(&dislike, expected_domain, false)?;
411 .as_single_kind_str()
412 .context(location_info!())?;
414 "Note" => receive_undo_dislike_comment(undo, &dislike, context, request_counter).await,
415 "Page" => receive_undo_dislike_post(undo, &dislike, context, request_counter).await,
416 d => Err(anyhow!("Undo Delete type {} not supported", d).into()),