diff --git a/crates/api_crud/src/community/delete.rs b/crates/api_crud/src/community/delete.rs index b94c153b2..fb01b08ec 100644 --- a/crates/api_crud/src/community/delete.rs +++ b/crates/api_crud/src/community/delete.rs @@ -52,9 +52,13 @@ impl PerformCrud for DeleteCommunity { // Send apub messages if deleted { - updated_community.send_delete(context).await?; + updated_community + .send_delete(local_user_view.person.to_owned(), context) + .await?; } else { - updated_community.send_undo_delete(context).await?; + updated_community + .send_undo_delete(local_user_view.person.to_owned(), context) + .await?; } let community_id = data.community_id; diff --git a/crates/api_crud/src/community/update.rs b/crates/api_crud/src/community/update.rs index 9e8339fbe..644581a5d 100644 --- a/crates/api_crud/src/community/update.rs +++ b/crates/api_crud/src/community/update.rs @@ -5,6 +5,7 @@ use lemmy_api_common::{ community::{CommunityResponse, EditCommunity}, get_local_user_view_from_jwt, }; +use lemmy_apub::CommunityType; use lemmy_db_queries::{diesel_option_overwrite_to_url, Crud}; use lemmy_db_schema::{ naive_now, @@ -70,17 +71,15 @@ impl PerformCrud for EditCommunity { }; let community_id = data.community_id; - match blocking(context.pool(), move |conn| { + let updated_community = blocking(context.pool(), move |conn| { Community::update(conn, community_id, &community_form) }) .await? - { - Ok(community) => community, - Err(_e) => return Err(ApiError::err("couldnt_update_community").into()), - }; + .map_err(|_| ApiError::err("couldnt_update_community"))?; - // TODO there needs to be some kind of an apub update - // process for communities and users + updated_community + .send_update(local_user_view.person.to_owned(), context) + .await?; let community_id = data.community_id; let person_id = local_user_view.person.id; diff --git a/crates/apub/src/activities/send/community.rs b/crates/apub/src/activities/send/community.rs index 5b93cbb2b..f5d321428 100644 --- a/crates/apub/src/activities/send/community.rs +++ b/crates/apub/src/activities/send/community.rs @@ -6,6 +6,7 @@ use crate::{ fetcher::{get_or_fetch_and_upsert_actor, person::get_or_fetch_and_upsert_person}, generate_moderators_url, insert_activity, + objects::ToApub, ActorType, CommunityType, }; @@ -20,6 +21,7 @@ use activitystreams::{ LikeType, RemoveType, UndoType, + UpdateType, }, Accept, ActorAndObjectRefExt, @@ -31,6 +33,7 @@ use activitystreams::{ OptTargetRefExt, Remove, Undo, + Update, }, base::{AnyBase, BaseExt, ExtendsExt}, object::ObjectExt, @@ -101,36 +104,95 @@ impl CommunityType for Community { Ok(()) } - /// If the creator of a community deletes the community, send this to all followers. - async fn send_delete(&self, context: &LemmyContext) -> Result<(), LemmyError> { - let mut delete = Delete::new(self.actor_id(), self.actor_id()); - delete - .set_many_contexts(lemmy_context()?) - .set_id(generate_activity_id(DeleteType::Delete)?) - .set_to(public()) - .set_many_ccs(vec![self.followers_url()]); + /// 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(), + self.to_apub(context.pool()).await?.into_any_base()?, + ); + 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(()) + } - send_to_community_followers(delete, self, None, context).await?; + /// If the creator of a community deletes the community, send this to all followers. + /// + /// We need to handle deletion by a remote mod separately. + async fn send_delete(&self, mod_: Person, context: &LemmyContext) -> Result<(), LemmyError> { + // Local mod, send directly from community to followers + if self.local { + let mut delete = Delete::new(self.actor_id(), self.actor_id()); + delete + .set_many_contexts(lemmy_context()?) + .set_id(generate_activity_id(DeleteType::Delete)?) + .set_to(public()) + .set_many_ccs(vec![self.followers_url()]); + + send_to_community_followers(delete, self, None, context).await?; + } + // Remote mod, send from mod to community + else { + let mut delete = Delete::new(mod_.actor_id(), self.actor_id()); + delete + .set_many_contexts(lemmy_context()?) + .set_id(generate_activity_id(DeleteType::Delete)?) + .set_to(public()) + .set_many_ccs(vec![self.actor_id()]); + + send_to_community(delete, &mod_, self, None, context).await?; + } Ok(()) } /// If the creator of a community reverts the deletion of a community, send this to all followers. - async fn send_undo_delete(&self, context: &LemmyContext) -> Result<(), LemmyError> { - let mut delete = Delete::new(self.actor_id(), self.actor_id()); - delete - .set_many_contexts(lemmy_context()?) - .set_id(generate_activity_id(DeleteType::Delete)?) - .set_to(public()) - .set_many_ccs(vec![self.followers_url()]); - - let mut undo = Undo::new(self.actor_id(), delete.into_any_base()?); - undo - .set_many_contexts(lemmy_context()?) - .set_id(generate_activity_id(UndoType::Undo)?) - .set_to(public()) - .set_many_ccs(vec![self.followers_url()]); - - send_to_community_followers(undo, self, None, context).await?; + /// + /// We need to handle undelete by a remote mod separately. + async fn send_undo_delete(&self, mod_: Person, context: &LemmyContext) -> Result<(), LemmyError> { + // Local mod, send directly from community to followers + if self.local { + let mut delete = Delete::new(self.actor_id(), self.actor_id()); + delete + .set_many_contexts(lemmy_context()?) + .set_id(generate_activity_id(DeleteType::Delete)?) + .set_to(public()) + .set_many_ccs(vec![self.followers_url()]); + + let mut undo = Undo::new(self.actor_id(), delete.into_any_base()?); + undo + .set_many_contexts(lemmy_context()?) + .set_id(generate_activity_id(UndoType::Undo)?) + .set_to(public()) + .set_many_ccs(vec![self.followers_url()]); + + send_to_community_followers(undo, self, None, context).await?; + } + // Remote mod, send from mod to community + else { + let mut delete = Delete::new(mod_.actor_id(), self.actor_id()); + delete + .set_many_contexts(lemmy_context()?) + .set_id(generate_activity_id(DeleteType::Delete)?) + .set_to(public()) + .set_many_ccs(vec![self.actor_id()]); + + let mut undo = Undo::new(mod_.actor_id(), delete.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, &mod_, self, None, context).await?; + } Ok(()) } diff --git a/crates/apub/src/lib.rs b/crates/apub/src/lib.rs index 37e959014..89109318a 100644 --- a/crates/apub/src/lib.rs +++ b/crates/apub/src/lib.rs @@ -7,11 +7,14 @@ pub mod extensions; pub mod fetcher; pub mod objects; -use crate::extensions::{ - group_extension::GroupExtension, - page_extension::PageExtension, - person_extension::PersonExtension, - signatures::{PublicKey, PublicKeyExtension}, +use crate::{ + extensions::{ + group_extension::GroupExtension, + page_extension::PageExtension, + person_extension::PersonExtension, + signatures::{PublicKey, PublicKeyExtension}, + }, + fetcher::community::get_or_fetch_and_upsert_community, }; use activitystreams::{ activity::Follow, @@ -44,7 +47,8 @@ use std::net::IpAddr; use url::{ParseError, Url}; /// Activitystreams type for community -type GroupExt = Ext2>, GroupExtension, PublicKeyExtension>; +pub type GroupExt = + Ext2>, GroupExtension, PublicKeyExtension>; /// Activitystreams type for person type PersonExt = Ext2>, PersonExtension, PublicKeyExtension>; /// Activitystreams type for post @@ -171,9 +175,11 @@ pub trait ActorType { /// Outbox URL is not generally used by Lemmy, so it can be generated on the fly (but only for /// local actors). fn get_outbox_url(&self) -> Result { + /* TODO if !self.is_local() { return Err(anyhow!("get_outbox_url() called for remote actor").into()); } + */ Ok(Url::parse(&format!("{}/outbox", &self.actor_id()))?) } @@ -199,8 +205,9 @@ pub trait CommunityType { context: &LemmyContext, ) -> Result<(), LemmyError>; - async fn send_delete(&self, context: &LemmyContext) -> Result<(), LemmyError>; - async fn send_undo_delete(&self, context: &LemmyContext) -> Result<(), LemmyError>; + async fn send_update(&self, mod_: Person, context: &LemmyContext) -> Result<(), LemmyError>; + async fn send_delete(&self, mod_: Person, context: &LemmyContext) -> Result<(), LemmyError>; + async fn send_undo_delete(&self, mod_: Person, context: &LemmyContext) -> Result<(), LemmyError>; async fn send_remove(&self, context: &LemmyContext) -> Result<(), LemmyError>; async fn send_undo_remove(&self, context: &LemmyContext) -> Result<(), LemmyError>; @@ -366,7 +373,7 @@ pub async fn find_post_or_comment_by_id( } #[derive(Debug)] -pub(crate) enum Object { +pub enum Object { Comment(Box), Post(Box), Community(Box), @@ -374,10 +381,7 @@ pub(crate) enum Object { PrivateMessage(Box), } -pub(crate) async fn find_object_by_id( - context: &LemmyContext, - apub_id: Url, -) -> Result { +pub async fn find_object_by_id(context: &LemmyContext, apub_id: Url) -> Result { let ap_id = apub_id.clone(); if let Ok(pc) = find_post_or_comment_by_id(context, ap_id.to_owned()).await { return Ok(match pc { @@ -460,3 +464,20 @@ where } to_and_cc } + +pub async fn get_community_from_to_or_cc( + activity: &T, + context: &LemmyContext, + request_counter: &mut i32, +) -> Result +where + T: AsObject, +{ + for cid in get_activity_to_and_cc(activity) { + let community = get_or_fetch_and_upsert_community(&cid, context, request_counter).await; + if community.is_ok() { + return community; + } + } + Err(NotFound.into()) +} diff --git a/crates/apub/src/objects/mod.rs b/crates/apub/src/objects/mod.rs index 8c18e3417..83c78282c 100644 --- a/crates/apub/src/objects/mod.rs +++ b/crates/apub/src/objects/mod.rs @@ -1,9 +1,7 @@ use crate::{ check_community_or_site_ban, check_is_apub_id_valid, - fetcher::{community::get_or_fetch_and_upsert_community, person::get_or_fetch_and_upsert_person}, - get_activity_to_and_cc, - PageExt, + fetcher::person::get_or_fetch_and_upsert_person, }; use activitystreams::{ base::{AsBase, BaseExt, ExtendsExt}, @@ -13,10 +11,9 @@ use activitystreams::{ }; use anyhow::{anyhow, Context}; use chrono::NaiveDateTime; -use diesel::result::Error::NotFound; use lemmy_api_common::blocking; use lemmy_db_queries::{ApubObject, Crud, DbPool}; -use lemmy_db_schema::{source::community::Community, CommunityId, DbUrl}; +use lemmy_db_schema::{CommunityId, DbUrl}; use lemmy_utils::{ location_info, settings::structs::Settings, @@ -61,7 +58,7 @@ pub trait FromApub { } #[async_trait::async_trait(?Send)] -pub(in crate::objects) trait FromApubToForm { +pub trait FromApubToForm { async fn from_apub( apub: &ApubType, context: &LemmyContext, @@ -175,7 +172,7 @@ pub(in crate::objects) fn check_is_markdown(mime: Option<&Mime>) -> Result<(), L /// Converts an ActivityPub object (eg `Note`) to a database object (eg `Comment`). If an object /// with the same ActivityPub ID already exists in the database, it is returned directly. Otherwise /// the apub object is parsed, inserted and returned. -pub(in crate::objects) async fn get_object_from_apub( +pub async fn get_object_from_apub( from: &From, context: &LemmyContext, expected_domain: Url, @@ -231,17 +228,3 @@ where let person = get_or_fetch_and_upsert_person(person_id, context, request_counter).await?; check_community_or_site_ban(&person, community_id, context.pool()).await } - -pub(in crate::objects) async fn get_community_from_to_or_cc( - page: &PageExt, - context: &LemmyContext, - request_counter: &mut i32, -) -> Result { - for cid in get_activity_to_and_cc(page) { - let community = get_or_fetch_and_upsert_community(&cid, context, request_counter).await; - if community.is_ok() { - return community; - } - } - Err(NotFound.into()) -} diff --git a/crates/apub/src/objects/post.rs b/crates/apub/src/objects/post.rs index 748e89c15..57cb76524 100644 --- a/crates/apub/src/objects/post.rs +++ b/crates/apub/src/objects/post.rs @@ -2,11 +2,11 @@ use crate::{ check_is_apub_id_valid, extensions::{context::lemmy_context, page_extension::PageExtension}, fetcher::person::get_or_fetch_and_upsert_person, + get_community_from_to_or_cc, objects::{ check_object_domain, check_object_for_community_or_site_ban, create_tombstone, - get_community_from_to_or_cc, get_object_from_apub, get_source_markdown_value, set_content_and_source, diff --git a/crates/apub_receive/src/activities/receive/community.rs b/crates/apub_receive/src/activities/receive/community.rs index a40bcdfa7..18ebbed32 100644 --- a/crates/apub_receive/src/activities/receive/community.rs +++ b/crates/apub_receive/src/activities/receive/community.rs @@ -1,10 +1,86 @@ +use crate::{ + activities::receive::get_actor_as_person, + inbox::receive_for_community::verify_actor_is_community_mod, +}; +use activitystreams::{ + activity::{ActorAndObjectRefExt, Delete, Undo, Update}, + base::ExtendsExt, +}; +use anyhow::{anyhow, Context}; use lemmy_api_common::{blocking, community::CommunityResponse}; -use lemmy_db_queries::source::community::Community_; -use lemmy_db_schema::source::community::Community; -use lemmy_db_views_actor::community_view::CommunityView; -use lemmy_utils::LemmyError; +use lemmy_apub::{ + get_community_from_to_or_cc, + objects::FromApubToForm, + ActorType, + CommunityType, + GroupExt, +}; +use lemmy_db_queries::{source::community::Community_, Crud}; +use lemmy_db_schema::source::{ + community::{Community, CommunityForm}, + person::Person, +}; +use lemmy_db_views_actor::{ + community_moderator_view::CommunityModeratorView, + community_view::CommunityView, +}; +use lemmy_utils::{location_info, LemmyError}; use lemmy_websocket::{messages::SendCommunityRoomMessage, LemmyContext, UserOperationCrud}; +/// This activity is received from a remote community mod, and updates the description or other +/// fields of a local community. +pub(crate) async fn receive_remote_mod_update_community( + update: Update, + context: &LemmyContext, + request_counter: &mut i32, +) -> Result<(), LemmyError> { + let community = get_community_from_to_or_cc(&update, context, request_counter).await?; + verify_actor_is_community_mod(&update, &community, context).await?; + let group = GroupExt::from_any_base(update.object().to_owned().one().context(location_info!())?)? + .context(location_info!())?; + let updated_community = CommunityForm::from_apub( + &group, + context, + community.actor_id(), + request_counter, + false, + ) + .await?; + let cf = CommunityForm { + name: updated_community.name, + title: updated_community.title, + description: updated_community.description, + nsfw: updated_community.nsfw, + // TODO: icon and banner would be hosted on the other instance, ideally we would copy it to ours + icon: updated_community.icon, + banner: updated_community.banner, + ..CommunityForm::default() + }; + blocking(context.pool(), move |conn| { + Community::update(conn, community.id, &cf) + }) + .await??; + + Ok(()) +} + +pub(crate) async fn receive_remote_mod_delete_community( + delete: Delete, + community: Community, + context: &LemmyContext, + request_counter: &mut i32, +) -> Result<(), LemmyError> { + verify_actor_is_community_mod(&delete, &community, context).await?; + let actor = get_actor_as_person(&delete, context, request_counter).await?; + verify_is_remote_community_creator(&actor, &community, context).await?; + let community_id = community.id; + blocking(context.pool(), move |conn| { + Community::update_deleted(conn, community_id, true) + }) + .await??; + community.send_delete(actor, context).await +} + pub(crate) async fn receive_delete_community( context: &LemmyContext, community: Community, @@ -61,6 +137,23 @@ pub(crate) async fn receive_remove_community( Ok(()) } +pub(crate) async fn receive_remote_mod_undo_delete_community( + undo: Undo, + community: Community, + context: &LemmyContext, + request_counter: &mut i32, +) -> Result<(), LemmyError> { + verify_actor_is_community_mod(&undo, &community, context).await?; + let actor = get_actor_as_person(&undo, context, request_counter).await?; + verify_is_remote_community_creator(&actor, &community, context).await?; + let community_id = community.id; + blocking(context.pool(), move |conn| { + Community::update_deleted(conn, community_id, false) + }) + .await??; + community.send_undo_delete(actor, context).await +} + pub(crate) async fn receive_undo_delete_community( context: &LemmyContext, community: Community, @@ -117,3 +210,23 @@ pub(crate) async fn receive_undo_remove_community( Ok(()) } + +/// Checks if the remote user is creator of the local community. This can only happen if a community +/// is created by a local user, and then transferred to a remote user. +async fn verify_is_remote_community_creator( + user: &Person, + community: &Community, + context: &LemmyContext, +) -> Result<(), LemmyError> { + let community_id = community.id; + let community_mods = blocking(context.pool(), move |conn| { + CommunityModeratorView::for_community(conn, community_id) + }) + .await??; + + if user.id != community_mods[0].moderator.id { + Err(anyhow!("Actor is not community creator").into()) + } else { + Ok(()) + } +} diff --git a/crates/apub_receive/src/inbox/receive_for_community.rs b/crates/apub_receive/src/inbox/receive_for_community.rs index 2fcd17af0..970f80ddd 100644 --- a/crates/apub_receive/src/inbox/receive_for_community.rs +++ b/crates/apub_receive/src/inbox/receive_for_community.rs @@ -14,6 +14,11 @@ use crate::{ receive_undo_like_comment, receive_undo_remove_comment, }, + community::{ + receive_remote_mod_delete_community, + receive_remote_mod_undo_delete_community, + receive_remote_mod_update_community, + }, post::{ receive_create_post, receive_delete_post, @@ -60,9 +65,12 @@ use lemmy_apub::{ objects::{get_or_fetch_and_insert_comment, get_or_fetch_and_insert_post}, person::get_or_fetch_and_upsert_person, }, + find_object_by_id, find_post_or_comment_by_id, generate_moderators_url, + ActorType, CommunityType, + Object, PostOrComment, }; use lemmy_db_queries::{ @@ -101,6 +109,14 @@ enum PageOrNote { Note, } +#[derive(EnumString)] +enum ObjectTypes { + Page, + Note, + Group, + Person, +} + /// This file is for post/comment activities received by the community, and for post/comment /// activities announced by the community and received by the person. @@ -120,8 +136,8 @@ pub(in crate::inbox) async fn receive_create_for_community( .as_single_kind_str() .and_then(|s| s.parse().ok()); match kind { - Some(PageOrNote::Page) => receive_create_post(create, context, request_counter).await, - Some(PageOrNote::Note) => receive_create_comment(create, context, request_counter).await, + Some(ObjectTypes::Page) => receive_create_post(create, context, request_counter).await, + Some(ObjectTypes::Note) => receive_create_comment(create, context, request_counter).await, _ => receive_unhandled_activity(create), } } @@ -134,7 +150,7 @@ pub(in crate::inbox) async fn receive_update_for_community( expected_domain: &Url, request_counter: &mut i32, ) -> Result<(), LemmyError> { - let update = Update::from_any_base(activity)?.context(location_info!())?; + let update = Update::from_any_base(activity.to_owned())?.context(location_info!())?; verify_activity_domains_valid(&update, &expected_domain, false)?; verify_is_addressed_to_public(&update)?; verify_modification_actor_instance(&update, &announce, context, request_counter).await?; @@ -144,8 +160,13 @@ pub(in crate::inbox) async fn receive_update_for_community( .as_single_kind_str() .and_then(|s| s.parse().ok()); match kind { - Some(PageOrNote::Page) => receive_update_post(update, announce, context, request_counter).await, - Some(PageOrNote::Note) => receive_update_comment(update, context, request_counter).await, + Some(ObjectTypes::Page) => { + receive_update_post(update, announce, context, request_counter).await + } + Some(ObjectTypes::Note) => receive_update_comment(update, context, request_counter).await, + Some(ObjectTypes::Group) => { + receive_remote_mod_update_community(update, context, request_counter).await + } _ => receive_unhandled_activity(update), } } @@ -215,7 +236,7 @@ pub(in crate::inbox) async fn receive_delete_for_community( request_counter: &mut i32, ) -> Result<(), LemmyError> { let delete = Delete::from_any_base(activity)?.context(location_info!())?; - verify_activity_domains_valid(&delete, &expected_domain, true)?; + // TODO: skip this check if action is done by remote mod verify_is_addressed_to_public(&delete)?; verify_modification_actor_instance(&delete, &announce, context, request_counter).await?; @@ -225,11 +246,20 @@ pub(in crate::inbox) async fn receive_delete_for_community( .single_xsd_any_uri() .context(location_info!())?; - match find_post_or_comment_by_id(context, object).await { - Ok(PostOrComment::Post(p)) => receive_delete_post(context, *p).await, - Ok(PostOrComment::Comment(c)) => receive_delete_comment(context, *c).await, - // if we dont have the object, no need to do anything - Err(_) => Ok(()), + match find_object_by_id(context, object).await { + Ok(Object::Post(p)) => { + verify_activity_domains_valid(&delete, &expected_domain, true)?; + receive_delete_post(context, *p).await + } + Ok(Object::Comment(c)) => { + verify_activity_domains_valid(&delete, &expected_domain, true)?; + receive_delete_comment(context, *c).await + } + Ok(Object::Community(c)) => { + receive_remote_mod_delete_community(delete, *c, context, request_counter).await + } + // if we dont have the object or dont support its deletion, no need to do anything + _ => Ok(()), } } @@ -314,7 +344,9 @@ pub(in crate::inbox) async fn receive_undo_for_community( .as_single_kind_str() .and_then(|s| s.parse().ok()) { - Some(Delete) => receive_undo_delete_for_community(context, undo, expected_domain).await, + Some(Delete) => { + receive_undo_delete_for_community(context, undo, expected_domain, request_counter).await + } Some(Remove) => { receive_undo_remove_for_community(context, undo, announce, expected_domain).await } @@ -338,15 +370,15 @@ pub(in crate::inbox) async fn receive_undo_for_community( } } -/// A post or comment deletion being reverted +/// A post, comment or community deletion being reverted pub(in crate::inbox) async fn receive_undo_delete_for_community( context: &LemmyContext, undo: Undo, expected_domain: &Url, + request_counter: &mut i32, ) -> Result<(), LemmyError> { let delete = Delete::from_any_base(undo.object().to_owned().one().context(location_info!())?)? .context(location_info!())?; - verify_activity_domains_valid(&delete, &expected_domain, true)?; verify_is_addressed_to_public(&delete)?; let object = delete @@ -354,11 +386,21 @@ pub(in crate::inbox) async fn receive_undo_delete_for_community( .to_owned() .single_xsd_any_uri() .context(location_info!())?; - match find_post_or_comment_by_id(context, object).await { - Ok(PostOrComment::Post(p)) => receive_undo_delete_post(context, *p).await, - Ok(PostOrComment::Comment(c)) => receive_undo_delete_comment(context, *c).await, - // if we dont have the object, no need to do anything - Err(_) => Ok(()), + match find_object_by_id(context, object).await { + Ok(Object::Post(p)) => { + verify_activity_domains_valid(&delete, &expected_domain, true)?; + receive_undo_delete_post(context, *p).await + } + Ok(Object::Comment(c)) => { + verify_activity_domains_valid(&delete, &expected_domain, true)?; + receive_undo_delete_comment(context, *c).await + } + Ok(Object::Community(c)) => { + verify_actor_is_community_mod(&undo, &c, context).await?; + receive_remote_mod_undo_delete_community(undo, *c, context, request_counter).await + } + // if we dont have the object or dont support its deletion, no need to do anything + _ => Ok(()), } } @@ -617,7 +659,7 @@ where /// /// This method should only be used for activities received by the community, not for activities /// used by community followers. -async fn verify_actor_is_community_mod( +pub(crate) async fn verify_actor_is_community_mod( activity: &T, community: &Community, context: &LemmyContext, @@ -722,9 +764,18 @@ where .map(|o| o.id()) .flatten() .context(location_info!())?; - let original_id = match fetch_post_or_comment_by_id(object_id, context, request_counter).await? { - PostOrComment::Post(p) => p.ap_id.into_inner(), - PostOrComment::Comment(c) => c.ap_id.into_inner(), + let original_id = match fetch_post_or_comment_by_id(object_id, context, request_counter).await { + Ok(PostOrComment::Post(p)) => p.ap_id.into_inner(), + Ok(PostOrComment::Comment(c)) => c.ap_id.into_inner(), + Err(_) => { + // We can also receive Update activity from remote mod for local activity + let object_id = object_id.to_owned().into(); + blocking(context.pool(), move |conn| { + Community::read_from_apub_id(conn, &object_id) + }) + .await?? + .actor_id() + } }; if actor_id.domain() != original_id.domain() { let community = extract_community_from_cc(activity, context).await?;