From: Nutomic Date: Thu, 19 Aug 2021 21:24:33 +0000 (+0000) Subject: Rewrite remaining activities (#1712) X-Git-Url: http://these/git/%22https:/nerdica.net/%7B%24%7B%60data:application/manifest%20json;base64%2C%24%7Bawait?a=commitdiff_plain;h=f6f169b4eb7542825cf17712b474b6a6ab71cb64;p=lemmy.git Rewrite remaining activities (#1712) * Limit type/method visibility in apub code * Simplify db_queries traits by removing generics * Simplify delete activity implementation * Rewrite delete activities * Implement helper functions for websocket message sending * When receiving delete reason as empty string, change to none * Rewrite remaining activities * Simplify inbox * Remove struct ActivityCommonFields, derive ActivityFields trait instead * Community should announce received activities to followers --- diff --git a/crates/api/src/community.rs b/crates/api/src/community.rs index 5aefd144..d68d27c2 100644 --- a/crates/api/src/community.rs +++ b/crates/api/src/community.rs @@ -8,12 +8,14 @@ use lemmy_api_common::{ get_local_user_view_from_jwt, is_mod_or_admin, }; -use lemmy_apub::{ - activities::following::{ - follow::FollowCommunity as FollowCommunityApub, - undo::UndoFollowCommunity, +use lemmy_apub::activities::{ + community::{ + add_mod::AddMod, + block_user::BlockUserFromCommunity, + remove_mod::RemoveMod, + undo_block_user::UndoBlockUserFromCommunity, }, - CommunityType, + following::{follow::FollowCommunity as FollowCommunityApub, undo::UndoFollowCommunity}, }; use lemmy_db_queries::{ source::{comment::Comment_, community::CommunityModerator_, post::Post_}, @@ -219,17 +221,20 @@ impl Perform for BanFromCommunity { .await? .ok(); - community - .send_block_user(&local_user_view.person, banned_person, context) + BlockUserFromCommunity::send(&community, &banned_person, &local_user_view.person, context) .await?; } else { let unban = move |conn: &'_ _| CommunityPersonBan::unban(conn, &community_user_ban_form); if blocking(context.pool(), unban).await?.is_err() { return Err(ApiError::err("community_user_already_banned").into()); } - community - .send_undo_block_user(&local_user_view.person, banned_person, context) - .await?; + UndoBlockUserFromCommunity::send( + &community, + &banned_person, + &local_user_view.person, + context, + ) + .await?; } // Remove/Restore their data if that's desired @@ -356,13 +361,9 @@ impl Perform for AddModToCommunity { }) .await??; if data.added { - community - .send_add_mod(&local_user_view.person, updated_mod, context) - .await?; + AddMod::send(&community, &updated_mod, &local_user_view.person, context).await?; } else { - community - .send_remove_mod(&local_user_view.person, updated_mod, context) - .await?; + RemoveMod::send(&community, &updated_mod, &local_user_view.person, context).await?; } // Note: in case a remote mod is added, this returns the old moderators list, it will only get diff --git a/crates/api_crud/src/community/update.rs b/crates/api_crud/src/community/update.rs index 2a99c76d..2196c57b 100644 --- a/crates/api_crud/src/community/update.rs +++ b/crates/api_crud/src/community/update.rs @@ -5,7 +5,7 @@ use lemmy_api_common::{ community::{CommunityResponse, EditCommunity}, get_local_user_view_from_jwt, }; -use lemmy_apub::CommunityType; +use lemmy_apub::activities::community::update::UpdateCommunity; use lemmy_db_queries::{diesel_option_overwrite_to_url, Crud}; use lemmy_db_schema::{ naive_now, @@ -69,9 +69,7 @@ impl PerformCrud for EditCommunity { .await? .map_err(|_| ApiError::err("couldnt_update_community"))?; - updated_community - .send_update(local_user_view.person.to_owned(), context) - .await?; + UpdateCommunity::send(&updated_community, &local_user_view.person, context).await?; let op = UserOperationCrud::EditCommunity; send_community_ws_message(data.community_id, op, websocket_id, None, context).await diff --git a/crates/apub/src/activities/comment/create_or_update.rs b/crates/apub/src/activities/comment/create_or_update.rs index cb9b2349..7f83d78f 100644 --- a/crates/apub/src/activities/comment/create_or_update.rs +++ b/crates/apub/src/activities/comment/create_or_update.rs @@ -13,14 +13,9 @@ use crate::{ objects::{comment::Note, FromApub, ToApub}, ActorType, }; -use activitystreams::link::Mention; +use activitystreams::{base::AnyBase, link::Mention, primitives::OneOrMany, unparsed::Unparsed}; use lemmy_api_common::blocking; -use lemmy_apub_lib::{ - values::PublicUrl, - verify_domains_match, - ActivityCommonFields, - ActivityHandler, -}; +use lemmy_apub_lib::{values::PublicUrl, verify_domains_match, ActivityFields, ActivityHandler}; use lemmy_db_queries::Crud; use lemmy_db_schema::source::{comment::Comment, community::Community, person::Person, post::Post}; use lemmy_utils::LemmyError; @@ -28,17 +23,21 @@ use lemmy_websocket::{send::send_comment_ws_message, LemmyContext, UserOperation use serde::{Deserialize, Serialize}; use url::Url; -#[derive(Clone, Debug, Deserialize, Serialize)] +#[derive(Clone, Debug, Deserialize, Serialize, ActivityFields)] #[serde(rename_all = "camelCase")] pub struct CreateOrUpdateComment { + actor: Url, to: PublicUrl, object: Note, cc: Vec, tag: Vec, #[serde(rename = "type")] kind: CreateOrUpdateType, + id: Url, + #[serde(rename = "@context")] + context: OneOrMany, #[serde(flatten)] - common: ActivityCommonFields, + unparsed: Unparsed, } impl CreateOrUpdateComment { @@ -61,17 +60,15 @@ impl CreateOrUpdateComment { let maa = collect_non_local_mentions(comment, &community, context).await?; let create_or_update = CreateOrUpdateComment { + actor: actor.actor_id(), to: PublicUrl::Public, object: comment.to_apub(context.pool()).await?, cc: maa.ccs, tag: maa.tags, kind, - common: ActivityCommonFields { - context: lemmy_context(), - id: id.clone(), - actor: actor.actor_id(), - unparsed: Default::default(), - }, + id: id.clone(), + context: lemmy_context(), + unparsed: Default::default(), }; let activity = AnnouncableActivities::CreateOrUpdateComment(create_or_update); @@ -88,15 +85,10 @@ impl ActivityHandler for CreateOrUpdateComment { ) -> Result<(), LemmyError> { let community = extract_community(&self.cc, context, request_counter).await?; - verify_activity(self.common())?; - verify_person_in_community( - &self.common.actor, - &community.actor_id(), - context, - request_counter, - ) - .await?; - verify_domains_match(&self.common.actor, self.object.id_unchecked())?; + verify_activity(self)?; + verify_person_in_community(&self.actor, &community.actor_id(), context, request_counter) + .await?; + verify_domains_match(&self.actor, self.object.id_unchecked())?; // TODO: should add a check that the correct community is in cc (probably needs changes to // comment deserialization) self.object.verify(context, request_counter).await?; @@ -108,10 +100,8 @@ impl ActivityHandler for CreateOrUpdateComment { context: &LemmyContext, request_counter: &mut i32, ) -> Result<(), LemmyError> { - let comment = - Comment::from_apub(&self.object, context, &self.common.actor, request_counter).await?; - let recipients = - get_notif_recipients(&self.common.actor, &comment, context, request_counter).await?; + let comment = Comment::from_apub(&self.object, context, &self.actor, request_counter).await?; + let recipients = get_notif_recipients(&self.actor, &comment, context, request_counter).await?; let notif_type = match self.kind { CreateOrUpdateType::Create => UserOperationCrud::CreateComment, CreateOrUpdateType::Update => UserOperationCrud::EditComment, @@ -122,8 +112,4 @@ impl ActivityHandler for CreateOrUpdateComment { .await?; Ok(()) } - - fn common(&self) -> &ActivityCommonFields { - &self.common - } } diff --git a/crates/apub/src/activities/community/add_mod.rs b/crates/apub/src/activities/community/add_mod.rs index db6d369a..f83f1463 100644 --- a/crates/apub/src/activities/community/add_mod.rs +++ b/crates/apub/src/activities/community/add_mod.rs @@ -1,33 +1,77 @@ use crate::{ activities::{ + community::announce::AnnouncableActivities, + generate_activity_id, verify_activity, verify_add_remove_moderator_target, verify_mod_action, verify_person_in_community, }, + activity_queue::send_to_community_new, + extensions::context::lemmy_context, fetcher::{community::get_or_fetch_and_upsert_community, person::get_or_fetch_and_upsert_person}, - CommunityType, + generate_moderators_url, + ActorType, +}; +use activitystreams::{ + activity::kind::AddType, + base::AnyBase, + primitives::OneOrMany, + unparsed::Unparsed, }; -use activitystreams::{activity::kind::AddType, base::AnyBase}; use lemmy_api_common::blocking; -use lemmy_apub_lib::{values::PublicUrl, ActivityCommonFields, ActivityHandler}; +use lemmy_apub_lib::{values::PublicUrl, ActivityFields, ActivityHandler}; use lemmy_db_queries::{source::community::CommunityModerator_, Joinable}; -use lemmy_db_schema::source::community::{CommunityModerator, CommunityModeratorForm}; +use lemmy_db_schema::source::{ + community::{Community, CommunityModerator, CommunityModeratorForm}, + person::Person, +}; use lemmy_utils::LemmyError; use lemmy_websocket::LemmyContext; +use serde::{Deserialize, Serialize}; use url::Url; -#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)] +#[derive(Clone, Debug, Deserialize, Serialize, ActivityFields)] #[serde(rename_all = "camelCase")] pub struct AddMod { + actor: Url, to: PublicUrl, object: Url, target: Url, cc: [Url; 1], #[serde(rename = "type")] kind: AddType, + id: Url, + #[serde(rename = "@context")] + context: OneOrMany, #[serde(flatten)] - common: ActivityCommonFields, + unparsed: Unparsed, +} + +impl AddMod { + pub async fn send( + community: &Community, + added_mod: &Person, + actor: &Person, + context: &LemmyContext, + ) -> Result<(), LemmyError> { + let id = generate_activity_id(AddType::Add)?; + let add = AddMod { + actor: actor.actor_id(), + to: PublicUrl::Public, + object: added_mod.actor_id(), + target: generate_moderators_url(&community.actor_id)?.into(), + cc: [community.actor_id()], + kind: AddType::Add, + id: id.clone(), + context: lemmy_context(), + unparsed: Default::default(), + }; + + let activity = AnnouncableActivities::AddMod(add); + let inboxes = vec![added_mod.get_shared_inbox_or_inbox_url()]; + send_to_community_new(activity, &id, actor, community, inboxes, context).await + } } #[async_trait::async_trait(?Send)] @@ -37,9 +81,9 @@ impl ActivityHandler for AddMod { context: &LemmyContext, request_counter: &mut i32, ) -> Result<(), LemmyError> { - verify_activity(self.common())?; - verify_person_in_community(&self.common.actor, &self.cc[0], context, request_counter).await?; - verify_mod_action(&self.common.actor, self.cc[0].clone(), context).await?; + verify_activity(self)?; + verify_person_in_community(&self.actor, &self.cc[0], context, request_counter).await?; + verify_mod_action(&self.actor, self.cc[0].clone(), context).await?; verify_add_remove_moderator_target(&self.target, self.cc[0].clone())?; Ok(()) } @@ -70,17 +114,7 @@ impl ActivityHandler for AddMod { }) .await??; } - if community.local { - let anybase = AnyBase::from_arbitrary_json(serde_json::to_string(&self)?)?; - community - .send_announce(anybase, Some(self.object.clone()), context) - .await?; - } // TODO: send websocket notification about added mod Ok(()) } - - fn common(&self) -> &ActivityCommonFields { - &self.common - } } diff --git a/crates/apub/src/activities/community/announce.rs b/crates/apub/src/activities/community/announce.rs index 734fed00..e7d917b7 100644 --- a/crates/apub/src/activities/community/announce.rs +++ b/crates/apub/src/activities/community/announce.rs @@ -5,12 +5,14 @@ use crate::{ add_mod::AddMod, block_user::BlockUserFromCommunity, list_community_follower_inboxes, + remove_mod::RemoveMod, undo_block_user::UndoBlockUserFromCommunity, + update::UpdateCommunity, }, deletion::{delete::Delete, undo_delete::UndoDelete}, generate_activity_id, post::create_or_update::CreateOrUpdatePost, - removal::{remove::RemoveMod, undo_remove::UndoRemovePostCommentOrCommunity}, + undo_remove::UndoRemovePostCommentOrCommunity, verify_activity, verify_community, voting::{undo_vote::UndoVote, vote::Vote}, @@ -22,15 +24,20 @@ use crate::{ ActorType, CommunityType, }; -use activitystreams::activity::kind::AnnounceType; -use lemmy_apub_lib::{values::PublicUrl, ActivityCommonFields, ActivityHandler}; +use activitystreams::{ + activity::kind::AnnounceType, + base::AnyBase, + primitives::OneOrMany, + unparsed::Unparsed, +}; +use lemmy_apub_lib::{values::PublicUrl, ActivityFields, ActivityHandler}; use lemmy_db_schema::source::community::Community; use lemmy_utils::LemmyError; use lemmy_websocket::LemmyContext; use serde::{Deserialize, Serialize}; use url::Url; -#[derive(Clone, Debug, Deserialize, Serialize, ActivityHandler)] +#[derive(Clone, Debug, Deserialize, Serialize, ActivityHandler, ActivityFields)] #[serde(untagged)] pub enum AnnouncableActivities { CreateOrUpdateComment(CreateOrUpdateComment), @@ -40,22 +47,27 @@ pub enum AnnouncableActivities { Delete(Delete), UndoDelete(UndoDelete), UndoRemovePostCommentOrCommunity(UndoRemovePostCommentOrCommunity), + UpdateCommunity(Box), BlockUserFromCommunity(BlockUserFromCommunity), UndoBlockUserFromCommunity(UndoBlockUserFromCommunity), AddMod(AddMod), RemoveMod(RemoveMod), } -#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)] +#[derive(Clone, Debug, Deserialize, Serialize, ActivityFields)] #[serde(rename_all = "camelCase")] pub struct AnnounceActivity { + actor: Url, to: PublicUrl, object: AnnouncableActivities, cc: Vec, #[serde(rename = "type")] kind: AnnounceType, + id: Url, + #[serde(rename = "@context")] + context: OneOrMany, #[serde(flatten)] - common: ActivityCommonFields, + unparsed: Unparsed, } impl AnnounceActivity { @@ -66,27 +78,17 @@ impl AnnounceActivity { context: &LemmyContext, ) -> Result<(), LemmyError> { let announce = AnnounceActivity { + actor: community.actor_id(), to: PublicUrl::Public, object, cc: vec![community.followers_url()], kind: AnnounceType::Announce, - common: ActivityCommonFields { - context: lemmy_context(), - id: generate_activity_id(&AnnounceType::Announce)?, - actor: community.actor_id(), - unparsed: Default::default(), - }, + id: generate_activity_id(&AnnounceType::Announce)?, + context: lemmy_context(), + unparsed: Default::default(), }; let inboxes = list_community_follower_inboxes(community, additional_inboxes, context).await?; - send_activity_new( - context, - &announce, - &announce.common.id, - community, - inboxes, - false, - ) - .await + send_activity_new(context, &announce, &announce.id, community, inboxes, false).await } } @@ -97,8 +99,8 @@ impl ActivityHandler for AnnounceActivity { context: &LemmyContext, request_counter: &mut i32, ) -> Result<(), LemmyError> { - verify_activity(self.common())?; - verify_community(&self.common.actor, context, request_counter).await?; + verify_activity(self)?; + verify_community(&self.actor, context, request_counter).await?; self.object.verify(context, request_counter).await?; Ok(()) } @@ -108,11 +110,11 @@ impl ActivityHandler for AnnounceActivity { context: &LemmyContext, request_counter: &mut i32, ) -> Result<(), LemmyError> { - if is_activity_already_known(context.pool(), self.object.common().id_unchecked()).await? { + if is_activity_already_known(context.pool(), self.object.id_unchecked()).await? { return Ok(()); } insert_activity( - self.object.common().id_unchecked(), + self.object.id_unchecked(), self.object.clone(), false, true, @@ -121,8 +123,4 @@ impl ActivityHandler for AnnounceActivity { .await?; self.object.receive(context, request_counter).await } - - fn common(&self) -> &ActivityCommonFields { - &self.common - } } diff --git a/crates/apub/src/activities/community/block_user.rs b/crates/apub/src/activities/community/block_user.rs index bf362c48..d31077df 100644 --- a/crates/apub/src/activities/community/block_user.rs +++ b/crates/apub/src/activities/community/block_user.rs @@ -1,31 +1,87 @@ use crate::{ - activities::{verify_activity, verify_mod_action, verify_person_in_community}, + activities::{ + community::announce::AnnouncableActivities, + generate_activity_id, + verify_activity, + verify_mod_action, + verify_person_in_community, + }, + activity_queue::send_to_community_new, + extensions::context::lemmy_context, fetcher::{community::get_or_fetch_and_upsert_community, person::get_or_fetch_and_upsert_person}, + ActorType, +}; +use activitystreams::{ + activity::kind::BlockType, + base::AnyBase, + primitives::OneOrMany, + unparsed::Unparsed, }; -use activitystreams::activity::kind::BlockType; use lemmy_api_common::blocking; -use lemmy_apub_lib::{values::PublicUrl, ActivityCommonFields, ActivityHandler}; +use lemmy_apub_lib::{values::PublicUrl, ActivityFields, ActivityHandler}; use lemmy_db_queries::{Bannable, Followable}; -use lemmy_db_schema::source::community::{ - CommunityFollower, - CommunityFollowerForm, - CommunityPersonBan, - CommunityPersonBanForm, +use lemmy_db_schema::source::{ + community::{ + Community, + CommunityFollower, + CommunityFollowerForm, + CommunityPersonBan, + CommunityPersonBanForm, + }, + person::Person, }; use lemmy_utils::LemmyError; use lemmy_websocket::LemmyContext; +use serde::{Deserialize, Serialize}; use url::Url; -#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)] +#[derive(Clone, Debug, Deserialize, Serialize, ActivityFields)] #[serde(rename_all = "camelCase")] pub struct BlockUserFromCommunity { + actor: Url, to: PublicUrl, pub(in crate::activities::community) object: Url, cc: [Url; 1], #[serde(rename = "type")] kind: BlockType, + id: Url, + #[serde(rename = "@context")] + context: OneOrMany, #[serde(flatten)] - common: ActivityCommonFields, + unparsed: Unparsed, +} + +impl BlockUserFromCommunity { + pub(in crate::activities::community) fn new( + community: &Community, + target: &Person, + actor: &Person, + ) -> Result { + Ok(BlockUserFromCommunity { + actor: actor.actor_id(), + to: PublicUrl::Public, + object: target.actor_id(), + cc: [community.actor_id()], + kind: BlockType::Block, + id: generate_activity_id(BlockType::Block)?, + context: lemmy_context(), + unparsed: Default::default(), + }) + } + + pub async fn send( + community: &Community, + target: &Person, + actor: &Person, + context: &LemmyContext, + ) -> Result<(), LemmyError> { + let block = BlockUserFromCommunity::new(community, target, actor)?; + let block_id = block.id.clone(); + + let activity = AnnouncableActivities::BlockUserFromCommunity(block); + let inboxes = vec![target.get_shared_inbox_or_inbox_url()]; + send_to_community_new(activity, &block_id, actor, community, inboxes, context).await + } } #[async_trait::async_trait(?Send)] @@ -35,9 +91,9 @@ impl ActivityHandler for BlockUserFromCommunity { context: &LemmyContext, request_counter: &mut i32, ) -> Result<(), LemmyError> { - verify_activity(self.common())?; - verify_person_in_community(&self.common.actor, &self.cc[0], context, request_counter).await?; - verify_mod_action(&self.common.actor, self.cc[0].clone(), context).await?; + verify_activity(self)?; + verify_person_in_community(&self.actor, &self.cc[0], context, request_counter).await?; + verify_mod_action(&self.actor, self.cc[0].clone(), context).await?; Ok(()) } @@ -75,8 +131,4 @@ impl ActivityHandler for BlockUserFromCommunity { Ok(()) } - - fn common(&self) -> &ActivityCommonFields { - &self.common - } } diff --git a/crates/apub/src/activities/community/mod.rs b/crates/apub/src/activities/community/mod.rs index 49cf9615..ba80ff4a 100644 --- a/crates/apub/src/activities/community/mod.rs +++ b/crates/apub/src/activities/community/mod.rs @@ -8,6 +8,7 @@ use url::Url; pub mod add_mod; pub mod announce; pub mod block_user; +pub mod remove_mod; pub mod undo_block_user; pub mod update; diff --git a/crates/apub/src/activities/community/remove_mod.rs b/crates/apub/src/activities/community/remove_mod.rs new file mode 100644 index 00000000..2d4eba56 --- /dev/null +++ b/crates/apub/src/activities/community/remove_mod.rs @@ -0,0 +1,130 @@ +use crate::{ + activities::{ + community::announce::AnnouncableActivities, + deletion::{delete::receive_remove_action, verify_delete_activity}, + generate_activity_id, + verify_activity, + verify_add_remove_moderator_target, + verify_mod_action, + verify_person_in_community, + }, + activity_queue::send_to_community_new, + extensions::context::lemmy_context, + fetcher::{community::get_or_fetch_and_upsert_community, person::get_or_fetch_and_upsert_person}, + generate_moderators_url, + ActorType, +}; +use activitystreams::{ + activity::kind::RemoveType, + base::AnyBase, + primitives::OneOrMany, + unparsed::Unparsed, +}; +use lemmy_api_common::blocking; +use lemmy_apub_lib::{values::PublicUrl, ActivityFields, ActivityHandler}; +use lemmy_db_queries::Joinable; +use lemmy_db_schema::source::{ + community::{Community, CommunityModerator, CommunityModeratorForm}, + person::Person, +}; +use lemmy_utils::LemmyError; +use lemmy_websocket::LemmyContext; +use serde::{Deserialize, Serialize}; +use url::Url; + +#[derive(Clone, Debug, Deserialize, Serialize, ActivityFields)] +#[serde(rename_all = "camelCase")] +pub struct RemoveMod { + actor: Url, + to: PublicUrl, + pub(in crate::activities) object: Url, + cc: [Url; 1], + #[serde(rename = "type")] + kind: RemoveType, + // if target is set, this is means remove mod from community + pub(in crate::activities) target: Option, + id: Url, + #[serde(rename = "@context")] + context: OneOrMany, + #[serde(flatten)] + unparsed: Unparsed, +} + +impl RemoveMod { + pub async fn send( + community: &Community, + removed_mod: &Person, + actor: &Person, + context: &LemmyContext, + ) -> Result<(), LemmyError> { + let id = generate_activity_id(RemoveType::Remove)?; + let remove = RemoveMod { + actor: actor.actor_id(), + to: PublicUrl::Public, + object: removed_mod.actor_id(), + target: Some(generate_moderators_url(&community.actor_id)?.into()), + id: id.clone(), + context: lemmy_context(), + cc: [community.actor_id()], + kind: RemoveType::Remove, + unparsed: Default::default(), + }; + + let activity = AnnouncableActivities::RemoveMod(remove); + let inboxes = vec![removed_mod.get_shared_inbox_or_inbox_url()]; + send_to_community_new(activity, &id, actor, community, inboxes, context).await + } +} + +#[async_trait::async_trait(?Send)] +impl ActivityHandler for RemoveMod { + async fn verify( + &self, + context: &LemmyContext, + request_counter: &mut i32, + ) -> Result<(), LemmyError> { + verify_activity(self)?; + if let Some(target) = &self.target { + verify_person_in_community(&self.actor, &self.cc[0], context, request_counter).await?; + verify_mod_action(&self.actor, self.cc[0].clone(), context).await?; + verify_add_remove_moderator_target(target, self.cc[0].clone())?; + } else { + verify_delete_activity( + &self.object, + self, + &self.cc[0], + true, + context, + request_counter, + ) + .await?; + } + Ok(()) + } + + async fn receive( + self, + context: &LemmyContext, + request_counter: &mut i32, + ) -> Result<(), LemmyError> { + if self.target.is_some() { + let community = + get_or_fetch_and_upsert_community(&self.cc[0], context, request_counter).await?; + let remove_mod = + get_or_fetch_and_upsert_person(&self.object, context, request_counter).await?; + + let form = CommunityModeratorForm { + community_id: community.id, + person_id: remove_mod.id, + }; + blocking(context.pool(), move |conn| { + CommunityModerator::leave(conn, &form) + }) + .await??; + // TODO: send websocket notification about removed mod + Ok(()) + } else { + receive_remove_action(&self.actor, &self.object, None, context, request_counter).await + } + } +} diff --git a/crates/apub/src/activities/community/undo_block_user.rs b/crates/apub/src/activities/community/undo_block_user.rs index 514c90cc..0a9665af 100644 --- a/crates/apub/src/activities/community/undo_block_user.rs +++ b/crates/apub/src/activities/community/undo_block_user.rs @@ -1,31 +1,75 @@ use crate::{ activities::{ - community::block_user::BlockUserFromCommunity, + community::{announce::AnnouncableActivities, block_user::BlockUserFromCommunity}, + generate_activity_id, verify_activity, verify_mod_action, verify_person_in_community, }, + activity_queue::send_to_community_new, + extensions::context::lemmy_context, fetcher::{community::get_or_fetch_and_upsert_community, person::get_or_fetch_and_upsert_person}, + ActorType, +}; +use activitystreams::{ + activity::kind::UndoType, + base::AnyBase, + primitives::OneOrMany, + unparsed::Unparsed, }; -use activitystreams::activity::kind::UndoType; use lemmy_api_common::blocking; -use lemmy_apub_lib::{values::PublicUrl, ActivityCommonFields, ActivityHandler}; +use lemmy_apub_lib::{values::PublicUrl, ActivityFields, ActivityHandler}; use lemmy_db_queries::Bannable; -use lemmy_db_schema::source::community::{CommunityPersonBan, CommunityPersonBanForm}; +use lemmy_db_schema::source::{ + community::{Community, CommunityPersonBan, CommunityPersonBanForm}, + person::Person, +}; use lemmy_utils::LemmyError; use lemmy_websocket::LemmyContext; +use serde::{Deserialize, Serialize}; use url::Url; -#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)] +#[derive(Clone, Debug, Deserialize, Serialize, ActivityFields)] #[serde(rename_all = "camelCase")] pub struct UndoBlockUserFromCommunity { + actor: Url, to: PublicUrl, object: BlockUserFromCommunity, cc: [Url; 1], #[serde(rename = "type")] kind: UndoType, + id: Url, + #[serde(rename = "@context")] + context: OneOrMany, #[serde(flatten)] - common: ActivityCommonFields, + unparsed: Unparsed, +} + +impl UndoBlockUserFromCommunity { + pub async fn send( + community: &Community, + target: &Person, + actor: &Person, + context: &LemmyContext, + ) -> Result<(), LemmyError> { + let block = BlockUserFromCommunity::new(community, target, actor)?; + + let id = generate_activity_id(UndoType::Undo)?; + let undo = UndoBlockUserFromCommunity { + actor: actor.actor_id(), + to: PublicUrl::Public, + object: block, + cc: [community.actor_id()], + kind: UndoType::Undo, + id: id.clone(), + context: lemmy_context(), + unparsed: Default::default(), + }; + + let activity = AnnouncableActivities::UndoBlockUserFromCommunity(undo); + let inboxes = vec![target.get_shared_inbox_or_inbox_url()]; + send_to_community_new(activity, &id, actor, community, inboxes, context).await + } } #[async_trait::async_trait(?Send)] @@ -35,9 +79,9 @@ impl ActivityHandler for UndoBlockUserFromCommunity { context: &LemmyContext, request_counter: &mut i32, ) -> Result<(), LemmyError> { - verify_activity(self.common())?; - verify_person_in_community(&self.common.actor, &self.cc[0], context, request_counter).await?; - verify_mod_action(&self.common.actor, self.cc[0].clone(), context).await?; + verify_activity(self)?; + verify_person_in_community(&self.actor, &self.cc[0], context, request_counter).await?; + verify_mod_action(&self.actor, self.cc[0].clone(), context).await?; self.object.verify(context, request_counter).await?; Ok(()) } @@ -64,8 +108,4 @@ impl ActivityHandler for UndoBlockUserFromCommunity { Ok(()) } - - fn common(&self) -> &ActivityCommonFields { - &self.common - } } diff --git a/crates/apub/src/activities/community/update.rs b/crates/apub/src/activities/community/update.rs index d38d722b..7539464d 100644 --- a/crates/apub/src/activities/community/update.rs +++ b/crates/apub/src/activities/community/update.rs @@ -1,28 +1,74 @@ use crate::{ - activities::{verify_activity, verify_mod_action, verify_person_in_community}, - objects::community::Group, + activities::{ + community::announce::AnnouncableActivities, + generate_activity_id, + verify_activity, + verify_mod_action, + verify_person_in_community, + }, + activity_queue::send_to_community_new, + extensions::context::lemmy_context, + objects::{community::Group, ToApub}, + ActorType, +}; +use activitystreams::{ + activity::kind::UpdateType, + base::AnyBase, + primitives::OneOrMany, + unparsed::Unparsed, }; -use activitystreams::activity::kind::UpdateType; use lemmy_api_common::blocking; -use lemmy_apub_lib::{values::PublicUrl, ActivityCommonFields, ActivityHandler}; +use lemmy_apub_lib::{values::PublicUrl, ActivityFields, ActivityHandler}; use lemmy_db_queries::{ApubObject, Crud}; -use lemmy_db_schema::source::community::{Community, CommunityForm}; +use lemmy_db_schema::source::{ + community::{Community, CommunityForm}, + person::Person, +}; use lemmy_utils::LemmyError; use lemmy_websocket::{send::send_community_ws_message, LemmyContext, UserOperationCrud}; +use serde::{Deserialize, Serialize}; use url::Url; /// This activity is received from a remote community mod, and updates the description or other /// fields of a local community. -#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)] +#[derive(Clone, Debug, Deserialize, Serialize, ActivityFields)] #[serde(rename_all = "camelCase")] pub struct UpdateCommunity { + actor: Url, to: PublicUrl, + // TODO: would be nice to use a separate struct here, which only contains the fields updated here object: Group, cc: [Url; 1], #[serde(rename = "type")] kind: UpdateType, + id: Url, + #[serde(rename = "@context")] + context: OneOrMany, #[serde(flatten)] - common: ActivityCommonFields, + unparsed: Unparsed, +} + +impl UpdateCommunity { + pub async fn send( + community: &Community, + actor: &Person, + context: &LemmyContext, + ) -> Result<(), LemmyError> { + let id = generate_activity_id(UpdateType::Update)?; + let update = UpdateCommunity { + actor: actor.actor_id(), + to: PublicUrl::Public, + object: community.to_apub(context.pool()).await?, + cc: [community.actor_id()], + kind: UpdateType::Update, + id: id.clone(), + context: lemmy_context(), + unparsed: Default::default(), + }; + + let activity = AnnouncableActivities::UpdateCommunity(Box::new(update)); + send_to_community_new(activity, &id, actor, community, vec![], context).await + } } #[async_trait::async_trait(?Send)] @@ -32,9 +78,9 @@ impl ActivityHandler for UpdateCommunity { context: &LemmyContext, request_counter: &mut i32, ) -> Result<(), LemmyError> { - verify_activity(self.common())?; - verify_person_in_community(&self.common.actor, &self.cc[0], context, request_counter).await?; - verify_mod_action(&self.common.actor, self.cc[0].clone(), context).await?; + verify_activity(self)?; + verify_person_in_community(&self.actor, &self.cc[0], context, request_counter).await?; + verify_mod_action(&self.actor, self.cc[0].clone(), context).await?; Ok(()) } @@ -76,8 +122,4 @@ impl ActivityHandler for UpdateCommunity { .await?; Ok(()) } - - fn common(&self) -> &ActivityCommonFields { - &self.common - } } diff --git a/crates/apub/src/activities/deletion/delete.rs b/crates/apub/src/activities/deletion/delete.rs index 093919b8..45567288 100644 --- a/crates/apub/src/activities/deletion/delete.rs +++ b/crates/apub/src/activities/deletion/delete.rs @@ -15,10 +15,15 @@ use crate::{ fetcher::person::get_or_fetch_and_upsert_person, ActorType, }; -use activitystreams::activity::kind::DeleteType; +use activitystreams::{ + activity::kind::DeleteType, + base::AnyBase, + primitives::OneOrMany, + unparsed::Unparsed, +}; use anyhow::anyhow; use lemmy_api_common::blocking; -use lemmy_apub_lib::{values::PublicUrl, ActivityCommonFields, ActivityHandler}; +use lemmy_apub_lib::{values::PublicUrl, ActivityFields, ActivityHandler}; use lemmy_db_queries::{ source::{comment::Comment_, community::Community_, post::Post_}, Crud, @@ -43,6 +48,7 @@ use lemmy_websocket::{ LemmyContext, UserOperationCrud, }; +use serde::{Deserialize, Serialize}; use url::Url; /// This is very confusing, because there are four distinct cases to handle: @@ -53,19 +59,23 @@ use url::Url; /// /// TODO: we should probably change how community deletions work to simplify this. Probably by /// wrapping it in an announce just like other activities, instead of having the community send it. -#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)] +#[derive(Clone, Debug, Deserialize, Serialize, ActivityFields)] #[serde(rename_all = "camelCase")] pub struct Delete { - pub(in crate::activities::deletion) to: PublicUrl, + actor: Url, + to: PublicUrl, pub(in crate::activities::deletion) object: Url, pub(in crate::activities::deletion) cc: [Url; 1], #[serde(rename = "type")] - pub(in crate::activities::deletion) kind: DeleteType, + kind: DeleteType, /// If summary is present, this is a mod action (Remove in Lemmy terms). Otherwise, its a user /// deleting their own content. pub(in crate::activities::deletion) summary: Option, + id: Url, + #[serde(rename = "@context")] + context: OneOrMany, #[serde(flatten)] - pub(in crate::activities::deletion) common: ActivityCommonFields, + unparsed: Unparsed, } #[async_trait::async_trait(?Send)] @@ -75,11 +85,11 @@ impl ActivityHandler for Delete { context: &LemmyContext, request_counter: &mut i32, ) -> Result<(), LemmyError> { - verify_activity(self.common())?; + verify_activity(self)?; verify_delete_activity( &self.object, + self, &self.cc[0], - &self.common, self.summary.is_some(), context, request_counter, @@ -101,18 +111,11 @@ impl ActivityHandler for Delete { } else { Some(reason) }; - receive_remove_action( - &self.common.actor, - &self.object, - reason, - context, - request_counter, - ) - .await + receive_remove_action(&self.actor, &self.object, reason, context, request_counter).await } else { receive_delete_action( &self.object, - &self.common.actor, + &self.actor, WebsocketMessages { community: UserOperationCrud::DeleteCommunity, post: UserOperationCrud::DeletePost, @@ -125,37 +128,39 @@ impl ActivityHandler for Delete { .await } } - - fn common(&self) -> &ActivityCommonFields { - &self.common - } } impl Delete { - pub(in crate::activities::deletion) async fn send( + pub(in crate::activities::deletion) fn new( actor: &Person, community: &Community, object_id: Url, summary: Option, - context: &LemmyContext, - ) -> Result<(), LemmyError> { - let id = generate_activity_id(DeleteType::Delete)?; - let delete = Delete { + ) -> Result { + Ok(Delete { + actor: actor.actor_id(), to: PublicUrl::Public, object: object_id, cc: [community.actor_id()], kind: DeleteType::Delete, summary, - common: ActivityCommonFields { - context: lemmy_context(), - id: id.clone(), - actor: actor.actor_id(), - unparsed: Default::default(), - }, - }; + id: generate_activity_id(DeleteType::Delete)?, + context: lemmy_context(), + unparsed: Default::default(), + }) + } + pub(in crate::activities::deletion) async fn send( + actor: &Person, + community: &Community, + object_id: Url, + summary: Option, + context: &LemmyContext, + ) -> Result<(), LemmyError> { + let delete = Delete::new(actor, community, object_id, summary)?; + let delete_id = delete.id.clone(); let activity = AnnouncableActivities::Delete(delete); - send_to_community_new(activity, &id, actor, community, vec![], context).await + send_to_community_new(activity, &delete_id, actor, community, vec![], context).await } } diff --git a/crates/apub/src/activities/deletion/mod.rs b/crates/apub/src/activities/deletion/mod.rs index cf73be09..350773f4 100644 --- a/crates/apub/src/activities/deletion/mod.rs +++ b/crates/apub/src/activities/deletion/mod.rs @@ -8,7 +8,7 @@ use crate::{ ActorType, }; use lemmy_api_common::blocking; -use lemmy_apub_lib::{verify_domains_match, ActivityCommonFields}; +use lemmy_apub_lib::{verify_domains_match, ActivityFields}; use lemmy_db_queries::{ source::{comment::Comment_, community::Community_, post::Post_}, ApubObject, @@ -98,8 +98,8 @@ impl DeletableObjects { pub(in crate::activities) async fn verify_delete_activity( object: &Url, - cc: &Url, - common: &ActivityCommonFields, + activity: &dyn ActivityFields, + community_id: &Url, is_mod_action: bool, context: &LemmyContext, request_counter: &mut i32, @@ -110,16 +110,17 @@ pub(in crate::activities) async fn verify_delete_activity( if c.local { // can only do this check for local community, in remote case it would try to fetch the // deleted community (which fails) - verify_person_in_community(&common.actor, cc, context, request_counter).await?; + verify_person_in_community(activity.actor(), community_id, context, request_counter) + .await?; } // community deletion is always a mod (or admin) action - verify_mod_action(&common.actor, c.actor_id(), context).await?; + verify_mod_action(activity.actor(), c.actor_id(), context).await?; } DeletableObjects::Post(p) => { verify_delete_activity_post_or_comment( - cc, - common, + activity, &p.ap_id.into(), + community_id, is_mod_action, context, request_counter, @@ -128,9 +129,9 @@ pub(in crate::activities) async fn verify_delete_activity( } DeletableObjects::Comment(c) => { verify_delete_activity_post_or_comment( - cc, - common, + activity, &c.ap_id.into(), + community_id, is_mod_action, context, request_counter, @@ -142,19 +143,19 @@ pub(in crate::activities) async fn verify_delete_activity( } async fn verify_delete_activity_post_or_comment( - cc: &Url, - common: &ActivityCommonFields, + activity: &dyn ActivityFields, object_id: &Url, + community_id: &Url, is_mod_action: bool, context: &LemmyContext, request_counter: &mut i32, ) -> Result<(), LemmyError> { - verify_person_in_community(&common.actor, cc, context, request_counter).await?; + verify_person_in_community(activity.actor(), community_id, context, request_counter).await?; if is_mod_action { - verify_mod_action(&common.actor, cc.clone(), context).await?; + verify_mod_action(activity.actor(), community_id.clone(), context).await?; } else { // domain of post ap_id and post.creator ap_id are identical, so we just check the former - verify_domains_match(&common.actor, object_id)?; + verify_domains_match(activity.actor(), object_id)?; } Ok(()) } diff --git a/crates/apub/src/activities/deletion/undo_delete.rs b/crates/apub/src/activities/deletion/undo_delete.rs index 0114acac..35369d44 100644 --- a/crates/apub/src/activities/deletion/undo_delete.rs +++ b/crates/apub/src/activities/deletion/undo_delete.rs @@ -15,10 +15,15 @@ use crate::{ extensions::context::lemmy_context, ActorType, }; -use activitystreams::activity::kind::{DeleteType, UndoType}; +use activitystreams::{ + activity::kind::UndoType, + base::AnyBase, + primitives::OneOrMany, + unparsed::Unparsed, +}; use anyhow::anyhow; use lemmy_api_common::blocking; -use lemmy_apub_lib::{values::PublicUrl, ActivityCommonFields, ActivityHandler}; +use lemmy_apub_lib::{values::PublicUrl, ActivityFields, ActivityHandler}; use lemmy_db_queries::source::{comment::Comment_, community::Community_, post::Post_}; use lemmy_db_schema::source::{comment::Comment, community::Community, person::Person, post::Post}; use lemmy_utils::LemmyError; @@ -27,18 +32,23 @@ use lemmy_websocket::{ LemmyContext, UserOperationCrud, }; +use serde::{Deserialize, Serialize}; use url::Url; -#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)] +#[derive(Clone, Debug, Deserialize, Serialize, ActivityFields)] #[serde(rename_all = "camelCase")] pub struct UndoDelete { + actor: Url, to: PublicUrl, object: Delete, cc: [Url; 1], #[serde(rename = "type")] kind: UndoType, + id: Url, + #[serde(rename = "@context")] + context: OneOrMany, #[serde(flatten)] - common: ActivityCommonFields, + unparsed: Unparsed, } #[async_trait::async_trait(?Send)] @@ -48,12 +58,12 @@ impl ActivityHandler for UndoDelete { context: &LemmyContext, request_counter: &mut i32, ) -> Result<(), LemmyError> { - verify_activity(self.common())?; + verify_activity(self)?; self.object.verify(context, request_counter).await?; verify_delete_activity( &self.object.object, + self, &self.cc[0], - &self.common, self.object.summary.is_some(), context, request_counter, @@ -72,7 +82,7 @@ impl ActivityHandler for UndoDelete { } else { receive_delete_action( &self.object.object, - &self.common.actor, + &self.actor, WebsocketMessages { community: UserOperationCrud::EditCommunity, post: UserOperationCrud::EditPost, @@ -85,10 +95,6 @@ impl ActivityHandler for UndoDelete { .await } } - - fn common(&self) -> &ActivityCommonFields { - &self.common - } } impl UndoDelete { @@ -99,32 +105,18 @@ impl UndoDelete { summary: Option, context: &LemmyContext, ) -> Result<(), LemmyError> { - let delete = Delete { - to: PublicUrl::Public, - object: object_id, - cc: [community.actor_id()], - kind: DeleteType::Delete, - summary, - common: ActivityCommonFields { - context: lemmy_context(), - id: generate_activity_id(DeleteType::Delete)?, - actor: actor.actor_id(), - unparsed: Default::default(), - }, - }; + let object = Delete::new(actor, community, object_id, summary)?; let id = generate_activity_id(UndoType::Undo)?; let undo = UndoDelete { + actor: actor.actor_id(), to: PublicUrl::Public, - object: delete, + object, cc: [community.actor_id()], kind: UndoType::Undo, - common: ActivityCommonFields { - context: lemmy_context(), - id: id.clone(), - actor: actor.actor_id(), - unparsed: Default::default(), - }, + id: id.clone(), + context: lemmy_context(), + unparsed: Default::default(), }; let activity = AnnouncableActivities::UndoDelete(undo); diff --git a/crates/apub/src/activities/following/accept.rs b/crates/apub/src/activities/following/accept.rs index bc689589..c76263cc 100644 --- a/crates/apub/src/activities/following/accept.rs +++ b/crates/apub/src/activities/following/accept.rs @@ -10,9 +10,14 @@ use crate::{ fetcher::{community::get_or_fetch_and_upsert_community, person::get_or_fetch_and_upsert_person}, ActorType, }; -use activitystreams::activity::kind::AcceptType; +use activitystreams::{ + activity::kind::AcceptType, + base::AnyBase, + primitives::OneOrMany, + unparsed::Unparsed, +}; use lemmy_api_common::blocking; -use lemmy_apub_lib::{verify_urls_match, ActivityCommonFields, ActivityHandler}; +use lemmy_apub_lib::{verify_urls_match, ActivityFields, ActivityHandler}; use lemmy_db_queries::{ApubObject, Followable}; use lemmy_db_schema::source::{ community::{Community, CommunityFollower}, @@ -20,17 +25,22 @@ use lemmy_db_schema::source::{ }; use lemmy_utils::LemmyError; use lemmy_websocket::LemmyContext; +use serde::{Deserialize, Serialize}; use url::Url; -#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)] +#[derive(Clone, Debug, Deserialize, Serialize, ActivityFields)] #[serde(rename_all = "camelCase")] pub struct AcceptFollowCommunity { + actor: Url, to: Url, object: FollowCommunity, #[serde(rename = "type")] kind: AcceptType, + id: Url, + #[serde(rename = "@context")] + context: OneOrMany, #[serde(flatten)] - common: ActivityCommonFields, + unparsed: Unparsed, } impl AcceptFollowCommunity { @@ -40,26 +50,23 @@ impl AcceptFollowCommunity { Community::read_from_apub_id(conn, &community_id.into()) }) .await??; - let person_id = follow.common.actor.clone(); + let person_id = follow.actor().clone(); let person = blocking(context.pool(), move |conn| { Person::read_from_apub_id(conn, &person_id.into()) }) .await??; - let id = generate_activity_id(AcceptType::Accept)?; let accept = AcceptFollowCommunity { + actor: community.actor_id(), to: person.actor_id(), object: follow, kind: AcceptType::Accept, - common: ActivityCommonFields { - context: lemmy_context(), - id: id.clone(), - actor: community.actor_id(), - unparsed: Default::default(), - }, + id: generate_activity_id(AcceptType::Accept)?, + context: lemmy_context(), + unparsed: Default::default(), }; let inbox = vec![person.inbox_url.into()]; - send_activity_new(context, &accept, &id, &community, inbox, true).await + send_activity_new(context, &accept, &accept.id, &community, inbox, true).await } } /// Handle accepted follows @@ -70,10 +77,10 @@ impl ActivityHandler for AcceptFollowCommunity { context: &LemmyContext, request_counter: &mut i32, ) -> Result<(), LemmyError> { - verify_activity(self.common())?; - verify_urls_match(&self.to, &self.object.common.actor)?; - verify_urls_match(&self.common.actor, &self.object.to)?; - verify_community(&self.common.actor, context, request_counter).await?; + verify_activity(self)?; + verify_urls_match(&self.to, self.object.actor())?; + verify_urls_match(&self.actor, &self.object.to)?; + verify_community(&self.actor, context, request_counter).await?; self.object.verify(context, request_counter).await?; Ok(()) } @@ -83,8 +90,7 @@ impl ActivityHandler for AcceptFollowCommunity { context: &LemmyContext, request_counter: &mut i32, ) -> Result<(), LemmyError> { - let actor = - get_or_fetch_and_upsert_community(&self.common.actor, context, request_counter).await?; + let actor = get_or_fetch_and_upsert_community(&self.actor, context, request_counter).await?; let to = 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| { @@ -94,8 +100,4 @@ impl ActivityHandler for AcceptFollowCommunity { Ok(()) } - - fn common(&self) -> &ActivityCommonFields { - &self.common - } } diff --git a/crates/apub/src/activities/following/follow.rs b/crates/apub/src/activities/following/follow.rs index aa96fb39..e6ca747a 100644 --- a/crates/apub/src/activities/following/follow.rs +++ b/crates/apub/src/activities/following/follow.rs @@ -10,9 +10,14 @@ use crate::{ fetcher::{community::get_or_fetch_and_upsert_community, person::get_or_fetch_and_upsert_person}, ActorType, }; -use activitystreams::activity::kind::FollowType; +use activitystreams::{ + activity::kind::FollowType, + base::AnyBase, + primitives::OneOrMany, + unparsed::Unparsed, +}; use lemmy_api_common::blocking; -use lemmy_apub_lib::{verify_urls_match, ActivityCommonFields, ActivityHandler}; +use lemmy_apub_lib::{verify_urls_match, ActivityFields, ActivityHandler}; use lemmy_db_queries::Followable; use lemmy_db_schema::source::{ community::{Community, CommunityFollower, CommunityFollowerForm}, @@ -20,20 +25,39 @@ use lemmy_db_schema::source::{ }; use lemmy_utils::LemmyError; use lemmy_websocket::LemmyContext; +use serde::{Deserialize, Serialize}; use url::Url; -#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)] +#[derive(Clone, Debug, Deserialize, Serialize, ActivityFields)] #[serde(rename_all = "camelCase")] pub struct FollowCommunity { + actor: Url, pub(in crate::activities::following) to: Url, pub(in crate::activities::following) object: Url, #[serde(rename = "type")] - pub(in crate::activities::following) kind: FollowType, + kind: FollowType, + id: Url, + #[serde(rename = "@context")] + context: OneOrMany, #[serde(flatten)] - pub(in crate::activities::following) common: ActivityCommonFields, + unparsed: Unparsed, } impl FollowCommunity { + pub(in crate::activities::following) fn new( + actor: &Person, + community: &Community, + ) -> Result { + Ok(FollowCommunity { + actor: actor.actor_id(), + to: community.actor_id(), + object: community.actor_id(), + kind: FollowType::Follow, + id: generate_activity_id(FollowType::Follow)?, + context: lemmy_context(), + unparsed: Default::default(), + }) + } pub async fn send( actor: &Person, community: &Community, @@ -49,20 +73,9 @@ impl FollowCommunity { }) .await?; - let id = generate_activity_id(FollowType::Follow)?; - let follow = FollowCommunity { - to: community.actor_id(), - object: community.actor_id(), - kind: FollowType::Follow, - common: ActivityCommonFields { - context: lemmy_context(), - id: id.clone(), - actor: actor.actor_id(), - unparsed: Default::default(), - }, - }; + let follow = FollowCommunity::new(actor, community)?; let inbox = vec![community.inbox_url.clone().into()]; - send_activity_new(context, &follow, &id, actor, inbox, true).await + send_activity_new(context, &follow, &follow.id, actor, inbox, true).await } } @@ -73,9 +86,9 @@ impl ActivityHandler for FollowCommunity { context: &LemmyContext, request_counter: &mut i32, ) -> Result<(), LemmyError> { - verify_activity(self.common())?; + verify_activity(self)?; verify_urls_match(&self.to, &self.object)?; - verify_person(&self.common.actor, context, request_counter).await?; + verify_person(&self.actor, context, request_counter).await?; Ok(()) } @@ -84,8 +97,7 @@ impl ActivityHandler for FollowCommunity { context: &LemmyContext, request_counter: &mut i32, ) -> Result<(), LemmyError> { - let actor = - get_or_fetch_and_upsert_person(&self.common.actor, context, request_counter).await?; + let actor = get_or_fetch_and_upsert_person(&self.actor, context, request_counter).await?; let community = get_or_fetch_and_upsert_community(&self.object, context, request_counter).await?; let community_follower_form = CommunityFollowerForm { @@ -102,8 +114,4 @@ impl ActivityHandler for FollowCommunity { AcceptFollowCommunity::send(self, context).await } - - fn common(&self) -> &ActivityCommonFields { - &self.common - } } diff --git a/crates/apub/src/activities/following/undo.rs b/crates/apub/src/activities/following/undo.rs index 7fbc7be5..092036bb 100644 --- a/crates/apub/src/activities/following/undo.rs +++ b/crates/apub/src/activities/following/undo.rs @@ -10,9 +10,14 @@ use crate::{ fetcher::{community::get_or_fetch_and_upsert_community, person::get_or_fetch_and_upsert_person}, ActorType, }; -use activitystreams::activity::kind::{FollowType, UndoType}; +use activitystreams::{ + activity::kind::UndoType, + base::AnyBase, + primitives::OneOrMany, + unparsed::Unparsed, +}; use lemmy_api_common::blocking; -use lemmy_apub_lib::{verify_urls_match, ActivityCommonFields, ActivityHandler}; +use lemmy_apub_lib::{verify_urls_match, ActivityFields, ActivityHandler}; use lemmy_db_queries::Followable; use lemmy_db_schema::source::{ community::{Community, CommunityFollower, CommunityFollowerForm}, @@ -20,17 +25,22 @@ use lemmy_db_schema::source::{ }; use lemmy_utils::LemmyError; use lemmy_websocket::LemmyContext; +use serde::{Deserialize, Serialize}; use url::Url; -#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)] +#[derive(Clone, Debug, Deserialize, Serialize, ActivityFields)] #[serde(rename_all = "camelCase")] pub struct UndoFollowCommunity { + actor: Url, to: Url, object: FollowCommunity, #[serde(rename = "type")] kind: UndoType, + id: Url, + #[serde(rename = "@context")] + context: OneOrMany, #[serde(flatten)] - common: ActivityCommonFields, + unparsed: Unparsed, } impl UndoFollowCommunity { @@ -39,30 +49,18 @@ impl UndoFollowCommunity { community: &Community, context: &LemmyContext, ) -> Result<(), LemmyError> { - let id = generate_activity_id(UndoType::Undo)?; + let object = FollowCommunity::new(actor, community)?; let undo = UndoFollowCommunity { + actor: actor.actor_id(), to: community.actor_id(), - object: FollowCommunity { - to: community.actor_id(), - object: community.actor_id(), - kind: FollowType::Follow, - common: ActivityCommonFields { - context: lemmy_context(), - id: generate_activity_id(FollowType::Follow)?, - actor: actor.actor_id(), - unparsed: Default::default(), - }, - }, + object, kind: UndoType::Undo, - common: ActivityCommonFields { - context: lemmy_context(), - id: id.clone(), - actor: actor.actor_id(), - unparsed: Default::default(), - }, + id: generate_activity_id(UndoType::Undo)?, + context: lemmy_context(), + unparsed: Default::default(), }; let inbox = vec![community.get_shared_inbox_or_inbox_url()]; - send_activity_new(context, &undo, &id, actor, inbox, true).await + send_activity_new(context, &undo, &undo.id, actor, inbox, true).await } } @@ -73,10 +71,10 @@ impl ActivityHandler for UndoFollowCommunity { context: &LemmyContext, request_counter: &mut i32, ) -> Result<(), LemmyError> { - verify_activity(self.common())?; + verify_activity(self)?; verify_urls_match(&self.to, &self.object.object)?; - verify_urls_match(&self.common.actor, &self.object.common.actor)?; - verify_person(&self.common.actor, context, request_counter).await?; + verify_urls_match(&self.actor, self.object.actor())?; + verify_person(&self.actor, context, request_counter).await?; self.object.verify(context, request_counter).await?; Ok(()) } @@ -86,8 +84,7 @@ impl ActivityHandler for UndoFollowCommunity { context: &LemmyContext, request_counter: &mut i32, ) -> Result<(), LemmyError> { - let actor = - get_or_fetch_and_upsert_person(&self.common.actor, context, request_counter).await?; + let actor = get_or_fetch_and_upsert_person(&self.actor, context, request_counter).await?; let community = get_or_fetch_and_upsert_community(&self.to, context, request_counter).await?; let community_follower_form = CommunityFollowerForm { @@ -103,8 +100,4 @@ impl ActivityHandler for UndoFollowCommunity { .await?; Ok(()) } - - fn common(&self) -> &ActivityCommonFields { - &self.common - } } diff --git a/crates/apub/src/activities/mod.rs b/crates/apub/src/activities/mod.rs index afbad6c8..a846a0e7 100644 --- a/crates/apub/src/activities/mod.rs +++ b/crates/apub/src/activities/mod.rs @@ -6,7 +6,7 @@ use crate::{ }; use anyhow::anyhow; use lemmy_api_common::blocking; -use lemmy_apub_lib::{verify_domains_match, ActivityCommonFields}; +use lemmy_apub_lib::{verify_domains_match, ActivityFields}; use lemmy_db_queries::ApubObject; use lemmy_db_schema::{ source::{community::Community, person::Person}, @@ -26,8 +26,8 @@ pub mod deletion; pub mod following; pub mod post; pub mod private_message; -pub mod removal; pub mod send; +pub mod undo_remove; pub mod voting; #[derive(Clone, Debug, ToString, Deserialize, Serialize)] @@ -90,9 +90,9 @@ async fn verify_community( Ok(()) } -fn verify_activity(common: &ActivityCommonFields) -> Result<(), LemmyError> { - check_is_apub_id_valid(&common.actor, false)?; - verify_domains_match(common.id_unchecked(), &common.actor)?; +fn verify_activity(activity: &dyn ActivityFields) -> Result<(), LemmyError> { + check_is_apub_id_valid(activity.actor(), false)?; + verify_domains_match(activity.id_unchecked(), activity.actor())?; Ok(()) } diff --git a/crates/apub/src/activities/post/create_or_update.rs b/crates/apub/src/activities/post/create_or_update.rs index 1a9c7768..c1b0703d 100644 --- a/crates/apub/src/activities/post/create_or_update.rs +++ b/crates/apub/src/activities/post/create_or_update.rs @@ -14,31 +14,37 @@ use crate::{ objects::{post::Page, FromApub, ToApub}, ActorType, }; +use activitystreams::{base::AnyBase, primitives::OneOrMany, unparsed::Unparsed}; use anyhow::anyhow; use lemmy_api_common::blocking; use lemmy_apub_lib::{ values::PublicUrl, verify_domains_match, verify_urls_match, - ActivityCommonFields, + ActivityFields, ActivityHandler, }; use lemmy_db_queries::Crud; use lemmy_db_schema::source::{community::Community, person::Person, post::Post}; use lemmy_utils::LemmyError; use lemmy_websocket::{send::send_post_ws_message, LemmyContext, UserOperationCrud}; +use serde::{Deserialize, Serialize}; use url::Url; -#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)] +#[derive(Clone, Debug, Deserialize, Serialize, ActivityFields)] #[serde(rename_all = "camelCase")] pub struct CreateOrUpdatePost { + actor: Url, to: PublicUrl, object: Page, cc: [Url; 1], #[serde(rename = "type")] kind: CreateOrUpdateType, + id: Url, + #[serde(rename = "@context")] + context: OneOrMany, #[serde(flatten)] - common: ActivityCommonFields, + unparsed: Unparsed, } impl CreateOrUpdatePost { @@ -56,16 +62,14 @@ impl CreateOrUpdatePost { let id = generate_activity_id(kind.clone())?; let create_or_update = CreateOrUpdatePost { + actor: actor.actor_id(), to: PublicUrl::Public, object: post.to_apub(context.pool()).await?, cc: [community.actor_id()], kind, - common: ActivityCommonFields { - context: lemmy_context(), - id: id.clone(), - actor: actor.actor_id(), - unparsed: Default::default(), - }, + id: id.clone(), + context: lemmy_context(), + unparsed: Default::default(), }; let activity = AnnouncableActivities::CreateOrUpdatePost(Box::new(create_or_update)); @@ -80,14 +84,14 @@ impl ActivityHandler for CreateOrUpdatePost { context: &LemmyContext, request_counter: &mut i32, ) -> Result<(), LemmyError> { - verify_activity(self.common())?; + verify_activity(self)?; let community = extract_community(&self.cc, context, request_counter).await?; let community_id = community.actor_id(); - verify_person_in_community(&self.common.actor, &community_id, context, request_counter).await?; + verify_person_in_community(&self.actor, &community_id, context, request_counter).await?; match self.kind { CreateOrUpdateType::Create => { - verify_domains_match(&self.common.actor, self.object.id_unchecked())?; - verify_urls_match(&self.common.actor, &self.object.attributed_to)?; + verify_domains_match(&self.actor, self.object.id_unchecked())?; + verify_urls_match(&self.actor, &self.object.attributed_to)?; // Check that the post isnt locked or stickied, as that isnt possible for newly created posts. // However, when fetching a remote post we generate a new create activity with the current // locked/stickied value, so this check may fail. So only check if its a local community, @@ -101,10 +105,10 @@ impl ActivityHandler for CreateOrUpdatePost { CreateOrUpdateType::Update => { let is_mod_action = self.object.is_mod_action(context.pool()).await?; if is_mod_action { - verify_mod_action(&self.common.actor, community_id, context).await?; + verify_mod_action(&self.actor, community_id, context).await?; } else { - verify_domains_match(&self.common.actor, self.object.id_unchecked())?; - verify_urls_match(&self.common.actor, &self.object.attributed_to)?; + verify_domains_match(&self.actor, self.object.id_unchecked())?; + verify_urls_match(&self.actor, &self.object.attributed_to)?; } } } @@ -117,8 +121,7 @@ impl ActivityHandler for CreateOrUpdatePost { context: &LemmyContext, request_counter: &mut i32, ) -> Result<(), LemmyError> { - let actor = - get_or_fetch_and_upsert_person(&self.common.actor, context, request_counter).await?; + let actor = get_or_fetch_and_upsert_person(&self.actor, context, request_counter).await?; let post = Post::from_apub(&self.object, context, &actor.actor_id(), request_counter).await?; let notif_type = match self.kind { @@ -128,8 +131,4 @@ impl ActivityHandler for CreateOrUpdatePost { send_post_ws_message(post.id, notif_type, None, None, context).await?; Ok(()) } - - fn common(&self) -> &ActivityCommonFields { - &self.common - } } diff --git a/crates/apub/src/activities/private_message/create_or_update.rs b/crates/apub/src/activities/private_message/create_or_update.rs index cd3c6574..98a26d80 100644 --- a/crates/apub/src/activities/private_message/create_or_update.rs +++ b/crates/apub/src/activities/private_message/create_or_update.rs @@ -5,23 +5,30 @@ use crate::{ objects::{private_message::Note, FromApub, ToApub}, ActorType, }; +use activitystreams::{base::AnyBase, primitives::OneOrMany, unparsed::Unparsed}; use lemmy_api_common::blocking; -use lemmy_apub_lib::{verify_domains_match, ActivityCommonFields, ActivityHandler}; +use lemmy_apub_lib::{verify_domains_match, ActivityFields, ActivityHandler}; use lemmy_db_queries::Crud; use lemmy_db_schema::source::{person::Person, private_message::PrivateMessage}; use lemmy_utils::LemmyError; use lemmy_websocket::{send::send_pm_ws_message, LemmyContext, UserOperationCrud}; +use serde::{Deserialize, Serialize}; use url::Url; -#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)] +#[derive(Clone, Debug, Deserialize, Serialize, ActivityFields)] #[serde(rename_all = "camelCase")] pub struct CreateOrUpdatePrivateMessage { + #[serde(rename = "@context")] + pub context: OneOrMany, + id: Url, + actor: Url, to: Url, + cc: [Url; 0], object: Note, #[serde(rename = "type")] kind: CreateOrUpdateType, #[serde(flatten)] - common: ActivityCommonFields, + pub unparsed: Unparsed, } impl CreateOrUpdatePrivateMessage { @@ -37,15 +44,14 @@ impl CreateOrUpdatePrivateMessage { let id = generate_activity_id(kind.clone())?; let create_or_update = CreateOrUpdatePrivateMessage { + context: lemmy_context(), + id: id.clone(), + actor: actor.actor_id(), to: recipient.actor_id(), + cc: [], object: private_message.to_apub(context.pool()).await?, kind, - common: ActivityCommonFields { - context: lemmy_context(), - id: id.clone(), - actor: actor.actor_id(), - unparsed: Default::default(), - }, + unparsed: Default::default(), }; let inbox = vec![recipient.get_shared_inbox_or_inbox_url()]; send_activity_new(context, &create_or_update, &id, actor, inbox, true).await @@ -58,9 +64,9 @@ impl ActivityHandler for CreateOrUpdatePrivateMessage { context: &LemmyContext, request_counter: &mut i32, ) -> Result<(), LemmyError> { - verify_activity(self.common())?; - verify_person(&self.common.actor, context, request_counter).await?; - verify_domains_match(&self.common.actor, self.object.id_unchecked())?; + verify_activity(self)?; + verify_person(&self.actor, context, request_counter).await?; + verify_domains_match(&self.actor, self.object.id_unchecked())?; self.object.verify(context, request_counter).await?; Ok(()) } @@ -71,7 +77,7 @@ impl ActivityHandler for CreateOrUpdatePrivateMessage { request_counter: &mut i32, ) -> Result<(), LemmyError> { let private_message = - PrivateMessage::from_apub(&self.object, context, &self.common.actor, request_counter).await?; + PrivateMessage::from_apub(&self.object, context, &self.actor, request_counter).await?; let notif_type = match self.kind { CreateOrUpdateType::Create => UserOperationCrud::CreatePrivateMessage, @@ -81,8 +87,4 @@ impl ActivityHandler for CreateOrUpdatePrivateMessage { Ok(()) } - - fn common(&self) -> &ActivityCommonFields { - &self.common - } } diff --git a/crates/apub/src/activities/private_message/delete.rs b/crates/apub/src/activities/private_message/delete.rs index e6c03093..47e1a71a 100644 --- a/crates/apub/src/activities/private_message/delete.rs +++ b/crates/apub/src/activities/private_message/delete.rs @@ -4,50 +4,64 @@ use crate::{ extensions::context::lemmy_context, ActorType, }; -use activitystreams::activity::kind::DeleteType; +use activitystreams::{ + activity::kind::DeleteType, + base::AnyBase, + primitives::OneOrMany, + unparsed::Unparsed, +}; use lemmy_api_common::blocking; -use lemmy_apub_lib::{verify_domains_match, ActivityCommonFields, ActivityHandler}; +use lemmy_apub_lib::{verify_domains_match, ActivityFields, ActivityHandler}; use lemmy_db_queries::{source::private_message::PrivateMessage_, ApubObject, Crud}; use lemmy_db_schema::source::{person::Person, private_message::PrivateMessage}; use lemmy_utils::LemmyError; use lemmy_websocket::{send::send_pm_ws_message, LemmyContext, UserOperationCrud}; +use serde::{Deserialize, Serialize}; use url::Url; -#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)] +#[derive(Clone, Debug, Deserialize, Serialize, ActivityFields)] #[serde(rename_all = "camelCase")] pub struct DeletePrivateMessage { - pub(in crate::activities::private_message) to: Url, + actor: Url, + to: Url, pub(in crate::activities::private_message) object: Url, #[serde(rename = "type")] - pub(in crate::activities::private_message) kind: DeleteType, + kind: DeleteType, + id: Url, + #[serde(rename = "@context")] + context: OneOrMany, #[serde(flatten)] - pub(in crate::activities::private_message) common: ActivityCommonFields, + unparsed: Unparsed, } impl DeletePrivateMessage { + pub(in crate::activities::private_message) fn new( + actor: &Person, + pm: &PrivateMessage, + ) -> Result { + Ok(DeletePrivateMessage { + actor: actor.actor_id(), + to: actor.actor_id(), + object: pm.ap_id.clone().into(), + kind: DeleteType::Delete, + id: generate_activity_id(DeleteType::Delete)?, + context: lemmy_context(), + unparsed: Default::default(), + }) + } pub async fn send( actor: &Person, pm: &PrivateMessage, context: &LemmyContext, ) -> Result<(), LemmyError> { + let delete = DeletePrivateMessage::new(actor, pm)?; + let delete_id = delete.id.clone(); + let recipient_id = pm.recipient_id; let recipient = blocking(context.pool(), move |conn| Person::read(conn, recipient_id)).await??; - - let id = generate_activity_id(DeleteType::Delete)?; - let delete = DeletePrivateMessage { - to: actor.actor_id(), - object: pm.ap_id.clone().into(), - kind: DeleteType::Delete, - common: ActivityCommonFields { - context: lemmy_context(), - id: id.clone(), - actor: actor.actor_id(), - unparsed: Default::default(), - }, - }; let inbox = vec![recipient.get_shared_inbox_or_inbox_url()]; - send_activity_new(context, &delete, &id, actor, inbox, true).await + send_activity_new(context, &delete, &delete_id, actor, inbox, true).await } } @@ -58,9 +72,9 @@ impl ActivityHandler for DeletePrivateMessage { context: &LemmyContext, request_counter: &mut i32, ) -> Result<(), LemmyError> { - verify_activity(self.common())?; - verify_person(&self.common.actor, context, request_counter).await?; - verify_domains_match(&self.common.actor, &self.object)?; + verify_activity(self)?; + verify_person(&self.actor, context, request_counter).await?; + verify_domains_match(&self.actor, &self.object)?; Ok(()) } @@ -89,8 +103,4 @@ impl ActivityHandler for DeletePrivateMessage { Ok(()) } - - fn common(&self) -> &ActivityCommonFields { - &self.common - } } diff --git a/crates/apub/src/activities/private_message/undo_delete.rs b/crates/apub/src/activities/private_message/undo_delete.rs index 4cd2a139..911a17c7 100644 --- a/crates/apub/src/activities/private_message/undo_delete.rs +++ b/crates/apub/src/activities/private_message/undo_delete.rs @@ -9,29 +9,34 @@ use crate::{ extensions::context::lemmy_context, ActorType, }; -use activitystreams::activity::kind::{DeleteType, UndoType}; -use lemmy_api_common::blocking; -use lemmy_apub_lib::{ - verify_domains_match, - verify_urls_match, - ActivityCommonFields, - ActivityHandler, +use activitystreams::{ + activity::kind::UndoType, + base::AnyBase, + primitives::OneOrMany, + unparsed::Unparsed, }; +use lemmy_api_common::blocking; +use lemmy_apub_lib::{verify_domains_match, verify_urls_match, ActivityFields, ActivityHandler}; use lemmy_db_queries::{source::private_message::PrivateMessage_, ApubObject, Crud}; use lemmy_db_schema::source::{person::Person, private_message::PrivateMessage}; use lemmy_utils::LemmyError; use lemmy_websocket::{send::send_pm_ws_message, LemmyContext, UserOperationCrud}; +use serde::{Deserialize, Serialize}; use url::Url; -#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)] +#[derive(Clone, Debug, Deserialize, Serialize, ActivityFields)] #[serde(rename_all = "camelCase")] pub struct UndoDeletePrivateMessage { + actor: Url, to: Url, object: DeletePrivateMessage, #[serde(rename = "type")] kind: UndoType, + id: Url, + #[serde(rename = "@context")] + context: OneOrMany, #[serde(flatten)] - common: ActivityCommonFields, + unparsed: Unparsed, } impl UndoDeletePrivateMessage { @@ -44,29 +49,16 @@ impl UndoDeletePrivateMessage { let recipient = blocking(context.pool(), move |conn| Person::read(conn, recipient_id)).await??; - let object = DeletePrivateMessage { - to: recipient.actor_id(), - object: pm.ap_id.clone().into(), - kind: DeleteType::Delete, - common: ActivityCommonFields { - context: lemmy_context(), - id: generate_activity_id(DeleteType::Delete)?, - actor: actor.actor_id(), - unparsed: Default::default(), - }, - }; - + let object = DeletePrivateMessage::new(actor, pm)?; let id = generate_activity_id(UndoType::Undo)?; let undo = UndoDeletePrivateMessage { + actor: actor.actor_id(), to: recipient.actor_id(), object, kind: UndoType::Undo, - common: ActivityCommonFields { - context: lemmy_context(), - id: id.clone(), - actor: actor.actor_id(), - unparsed: Default::default(), - }, + id: id.clone(), + context: lemmy_context(), + unparsed: Default::default(), }; let inbox = vec![recipient.get_shared_inbox_or_inbox_url()]; send_activity_new(context, &undo, &id, actor, inbox, true).await @@ -80,10 +72,10 @@ impl ActivityHandler for UndoDeletePrivateMessage { context: &LemmyContext, request_counter: &mut i32, ) -> Result<(), LemmyError> { - verify_activity(self.common())?; - verify_person(&self.common.actor, context, request_counter).await?; - verify_urls_match(&self.common.actor, &self.object.common.actor)?; - verify_domains_match(&self.common.actor, &self.object.object)?; + verify_activity(self)?; + verify_person(&self.actor, context, request_counter).await?; + verify_urls_match(&self.actor, self.object.actor())?; + verify_domains_match(&self.actor, &self.object.object)?; self.object.verify(context, request_counter).await?; Ok(()) } @@ -114,8 +106,4 @@ impl ActivityHandler for UndoDeletePrivateMessage { Ok(()) } - - fn common(&self) -> &ActivityCommonFields { - &self.common - } } diff --git a/crates/apub/src/activities/removal/mod.rs b/crates/apub/src/activities/removal/mod.rs deleted file mode 100644 index 01c031dd..00000000 --- a/crates/apub/src/activities/removal/mod.rs +++ /dev/null @@ -1,2 +0,0 @@ -pub mod remove; -pub mod undo_remove; diff --git a/crates/apub/src/activities/removal/remove.rs b/crates/apub/src/activities/removal/remove.rs deleted file mode 100644 index 1bd6e0a0..00000000 --- a/crates/apub/src/activities/removal/remove.rs +++ /dev/null @@ -1,101 +0,0 @@ -use crate::{ - activities::{ - deletion::{delete::receive_remove_action, verify_delete_activity}, - verify_activity, - verify_add_remove_moderator_target, - verify_mod_action, - verify_person_in_community, - }, - fetcher::{community::get_or_fetch_and_upsert_community, person::get_or_fetch_and_upsert_person}, - CommunityType, -}; -use activitystreams::{activity::kind::RemoveType, base::AnyBase}; -use lemmy_api_common::blocking; -use lemmy_apub_lib::{values::PublicUrl, ActivityCommonFields, ActivityHandler}; -use lemmy_db_queries::Joinable; -use lemmy_db_schema::source::community::{CommunityModerator, CommunityModeratorForm}; -use lemmy_utils::LemmyError; -use lemmy_websocket::LemmyContext; -use url::Url; - -#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)] -#[serde(rename_all = "camelCase")] -pub struct RemoveMod { - to: PublicUrl, - pub(in crate::activities::removal) object: Url, - cc: [Url; 1], - #[serde(rename = "type")] - kind: RemoveType, - // if target is set, this is means remove mod from community - pub(in crate::activities::removal) target: Option, - #[serde(flatten)] - common: ActivityCommonFields, -} - -#[async_trait::async_trait(?Send)] -impl ActivityHandler for RemoveMod { - async fn verify( - &self, - context: &LemmyContext, - request_counter: &mut i32, - ) -> Result<(), LemmyError> { - verify_activity(self.common())?; - if let Some(target) = &self.target { - verify_person_in_community(&self.common.actor, &self.cc[0], context, request_counter).await?; - verify_mod_action(&self.common.actor, self.cc[0].clone(), context).await?; - verify_add_remove_moderator_target(target, self.cc[0].clone())?; - } else { - verify_delete_activity( - &self.object, - &self.cc[0], - self.common(), - true, - context, - request_counter, - ) - .await?; - } - Ok(()) - } - - async fn receive( - self, - context: &LemmyContext, - request_counter: &mut i32, - ) -> Result<(), LemmyError> { - if self.target.is_some() { - let community = - get_or_fetch_and_upsert_community(&self.cc[0], context, request_counter).await?; - let remove_mod = - get_or_fetch_and_upsert_person(&self.object, context, request_counter).await?; - - let form = CommunityModeratorForm { - community_id: community.id, - person_id: remove_mod.id, - }; - blocking(context.pool(), move |conn| { - CommunityModerator::leave(conn, &form) - }) - .await??; - let anybase = AnyBase::from_arbitrary_json(serde_json::to_string(&self)?)?; - community - .send_announce(anybase, Some(self.object.clone()), context) - .await?; - // TODO: send websocket notification about removed mod - Ok(()) - } else { - receive_remove_action( - &self.common.actor, - &self.object, - None, - context, - request_counter, - ) - .await - } - } - - fn common(&self) -> &ActivityCommonFields { - &self.common - } -} diff --git a/crates/apub/src/activities/send/community.rs b/crates/apub/src/activities/send/community.rs index a103f95c..29aa9dba 100644 --- a/crates/apub/src/activities/send/community.rs +++ b/crates/apub/src/activities/send/community.rs @@ -1,38 +1,10 @@ -use crate::{ - activities::generate_activity_id, - activity_queue::{send_to_community, send_to_community_followers}, - check_is_apub_id_valid, - extensions::context::lemmy_context, - fetcher::get_or_fetch_and_upsert_actor, - generate_moderators_url, - insert_activity, - objects::ToApub, - ActorType, - CommunityType, -}; -use activitystreams::{ - activity::{ - kind::{AddType, AnnounceType, BlockType, RemoveType, UndoType, UpdateType}, - Add, - Announce, - Block, - OptTargetRefExt, - Remove, - Undo, - Update, - }, - base::{AnyBase, BaseExt, ExtendsExt}, - object::ObjectExt, - public, -}; -use anyhow::Context; +use crate::{check_is_apub_id_valid, ActorType, CommunityType}; use itertools::Itertools; use lemmy_api_common::blocking; use lemmy_db_queries::DbPool; -use lemmy_db_schema::source::{community::Community, person::Person}; +use lemmy_db_schema::source::community::Community; use lemmy_db_views_actor::community_follower_view::CommunityFollowerView; -use lemmy_utils::{location_info, settings::structs::Settings, LemmyError}; -use lemmy_websocket::LemmyContext; +use lemmy_utils::LemmyError; use url::Url; impl ActorType for Community { @@ -67,71 +39,6 @@ impl CommunityType for Community { self.followers_url.clone().into() } - /// If a remote community is updated by a local mod, send the updated info to the community's - /// instance. - async fn send_update(&self, mod_: Person, context: &LemmyContext) -> Result<(), LemmyError> { - if self.local { - // Do nothing, other instances will automatically refetch the community - } else { - let mut update = Update::new( - mod_.actor_id(), - AnyBase::from_arbitrary_json(self.to_apub(context.pool()).await?)?, - ); - update - .set_many_contexts(lemmy_context()) - .set_id(generate_activity_id(UpdateType::Update)?) - .set_to(public()) - .set_many_ccs(vec![self.actor_id()]); - send_to_community(update, &mod_, self, None, context).await?; - } - Ok(()) - } - - /// Wraps an activity sent to the community in an announce, and then sends the announce to all - /// community followers. - /// - /// If we are announcing a local activity, it hasn't been stored in the database yet, and we need - /// to do it here, so that it can be fetched by ID. Remote activities are inserted into DB in the - /// inbox. - /// - /// If the `object` of the announced activity is an actor, the actor ID needs to be passed as - /// `object_actor`, so that the announce can be delivered to that user. - async fn send_announce( - &self, - activity: AnyBase, - object_actor: Option, - context: &LemmyContext, - ) -> Result<(), LemmyError> { - let inner_id = activity.id().context(location_info!())?; - if inner_id.domain() == Some(&Settings::get().get_hostname_without_port()?) { - insert_activity(inner_id, activity.clone(), true, false, context.pool()).await?; - } - - let mut ccs = vec![self.followers_url()]; - let mut object_actor_inbox: Option = None; - if let Some(actor_id) = object_actor { - // Ignore errors, maybe its not actually an actor - // TODO: should pass the actual request counter in, but that seems complicated - let actor = get_or_fetch_and_upsert_actor(&actor_id, context, &mut 0) - .await - .ok(); - if let Some(actor) = actor { - ccs.push(actor_id); - object_actor_inbox = Some(actor.get_shared_inbox_or_inbox_url()); - } - } - let mut announce = Announce::new(self.actor_id(), activity); - announce - .set_many_contexts(lemmy_context()) - .set_id(generate_activity_id(AnnounceType::Announce)?) - .set_to(public()) - .set_many_ccs(ccs); - - send_to_community_followers(announce, self, object_actor_inbox, context).await?; - - Ok(()) - } - /// For a given community, returns the inboxes of all followers. async fn get_follower_inboxes(&self, pool: &DbPool) -> Result, LemmyError> { let id = self.id; @@ -152,82 +59,4 @@ impl CommunityType for Community { Ok(inboxes) } - - async fn send_add_mod( - &self, - actor: &Person, - added_mod: Person, - context: &LemmyContext, - ) -> Result<(), LemmyError> { - let mut add = Add::new(actor.actor_id(), added_mod.actor_id()); - add - .set_many_contexts(lemmy_context()) - .set_id(generate_activity_id(AddType::Add)?) - .set_to(public()) - .set_many_ccs(vec![self.actor_id()]) - .set_target(generate_moderators_url(&self.actor_id)?.into_inner()); - - send_to_community(add, actor, self, Some(added_mod.actor_id()), context).await?; - Ok(()) - } - - async fn send_remove_mod( - &self, - actor: &Person, - removed_mod: Person, - context: &LemmyContext, - ) -> Result<(), LemmyError> { - let mut remove = Remove::new(actor.actor_id(), removed_mod.actor_id()); - remove - .set_many_contexts(lemmy_context()) - .set_id(generate_activity_id(RemoveType::Remove)?) - .set_to(public()) - .set_many_ccs(vec![self.actor_id()]) - .set_target(generate_moderators_url(&self.actor_id)?.into_inner()); - - send_to_community(remove, actor, self, Some(removed_mod.actor_id()), context).await?; - Ok(()) - } - - async fn send_block_user( - &self, - actor: &Person, - blocked_user: Person, - context: &LemmyContext, - ) -> Result<(), LemmyError> { - let mut block = Block::new(actor.actor_id(), blocked_user.actor_id()); - block - .set_many_contexts(lemmy_context()) - .set_id(generate_activity_id(BlockType::Block)?) - .set_to(public()) - .set_many_ccs(vec![self.actor_id()]); - - send_to_community(block, actor, self, Some(blocked_user.actor_id()), context).await?; - Ok(()) - } - - async fn send_undo_block_user( - &self, - actor: &Person, - unblocked_user: Person, - context: &LemmyContext, - ) -> Result<(), LemmyError> { - let mut block = Block::new(actor.actor_id(), unblocked_user.actor_id()); - block - .set_many_contexts(lemmy_context()) - .set_id(generate_activity_id(BlockType::Block)?) - .set_to(public()) - .set_many_ccs(vec![self.actor_id()]); - - // Undo that fake activity - let mut undo = Undo::new(actor.actor_id(), block.into_any_base()?); - undo - .set_many_contexts(lemmy_context()) - .set_id(generate_activity_id(UndoType::Undo)?) - .set_to(public()) - .set_many_ccs(vec![self.actor_id()]); - - send_to_community(undo, actor, self, Some(unblocked_user.actor_id()), context).await?; - Ok(()) - } } diff --git a/crates/apub/src/activities/removal/undo_remove.rs b/crates/apub/src/activities/undo_remove.rs similarity index 69% rename from crates/apub/src/activities/removal/undo_remove.rs rename to crates/apub/src/activities/undo_remove.rs index e2c7ef76..03b2f888 100644 --- a/crates/apub/src/activities/removal/undo_remove.rs +++ b/crates/apub/src/activities/undo_remove.rs @@ -1,25 +1,35 @@ use crate::activities::{ + community::remove_mod::RemoveMod, deletion::{undo_delete::UndoDelete, verify_delete_activity}, - removal::remove::RemoveMod, verify_activity, }; -use activitystreams::activity::kind::UndoType; -use lemmy_apub_lib::{values::PublicUrl, ActivityCommonFields, ActivityHandler}; +use activitystreams::{ + activity::kind::UndoType, + base::AnyBase, + primitives::OneOrMany, + unparsed::Unparsed, +}; +use lemmy_apub_lib::{values::PublicUrl, ActivityFields, ActivityHandler}; use lemmy_utils::LemmyError; use lemmy_websocket::LemmyContext; +use serde::{Deserialize, Serialize}; use url::Url; -#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)] +#[derive(Clone, Debug, Deserialize, Serialize, ActivityFields)] #[serde(rename_all = "camelCase")] pub struct UndoRemovePostCommentOrCommunity { + actor: Url, to: PublicUrl, // Note, there is no such thing as Undo/Remove/Mod, so we ignore that object: RemoveMod, cc: [Url; 1], #[serde(rename = "type")] kind: UndoType, + id: Url, + #[serde(rename = "@context")] + context: OneOrMany, #[serde(flatten)] - common: ActivityCommonFields, + unparsed: Unparsed, } #[async_trait::async_trait(?Send)] @@ -29,13 +39,13 @@ impl ActivityHandler for UndoRemovePostCommentOrCommunity { context: &LemmyContext, request_counter: &mut i32, ) -> Result<(), LemmyError> { - verify_activity(self.common())?; + verify_activity(self)?; self.object.verify(context, request_counter).await?; verify_delete_activity( &self.object.object, + self, &self.cc[0], - self.common(), true, context, request_counter, @@ -51,8 +61,4 @@ impl ActivityHandler for UndoRemovePostCommentOrCommunity { ) -> Result<(), LemmyError> { UndoDelete::receive_undo_remove_action(&self.object.object, context).await } - - fn common(&self) -> &ActivityCommonFields { - &self.common - } } diff --git a/crates/apub/src/activities/voting/undo_vote.rs b/crates/apub/src/activities/voting/undo_vote.rs index 5b5feac9..6e18b3cb 100644 --- a/crates/apub/src/activities/voting/undo_vote.rs +++ b/crates/apub/src/activities/voting/undo_vote.rs @@ -19,9 +19,14 @@ use crate::{ ActorType, PostOrComment, }; -use activitystreams::activity::kind::UndoType; +use activitystreams::{ + activity::kind::UndoType, + base::AnyBase, + primitives::OneOrMany, + unparsed::Unparsed, +}; use lemmy_api_common::blocking; -use lemmy_apub_lib::{values::PublicUrl, verify_urls_match, ActivityCommonFields, ActivityHandler}; +use lemmy_apub_lib::{values::PublicUrl, verify_urls_match, ActivityFields, ActivityHandler}; use lemmy_db_queries::Crud; use lemmy_db_schema::{ source::{community::Community, person::Person}, @@ -29,19 +34,24 @@ use lemmy_db_schema::{ }; use lemmy_utils::LemmyError; use lemmy_websocket::LemmyContext; +use serde::{Deserialize, Serialize}; use std::ops::Deref; use url::Url; -#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)] +#[derive(Clone, Debug, Deserialize, Serialize, ActivityFields)] #[serde(rename_all = "camelCase")] pub struct UndoVote { + actor: Url, to: PublicUrl, object: Vote, cc: [Url; 1], #[serde(rename = "type")] kind: UndoType, + id: Url, + #[serde(rename = "@context")] + context: OneOrMany, #[serde(flatten)] - common: ActivityCommonFields, + unparsed: Unparsed, } impl UndoVote { @@ -56,30 +66,18 @@ impl UndoVote { Community::read(conn, community_id) }) .await??; - let id = generate_activity_id(UndoType::Undo)?; + let object = Vote::new(object, actor, &community, kind.clone())?; + let id = generate_activity_id(UndoType::Undo)?; let undo_vote = UndoVote { + actor: actor.actor_id(), to: PublicUrl::Public, - object: Vote { - to: PublicUrl::Public, - object: object.ap_id(), - cc: [community.actor_id()], - kind: kind.clone(), - common: ActivityCommonFields { - context: lemmy_context(), - id: generate_activity_id(kind)?, - actor: actor.actor_id(), - unparsed: Default::default(), - }, - }, + object, cc: [community.actor_id()], kind: UndoType::Undo, - common: ActivityCommonFields { - context: lemmy_context(), - id: id.clone(), - actor: actor.actor_id(), - unparsed: Default::default(), - }, + id: id.clone(), + context: lemmy_context(), + unparsed: Default::default(), }; let activity = AnnouncableActivities::UndoVote(undo_vote); send_to_community_new(activity, &id, actor, &community, vec![], context).await @@ -93,9 +91,9 @@ impl ActivityHandler for UndoVote { context: &LemmyContext, request_counter: &mut i32, ) -> Result<(), LemmyError> { - verify_activity(self.common())?; - verify_person_in_community(&self.common.actor, &self.cc[0], context, request_counter).await?; - verify_urls_match(&self.common.actor, &self.object.common().actor)?; + verify_activity(self)?; + verify_person_in_community(&self.actor, &self.cc[0], context, request_counter).await?; + verify_urls_match(&self.actor, self.object.actor())?; self.object.verify(context, request_counter).await?; Ok(()) } @@ -105,8 +103,7 @@ impl ActivityHandler for UndoVote { context: &LemmyContext, request_counter: &mut i32, ) -> Result<(), LemmyError> { - let actor = - get_or_fetch_and_upsert_person(&self.common.actor, context, request_counter).await?; + let actor = get_or_fetch_and_upsert_person(&self.actor, context, request_counter).await?; let object = get_or_fetch_and_insert_post_or_comment(&self.object.object, context, request_counter) .await?; @@ -115,8 +112,4 @@ impl ActivityHandler for UndoVote { PostOrComment::Comment(c) => undo_vote_comment(actor, c.deref(), context).await, } } - - fn common(&self) -> &ActivityCommonFields { - &self.common - } } diff --git a/crates/apub/src/activities/voting/vote.rs b/crates/apub/src/activities/voting/vote.rs index 4183c2ad..cd7d04c5 100644 --- a/crates/apub/src/activities/voting/vote.rs +++ b/crates/apub/src/activities/voting/vote.rs @@ -15,9 +15,10 @@ use crate::{ ActorType, PostOrComment, }; +use activitystreams::{base::AnyBase, primitives::OneOrMany, unparsed::Unparsed}; use anyhow::anyhow; use lemmy_api_common::blocking; -use lemmy_apub_lib::{values::PublicUrl, ActivityCommonFields, ActivityHandler}; +use lemmy_apub_lib::{values::PublicUrl, ActivityFields, ActivityHandler}; use lemmy_db_queries::Crud; use lemmy_db_schema::{ source::{community::Community, person::Person}, @@ -57,19 +58,41 @@ impl From<&VoteType> for i16 { } } -#[derive(Clone, Debug, Deserialize, Serialize)] +#[derive(Clone, Debug, Deserialize, Serialize, ActivityFields)] #[serde(rename_all = "camelCase")] pub struct Vote { - pub(in crate::activities::voting) to: PublicUrl, + actor: Url, + to: PublicUrl, pub(in crate::activities::voting) object: Url, - pub(in crate::activities::voting) cc: [Url; 1], + cc: [Url; 1], #[serde(rename = "type")] pub(in crate::activities::voting) kind: VoteType, + id: Url, + #[serde(rename = "@context")] + context: OneOrMany, #[serde(flatten)] - pub(in crate::activities::voting) common: ActivityCommonFields, + unparsed: Unparsed, } impl Vote { + pub(in crate::activities::voting) fn new( + object: &PostOrComment, + actor: &Person, + community: &Community, + kind: VoteType, + ) -> Result { + Ok(Vote { + actor: actor.actor_id(), + to: PublicUrl::Public, + object: object.ap_id(), + cc: [community.actor_id()], + kind: kind.clone(), + id: generate_activity_id(kind)?, + context: lemmy_context(), + unparsed: Default::default(), + }) + } + pub async fn send( object: &PostOrComment, actor: &Person, @@ -81,22 +104,11 @@ impl Vote { Community::read(conn, community_id) }) .await??; - let id = generate_activity_id(kind.clone())?; + let vote = Vote::new(object, actor, &community, kind)?; + let vote_id = vote.id.clone(); - let vote = Vote { - to: PublicUrl::Public, - object: object.ap_id(), - cc: [community.actor_id()], - kind, - common: ActivityCommonFields { - context: lemmy_context(), - id: id.clone(), - actor: actor.actor_id(), - unparsed: Default::default(), - }, - }; let activity = AnnouncableActivities::Vote(vote); - send_to_community_new(activity, &id, actor, &community, vec![], context).await + send_to_community_new(activity, &vote_id, actor, &community, vec![], context).await } } @@ -107,8 +119,8 @@ impl ActivityHandler for Vote { context: &LemmyContext, request_counter: &mut i32, ) -> Result<(), LemmyError> { - verify_activity(self.common())?; - verify_person_in_community(&self.common.actor, &self.cc[0], context, request_counter).await?; + verify_activity(self)?; + verify_person_in_community(&self.actor, &self.cc[0], context, request_counter).await?; Ok(()) } @@ -117,8 +129,7 @@ impl ActivityHandler for Vote { context: &LemmyContext, request_counter: &mut i32, ) -> Result<(), LemmyError> { - let actor = - get_or_fetch_and_upsert_person(&self.common.actor, context, request_counter).await?; + let actor = get_or_fetch_and_upsert_person(&self.actor, context, request_counter).await?; let object = get_or_fetch_and_insert_post_or_comment(&self.object, context, request_counter).await?; match object { @@ -126,8 +137,4 @@ impl ActivityHandler for Vote { PostOrComment::Comment(c) => vote_comment(&self.kind, actor, c.deref(), context).await, } } - - fn common(&self) -> &ActivityCommonFields { - &self.common - } } diff --git a/crates/apub/src/activity_queue.rs b/crates/apub/src/activity_queue.rs index 48cccc61..01959c7d 100644 --- a/crates/apub/src/activity_queue.rs +++ b/crates/apub/src/activity_queue.rs @@ -1,16 +1,10 @@ use crate::{ activities::community::announce::{AnnouncableActivities, AnnounceActivity}, - check_is_apub_id_valid, extensions::signatures::sign_and_send, insert_activity, ActorType, - CommunityType, APUB_JSON_CONTENT_TYPE, }; -use activitystreams::{ - base::{BaseExt, Extends, ExtendsExt}, - object::AsObject, -}; use anyhow::{anyhow, Context, Error}; use background_jobs::{ create_server, @@ -21,95 +15,15 @@ use background_jobs::{ QueueHandle, WorkerConfig, }; -use itertools::Itertools; -use lemmy_db_schema::source::{community::Community, person::Person}; +use lemmy_db_schema::source::community::Community; use lemmy_utils::{location_info, settings::structs::Settings, LemmyError}; use lemmy_websocket::LemmyContext; -use log::{debug, info, warn}; +use log::{info, warn}; use reqwest::Client; use serde::{Deserialize, Serialize}; use std::{collections::BTreeMap, env, fmt::Debug, future::Future, pin::Pin}; use url::Url; -/// From a local community, send activity to all remote followers. -/// -/// * `activity` the apub activity to send -/// * `community` the sending community -/// * `extra_inbox` actor inbox which should receive the activity, in addition to followers -pub(crate) async fn send_to_community_followers( - activity: T, - community: &Community, - extra_inbox: Option, - context: &LemmyContext, -) -> Result<(), LemmyError> -where - T: AsObject + Extends + Debug + BaseExt, - Kind: Serialize, - >::Error: From + Send + Sync + 'static, -{ - let extra_inbox: Vec = extra_inbox.into_iter().collect(); - let follower_inboxes: Vec = vec![ - community.get_follower_inboxes(context.pool()).await?, - extra_inbox, - ] - .iter() - .flatten() - .unique() - .filter(|inbox| inbox.host_str() != Some(&Settings::get().hostname)) - .filter(|inbox| check_is_apub_id_valid(inbox, false).is_ok()) - .map(|inbox| inbox.to_owned()) - .collect(); - debug!( - "Sending activity {:?} to followers of {}", - &activity.id_unchecked().map(ToString::to_string), - &community.actor_id - ); - - send_activity_internal(context, activity, community, follower_inboxes, true, false).await?; - - Ok(()) -} - -/// Sends an activity from a local person to a remote community. -/// -/// * `activity` the activity to send -/// * `creator` the creator of the activity -/// * `community` the destination community -/// * `object_actor` if the object of the activity is an actor, it should be passed here so it can -/// be sent directly to the actor -/// -pub(crate) async fn send_to_community( - activity: T, - creator: &Person, - community: &Community, - object_actor: Option, - context: &LemmyContext, -) -> Result<(), LemmyError> -where - T: AsObject + Extends + Debug + BaseExt, - Kind: Serialize, - >::Error: From + Send + Sync + 'static, -{ - // if this is a local community, we need to do an announce from the community instead - if community.local { - community - .send_announce(activity.into_any_base()?, object_actor, context) - .await?; - } else { - let inbox = community.get_shared_inbox_or_inbox_url(); - check_is_apub_id_valid(&inbox, false)?; - debug!( - "Sending activity {:?} to community {}", - &activity.id_unchecked().map(ToString::to_string), - &community.actor_id - ); - // dont send to object_actor here, as that is responsibility of the community itself - send_activity_internal(context, activity, creator, vec![inbox], true, false).await?; - } - - Ok(()) -} - pub(crate) async fn send_to_community_new( activity: AnnouncableActivities, activity_id: &Url, @@ -184,62 +98,6 @@ where Ok(()) } -/// Create new `SendActivityTasks`, which will deliver the given activity to inboxes, as well as -/// handling signing and retrying failed deliveres. -/// -/// The caller of this function needs to remove any blocked domains from `to`, -/// using `check_is_apub_id_valid()`. -async fn send_activity_internal( - context: &LemmyContext, - activity: T, - actor: &dyn ActorType, - inboxes: Vec, - insert_into_db: bool, - sensitive: bool, -) -> Result<(), LemmyError> -where - T: AsObject + Extends + Debug, - Kind: Serialize, - >::Error: From + Send + Sync + 'static, -{ - if !Settings::get().federation.enabled || inboxes.is_empty() { - return Ok(()); - } - - // Don't send anything to ourselves - let hostname = Settings::get().get_hostname_without_port()?; - let inboxes: Vec<&Url> = inboxes - .iter() - .filter(|i| i.domain().expect("valid inbox url") != hostname) - .collect(); - - let activity = activity.into_any_base()?; - let serialised_activity = serde_json::to_string(&activity)?; - - // This is necessary because send_comment and send_comment_mentions - // might send the same ap_id - if insert_into_db { - let id = activity.id().context(location_info!())?; - insert_activity(id, activity.clone(), true, sensitive, context.pool()).await?; - } - - for i in inboxes { - let message = SendActivityTask { - activity: serialised_activity.to_owned(), - inbox: i.to_owned(), - actor_id: actor.actor_id(), - private_key: actor.private_key().context(location_info!())?, - }; - if env::var("LEMMY_TEST_SEND_SYNC").is_ok() { - do_send(message, &Client::default()).await?; - } else { - context.activity_queue.queue::(message)?; - } - } - - Ok(()) -} - #[derive(Clone, Debug, Deserialize, Serialize)] struct SendActivityTask { activity: String, diff --git a/crates/apub/src/http/community.rs b/crates/apub/src/http/community.rs index 587c6cfb..cd75031d 100644 --- a/crates/apub/src/http/community.rs +++ b/crates/apub/src/http/community.rs @@ -1,10 +1,14 @@ use crate::{ + activities::{ + community::announce::{AnnouncableActivities, AnnounceActivity}, + extract_community, + following::{follow::FollowCommunity, undo::UndoFollowCommunity}, + }, extensions::context::lemmy_context, generate_moderators_url, http::{ create_apub_response, create_apub_tombstone_response, - inbox_enums::GroupInboxActivities, payload_to_string, receive_activity, }, @@ -18,6 +22,7 @@ use activitystreams::{ }; use actix_web::{body::Body, web, web::Payload, HttpRequest, HttpResponse}; use lemmy_api_common::blocking; +use lemmy_apub_lib::{ActivityFields, ActivityHandler}; use lemmy_db_queries::source::{activity::Activity_, community::Community_}; use lemmy_db_schema::source::{activity::Activity, community::Community}; use lemmy_db_views_actor::{ @@ -26,7 +31,8 @@ use lemmy_db_views_actor::{ }; use lemmy_utils::LemmyError; use lemmy_websocket::LemmyContext; -use serde::Deserialize; +use log::trace; +use serde::{Deserialize, Serialize}; #[derive(Deserialize)] pub(crate) struct CommunityQuery { @@ -52,6 +58,14 @@ pub(crate) async fn get_apub_community_http( } } +#[derive(Clone, Debug, Deserialize, Serialize, ActivityHandler, ActivityFields)] +#[serde(untagged)] +pub enum GroupInboxActivities { + FollowCommunity(FollowCommunity), + UndoFollowCommunity(UndoFollowCommunity), + AnnouncableActivities(AnnouncableActivities), +} + /// Handler for all incoming receive to community inboxes. pub async fn community_inbox( request: HttpRequest, @@ -60,7 +74,26 @@ pub async fn community_inbox( context: web::Data, ) -> Result { let unparsed = payload_to_string(payload).await?; - receive_activity::(request, &unparsed, context).await + trace!("Received community inbox activity {}", unparsed); + let activity = serde_json::from_str::(&unparsed)?; + + receive_group_inbox(activity.clone(), request, &context).await?; + + if let GroupInboxActivities::AnnouncableActivities(announcable) = activity { + let community = extract_community(&announcable.cc(), &context, &mut 0).await?; + if community.local { + AnnounceActivity::send(announcable, &community, vec![], &context).await?; + } + } + Ok(HttpResponse::Ok().finish()) +} + +pub(in crate::http) async fn receive_group_inbox( + activity: GroupInboxActivities, + request: HttpRequest, + context: &LemmyContext, +) -> Result { + receive_activity(request, activity.clone(), context).await } /// Returns an empty followers collection, only populating the size (for privacy). diff --git a/crates/apub/src/http/inbox_enums.rs b/crates/apub/src/http/inbox_enums.rs deleted file mode 100644 index 1ed210bb..00000000 --- a/crates/apub/src/http/inbox_enums.rs +++ /dev/null @@ -1,81 +0,0 @@ -use crate::activities::{ - comment::create_or_update::CreateOrUpdateComment, - community::{ - add_mod::AddMod, - announce::AnnounceActivity, - block_user::BlockUserFromCommunity, - undo_block_user::UndoBlockUserFromCommunity, - update::UpdateCommunity, - }, - deletion::{delete::Delete, undo_delete::UndoDelete}, - following::{accept::AcceptFollowCommunity, follow::FollowCommunity, undo::UndoFollowCommunity}, - post::create_or_update::CreateOrUpdatePost, - private_message::{ - create_or_update::CreateOrUpdatePrivateMessage, - delete::DeletePrivateMessage, - undo_delete::UndoDeletePrivateMessage, - }, - removal::{remove::RemoveMod, undo_remove::UndoRemovePostCommentOrCommunity}, - voting::{undo_vote::UndoVote, vote::Vote}, -}; -use lemmy_apub_lib::{ActivityCommonFields, ActivityHandler}; -use lemmy_utils::LemmyError; -use lemmy_websocket::LemmyContext; -use serde::{Deserialize, Serialize}; - -#[derive(Clone, Debug, Deserialize, Serialize, ActivityHandler)] -#[serde(untagged)] -pub enum PersonInboxActivities { - AcceptFollowCommunity(AcceptFollowCommunity), - CreateOrUpdatePrivateMessage(CreateOrUpdatePrivateMessage), - DeletePrivateMessage(DeletePrivateMessage), - UndoDeletePrivateMessage(UndoDeletePrivateMessage), - AnnounceActivity(Box), -} - -#[derive(Clone, Debug, Deserialize, Serialize, ActivityHandler)] -#[serde(untagged)] -pub enum GroupInboxActivities { - FollowCommunity(FollowCommunity), - UndoFollowCommunity(UndoFollowCommunity), - CreateOrUpdateComment(CreateOrUpdateComment), - CreateOrUpdatePost(Box), - Vote(Vote), - UndoVote(UndoVote), - DeletePostCommentOrCommunity(Delete), - UndoDeletePostCommentOrCommunity(UndoDelete), - UndoRemovePostCommentOrCommunity(UndoRemovePostCommentOrCommunity), - UpdateCommunity(Box), - BlockUserFromCommunity(BlockUserFromCommunity), - UndoBlockUserFromCommunity(UndoBlockUserFromCommunity), - AddMod(AddMod), - RemoveMod(RemoveMod), -} - -#[derive(Clone, Debug, Deserialize, Serialize, ActivityHandler)] -#[serde(untagged)] -pub enum SharedInboxActivities { - // received by group - FollowCommunity(FollowCommunity), - UndoFollowCommunity(UndoFollowCommunity), - CreateOrUpdateComment(CreateOrUpdateComment), - CreateOrUpdatePost(Box), - Vote(Vote), - UndoVote(UndoVote), - Delete(Delete), - UndoDelete(UndoDelete), - UndoRemovePostCommentOrCommunity(UndoRemovePostCommentOrCommunity), - UpdateCommunity(Box), - BlockUserFromCommunity(BlockUserFromCommunity), - UndoBlockUserFromCommunity(UndoBlockUserFromCommunity), - AddMod(AddMod), - RemoveMod(RemoveMod), - // received by person - AcceptFollowCommunity(AcceptFollowCommunity), - // Note, pm activities need to be at the end, otherwise comments will end up here. We can probably - // avoid this problem by replacing createpm.object with our own struct, instead of NoteExt. - CreateOrUpdatePrivateMessage(CreateOrUpdatePrivateMessage), - DeletePrivateMessage(DeletePrivateMessage), - UndoDeletePrivateMessage(UndoDeletePrivateMessage), - AnnounceActivity(Box), -} diff --git a/crates/apub/src/http/mod.rs b/crates/apub/src/http/mod.rs index 477345fd..6fe8d182 100644 --- a/crates/apub/src/http/mod.rs +++ b/crates/apub/src/http/mod.rs @@ -2,7 +2,10 @@ use crate::{ check_is_apub_id_valid, extensions::signatures::verify_signature, fetcher::get_or_fetch_and_upsert_actor, - http::inbox_enums::SharedInboxActivities, + http::{ + community::{receive_group_inbox, GroupInboxActivities}, + person::{receive_person_inbox, PersonInboxActivities}, + }, insert_activity, APUB_JSON_CONTENT_TYPE, }; @@ -17,29 +20,47 @@ use anyhow::{anyhow, Context}; use futures::StreamExt; use http::StatusCode; use lemmy_api_common::blocking; -use lemmy_apub_lib::ActivityHandler; +use lemmy_apub_lib::{ActivityFields, ActivityHandler}; use lemmy_db_queries::{source::activity::Activity_, DbPool}; use lemmy_db_schema::source::activity::Activity; use lemmy_utils::{location_info, settings::structs::Settings, LemmyError}; use lemmy_websocket::LemmyContext; +use log::{info, trace}; use serde::{Deserialize, Serialize}; use std::{fmt::Debug, io::Read}; use url::Url; mod comment; mod community; -mod inbox_enums; mod person; mod post; pub mod routes; +#[derive(Clone, Debug, Deserialize, Serialize, ActivityHandler, ActivityFields)] +#[serde(untagged)] +pub enum SharedInboxActivities { + GroupInboxActivities(GroupInboxActivities), + // Note, pm activities need to be at the end, otherwise comments will end up here. We can probably + // avoid this problem by replacing createpm.object with our own struct, instead of NoteExt. + PersonInboxActivities(PersonInboxActivities), +} + pub async fn shared_inbox( request: HttpRequest, payload: Payload, context: web::Data, ) -> Result { let unparsed = payload_to_string(payload).await?; - receive_activity::(request, &unparsed, context).await + trace!("Received shared inbox activity {}", unparsed); + let activity = serde_json::from_str::(&unparsed)?; + match activity { + SharedInboxActivities::GroupInboxActivities(g) => { + receive_group_inbox(g, request, &context).await + } + SharedInboxActivities::PersonInboxActivities(p) => { + receive_person_inbox(p, request, &context).await + } + } } async fn payload_to_string(mut payload: Payload) -> Result { @@ -55,36 +76,36 @@ async fn payload_to_string(mut payload: Payload) -> Result { // TODO: move most of this code to library async fn receive_activity<'a, T>( request: HttpRequest, - activity: &'a str, - context: web::Data, + activity: T, + context: &LemmyContext, ) -> Result where - T: ActivityHandler + Clone + Deserialize<'a> + Serialize + std::fmt::Debug + Send + 'static, + T: ActivityHandler + + ActivityFields + + Clone + + Deserialize<'a> + + Serialize + + std::fmt::Debug + + Send + + 'static, { - let activity = serde_json::from_str::(activity)?; - let activity_data = activity.common(); - let request_counter = &mut 0; - let actor = - get_or_fetch_and_upsert_actor(&activity_data.actor, &context, request_counter).await?; + let actor = get_or_fetch_and_upsert_actor(activity.actor(), context, request_counter).await?; verify_signature(&request, &actor.public_key().context(location_info!())?)?; // Do nothing if we received the same activity before - if is_activity_already_known(context.pool(), activity_data.id_unchecked()).await? { + if is_activity_already_known(context.pool(), activity.id_unchecked()).await? { return Ok(HttpResponse::Ok().finish()); } - check_is_apub_id_valid(&activity_data.actor, false)?; - println!( - "Verifying activity {}", - activity_data.id_unchecked().to_string() - ); - activity.verify(&context, request_counter).await?; + check_is_apub_id_valid(activity.actor(), false)?; + info!("Verifying activity {}", activity.id_unchecked().to_string()); + activity.verify(context, request_counter).await?; assert_activity_not_local(&activity)?; // Log the activity, so we avoid receiving and parsing it twice. Note that this could still happen // if we receive the same activity twice in very quick succession. insert_activity( - activity_data.id_unchecked(), + activity.id_unchecked(), activity.clone(), false, true, @@ -92,11 +113,8 @@ where ) .await?; - println!( - "Receiving activity {}", - activity_data.id_unchecked().to_string() - ); - activity.receive(&context, request_counter).await?; + info!("Receiving activity {}", activity.id_unchecked().to_string()); + activity.receive(context, request_counter).await?; Ok(HttpResponse::Ok().finish()) } @@ -168,12 +186,8 @@ pub(crate) async fn is_activity_already_known( } } -fn assert_activity_not_local(activity: &T) -> Result<(), LemmyError> { - let activity_domain = activity - .common() - .id_unchecked() - .domain() - .context(location_info!())?; +fn assert_activity_not_local(activity: &T) -> Result<(), LemmyError> { + let activity_domain = activity.id_unchecked().domain().context(location_info!())?; if activity_domain == Settings::get().hostname { return Err( diff --git a/crates/apub/src/http/person.rs b/crates/apub/src/http/person.rs index 42f25bb6..dcab6001 100644 --- a/crates/apub/src/http/person.rs +++ b/crates/apub/src/http/person.rs @@ -1,9 +1,17 @@ use crate::{ + activities::{ + community::announce::{AnnouncableActivities, AnnounceActivity}, + following::accept::AcceptFollowCommunity, + private_message::{ + create_or_update::CreateOrUpdatePrivateMessage, + delete::DeletePrivateMessage, + undo_delete::UndoDeletePrivateMessage, + }, + }, extensions::context::lemmy_context, http::{ create_apub_response, create_apub_tombstone_response, - inbox_enums::PersonInboxActivities, payload_to_string, receive_activity, }, @@ -16,11 +24,13 @@ use activitystreams::{ }; use actix_web::{body::Body, web, web::Payload, HttpRequest, HttpResponse}; use lemmy_api_common::blocking; +use lemmy_apub_lib::{ActivityFields, ActivityHandler}; use lemmy_db_queries::source::person::Person_; use lemmy_db_schema::source::person::Person; use lemmy_utils::LemmyError; use lemmy_websocket::LemmyContext; -use serde::Deserialize; +use log::trace; +use serde::{Deserialize, Serialize}; use url::Url; #[derive(Deserialize)] @@ -49,6 +59,18 @@ pub(crate) async fn get_apub_person_http( } } +#[derive(Clone, Debug, Deserialize, Serialize, ActivityHandler, ActivityFields)] +#[serde(untagged)] +pub enum PersonInboxActivities { + AcceptFollowCommunity(AcceptFollowCommunity), + /// Some activities can also be sent from user to user, eg a comment with mentions + AnnouncableActivities(AnnouncableActivities), + CreateOrUpdatePrivateMessage(CreateOrUpdatePrivateMessage), + DeletePrivateMessage(DeletePrivateMessage), + UndoDeletePrivateMessage(UndoDeletePrivateMessage), + AnnounceActivity(Box), +} + pub async fn person_inbox( request: HttpRequest, payload: Payload, @@ -56,7 +78,17 @@ pub async fn person_inbox( context: web::Data, ) -> Result { let unparsed = payload_to_string(payload).await?; - receive_activity::(request, &unparsed, context).await + trace!("Received person inbox activity {}", unparsed); + let activity = serde_json::from_str::(&unparsed)?; + receive_person_inbox(activity, request, &context).await +} + +pub(in crate::http) async fn receive_person_inbox( + activity: PersonInboxActivities, + request: HttpRequest, + context: &LemmyContext, +) -> Result { + receive_activity(request, activity, context).await } pub(crate) async fn get_apub_person_outbox( diff --git a/crates/apub/src/lib.rs b/crates/apub/src/lib.rs index 47714300..839e7d14 100644 --- a/crates/apub/src/lib.rs +++ b/crates/apub/src/lib.rs @@ -10,7 +10,6 @@ pub mod migrations; pub mod objects; use crate::extensions::signatures::PublicKey; -use activitystreams::base::AnyBase; use anyhow::{anyhow, Context}; use diesel::NotFound; use lemmy_api_common::blocking; @@ -139,41 +138,6 @@ trait ActorType { pub trait CommunityType { fn followers_url(&self) -> Url; async fn get_follower_inboxes(&self, pool: &DbPool) -> Result, LemmyError>; - - async fn send_update(&self, mod_: Person, context: &LemmyContext) -> Result<(), LemmyError>; - - async fn send_announce( - &self, - activity: AnyBase, - object: Option, - context: &LemmyContext, - ) -> Result<(), LemmyError>; - - async fn send_add_mod( - &self, - actor: &Person, - added_mod: Person, - context: &LemmyContext, - ) -> Result<(), LemmyError>; - async fn send_remove_mod( - &self, - actor: &Person, - removed_mod: Person, - context: &LemmyContext, - ) -> Result<(), LemmyError>; - - async fn send_block_user( - &self, - actor: &Person, - blocked_user: Person, - context: &LemmyContext, - ) -> Result<(), LemmyError>; - async fn send_undo_block_user( - &self, - actor: &Person, - blocked_user: Person, - context: &LemmyContext, - ) -> Result<(), LemmyError>; } pub enum EndpointType { diff --git a/crates/apub_lib/src/lib.rs b/crates/apub_lib/src/lib.rs index df73d5e8..cc88b79c 100644 --- a/crates/apub_lib/src/lib.rs +++ b/crates/apub_lib/src/lib.rs @@ -1,33 +1,15 @@ pub mod values; -use activitystreams::{ - base::AnyBase, - error::DomainError, - primitives::OneOrMany, - unparsed::Unparsed, -}; +use activitystreams::error::DomainError; pub use lemmy_apub_lib_derive::*; use lemmy_utils::LemmyError; use lemmy_websocket::LemmyContext; use url::Url; -#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)] -#[serde(rename_all = "camelCase")] -pub struct ActivityCommonFields { - #[serde(rename = "@context")] - pub context: OneOrMany, - pub id: Url, - pub actor: Url, - - // unparsed fields - #[serde(flatten)] - pub unparsed: Unparsed, -} - -impl ActivityCommonFields { - pub fn id_unchecked(&self) -> &Url { - &self.id - } +pub trait ActivityFields { + fn id_unchecked(&self) -> &Url; + fn actor(&self) -> &Url; + fn cc(&self) -> Vec; } #[async_trait::async_trait(?Send)] @@ -43,7 +25,6 @@ pub trait ActivityHandler { context: &LemmyContext, request_counter: &mut i32, ) -> Result<(), LemmyError>; - fn common(&self) -> &ActivityCommonFields; } pub fn verify_domains_match(a: &Url, b: &Url) -> Result<(), LemmyError> { @@ -53,13 +34,6 @@ pub fn verify_domains_match(a: &Url, b: &Url) -> Result<(), LemmyError> { Ok(()) } -pub fn verify_domains_match_opt(a: &Url, b: Option<&Url>) -> Result<(), LemmyError> { - if let Some(b2) = b { - return verify_domains_match(a, b2); - } - Ok(()) -} - pub fn verify_urls_match(a: &Url, b: &Url) -> Result<(), LemmyError> { if a != b { return Err(DomainError.into()); diff --git a/crates/apub_lib_derive/src/lib.rs b/crates/apub_lib_derive/src/lib.rs index d35454a3..e7a1912c 100644 --- a/crates/apub_lib_derive/src/lib.rs +++ b/crates/apub_lib_derive/src/lib.rs @@ -1,6 +1,6 @@ use proc_macro2::TokenStream; use quote::quote; -use syn::{parse_macro_input, Data, DeriveInput}; +use syn::{parse_macro_input, Data, DeriveInput, Fields::Unnamed, Ident, Variant}; /// Generates implementation ActivityHandler for an enum, which looks like the following (handling /// all enum variants). @@ -46,104 +46,118 @@ use syn::{parse_macro_input, Data, DeriveInput}; /// } /// /// ``` -/// -/// TODO: consider replacing this macro with https://crates.io/crates/typetag crate, though it -/// doesnt support untagged enums which we need for apub. #[proc_macro_derive(ActivityHandler)] pub fn derive_activity_handler(input: proc_macro::TokenStream) -> proc_macro::TokenStream { - // Parse the input tokens into a syntax tree. let input = parse_macro_input!(input as DeriveInput); - // Used in the quasi-quotation below as `#name`. - let name = input.ident; + let enum_name = input.ident; let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl(); - let input_enum = if let Data::Enum(d) = input.data { - d + let enum_variants = if let Data::Enum(d) = input.data { + d.variants } else { unimplemented!() }; - let impl_verify = input_enum - .variants + let body_verify = quote! {a.verify(context, request_counter).await}; + let impl_verify = enum_variants .iter() - .map(|variant| variant_impl_verify(&name, variant)); - let impl_receive = input_enum - .variants + .map(|v| generate_match_arm(&enum_name, v, &body_verify)); + let body_receive = quote! {a.receive(context, request_counter).await}; + let impl_receive = enum_variants .iter() - .map(|variant| variant_impl_receive(&name, variant)); - let impl_common = input_enum - .variants - .iter() - .map(|variant| variant_impl_common(&name, variant)); + .map(|v| generate_match_arm(&enum_name, v, &body_receive)); - // The generated impl. let expanded = quote! { #[async_trait::async_trait(?Send)] - impl #impl_generics lemmy_apub_lib::ActivityHandler for #name #ty_generics #where_clause { + impl #impl_generics lemmy_apub_lib::ActivityHandler for #enum_name #ty_generics #where_clause { async fn verify( &self, - context: &LemmyContext, + context: &lemmy_websocket::LemmyContext, request_counter: &mut i32, - ) -> Result<(), LemmyError> { + ) -> Result<(), lemmy_utils::LemmyError> { match self { #(#impl_verify)* } } async fn receive( self, - context: &LemmyContext, + context: &lemmy_websocket::LemmyContext, request_counter: &mut i32, - ) -> Result<(), LemmyError> { + ) -> Result<(), lemmy_utils::LemmyError> { match self { #(#impl_receive)* } } - fn common(&self) -> &ActivityCommonFields { - match self { - #(#impl_common)* - } - } } }; - - // Hand the output tokens back to the compiler. - proc_macro::TokenStream::from(expanded) + expanded.into() } -fn variant_impl_common(name: &syn::Ident, variant: &syn::Variant) -> TokenStream { +fn generate_match_arm(enum_name: &Ident, variant: &Variant, body: &TokenStream) -> TokenStream { let id = &variant.ident; match &variant.fields { - syn::Fields::Unnamed(_) => { + Unnamed(_) => { quote! { - #name::#id(a) => a.common(), + #enum_name::#id(a) => #body, } } _ => unimplemented!(), } } -fn variant_impl_verify(name: &syn::Ident, variant: &syn::Variant) -> TokenStream { - let id = &variant.ident; - match &variant.fields { - syn::Fields::Unnamed(_) => { +#[proc_macro_derive(ActivityFields)] +pub fn derive_activity_fields(input: proc_macro::TokenStream) -> proc_macro::TokenStream { + let input = parse_macro_input!(input as DeriveInput); + + let name = input.ident; + + let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl(); + + let expanded = match input.data { + Data::Enum(e) => { + let variants = e.variants; + let impl_id = variants + .iter() + .map(|v| generate_match_arm(&name, v, "e! {a.id_unchecked()})); + let impl_actor = variants + .iter() + .map(|v| generate_match_arm(&name, v, "e! {a.actor()})); + let impl_cc = variants + .iter() + .map(|v| generate_match_arm(&name, v, "e! {a.cc()})); quote! { - #name::#id(a) => a.verify(context, request_counter).await, + impl #impl_generics lemmy_apub_lib::ActivityFields for #name #ty_generics #where_clause { + fn id_unchecked(&self) -> &url::Url { match self { #(#impl_id)* } } + fn actor(&self) -> &url::Url { match self { #(#impl_actor)* } } + fn cc(&self) -> Vec { match self { #(#impl_cc)* } } + } } } - _ => unimplemented!(), - } -} - -fn variant_impl_receive(name: &syn::Ident, variant: &syn::Variant) -> TokenStream { - let id = &variant.ident; - match &variant.fields { - syn::Fields::Unnamed(_) => { + Data::Struct(s) => { + // check if the struct has a field "cc", and generate impl for cc() function depending on that + let has_cc = if let syn::Fields::Named(n) = s.fields { + n.named + .iter() + .any(|i| format!("{}", i.ident.as_ref().unwrap()) == "cc") + } else { + unimplemented!() + }; + let cc_impl = if has_cc { + quote! {self.cc.clone().into()} + } else { + quote! {vec![]} + }; quote! { - #name::#id(a) => a.receive(context, request_counter).await, + impl #impl_generics lemmy_apub_lib::ActivityFields for #name #ty_generics #where_clause { + fn id_unchecked(&self) -> &url::Url { &self.id } + fn actor(&self) -> &url::Url { &self.actor } + fn cc(&self) -> Vec { #cc_impl } + } } } _ => unimplemented!(), - } + }; + expanded.into() }