got something that almost works

This commit is contained in:
Felix Ableitner 2021-06-23 19:28:37 +02:00
parent 388b80d64e
commit a619a0d52a
6 changed files with 148 additions and 116 deletions

View File

@ -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<Self::Kind>, 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<Follow>,
#[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<Self::Kind>, 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(())
}
}

View File

@ -0,0 +1 @@
pub mod follow;

View File

@ -5,7 +5,6 @@ use crate::{
get_activity_id, get_activity_id,
inbox_verify_http_signature, inbox_verify_http_signature,
is_activity_already_known, is_activity_already_known,
new_inbox_routing::receive_activity,
receive_for_community::{ receive_for_community::{
receive_add_for_community, receive_add_for_community,
receive_block_user_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 any_base = activity.clone().into_any_base()?;
let actor_url = actor.actor_id(); let actor_url = actor.actor_id();
let activity_kind = activity.kind().context(location_info!())?; let activity_kind = activity.kind().context(location_info!())?;
receive_activity(any_base.clone(), context)?;
let do_announce = match activity_kind { let do_announce = match activity_kind {
CommunityValidTypes::Follow => { CommunityValidTypes::Follow => {
Box::pin(handle_follow( Box::pin(handle_follow(

View File

@ -1,9 +1,12 @@
use activitystreams::base::AnyBase; use activitystreams::base::{AnyBase};
use anyhow::Context; use lemmy_utils::{LemmyError};
use lemmy_utils::{location_info, LemmyError};
use lemmy_websocket::LemmyContext; use lemmy_websocket::LemmyContext;
use std::{collections::HashMap, str::FromStr}; use activitystreams::unparsed::Unparsed;
use strum_macros::EnumString; 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 // for now, limit it to activity routing only, no http sigs, parsing or any of that
// need to route in this order: // 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) // 3. inner object (recursively until object is empty or an url)
// library part // library part
// todo: move this to separate crate
/// macro shorthand to create hashmap // TODO: turn this into a trait in which app has to implement the following functions:
/// usage: `let counts = hashmap!['A' => 0, 'C' => 0, 'G' => 0, 'T' => 0];` // .checkIdValid() - for unique, instance block etc
/// from https://stackoverflow.com/questions/28392008/more-concise-hashmap-initialization // .checkHttpSig::<RequestType>()
macro_rules! hashmap { // .fetchObject() - for custom http client
($( $key: expr => $val: expr ),*) => {{ // .checkActivity() - for common validity checks
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
}
struct InboxConfig { struct InboxConfig {
actors: Vec<ActorConfig>, //actors: Vec<ActorConfig>,
} }
impl InboxConfig { impl InboxConfig {
@ -49,52 +32,71 @@ impl InboxConfig {
} }
} }
type AcceptedTypes = HashMap<ActivityTypes, InnerType>; pub fn verify_domains_match(a: &Url, b: &Url) -> Result<(), LemmyError> {
if a.domain() != b.domain() {
// TODO: need to provide a handler function for each value return Err(DomainError.into());
enum InnerType {
Simple(ObjectTypes),
Nested(AcceptedTypes),
}
struct ActorConfig {
accepted_types: AcceptedTypes,
}
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 Ok(())
}
// 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<Self::Kind>, context: &LemmyContext, request_counter: &mut i32) -> Result<(), LemmyError>;
}
// todo: instead of phantomdata, might use option<kind> to cache the fetched object (or just fetch on construction)
pub struct ObjectId<'a, Kind>(Url, &'a PhantomData<Kind>);
impl<Kind> ObjectId<'_, Kind> {
pub fn url(self) -> Url {self.0}
pub fn dereference(self) -> Result<Kind, LemmyError> {
// todo: fetch object from http or database
todo!() todo!()
} }
} }
#[derive(Debug, Clone, serde::Deserialize, serde::Serialize)]
#[serde(rename_all = "camelCase")]
pub struct Activity<Kind> {
#[serde(rename = "@context")]
context: OneOrMany<AnyBase>,
id: Url,
/// type-specific fields
#[serde(flatten)]
pub inner: Kind,
// unparsed fields
// todo: can probably remove this field
#[serde(flatten)]
unparsed: Unparsed,
}
impl<Kind> Activity<Kind> {
pub fn id_unchecked(&self) -> &Url {
&self.id
}
}
// application part // application part
pub(crate) fn receive_activity( #[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
activity: AnyBase, pub enum PersonAcceptedActivitiesNew {
context: &LemmyContext, Accept(Accept),
) -> Result<(), LemmyError> { }
use ActivityTypes::*;
use InnerType::*; // todo: there should be a better way to do this (maybe needs a derive macro)
use ObjectTypes::*; #[async_trait::async_trait(?Send)]
impl ReceiveActivity<Kind> for PersonAcceptedActivitiesNew {
let accepted_types = hashmap![Follow => Simple(Url), async fn receive(&self, activity: Activity<Kind>, context: &LemmyContext, request_counter: &mut i32) -> Result<(), LemmyError> {
Announce => use PersonAcceptedActivitiesNew::*;
Nested(hashmap![Create => Simple(Note), Create => Simple(Page)])]; match self {
let community_inbox_config = ActorConfig { accepted_types }; Accept(a) => a.receive(activity, context, request_counter)
let inbox_config = InboxConfig { actors: vec![] }; }.await
community_inbox_config.actor_inbox_handler(activity, context)?; }
Ok(())
} }

View File

@ -66,6 +66,7 @@ use serde::{Deserialize, Serialize};
use std::fmt::Debug; use std::fmt::Debug;
use strum_macros::EnumString; use strum_macros::EnumString;
use url::Url; use url::Url;
use crate::inbox::new_inbox_routing::{PersonAcceptedActivitiesNew, ReceiveActivity, Activity};
/// Allowed activities for person inbox. /// Allowed activities for person inbox.
#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd, Deserialize, Serialize)] #[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd, Deserialize, Serialize)]
@ -85,11 +86,15 @@ pub type PersonAcceptedActivities = ActorAndObject<PersonValidTypes>;
/// Handler for all incoming activities to person inboxes. /// Handler for all incoming activities to person inboxes.
pub async fn person_inbox( pub async fn person_inbox(
request: HttpRequest, request: HttpRequest,
input: web::Json<PersonAcceptedActivities>, input: web::Json<Activity<PersonAcceptedActivitiesNew>>,
path: web::Path<String>, path: web::Path<String>,
context: web::Data<LemmyContext>, context: web::Data<LemmyContext>,
) -> Result<HttpResponse, LemmyError> { ) -> Result<HttpResponse, LemmyError> {
let activity = input.into_inner(); let activity = input.into_inner();
let request_counter = &mut 0;
activity.inner.receive(&context, request_counter);
todo!()
/*
// First of all check the http signature // First of all check the http signature
let request_counter = &mut 0; let request_counter = &mut 0;
let actor = inbox_verify_http_signature(&activity, &context, request, request_counter).await?; let actor = inbox_verify_http_signature(&activity, &context, request, request_counter).await?;
@ -123,6 +128,7 @@ pub async fn person_inbox(
request_counter, request_counter,
) )
.await .await
*/
} }
/// Receives Accept/Follow, Announce, private messages and community (undo) remove, (undo) delete /// 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(); let actor_url = actor.actor_id();
match kind { match kind {
PersonValidTypes::Accept => { PersonValidTypes::Accept => {
receive_accept(
&context,
any_base,
actor,
to_person.expect("person provided"),
request_counter,
)
.await?;
} }
PersonValidTypes::Announce => { PersonValidTypes::Announce => {
Box::pin(receive_announce(&context, any_base, actor, request_counter)).await? 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()) 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)] #[derive(EnumString)]
enum AnnouncableActivities { enum AnnouncableActivities {
Create, Create,

View File

@ -2,3 +2,4 @@ mod activities;
mod http; mod http;
mod inbox; mod inbox;
pub mod routes; pub mod routes;
pub mod activities_new;