X-Git-Url: http://these/git/?a=blobdiff_plain;f=crates%2Fapub%2Fsrc%2Fhttp%2Fmod.rs;h=c261d9e4929c363385e10756d491967a25f29148;hb=e9e76549a88cfbdab36f00d302cceabcaaa24f4c;hp=a70ed6f00a1c1d395f52038d70c8efb1840f590c;hpb=329a282aac7c3f5f21e7fa70f79045de21a26a9d;p=lemmy.git diff --git a/crates/apub/src/http/mod.rs b/crates/apub/src/http/mod.rs index a70ed6f0..c261d9e4 100644 --- a/crates/apub/src/http/mod.rs +++ b/crates/apub/src/http/mod.rs @@ -1,33 +1,22 @@ use crate::{ - check_is_apub_id_valid, - extensions::signatures::verify_signature, - fetcher::get_or_fetch_and_upsert_actor, - http::{ - community::{receive_group_inbox, GroupInboxActivities}, - person::{receive_person_inbox, PersonInboxActivities}, - }, - insert_activity, - APUB_JSON_CONTENT_TYPE, + activity_lists::SharedInboxActivities, + fetcher::user_or_community::UserOrCommunity, + protocol::objects::tombstone::Tombstone, + CONTEXT, }; -use actix_web::{ - body::Body, - web, - web::{Bytes, BytesMut, Payload}, - HttpRequest, - HttpResponse, +use activitypub_federation::{ + actix_web::inbox::receive_activity, + config::Data, + protocol::context::WithContext, + FEDERATION_CONTENT_TYPE, }; -use anyhow::{anyhow, Context}; -use futures::StreamExt; +use actix_web::{web, web::Bytes, HttpRequest, HttpResponse}; use http::StatusCode; -use lemmy_api_common::blocking; -use lemmy_apub_lib::{ActivityFields, ActivityHandler}; -use lemmy_db_queries::{source::activity::Activity_, DbPool}; -use lemmy_db_schema::source::activity::Activity; -use lemmy_utils::{location_info, LemmyError}; -use lemmy_websocket::LemmyContext; -use log::{info, trace}; +use lemmy_api_common::context::LemmyContext; +use lemmy_db_schema::source::activity::SentActivity; +use lemmy_utils::error::{LemmyError, LemmyErrorType, LemmyResult}; use serde::{Deserialize, Serialize}; -use std::{fmt::Debug, io::Read}; +use std::ops::Deref; use url::Url; mod comment; @@ -35,109 +24,48 @@ mod community; mod person; mod post; pub mod routes; - -#[derive(Clone, Debug, Deserialize, Serialize, ActivityHandler, ActivityFields)] -#[serde(untagged)] -pub enum SharedInboxActivities { - GroupInboxActivities(GroupInboxActivities), - // Note, pm activities need to be at the end, otherwise comments will end up here. We can probably - // avoid this problem by replacing createpm.object with our own struct, instead of NoteExt. - PersonInboxActivities(PersonInboxActivities), -} +pub mod site; pub async fn shared_inbox( request: HttpRequest, - payload: Payload, - context: web::Data, -) -> Result { - let unparsed = payload_to_string(payload).await?; - trace!("Received shared inbox activity {}", unparsed); - let activity = serde_json::from_str::(&unparsed)?; - match activity { - SharedInboxActivities::GroupInboxActivities(g) => { - receive_group_inbox(g, request, &context).await - } - SharedInboxActivities::PersonInboxActivities(p) => { - receive_person_inbox(p, request, &context).await - } - } + body: Bytes, + data: Data, +) -> LemmyResult { + receive_activity::(request, body, &data) + .await } -async fn payload_to_string(mut payload: Payload) -> Result { - let mut bytes = BytesMut::new(); - while let Some(item) = payload.next().await { - bytes.extend_from_slice(&item?); - } - let mut unparsed = String::new(); - Bytes::from(bytes).as_ref().read_to_string(&mut unparsed)?; - Ok(unparsed) -} - -// TODO: move most of this code to library -async fn receive_activity<'a, T>( - request: HttpRequest, - activity: T, - context: &LemmyContext, -) -> Result +/// Convert the data to json and turn it into an HTTP Response with the correct ActivityPub +/// headers. +/// +/// actix-web doesn't allow pretty-print for json so we need to do this manually. +fn create_apub_response(data: &T) -> LemmyResult where - T: ActivityHandler - + ActivityFields - + Clone - + Deserialize<'a> - + Serialize - + std::fmt::Debug - + Send - + 'static, + T: Serialize, { - let request_counter = &mut 0; - let actor = - get_or_fetch_and_upsert_actor(activity.actor().clone(), context, request_counter).await?; - verify_signature(&request, &actor.public_key().context(location_info!())?)?; - - // Do nothing if we received the same activity before - if is_activity_already_known(context.pool(), activity.id_unchecked()).await? { - return Ok(HttpResponse::Ok().finish()); - } - check_is_apub_id_valid(activity.actor(), false, &context.settings())?; - info!("Verifying activity {}", activity.id_unchecked().to_string()); - activity.verify(context, request_counter).await?; - assert_activity_not_local(&activity, &context.settings().hostname)?; + let json = serde_json::to_string_pretty(&WithContext::new(data, CONTEXT.clone()))?; - // Log the activity, so we avoid receiving and parsing it twice. Note that this could still happen - // if we receive the same activity twice in very quick succession. - insert_activity( - activity.id_unchecked(), - activity.clone(), - false, - true, - context.pool(), + Ok( + HttpResponse::Ok() + .content_type(FEDERATION_CONTENT_TYPE) + .body(json), ) - .await?; - - info!("Receiving activity {}", activity.id_unchecked().to_string()); - activity.receive(context, request_counter).await?; - Ok(HttpResponse::Ok().finish()) } -/// Convert the data to json and turn it into an HTTP Response with the correct ActivityPub -/// headers. -fn create_apub_response(data: &T) -> HttpResponse -where - T: Serialize, -{ - HttpResponse::Ok() - .content_type(APUB_JSON_CONTENT_TYPE) - .json(data) +fn create_apub_tombstone_response>(id: T) -> LemmyResult { + let tombstone = Tombstone::new(id.into()); + let json = serde_json::to_string_pretty(&WithContext::new(tombstone, CONTEXT.deref().clone()))?; + + Ok( + HttpResponse::Gone() + .content_type(FEDERATION_CONTENT_TYPE) + .status(StatusCode::GONE) + .body(json), + ) } -fn create_apub_tombstone_response(data: &T) -> HttpResponse -where - T: Serialize, -{ - HttpResponse::Gone() - .content_type(APUB_JSON_CONTENT_TYPE) - .status(StatusCode::GONE) - .json(data) +fn err_object_not_local() -> LemmyError { + LemmyErrorType::ObjectNotLocal.into() } #[derive(Deserialize)] @@ -147,10 +75,11 @@ pub struct ActivityQuery { } /// Return the ActivityPub json representation of a local activity over HTTP. +#[tracing::instrument(skip_all)] pub(crate) async fn get_activity( info: web::Path, context: web::Data, -) -> Result, LemmyError> { +) -> Result { let settings = context.settings(); let activity_id = Url::parse(&format!( "{}/activities/{}/{}", @@ -159,48 +88,12 @@ pub(crate) async fn get_activity( info.id ))? .into(); - let activity = blocking(context.pool(), move |conn| { - Activity::read_from_apub_id(conn, &activity_id) - }) - .await??; + let activity = SentActivity::read_from_apub_id(&mut context.pool(), &activity_id).await?; - let sensitive = activity.sensitive.unwrap_or(true); - if !activity.local || sensitive { - Ok(HttpResponse::NotFound().finish()) + let sensitive = activity.sensitive; + if sensitive { + Ok(HttpResponse::Forbidden().finish()) } else { - Ok(create_apub_response(&activity.data)) - } -} - -pub(crate) async fn is_activity_already_known( - pool: &DbPool, - activity_id: &Url, -) -> Result { - let activity_id = activity_id.to_owned().into(); - let existing = blocking(pool, move |conn| { - Activity::read_from_apub_id(conn, &activity_id) - }) - .await?; - match existing { - Ok(_) => Ok(true), - Err(_) => Ok(false), - } -} - -fn assert_activity_not_local( - activity: &T, - hostname: &str, -) -> Result<(), LemmyError> { - let activity_domain = activity.id_unchecked().domain().context(location_info!())?; - - if activity_domain == hostname { - return Err( - anyhow!( - "Error: received activity which was sent by local instance: {:?}", - activity - ) - .into(), - ); + create_apub_response(&activity.data) } - Ok(()) }