From 3a79ca05a2cbf919d06553bfb8a6ae5fd8579eeb Mon Sep 17 00:00:00 2001 From: alemi Date: Thu, 18 Apr 2024 04:48:49 +0200 Subject: [PATCH] feat: local users can request to fetch remote stuff --- src/routes/activitypub/activity.rs | 23 ++++++++++++----------- src/routes/activitypub/mod.rs | 6 ++++++ src/routes/activitypub/object.rs | 25 +++++++++++++------------ src/routes/activitypub/user/mod.rs | 17 +++++++++++------ src/server/auth.rs | 12 ++++++++++-- 5 files changed, 52 insertions(+), 31 deletions(-) diff --git a/src/routes/activitypub/activity.rs b/src/routes/activitypub/activity.rs index 59e8ba56..0d4ecdc8 100644 --- a/src/routes/activitypub/activity.rs +++ b/src/routes/activitypub/activity.rs @@ -1,9 +1,9 @@ -use axum::{extract::{Path, State}, http::StatusCode}; +use axum::extract::{Path, Query, State}; use sea_orm::{ColumnTrait, QueryFilter}; -use crate::{model::{self, addressing::EmbeddedActivity}, server::{auth::AuthIdentity, Context}}; +use crate::{errors::UpubError, model::{self, addressing::EmbeddedActivity}, server::{auth::AuthIdentity, Context}}; use apb::{ActivityMut, ObjectMut, BaseMut, Node}; -use super::{jsonld::LD, JsonLD}; +use super::{jsonld::LD, JsonLD, TryFetch}; // TODO this is used outside /routes, maybe move in model? pub fn ap_activity(activity: model::activity::Model) -> serde_json::Value { @@ -24,24 +24,25 @@ pub async fn view( State(ctx): State, Path(id): Path, AuthIdentity(auth): AuthIdentity, -) -> Result, StatusCode> { + Query(query): Query, +) -> crate::Result> { let aid = if id.starts_with('+') { format!("https://{}", id.replacen('+', "", 1).replace('@', "/")) } else { ctx.aid(id.clone()) }; match model::addressing::Entity::find_activities() - .filter(model::activity::Column::Id.eq(aid)) + .filter(model::activity::Column::Id.eq(&aid)) .filter(auth.filter_condition()) .into_model::() .one(ctx.db()) - .await + .await? { - Ok(Some(activity)) => Ok(JsonLD(serde_json::Value::from(activity).ld_context())), - Ok(None) => Err(StatusCode::NOT_FOUND), - Err(e) => { - tracing::error!("error querying for activity: {e}"); - Err(StatusCode::INTERNAL_SERVER_ERROR) + Some(activity) => Ok(JsonLD(serde_json::Value::from(activity).ld_context())), + None => if auth.is_local() && query.fetch { + Ok(JsonLD(ap_activity(ctx.fetch().activity(&aid).await?).ld_context())) + } else { + Err(UpubError::not_found()) }, } } diff --git a/src/routes/activitypub/mod.rs b/src/routes/activitypub/mod.rs index c4cd3a92..3eaf9cc1 100644 --- a/src/routes/activitypub/mod.rs +++ b/src/routes/activitypub/mod.rs @@ -65,6 +65,12 @@ impl ActivityPubRouter for Router { } } +#[derive(Debug, serde::Deserialize)] +pub struct TryFetch { + #[serde(default)] + pub fetch: bool, +} + #[derive(Debug, serde::Deserialize)] // TODO i don't really like how pleroma/mastodon do it actually, maybe change this? pub struct Pagination { diff --git a/src/routes/activitypub/object.rs b/src/routes/activitypub/object.rs index 23e9cb42..72301fe6 100644 --- a/src/routes/activitypub/object.rs +++ b/src/routes/activitypub/object.rs @@ -1,10 +1,10 @@ -use axum::{extract::{Path, State}, http::StatusCode}; +use axum::extract::{Path, Query, State}; use sea_orm::{ColumnTrait, QueryFilter}; use apb::{ObjectMut, BaseMut, Node}; -use crate::{model::{self, addressing::EmbeddedActivity}, server::{auth::AuthIdentity, Context}}; +use crate::{errors::UpubError, model::{self, addressing::EmbeddedActivity}, server::{auth::AuthIdentity, Context}}; -use super::{jsonld::LD, JsonLD}; +use super::{jsonld::LD, JsonLD, TryFetch}; // TODO this is used outside /routes, maybe move in model? pub fn ap_object(object: model::object::Model) -> serde_json::Value { @@ -28,25 +28,26 @@ pub async fn view( State(ctx): State, Path(id): Path, AuthIdentity(auth): AuthIdentity, -) -> Result, StatusCode> { + Query(query): Query, +) -> crate::Result> { let oid = if id.starts_with('+') { format!("https://{}", id.replacen('+', "", 1).replace('@', "/")) } else { ctx.oid(id.clone()) }; match model::addressing::Entity::find_activities() - .filter(model::object::Column::Id.eq(oid)) + .filter(model::object::Column::Id.eq(&oid)) .filter(auth.filter_condition()) .into_model::() .one(ctx.db()) - .await + .await? { - Ok(Some(EmbeddedActivity { activity: _, object: Some(object) })) => Ok(JsonLD(ap_object(object).ld_context())), - Ok(Some(EmbeddedActivity { activity: _, object: None })) => Err(StatusCode::NOT_FOUND), - Ok(None) => Err(StatusCode::NOT_FOUND), - Err(e) => { - tracing::error!("error querying for object: {e}"); - Err(StatusCode::INTERNAL_SERVER_ERROR) + Some(EmbeddedActivity { activity: _, object: Some(object) }) => Ok(JsonLD(ap_object(object).ld_context())), + Some(EmbeddedActivity { activity: _, object: None }) => Err(UpubError::not_found()), + None => if auth.is_local() && query.fetch { + Ok(JsonLD(ap_object(ctx.fetch().object(&oid).await?).ld_context())) + } else { + Err(UpubError::not_found()) }, } } diff --git a/src/routes/activitypub/user/mod.rs b/src/routes/activitypub/user/mod.rs index 429d1a86..19d1a4fd 100644 --- a/src/routes/activitypub/user/mod.rs +++ b/src/routes/activitypub/user/mod.rs @@ -4,13 +4,13 @@ pub mod outbox; pub mod following; -use axum::extract::{Path, State}; +use axum::extract::{Path, Query, State}; use sea_orm::EntityTrait; use apb::{ActorMut, BaseMut, CollectionMut, DocumentMut, DocumentType, Node, ObjectMut, PublicKeyMut}; use crate::{errors::UpubError, model::{self, user}, server::{auth::AuthIdentity, Context}, url}; -use super::{jsonld::LD, JsonLD}; +use super::{jsonld::LD, JsonLD, TryFetch}; pub fn ap_user(user: model::user::Model) -> serde_json::Value { serde_json::Value::new_object() @@ -48,13 +48,14 @@ pub async fn view( State(ctx) : State, AuthIdentity(auth): AuthIdentity, Path(id): Path, + Query(query): Query, ) -> crate::Result> { let uid = if id.starts_with('+') { format!("https://{}", id.replacen('+', "", 1).replace('@', "/")) } else { ctx.uid(id.clone()) }; - match user::Entity::find_by_id(uid) + match user::Entity::find_by_id(&uid) .find_also_related(model::config::Entity) .one(ctx.db()).await? { @@ -75,7 +76,7 @@ pub async fn view( .set_collection_type(Some(apb::CollectionType::OrderedCollection)) .set_first(Node::link(url!(ctx, "/users/{id}/following/page"))) .set_total_items( - if auth.is_user(&user.id) || cfg.show_following { + if auth.is_local_user(&user.id) || cfg.show_following { Some(user.following_count as u64) } else { None @@ -88,7 +89,7 @@ pub async fn view( .set_collection_type(Some(apb::CollectionType::OrderedCollection)) .set_first(Node::link(url!(ctx, "/users/{id}/followers/page"))) .set_total_items( - if auth.is_user(&user.id) || cfg.show_followers { + if auth.is_local_user(&user.id) || cfg.show_followers { Some(user.followers_count as u64) } else { None @@ -101,7 +102,11 @@ pub async fn view( }, // remote user TODDO doesn't work? Some((user, None)) => Ok(JsonLD(ap_user(user).ld_context())), - None => Err(UpubError::not_found()), + None => if auth.is_local() && query.fetch { + Ok(JsonLD(ap_user(ctx.fetch().user(&uid).await?).ld_context())) + } else { + Err(UpubError::not_found()) + }, } } diff --git a/src/server/auth.rs b/src/server/auth.rs index 89adc186..5132939a 100644 --- a/src/server/auth.rs +++ b/src/server/auth.rs @@ -30,14 +30,22 @@ impl Identity { matches!(self, Self::Anonymous) } - pub fn is_user(&self, uid: &str) -> bool { + pub fn is_local(&self) -> bool { + matches!(self, Self::Local(_)) + } + + pub fn is_remote(&self) -> bool { + matches!(self, Self::Remote(_)) + } + + pub fn is_local_user(&self, uid: &str) -> bool { match self { Self::Local(x) => x == uid, _ => false, } } - pub fn is_server(&self, uid: &str) -> bool { + pub fn is_remote_server(&self, uid: &str) -> bool { match self { Self::Remote(x) => x == uid, _ => false,