From a619a0d52ab02c23d7eb6da4f65b9fc5ca06d063 Mon Sep 17 00:00:00 2001 From: Felix Ableitner Date: Wed, 23 Jun 2021 19:28:37 +0200 Subject: [PATCH] got something that almost works --- .../apub_receive/src/activities_new/follow.rs | 67 +++++++++ crates/apub_receive/src/activities_new/mod.rs | 1 + .../apub_receive/src/inbox/community_inbox.rs | 2 - .../src/inbox/new_inbox_routing.rs | 142 +++++++++--------- crates/apub_receive/src/inbox/person_inbox.rs | 51 +------ crates/apub_receive/src/lib.rs | 1 + 6 files changed, 148 insertions(+), 116 deletions(-) create mode 100644 crates/apub_receive/src/activities_new/follow.rs create mode 100644 crates/apub_receive/src/activities_new/mod.rs diff --git a/crates/apub_receive/src/activities_new/follow.rs b/crates/apub_receive/src/activities_new/follow.rs new file mode 100644 index 00000000..3c0c9209 --- /dev/null +++ b/crates/apub_receive/src/activities_new/follow.rs @@ -0,0 +1,67 @@ +use url::Url; +use crate::inbox::new_inbox_routing::{ReceiveActivity, Activity, verify_domains_match}; +use activitystreams::activity::kind::FollowType; +use activitystreams::activity::kind::AcceptType; +use crate::activities::receive::verify_activity_domains_valid; +use activitystreams::base::ExtendsExt; +use anyhow::Context; +use lemmy_apub::fetcher::community::get_or_fetch_and_upsert_community; +use lemmy_api_common::blocking; +use lemmy_db_schema::source::community::CommunityFollower; +use lemmy_websocket::LemmyContext; +use lemmy_utils::LemmyError; +use lemmy_utils::location_info; +use lemmy_db_queries::Followable; +use lemmy_apub::fetcher::person::get_or_fetch_and_upsert_person; + +#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)] +#[serde(rename_all = "camelCase")] +pub struct Follow { + // todo: implement newtypes PersonUrl, GroupUrl etc (with deref function) + actor: Url, + to: Url, + object: Url, + #[serde(rename = "type")] + kind: FollowType, +} + +#[async_trait::async_trait(?Send)] +impl ReceiveActivity for Follow { + type Kind = FollowType; + async fn receive(&self,activity: Activity, context: &LemmyContext, request_counter: &mut i32) -> Result<(), LemmyError> { + println!("receive follow"); + todo!() + } +} + +#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)] +#[serde(rename_all = "camelCase")] +pub struct Accept { + // todo: implement newtypes PersonUrl, GroupUrl etc (with deref function) + actor: Url, + to: Url, + object: Activity, + #[serde(rename = "type")] + kind: AcceptType, +} + +/// Handle accepted follows +#[async_trait::async_trait(?Send)] +impl ReceiveActivity for Accept { + type Kind = AcceptType; + async fn receive(&self, activity: Activity, context: &LemmyContext, request_counter: &mut i32) -> Result<(), LemmyError> { + verify_domains_match(&self.actor, &activity.id_unchecked())?; + verify_domains_match(&self.object.inner.actor, &self.object.id_unchecked())?; + + let community = + get_or_fetch_and_upsert_community(&self.actor, context, request_counter).await?; + let person = get_or_fetch_and_upsert_person(&self.to, context, request_counter).await?; + // This will throw an error if no follow was requested + blocking(&context.pool(), move |conn| { + CommunityFollower::follow_accepted(conn, community.id, person.id) + }) + .await??; + + Ok(()) + } +} \ No newline at end of file diff --git a/crates/apub_receive/src/activities_new/mod.rs b/crates/apub_receive/src/activities_new/mod.rs new file mode 100644 index 00000000..42eb8930 --- /dev/null +++ b/crates/apub_receive/src/activities_new/mod.rs @@ -0,0 +1 @@ +pub mod follow; \ No newline at end of file diff --git a/crates/apub_receive/src/inbox/community_inbox.rs b/crates/apub_receive/src/inbox/community_inbox.rs index 8ab65813..dfea8605 100644 --- a/crates/apub_receive/src/inbox/community_inbox.rs +++ b/crates/apub_receive/src/inbox/community_inbox.rs @@ -5,7 +5,6 @@ use crate::{ get_activity_id, inbox_verify_http_signature, is_activity_already_known, - new_inbox_routing::receive_activity, receive_for_community::{ receive_add_for_community, receive_block_user_for_community, @@ -137,7 +136,6 @@ pub(crate) async fn community_receive_message( let any_base = activity.clone().into_any_base()?; let actor_url = actor.actor_id(); let activity_kind = activity.kind().context(location_info!())?; - receive_activity(any_base.clone(), context)?; let do_announce = match activity_kind { CommunityValidTypes::Follow => { Box::pin(handle_follow( diff --git a/crates/apub_receive/src/inbox/new_inbox_routing.rs b/crates/apub_receive/src/inbox/new_inbox_routing.rs index 99cf50fb..2907b9b7 100644 --- a/crates/apub_receive/src/inbox/new_inbox_routing.rs +++ b/crates/apub_receive/src/inbox/new_inbox_routing.rs @@ -1,9 +1,12 @@ -use activitystreams::base::AnyBase; -use anyhow::Context; -use lemmy_utils::{location_info, LemmyError}; +use activitystreams::base::{AnyBase}; +use lemmy_utils::{LemmyError}; use lemmy_websocket::LemmyContext; -use std::{collections::HashMap, str::FromStr}; -use strum_macros::EnumString; +use activitystreams::unparsed::Unparsed; +use activitystreams::primitives::{OneOrMany}; +use url::Url; +use crate::activities_new::follow::Accept; +use std::marker::PhantomData; +use activitystreams::error::DomainError; // for now, limit it to activity routing only, no http sigs, parsing or any of that // need to route in this order: @@ -12,35 +15,15 @@ use strum_macros::EnumString; // 3. inner object (recursively until object is empty or an url) // library part +// todo: move this to separate crate -/// macro shorthand to create hashmap -/// usage: `let counts = hashmap!['A' => 0, 'C' => 0, 'G' => 0, 'T' => 0];` -/// from https://stackoverflow.com/questions/28392008/more-concise-hashmap-initialization -macro_rules! hashmap { - ($( $key: expr => $val: expr ),*) => {{ - let mut map = ::std::collections::HashMap::new(); - $( map.insert($key, $val); )* - map - }} -} - -#[derive(Hash, Eq, PartialEq, EnumString)] -enum ActivityTypes { - Follow, - Announce, - Create, -} - -#[derive(Eq, PartialEq)] -enum ObjectTypes { - Page, - Note, - Url, // we dont dereference urls in object field, so we dont know what exactly it refers to - None, // object field doesnt exist -} - +// TODO: turn this into a trait in which app has to implement the following functions: +// .checkIdValid() - for unique, instance block etc +// .checkHttpSig::() +// .fetchObject() - for custom http client +// .checkActivity() - for common validity checks struct InboxConfig { - actors: Vec, + //actors: Vec, } impl InboxConfig { @@ -49,52 +32,71 @@ impl InboxConfig { } } -type AcceptedTypes = HashMap; - -// TODO: need to provide a handler function for each value -enum InnerType { - Simple(ObjectTypes), - Nested(AcceptedTypes), +pub fn verify_domains_match(a: &Url, b: &Url) -> Result<(), LemmyError> { + if a.domain() != b.domain() { + return Err(DomainError.into()); + } + Ok(()) } -struct ActorConfig { - accepted_types: AcceptedTypes, +// todo: later add a similar trait SendActivity +// todo: maybe add a separate method verify() +#[async_trait::async_trait(?Send)] +pub trait ReceiveActivity { + type Kind; + // todo: would be nice if we didnt have to pass Activity and Self separately + // todo: later handle request_counter completely inside library + async fn receive(&self, activity: Activity, context: &LemmyContext, request_counter: &mut i32) -> Result<(), LemmyError>; } -impl ActorConfig { - pub(crate) fn actor_inbox_handler( - self, - activity: AnyBase, - _context: &LemmyContext, - ) -> Result<(), LemmyError> { - // TODO: probably better to define our own struct with the fields we need + unparsed, and later - // convert to activity (needs id_unchecked(), kind, object) - let kind = ActivityTypes::from_str(activity.kind_str().context(location_info!())?)?; - use InnerType::*; - match self.accepted_types.get(&kind).context(location_info!())? { - Simple(o) => {} - Nested(a) => {} - } - // TODO: correctly route the activity to handle_follow, receive_create_comment or receive_create_post +// todo: instead of phantomdata, might use option to cache the fetched object (or just fetch on construction) +pub struct ObjectId<'a, Kind>(Url, &'a PhantomData); + +impl ObjectId<'_, Kind> { + pub fn url(self) -> Url {self.0} + pub fn dereference(self) -> Result { + // todo: fetch object from http or database todo!() } } +#[derive(Debug, Clone, serde::Deserialize, serde::Serialize)] +#[serde(rename_all = "camelCase")] +pub struct Activity { + #[serde(rename = "@context")] + context: OneOrMany, + id: Url, + + /// type-specific fields + #[serde(flatten)] + pub inner: Kind, + + // unparsed fields + // todo: can probably remove this field + #[serde(flatten)] + unparsed: Unparsed, +} + +impl Activity { + pub fn id_unchecked(&self) -> &Url { + &self.id + } +} + // application part -pub(crate) fn receive_activity( - activity: AnyBase, - context: &LemmyContext, -) -> Result<(), LemmyError> { - use ActivityTypes::*; - use InnerType::*; - use ObjectTypes::*; - - let accepted_types = hashmap![Follow => Simple(Url), - Announce => - Nested(hashmap![Create => Simple(Note), Create => Simple(Page)])]; - let community_inbox_config = ActorConfig { accepted_types }; - let inbox_config = InboxConfig { actors: vec![] }; - community_inbox_config.actor_inbox_handler(activity, context)?; - Ok(()) +#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)] +pub enum PersonAcceptedActivitiesNew { + Accept(Accept), } + +// todo: there should be a better way to do this (maybe needs a derive macro) +#[async_trait::async_trait(?Send)] +impl ReceiveActivity for PersonAcceptedActivitiesNew { + async fn receive(&self, activity: Activity, context: &LemmyContext, request_counter: &mut i32) -> Result<(), LemmyError> { + use PersonAcceptedActivitiesNew::*; + match self { + Accept(a) => a.receive(activity, context, request_counter) + }.await + } +} \ No newline at end of file diff --git a/crates/apub_receive/src/inbox/person_inbox.rs b/crates/apub_receive/src/inbox/person_inbox.rs index 02d1d8be..d61a173b 100644 --- a/crates/apub_receive/src/inbox/person_inbox.rs +++ b/crates/apub_receive/src/inbox/person_inbox.rs @@ -66,6 +66,7 @@ use serde::{Deserialize, Serialize}; use std::fmt::Debug; use strum_macros::EnumString; use url::Url; +use crate::inbox::new_inbox_routing::{PersonAcceptedActivitiesNew, ReceiveActivity, Activity}; /// Allowed activities for person inbox. #[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd, Deserialize, Serialize)] @@ -85,11 +86,15 @@ pub type PersonAcceptedActivities = ActorAndObject; /// Handler for all incoming activities to person inboxes. pub async fn person_inbox( request: HttpRequest, - input: web::Json, + input: web::Json>, path: web::Path, context: web::Data, ) -> Result { let activity = input.into_inner(); + let request_counter = &mut 0; + activity.inner.receive(&context, request_counter); + todo!() + /* // First of all check the http signature let request_counter = &mut 0; let actor = inbox_verify_http_signature(&activity, &context, request, request_counter).await?; @@ -123,6 +128,7 @@ pub async fn person_inbox( request_counter, ) .await + */ } /// Receives Accept/Follow, Announce, private messages and community (undo) remove, (undo) delete @@ -149,14 +155,6 @@ pub(crate) async fn person_receive_message( let actor_url = actor.actor_id(); match kind { PersonValidTypes::Accept => { - receive_accept( - &context, - any_base, - actor, - to_person.expect("person provided"), - request_counter, - ) - .await?; } PersonValidTypes::Announce => { Box::pin(receive_announce(&context, any_base, actor, request_counter)).await? @@ -234,41 +232,6 @@ async fn is_for_person_inbox( Err(anyhow!("Not addressed for any local person").into()) } -/// Handle accepted follows. -async fn receive_accept( - context: &LemmyContext, - activity: AnyBase, - actor: &dyn ActorType, - person: Person, - request_counter: &mut i32, -) -> Result<(), LemmyError> { - let accept = Accept::from_any_base(activity)?.context(location_info!())?; - verify_activity_domains_valid(&accept, &actor.actor_id(), false)?; - - let object = accept.object().to_owned().one().context(location_info!())?; - let follow = Follow::from_any_base(object)?.context(location_info!())?; - verify_activity_domains_valid(&follow, &person.actor_id(), false)?; - - let community_uri = accept - .actor()? - .to_owned() - .single_xsd_any_uri() - .context(location_info!())?; - - let community = - get_or_fetch_and_upsert_community(&community_uri, context, request_counter).await?; - - let community_id = community.id; - let person_id = person.id; - // This will throw an error if no follow was requested - blocking(&context.pool(), move |conn| { - CommunityFollower::follow_accepted(conn, community_id, person_id) - }) - .await??; - - Ok(()) -} - #[derive(EnumString)] enum AnnouncableActivities { Create, diff --git a/crates/apub_receive/src/lib.rs b/crates/apub_receive/src/lib.rs index 69c32b35..999c178a 100644 --- a/crates/apub_receive/src/lib.rs +++ b/crates/apub_receive/src/lib.rs @@ -2,3 +2,4 @@ mod activities; mod http; mod inbox; pub mod routes; +pub mod activities_new;