From c572dc0cc61418590b9e28ce27f9679e327eca29 Mon Sep 17 00:00:00 2001
From: Felix Ableitner <me@nutomic.com>
Date: Tue, 13 Apr 2021 13:48:30 +0200
Subject: [PATCH] Remote mods can update/delete/undelete communities

---
 crates/api_crud/src/community/delete.rs       |   8 +-
 crates/api_crud/src/community/update.rs       |  13 +-
 crates/apub/src/activities/send/community.rs  | 112 ++++++++++++----
 crates/apub/src/lib.rs                        |  47 +++++--
 crates/apub/src/objects/mod.rs                |  25 +---
 crates/apub/src/objects/post.rs               |   2 +-
 .../src/activities/receive/community.rs       | 121 +++++++++++++++++-
 .../src/inbox/receive_for_community.rs        |  97 ++++++++++----
 8 files changed, 329 insertions(+), 96 deletions(-)

diff --git a/crates/api_crud/src/community/delete.rs b/crates/api_crud/src/community/delete.rs
index b94c153b..fb01b08e 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 9e8339fb..644581a5 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 5b93cbb2..f5d32142 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 37e95901..89109318 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<actor::ApActor<ApObject<actor::Group>>, GroupExtension, PublicKeyExtension>;
+pub type GroupExt =
+  Ext2<actor::ApActor<ApObject<actor::Group>>, GroupExtension, PublicKeyExtension>;
 /// Activitystreams type for person
 type PersonExt = Ext2<actor::ApActor<ApObject<actor::Person>>, 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<Url, LemmyError> {
+    /* 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<Comment>),
   Post(Box<Post>),
   Community(Box<Community>),
@@ -374,10 +381,7 @@ pub(crate) enum Object {
   PrivateMessage(Box<PrivateMessage>),
 }
 
-pub(crate) async fn find_object_by_id(
-  context: &LemmyContext,
-  apub_id: Url,
-) -> Result<Object, LemmyError> {
+pub async fn find_object_by_id(context: &LemmyContext, apub_id: Url) -> Result<Object, LemmyError> {
   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<T, Kind>(
+  activity: &T,
+  context: &LemmyContext,
+  request_counter: &mut i32,
+) -> Result<Community, LemmyError>
+where
+  T: AsObject<Kind>,
+{
+  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 8c18e341..83c78282 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<ApubType> {
+pub trait FromApubToForm<ApubType> {
   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<From, Kind, To, ToForm, IdType>(
+pub async fn get_object_from_apub<From, Kind, To, ToForm, IdType>(
   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<Community, LemmyError> {
-  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 748e89c1..57cb7652 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 a40bcdfa..18ebbed3 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 2fcd17af..970f80dd 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<T, Kind>(
+pub(crate) async fn verify_actor_is_community_mod<T, Kind>(
   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?;
-- 
2.44.1