From 77c87f2b4dea6728d551b2be79af349658e0fe31 Mon Sep 17 00:00:00 2001 From: "Aode (lion)" Date: Mon, 29 Nov 2021 19:39:49 -0600 Subject: [PATCH] ingest: new utility crate, core: rename deliver::Activity -> Deliverable --- apub-core/src/activitypub.rs | 2 +- apub-core/src/deliver.rs | 6 +- apub-core/src/ingest.rs | 10 ++ apub-ingest/Cargo.toml | 11 ++ apub-ingest/src/lib.rs | 201 +++++++++++++++++++++++++++++++++++ 5 files changed, 226 insertions(+), 4 deletions(-) create mode 100644 apub-ingest/Cargo.toml create mode 100644 apub-ingest/src/lib.rs diff --git a/apub-core/src/activitypub.rs b/apub-core/src/activitypub.rs index f6beb8b..b972d38 100644 --- a/apub-core/src/activitypub.rs +++ b/apub-core/src/activitypub.rs @@ -4,7 +4,7 @@ use std::{rc::Rc, sync::Arc}; use url::Url; -static PUBLIC: &'static str = "https://www.w3.org/ns/activitystreams#Public"; +static PUBLIC: &str = "https://www.w3.org/ns/activitystreams#Public"; /// A type that represents a Public Key pub trait PublicKey { diff --git a/apub-core/src/deliver.rs b/apub-core/src/deliver.rs index 9eefad5..576b292 100644 --- a/apub-core/src/deliver.rs +++ b/apub-core/src/deliver.rs @@ -44,7 +44,7 @@ pub trait Deliver { /// The inverse of [`Deliver`], allows for `item.deliver(&client)` syntax /// /// ```rust -/// use apub_core::deliver::Activity; +/// use apub_core::deliver::Deliverable; /// use url::Url; /// /// #[derive(serde::Deserialize, serde::Serialize)] @@ -53,10 +53,10 @@ pub trait Deliver { /// // etc... /// } /// -/// impl Activity for MyActivity {} +/// impl Deliverable for MyActivity {} /// ``` #[async_trait::async_trait(?Send)] -pub trait Activity: serde::ser::Serialize { +pub trait Deliverable: serde::ser::Serialize { /// Deliver the activity to the inbox async fn deliver( &self, diff --git a/apub-core/src/ingest.rs b/apub-core/src/ingest.rs index 6585990..52c7874 100644 --- a/apub-core/src/ingest.rs +++ b/apub-core/src/ingest.rs @@ -156,6 +156,16 @@ pub trait FullRepo { } } +impl std::fmt::Display for Authority { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Authority::Actor(actor_id) => write!(f, "Actor - '{}'", actor_id), + Authority::Server(url) => write!(f, "Server URL - '{}'", url), + Authority::None => write!(f, "None"), + } + } +} + fn borrow_host(host: &Host) -> Host<&str> { match host { Host::Ipv4(ip) => Host::Ipv4(*ip), diff --git a/apub-ingest/Cargo.toml b/apub-ingest/Cargo.toml new file mode 100644 index 0000000..dee4951 --- /dev/null +++ b/apub-ingest/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "apub-ingest" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +apub-core = { version = "0.1.0", path = "../apub-core/" } +async-trait = "0.1.51" +url = "2" diff --git a/apub-ingest/src/lib.rs b/apub-ingest/src/lib.rs new file mode 100644 index 0000000..6116206 --- /dev/null +++ b/apub-ingest/src/lib.rs @@ -0,0 +1,201 @@ +use apub_core::{ + activitypub::{Activity, DeliverableObject}, + ingest::{Authority, FullRepo, Ingest}, + session::Session, +}; +use url::Url; + +pub trait InboxType { + fn is_shared(&self) -> bool; +} + +/// Rejects if the Authority does not have permission to act on behalf of Actor +#[derive(Clone, Debug)] +pub struct ValidateAuthority { + ingest: I, +} + +/// Rejects if a public message was not sent to a shared inbox +/// Rejects if a private message was sent to a shared inbox +#[derive(Clone, Debug)] +pub struct ValidateInbox { + ingest: I, +} + +/// Rejects if Activity's host does not match Actor's host +#[derive(Clone, Debug)] +pub struct ValidateHosts { + ingest: I, +} + +/// Rejects if the Authority does not have permission to act on behalf of Actor +pub fn validate_authority(ingest: I) -> ValidateAuthority { + ValidateAuthority { ingest } +} + +/// Rejects if a public message was not sent to a shared inbox +/// Rejects if a private message was sent to a shared inbox +pub fn validate_inbox(ingest: I) -> ValidateInbox { + ValidateInbox { ingest } +} + +/// Rejects if Activity's host does not match Actor's host +pub fn validate_hosts(ingest: I) -> ValidateHosts { + ValidateHosts { ingest } +} + +#[derive(Clone, Debug)] +pub struct AuthorityError { + authority: Authority, + actor: Url, +} + +#[derive(Clone, Debug)] +pub struct InboxError; + +#[derive(Clone, Debug)] +pub struct HostError; + +#[async_trait::async_trait(?Send)] +impl Ingest for ValidateAuthority +where + A: Activity + 'static, + I: Ingest, + I::Error: From, +{ + type Error = I::Error; + type ActorId = I::ActorId; + + async fn ingest( + &self, + authority: Authority, + actor_id: Self::ActorId, + activity: A, + repo: R, + remote_repo: &R::Remote, + session: S, + ) -> Result<(), R::Error> + where + R::Error: From, + { + let activity_actor = activity.actor_id(); + + let valid = match &authority { + Authority::Server(url) => { + url.host() == activity_actor.host() && url.port() == activity_actor.port() + } + Authority::Actor(actor) => actor == activity_actor, + Authority::None => false, + }; + + if !valid { + return Err(I::Error::from(AuthorityError { + authority, + actor: activity_actor.clone(), + }) + .into()); + } + + self.ingest + .ingest(authority, actor_id, activity, repo, remote_repo, session) + .await + } +} + +#[async_trait::async_trait(?Send)] +impl Ingest for ValidateInbox +where + A: DeliverableObject + 'static, + I: Ingest, + I::Error: From, + I::ActorId: InboxType, +{ + type Error = I::Error; + type ActorId = I::ActorId; + + async fn ingest( + &self, + authority: Authority, + actor_id: Self::ActorId, + activity: A, + repo: R, + remote_repo: &R::Remote, + session: S, + ) -> Result<(), R::Error> + where + R::Error: From, + { + // delivered a public activity to a user inbox + if !actor_id.is_shared() && activity.is_public() { + return Err(I::Error::from(InboxError).into()); + } + + // delivered a private activity to a shared inbox + if actor_id.is_shared() && !activity.is_public() { + return Err(I::Error::from(InboxError).into()); + } + + self.ingest + .ingest(authority, actor_id, activity, repo, remote_repo, session) + .await + } +} + +#[async_trait::async_trait(?Send)] +impl Ingest for ValidateHosts +where + A: Activity + 'static, + I: Ingest, + I::Error: From, +{ + type Error = I::Error; + type ActorId = I::ActorId; + + async fn ingest( + &self, + authority: Authority, + actor_id: Self::ActorId, + activity: A, + repo: R, + remote_repo: &R::Remote, + session: S, + ) -> Result<(), R::Error> + where + R::Error: From, + { + if activity.id().host() != activity.actor_id().host() + || activity.id().port() != activity.actor_id().port() + { + return Err(I::Error::from(HostError).into()); + } + + self.ingest + .ingest(authority, actor_id, activity, repo, remote_repo, session) + .await + } +} + +impl std::fmt::Display for AuthorityError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "Authority '{}' not permitted to act on behalf of actor '{}'", + self.authority, self.actor + ) + } +} +impl std::error::Error for AuthorityError {} + +impl std::fmt::Display for InboxError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "Object delivered to invalid inbox") + } +} +impl std::error::Error for InboxError {} + +impl std::fmt::Display for HostError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "Activity and Actor host do not match") + } +} +impl std::error::Error for HostError {}