From 8dece0f004cb39a82fc716859e463311eed61df6 Mon Sep 17 00:00:00 2001 From: alemi Date: Mon, 22 Apr 2024 22:52:05 +0200 Subject: [PATCH] feat: implemented authed inbox --- src/routes/activitypub/inbox.rs | 37 +++++++++++++-------- src/server/inbox.rs | 59 +++++++++++++++++++++++++-------- 2 files changed, 69 insertions(+), 27 deletions(-) diff --git a/src/routes/activitypub/inbox.rs b/src/routes/activitypub/inbox.rs index 896c124..7fccc4a 100644 --- a/src/routes/activitypub/inbox.rs +++ b/src/routes/activitypub/inbox.rs @@ -51,15 +51,24 @@ pub async fn post( AuthIdentity(auth): AuthIdentity, Json(activity): Json ) -> crate::Result<()> { - if !matches!(auth, Identity::Remote(_)) { + let Identity::Remote(server) = auth else { if activity.activity_type() != Some(ActivityType::Delete) { // this is spammy af, ignore them! tracing::warn!("refusing unauthorized activity: {}", pretty_json!(activity)); } - match auth { - Identity::Local(_user) => return Err(UpubError::forbidden()), - Identity::Anonymous => return Err(UpubError::unauthorized()), - _ => {}, + if matches!(auth, Identity::Anonymous) { + return Err(UpubError::unauthorized()); + } else { + return Err(UpubError::forbidden()); } + }; + + let Some(actor) = activity.actor().id() else { + return Err(UpubError::bad_request()); + }; + + // TODO add whitelist of relays + if !server.ends_with(&Context::server(&actor)) { + return Err(UpubError::unauthorized()); } // TODO we could process Links and bare Objects maybe, but probably out of AP spec? @@ -69,15 +78,15 @@ pub async fn post( Err(StatusCode::UNPROCESSABLE_ENTITY.into()) // won't ingest useless stuff }, - ActivityType::Delete => Ok(ctx.delete(activity).await?), - ActivityType::Follow => Ok(ctx.follow(activity).await?), - ActivityType::Accept(_) => Ok(ctx.accept(activity).await?), - ActivityType::Reject(_) => Ok(ctx.reject(activity).await?), - ActivityType::Like => Ok(ctx.like(activity).await?), - ActivityType::Create => Ok(ctx.create(activity).await?), - ActivityType::Update => Ok(ctx.update(activity).await?), - ActivityType::Undo => Ok(ctx.undo(activity).await?), - ActivityType::Announce => Ok(ctx.announce(activity).await?), + ActivityType::Create => Ok(ctx.create(server, activity).await?), + ActivityType::Like => Ok(ctx.like(server, activity).await?), + ActivityType::Follow => Ok(ctx.follow(server, activity).await?), + ActivityType::Announce => Ok(ctx.announce(server, activity).await?), + ActivityType::Accept(_) => Ok(ctx.accept(server, activity).await?), + ActivityType::Reject(_) => Ok(ctx.reject(server, activity).await?), + ActivityType::Undo => Ok(ctx.undo(server, activity).await?), + ActivityType::Delete => Ok(ctx.delete(server, activity).await?), + ActivityType::Update => Ok(ctx.update(server, activity).await?), _x => { tracing::info!("received unimplemented activity on inbox: {}", pretty_json!(activity)); diff --git a/src/server/inbox.rs b/src/server/inbox.rs index 94a4c72..e1097cf 100644 --- a/src/server/inbox.rs +++ b/src/server/inbox.rs @@ -12,14 +12,35 @@ impl apb::server::Inbox for Context { type Error = UpubError; type Activity = serde_json::Value; - async fn create(&self, activity: serde_json::Value) -> crate::Result<()> { + async fn create(&self, server: String, activity: serde_json::Value) -> crate::Result<()> { let activity_model = model::activity::Model::new(&activity)?; + let aid = activity_model.id.clone(); let Some(object_node) = activity.object().extract() else { // TODO we could process non-embedded activities or arrays but im lazy rn tracing::error!("refusing to process activity without embedded object: {}", serde_json::to_string_pretty(&activity).unwrap()); return Err(UpubError::unprocessable()); }; - let object_model = model::object::Model::new(&object_node)?; + let mut object_model = model::object::Model::new(&object_node)?; + let oid = object_model.id.clone(); + // make sure we're allowed to edit this object + if let Some(object_author) = &object_model.attributed_to { + if server != Context::server(object_author) { + return Err(UpubError::forbidden()); + } + } else if server != Context::server(&object_model.id) { + return Err(UpubError::forbidden()); + }; + // fix context also for remote posts + // TODO this is not really appropriate because we're mirroring incorrectly remote objects, but + // it makes it SOO MUCH EASIER for us to fetch threads and stuff, so we're filling it for them + match (&object_model.in_reply_to, &object_model.context) { + (Some(reply_id), None) => // get context from replied object + object_model.context = self.fetch_object(reply_id).await?.context, + (None, None) => // generate a new context + object_model.context = Some(crate::url!(self, "/context/{}", uuid::Uuid::new_v4().to_string())), + (_, Some(_)) => {}, // leave it as set by user + } + // update replies counter if let Some(ref in_reply_to) = object_model.in_reply_to { if self.fetch_object(in_reply_to).await.is_ok() { model::object::Entity::update_many() @@ -29,8 +50,6 @@ impl apb::server::Inbox for Context { .await?; } } - let aid = activity_model.id.clone(); - let oid = object_model.id.clone(); model::object::Entity::insert(object_model.into_active_model()).exec(self.db()).await?; model::activity::Entity::insert(activity_model.into_active_model()).exec(self.db()).await?; for attachment in object_node.attachment() { @@ -45,7 +64,7 @@ impl apb::server::Inbox for Context { Ok(()) } - async fn like(&self, activity: serde_json::Value) -> crate::Result<()> { + async fn like(&self, _: String, activity: serde_json::Value) -> crate::Result<()> { let aid = activity.id().ok_or(UpubError::bad_request())?; let uid = activity.actor().id().ok_or(UpubError::bad_request())?; let oid = activity.object().id().ok_or(UpubError::bad_request())?; @@ -83,7 +102,7 @@ impl apb::server::Inbox for Context { } } - async fn follow(&self, activity: serde_json::Value) -> crate::Result<()> { + async fn follow(&self, _: String, activity: serde_json::Value) -> crate::Result<()> { let activity_model = model::activity::Model::new(&activity)?; let aid = activity_model.id.clone(); tracing::info!("{} wants to follow {}", activity_model.actor, activity_model.object.as_deref().unwrap_or("")); @@ -94,7 +113,7 @@ impl apb::server::Inbox for Context { Ok(()) } - async fn accept(&self, activity: serde_json::Value) -> crate::Result<()> { + async fn accept(&self, _: String, activity: serde_json::Value) -> crate::Result<()> { // TODO what about TentativeAccept let activity_model = model::activity::Model::new(&activity)?; let Some(follow_request_id) = &activity_model.object else { @@ -135,7 +154,7 @@ impl apb::server::Inbox for Context { Ok(()) } - async fn reject(&self, activity: serde_json::Value) -> crate::Result<()> { + async fn reject(&self, _: String, activity: serde_json::Value) -> crate::Result<()> { // TODO what about TentativeReject? let activity_model = model::activity::Model::new(&activity)?; let Some(follow_request_id) = &activity_model.object else { @@ -161,7 +180,7 @@ impl apb::server::Inbox for Context { Ok(()) } - async fn delete(&self, activity: serde_json::Value) -> crate::Result<()> { + async fn delete(&self, _: String, activity: serde_json::Value) -> crate::Result<()> { // TODO verify the signature before just deleting lmao let oid = activity.object().id().ok_or(UpubError::bad_request())?; tracing::debug!("deleting '{oid}'"); // this is so spammy wtf! @@ -172,17 +191,25 @@ impl apb::server::Inbox for Context { Ok(()) } - async fn update(&self, activity: serde_json::Value) -> crate::Result<()> { + async fn update(&self, server: String, activity: serde_json::Value) -> crate::Result<()> { let activity_model = model::activity::Model::new(&activity)?; + let aid = activity_model.id.clone(); let Some(object_node) = activity.object().extract() else { // TODO we could process non-embedded activities or arrays but im lazy rn tracing::error!("refusing to process activity without embedded object: {}", serde_json::to_string_pretty(&activity).unwrap()); return Err(UpubError::unprocessable()); }; - let aid = activity_model.id.clone(); let Some(oid) = object_node.id().map(|x| x.to_string()) else { return Err(UpubError::bad_request()); }; + // make sure we're allowed to edit this object + if let Some(object_author) = object_node.attributed_to().id() { + if server != Context::server(&object_author) { + return Err(UpubError::forbidden()); + } + } else if server != Context::server(&oid) { + return Err(UpubError::forbidden()); + }; match object_node.object_type() { Some(apb::ObjectType::Actor(_)) => { // TODO oof here is an example of the weakness of this model, we have to go all the way @@ -209,13 +236,19 @@ impl apb::server::Inbox for Context { Ok(()) } - async fn undo(&self, activity: serde_json::Value) -> crate::Result<()> { + async fn undo(&self, server: String, activity: serde_json::Value) -> crate::Result<()> { let uid = activity.actor().id().ok_or_else(UpubError::bad_request)?; // TODO in theory we could work with just object_id but right now only accept embedded let undone_activity = activity.object().extract().ok_or_else(UpubError::bad_request)?; let undone_aid = undone_activity.id().ok_or_else(UpubError::bad_request)?; let undone_object_id = undone_activity.object().id().ok_or_else(UpubError::bad_request)?; let activity_type = undone_activity.activity_type().ok_or_else(UpubError::bad_request)?; + let undone_activity_author = undone_activity.actor().id().ok_or_else(UpubError::bad_request)?; + + // can't undo activities from remote actors! + if server != Context::server(&undone_activity_author) { + return Err(UpubError::forbidden()); + }; match activity_type { apb::ActivityType::Like => { @@ -255,7 +288,7 @@ impl apb::server::Inbox for Context { } - async fn announce(&self, activity: serde_json::Value) -> crate::Result<()> { + async fn announce(&self, _: String, activity: serde_json::Value) -> crate::Result<()> { let activity_model = model::activity::Model::new(&activity)?; let Some(oid) = &activity_model.object else { return Err(FieldError("object").into());