From: Nutomic <me@nutomic.com>
Date: Fri, 9 Apr 2021 15:01:26 +0000 (+0000)
Subject: Implement federated bans (fixes #1298) (#1553)
X-Git-Url: http://these/git/%22https:/image.com/%7B%60%24%7BghostArchiveUrl%7D/search?a=commitdiff_plain;h=aa79c5131f0c09ef30ff180d51f63ef05a2fd082;p=lemmy.git

Implement federated bans (fixes #1298) (#1553)

* Implement federated bans (fixes #1298)

* mod actions should always be federated to affected user, in addition to followers

* Make Undo/Block work for remote mods

* clippy fix

* fix federation test

* vscodium doesnt auto-save changes...
---

diff --git a/api_tests/src/post.spec.ts b/api_tests/src/post.spec.ts
index db5f3ede..36e5ac3f 100644
--- a/api_tests/src/post.spec.ts
+++ b/api_tests/src/post.spec.ts
@@ -340,17 +340,9 @@ test('Enforce community ban for federated user', async () => {
   let banAlpha = await banPersonFromCommunity(beta, alphaUser.person.id, 2, true);
   expect(banAlpha.banned).toBe(true);
 
-  // Alpha makes post on beta
+  // Alpha tries to make post on beta, but it fails because of ban
   let postRes = await createPost(alpha, betaCommunity.community.id);
-  expect(postRes.post_view.post).toBeDefined();
-  expect(postRes.post_view.community.local).toBe(false);
-  expect(postRes.post_view.creator.local).toBe(true);
-  expect(postRes.post_view.counts.score).toBe(1);
-
-  // Make sure that post doesn't make it to beta community
-  let searchBeta = await searchPostLocal(beta, postRes.post_view.post);
-  let betaPost = searchBeta.posts[0];
-  expect(betaPost).toBeUndefined();
+  expect(postRes.post_view).toBeUndefined();
 
   // Unban alpha
   let unBanAlpha = await banPersonFromCommunity(
@@ -360,4 +352,14 @@ test('Enforce community ban for federated user', async () => {
     false
   );
   expect(unBanAlpha.banned).toBe(false);
+  let postRes2 = await createPost(alpha, betaCommunity.community.id);
+  expect(postRes2.post_view.post).toBeDefined();
+  expect(postRes2.post_view.community.local).toBe(false);
+  expect(postRes2.post_view.creator.local).toBe(true);
+  expect(postRes2.post_view.counts.score).toBe(1);
+
+  // Make sure that post makes it to beta community
+  let searchBeta = await searchPostLocal(beta, postRes2.post_view.post);
+  let betaPost = searchBeta.posts[0];
+  expect(betaPost).toBeDefined();
 });
diff --git a/crates/api/src/community.rs b/crates/api/src/community.rs
index 3e6ae3e4..c451cbef 100644
--- a/crates/api/src/community.rs
+++ b/crates/api/src/community.rs
@@ -130,6 +130,15 @@ impl Perform for BanFromCommunity {
       person_id: data.person_id,
     };
 
+    let community = blocking(context.pool(), move |conn: &'_ _| {
+      Community::read(conn, community_id)
+    })
+    .await??;
+    let banned_person = blocking(context.pool(), move |conn: &'_ _| {
+      Person::read(conn, banned_person_id)
+    })
+    .await??;
+
     if data.ban {
       let ban = move |conn: &'_ _| CommunityPersonBan::ban(conn, &community_user_ban_form);
       if blocking(context.pool(), ban).await?.is_err() {
@@ -147,11 +156,18 @@ impl Perform for BanFromCommunity {
       })
       .await?
       .ok();
+
+      community
+        .send_block_user(&local_user_view.person, banned_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?;
     }
 
     // Remove/Restore their data if that's desired
diff --git a/crates/apub/src/activities/send/comment.rs b/crates/apub/src/activities/send/comment.rs
index 76371b99..b2725575 100644
--- a/crates/apub/src/activities/send/comment.rs
+++ b/crates/apub/src/activities/send/comment.rs
@@ -71,7 +71,7 @@ impl ApubObjectType for Comment {
       // Set the mention tags
       .set_many_tags(maa.get_tags()?);
 
-    send_to_community(create.clone(), &creator, &community, context).await?;
+    send_to_community(create.clone(), &creator, &community, None, context).await?;
     send_comment_mentions(&creator, maa.inboxes, create, context).await?;
     Ok(())
   }
@@ -104,7 +104,7 @@ impl ApubObjectType for Comment {
       // Set the mention tags
       .set_many_tags(maa.get_tags()?);
 
-    send_to_community(update.clone(), &creator, &community, context).await?;
+    send_to_community(update.clone(), &creator, &community, None, context).await?;
     send_comment_mentions(&creator, maa.inboxes, update, context).await?;
     Ok(())
   }
@@ -129,7 +129,7 @@ impl ApubObjectType for Comment {
       .set_to(public())
       .set_many_ccs(vec![community.actor_id()]);
 
-    send_to_community(delete, &creator, &community, context).await?;
+    send_to_community(delete, &creator, &community, None, context).await?;
     Ok(())
   }
 
@@ -169,7 +169,7 @@ impl ApubObjectType for Comment {
       .set_to(public())
       .set_many_ccs(vec![community.actor_id()]);
 
-    send_to_community(undo, &creator, &community, context).await?;
+    send_to_community(undo, &creator, &community, None, context).await?;
     Ok(())
   }
 
@@ -193,7 +193,7 @@ impl ApubObjectType for Comment {
       .set_to(public())
       .set_many_ccs(vec![community.actor_id()]);
 
-    send_to_community(remove, &mod_, &community, context).await?;
+    send_to_community(remove, &mod_, &community, None, context).await?;
     Ok(())
   }
 
@@ -233,7 +233,7 @@ impl ApubObjectType for Comment {
       .set_to(public())
       .set_many_ccs(vec![community.actor_id()]);
 
-    send_to_community(undo, &mod_, &community, context).await?;
+    send_to_community(undo, &mod_, &community, None, context).await?;
     Ok(())
   }
 }
@@ -260,7 +260,7 @@ impl ApubLikeableType for Comment {
       .set_to(public())
       .set_many_ccs(vec![community.actor_id()]);
 
-    send_to_community(like, &creator, &community, context).await?;
+    send_to_community(like, &creator, &community, None, context).await?;
     Ok(())
   }
 
@@ -284,7 +284,7 @@ impl ApubLikeableType for Comment {
       .set_to(public())
       .set_many_ccs(vec![community.actor_id()]);
 
-    send_to_community(dislike, &creator, &community, context).await?;
+    send_to_community(dislike, &creator, &community, None, context).await?;
     Ok(())
   }
 
@@ -323,7 +323,7 @@ impl ApubLikeableType for Comment {
       .set_to(public())
       .set_many_ccs(vec![community.actor_id()]);
 
-    send_to_community(undo, &creator, &community, context).await?;
+    send_to_community(undo, &creator, &community, None, context).await?;
     Ok(())
   }
 }
diff --git a/crates/apub/src/activities/send/community.rs b/crates/apub/src/activities/send/community.rs
index f31eb260..5b93cbb2 100644
--- a/crates/apub/src/activities/send/community.rs
+++ b/crates/apub/src/activities/send/community.rs
@@ -3,7 +3,7 @@ use crate::{
   activity_queue::{send_activity_single_dest, send_to_community, send_to_community_followers},
   check_is_apub_id_valid,
   extensions::context::lemmy_context,
-  fetcher::person::get_or_fetch_and_upsert_person,
+  fetcher::{get_or_fetch_and_upsert_actor, person::get_or_fetch_and_upsert_person},
   generate_moderators_url,
   insert_activity,
   ActorType,
@@ -11,11 +11,21 @@ use crate::{
 };
 use activitystreams::{
   activity::{
-    kind::{AcceptType, AddType, AnnounceType, DeleteType, LikeType, RemoveType, UndoType},
+    kind::{
+      AcceptType,
+      AddType,
+      AnnounceType,
+      BlockType,
+      DeleteType,
+      LikeType,
+      RemoveType,
+      UndoType,
+    },
     Accept,
     ActorAndObjectRefExt,
     Add,
     Announce,
+    Block,
     Delete,
     Follow,
     OptTargetRefExt,
@@ -62,6 +72,10 @@ impl ActorType for Community {
 
 #[async_trait::async_trait(?Send)]
 impl CommunityType for Community {
+  fn followers_url(&self) -> Url {
+    self.followers_url.clone().into_inner()
+  }
+
   /// As a local community, accept the follow request from a remote person.
   async fn send_accept_follow(
     &self,
@@ -94,9 +108,9 @@ impl CommunityType for Community {
       .set_many_contexts(lemmy_context()?)
       .set_id(generate_activity_id(DeleteType::Delete)?)
       .set_to(public())
-      .set_many_ccs(vec![self.followers_url.clone().into_inner()]);
+      .set_many_ccs(vec![self.followers_url()]);
 
-    send_to_community_followers(delete, self, context).await?;
+    send_to_community_followers(delete, self, None, context).await?;
     Ok(())
   }
 
@@ -107,16 +121,16 @@ impl CommunityType for Community {
       .set_many_contexts(lemmy_context()?)
       .set_id(generate_activity_id(DeleteType::Delete)?)
       .set_to(public())
-      .set_many_ccs(vec![self.followers_url.clone().into_inner()]);
+      .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.clone().into_inner()]);
+      .set_many_ccs(vec![self.followers_url()]);
 
-    send_to_community_followers(undo, self, context).await?;
+    send_to_community_followers(undo, self, None, context).await?;
     Ok(())
   }
 
@@ -127,9 +141,9 @@ impl CommunityType for Community {
       .set_many_contexts(lemmy_context()?)
       .set_id(generate_activity_id(RemoveType::Remove)?)
       .set_to(public())
-      .set_many_ccs(vec![self.followers_url.clone().into_inner()]);
+      .set_many_ccs(vec![self.followers_url()]);
 
-    send_to_community_followers(remove, self, context).await?;
+    send_to_community_followers(remove, self, None, context).await?;
     Ok(())
   }
 
@@ -140,7 +154,7 @@ impl CommunityType for Community {
       .set_many_contexts(lemmy_context()?)
       .set_id(generate_activity_id(RemoveType::Remove)?)
       .set_to(public())
-      .set_many_ccs(vec![self.followers_url.clone().into_inner()]);
+      .set_many_ccs(vec![self.followers_url()]);
 
     // Undo that fake activity
     let mut undo = Undo::new(self.actor_id(), remove.into_any_base()?);
@@ -148,9 +162,9 @@ impl CommunityType for Community {
       .set_many_contexts(lemmy_context()?)
       .set_id(generate_activity_id(LikeType::Like)?)
       .set_to(public())
-      .set_many_ccs(vec![self.followers_url.clone().into_inner()]);
+      .set_many_ccs(vec![self.followers_url()]);
 
-    send_to_community_followers(undo, self, context).await?;
+    send_to_community_followers(undo, self, None, context).await?;
     Ok(())
   }
 
@@ -160,9 +174,13 @@ impl CommunityType for Community {
   /// 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<Url>,
     context: &LemmyContext,
   ) -> Result<(), LemmyError> {
     let inner_id = activity.id().context(location_info!())?;
@@ -170,14 +188,27 @@ impl CommunityType for Community {
       insert_activity(inner_id, activity.clone(), true, false, context.pool()).await?;
     }
 
-    let mut announce = Announce::new(self.actor_id.to_owned().into_inner(), activity);
+    let mut ccs = vec![self.followers_url()];
+    let mut object_actor_inbox: Option<Url> = 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(vec![self.followers_url.clone().into_inner()]);
+      .set_many_ccs(ccs);
 
-    send_to_community_followers(announce, self, context).await?;
+    send_to_community_followers(announce, self, object_actor_inbox, context).await?;
 
     Ok(())
   }
@@ -209,10 +240,7 @@ impl CommunityType for Community {
     added_mod: Person,
     context: &LemmyContext,
   ) -> Result<(), LemmyError> {
-    let mut add = Add::new(
-      actor.actor_id.clone().into_inner(),
-      added_mod.actor_id.into_inner(),
-    );
+    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)?)
@@ -220,7 +248,7 @@ impl CommunityType for Community {
       .set_many_ccs(vec![self.actor_id()])
       .set_target(generate_moderators_url(&self.actor_id)?.into_inner());
 
-    send_to_community(add, actor, self, context).await?;
+    send_to_community(add, actor, self, Some(added_mod.actor_id()), context).await?;
     Ok(())
   }
 
@@ -230,10 +258,7 @@ impl CommunityType for Community {
     removed_mod: Person,
     context: &LemmyContext,
   ) -> Result<(), LemmyError> {
-    let mut remove = Remove::new(
-      actor.actor_id.clone().into_inner(),
-      removed_mod.actor_id.into_inner(),
-    );
+    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)?)
@@ -241,7 +266,49 @@ impl CommunityType for Community {
       .set_many_ccs(vec![self.actor_id()])
       .set_target(generate_moderators_url(&self.actor_id)?.into_inner());
 
-    send_to_community(remove, &actor, self, context).await?;
+    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/send/person.rs b/crates/apub/src/activities/send/person.rs
index c034f593..286778c7 100644
--- a/crates/apub/src/activities/send/person.rs
+++ b/crates/apub/src/activities/send/person.rs
@@ -74,7 +74,7 @@ impl UserType for Person {
     })
     .await?;
 
-    let mut follow = Follow::new(self.actor_id.to_owned().into_inner(), community.actor_id());
+    let mut follow = Follow::new(self.actor_id(), community.actor_id());
     follow
       .set_many_contexts(lemmy_context()?)
       .set_id(generate_activity_id(FollowType::Follow)?)
@@ -95,7 +95,7 @@ impl UserType for Person {
     })
     .await??;
 
-    let mut follow = Follow::new(self.actor_id.to_owned().into_inner(), community.actor_id());
+    let mut follow = Follow::new(self.actor_id(), community.actor_id());
     follow
       .set_many_contexts(lemmy_context()?)
       .set_id(generate_activity_id(FollowType::Follow)?)
diff --git a/crates/apub/src/activities/send/post.rs b/crates/apub/src/activities/send/post.rs
index 9f8be1e1..0af7369a 100644
--- a/crates/apub/src/activities/send/post.rs
+++ b/crates/apub/src/activities/send/post.rs
@@ -49,7 +49,7 @@ impl ApubObjectType for Post {
       .set_to(public())
       .set_many_ccs(vec![community.actor_id()]);
 
-    send_to_community(create, creator, &community, context).await?;
+    send_to_community(create, creator, &community, None, context).await?;
     Ok(())
   }
 
@@ -73,7 +73,7 @@ impl ApubObjectType for Post {
       .set_to(public())
       .set_many_ccs(vec![community.actor_id()]);
 
-    send_to_community(update, creator, &community, context).await?;
+    send_to_community(update, creator, &community, None, context).await?;
     Ok(())
   }
 
@@ -94,7 +94,7 @@ impl ApubObjectType for Post {
       .set_to(public())
       .set_many_ccs(vec![community.actor_id()]);
 
-    send_to_community(delete, creator, &community, context).await?;
+    send_to_community(delete, creator, &community, None, context).await?;
     Ok(())
   }
 
@@ -130,7 +130,7 @@ impl ApubObjectType for Post {
       .set_to(public())
       .set_many_ccs(vec![community.actor_id()]);
 
-    send_to_community(undo, creator, &community, context).await?;
+    send_to_community(undo, creator, &community, None, context).await?;
     Ok(())
   }
 
@@ -151,7 +151,7 @@ impl ApubObjectType for Post {
       .set_to(public())
       .set_many_ccs(vec![community.actor_id()]);
 
-    send_to_community(remove, mod_, &community, context).await?;
+    send_to_community(remove, mod_, &community, None, context).await?;
     Ok(())
   }
 
@@ -187,7 +187,7 @@ impl ApubObjectType for Post {
       .set_to(public())
       .set_many_ccs(vec![community.actor_id()]);
 
-    send_to_community(undo, mod_, &community, context).await?;
+    send_to_community(undo, mod_, &community, None, context).await?;
     Ok(())
   }
 }
@@ -211,7 +211,7 @@ impl ApubLikeableType for Post {
       .set_to(public())
       .set_many_ccs(vec![community.actor_id()]);
 
-    send_to_community(like, &creator, &community, context).await?;
+    send_to_community(like, &creator, &community, None, context).await?;
     Ok(())
   }
 
@@ -232,7 +232,7 @@ impl ApubLikeableType for Post {
       .set_to(public())
       .set_many_ccs(vec![community.actor_id()]);
 
-    send_to_community(dislike, &creator, &community, context).await?;
+    send_to_community(dislike, &creator, &community, None, context).await?;
     Ok(())
   }
 
@@ -268,7 +268,7 @@ impl ApubLikeableType for Post {
       .set_to(public())
       .set_many_ccs(vec![community.actor_id()]);
 
-    send_to_community(undo, &creator, &community, context).await?;
+    send_to_community(undo, &creator, &community, None, context).await?;
     Ok(())
   }
 }
diff --git a/crates/apub/src/activity_queue.rs b/crates/apub/src/activity_queue.rs
index b1913b4b..924b3dd6 100644
--- a/crates/apub/src/activity_queue.rs
+++ b/crates/apub/src/activity_queue.rs
@@ -21,7 +21,6 @@ use background_jobs::{
   WorkerConfig,
 };
 use itertools::Itertools;
-use lemmy_db_queries::DbPool;
 use lemmy_db_schema::source::{community::Community, person::Person};
 use lemmy_utils::{location_info, settings::structs::Settings, LemmyError};
 use lemmy_websocket::LemmyContext;
@@ -53,16 +52,7 @@ where
       &activity.id_unchecked(),
       &inbox
     );
-    send_activity_internal(
-      context.activity_queue(),
-      activity,
-      creator,
-      vec![inbox],
-      context.pool(),
-      true,
-      true,
-    )
-    .await?;
+    send_activity_internal(context, activity, creator, vec![inbox], true, true).await?;
   }
 
   Ok(())
@@ -72,11 +62,11 @@ where
 ///
 /// * `activity` the apub activity to send
 /// * `community` the sending community
-/// * `sender_shared_inbox` in case of an announce, this should be the shared inbox of the inner
-///                         activities creator, as receiving a known activity will cause an error
+/// * `extra_inbox` actor inbox which should receive the activity, in addition to followers
 pub(crate) async fn send_to_community_followers<T, Kind>(
   activity: T,
   community: &Community,
+  extra_inbox: Option<Url>,
   context: &LemmyContext,
 ) -> Result<(), LemmyError>
 where
@@ -84,31 +74,25 @@ where
   Kind: Serialize,
   <T as Extends<Kind>>::Error: From<serde_json::Error> + Send + Sync + 'static,
 {
-  let follower_inboxes: Vec<Url> = community
-    .get_follower_inboxes(context.pool())
-    .await?
-    .iter()
-    .unique()
-    .filter(|inbox| inbox.host_str() != Some(&Settings::get().hostname()))
-    .filter(|inbox| check_is_apub_id_valid(inbox).is_ok())
-    .map(|inbox| inbox.to_owned())
-    .collect();
+  let extra_inbox: Vec<Url> = extra_inbox.into_iter().collect();
+  let follower_inboxes: Vec<Url> = 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).is_ok())
+  .map(|inbox| inbox.to_owned())
+  .collect();
   debug!(
     "Sending activity {:?} to followers of {}",
     &activity.id_unchecked().map(|i| i.to_string()),
     &community.actor_id
   );
 
-  send_activity_internal(
-    context.activity_queue(),
-    activity,
-    community,
-    follower_inboxes,
-    context.pool(),
-    true,
-    false,
-  )
-  .await?;
+  send_activity_internal(context, activity, community, follower_inboxes, true, false).await?;
 
   Ok(())
 }
@@ -118,11 +102,14 @@ where
 /// * `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<T, Kind>(
   activity: T,
   creator: &Person,
   community: &Community,
+  object_actor: Option<Url>,
   context: &LemmyContext,
 ) -> Result<(), LemmyError>
 where
@@ -133,7 +120,7 @@ where
   // 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()?, context)
+      .send_announce(activity.into_any_base()?, object_actor, context)
       .await?;
   } else {
     let inbox = community.get_shared_inbox_or_inbox_url();
@@ -143,16 +130,8 @@ where
       &activity.id_unchecked(),
       &community.actor_id
     );
-    send_activity_internal(
-      context.activity_queue(),
-      activity,
-      creator,
-      vec![inbox],
-      context.pool(),
-      true,
-      false,
-    )
-    .await?;
+    // 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(())
@@ -185,12 +164,7 @@ where
     .map(|i| i.to_owned())
     .collect();
   send_activity_internal(
-    context.activity_queue(),
-    activity,
-    creator,
-    mentions,
-    context.pool(),
-    false, // Don't create a new DB row
+    context, activity, creator, mentions, false, // Don't create a new DB row
     false,
   )
   .await?;
@@ -203,11 +177,10 @@ where
 /// The caller of this function needs to remove any blocked domains from `to`,
 /// using `check_is_apub_id_valid()`.
 async fn send_activity_internal<T, Kind>(
-  activity_sender: &QueueHandle,
+  context: &LemmyContext,
   activity: T,
   actor: &dyn ActorType,
   inboxes: Vec<Url>,
-  pool: &DbPool,
   insert_into_db: bool,
   sensitive: bool,
 ) -> Result<(), LemmyError>
@@ -234,7 +207,7 @@ where
   // might send the same ap_id
   if insert_into_db {
     let id = activity.id().context(location_info!())?;
-    insert_activity(id, activity.clone(), true, sensitive, pool).await?;
+    insert_activity(id, activity.clone(), true, sensitive, context.pool()).await?;
   }
 
   for i in inboxes {
@@ -247,7 +220,7 @@ where
     if env::var("LEMMY_TEST_SEND_SYNC").is_ok() {
       do_send(message, &Client::default()).await?;
     } else {
-      activity_sender.queue::<SendActivityTask>(message)?;
+      context.activity_queue.queue::<SendActivityTask>(message)?;
     }
   }
 
diff --git a/crates/apub/src/lib.rs b/crates/apub/src/lib.rs
index 8dd35ea3..37e95901 100644
--- a/crates/apub/src/lib.rs
+++ b/crates/apub/src/lib.rs
@@ -191,6 +191,7 @@ pub trait ActorType {
 
 #[async_trait::async_trait(?Send)]
 pub trait CommunityType {
+  fn followers_url(&self) -> Url;
   async fn get_follower_inboxes(&self, pool: &DbPool) -> Result<Vec<Url>, LemmyError>;
   async fn send_accept_follow(
     &self,
@@ -207,6 +208,7 @@ pub trait CommunityType {
   async fn send_announce(
     &self,
     activity: AnyBase,
+    object: Option<Url>,
     context: &LemmyContext,
   ) -> Result<(), LemmyError>;
 
@@ -222,6 +224,19 @@ pub trait CommunityType {
     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>;
 }
 
 #[async_trait::async_trait(?Send)]
diff --git a/crates/apub_receive/src/inbox/community_inbox.rs b/crates/apub_receive/src/inbox/community_inbox.rs
index 486636ca..84b01699 100644
--- a/crates/apub_receive/src/inbox/community_inbox.rs
+++ b/crates/apub_receive/src/inbox/community_inbox.rs
@@ -7,6 +7,7 @@ use crate::{
     is_activity_already_known,
     receive_for_community::{
       receive_add_for_community,
+      receive_block_user_for_community,
       receive_create_for_community,
       receive_delete_for_community,
       receive_dislike_for_community,
@@ -58,6 +59,7 @@ pub enum CommunityValidTypes {
   Delete,  // post or comment deleted by creator
   Remove,  // post or comment removed by mod or admin, or mod removed from community
   Add,     // mod added to community
+  Block,   // user blocked by community
 }
 
 pub type CommunityAcceptedActivities = ActorAndObject<CommunityValidTypes>;
@@ -224,13 +226,35 @@ pub(crate) async fn community_receive_message(
       .await?;
       true
     }
+    CommunityValidTypes::Block => {
+      Box::pin(receive_block_user_for_community(
+        context,
+        any_base.clone(),
+        None,
+        request_counter,
+      ))
+      .await?;
+      true
+    }
   };
 
   if do_announce {
     // Check again that the activity is public, just to be sure
     verify_is_addressed_to_public(&activity)?;
+    let mut object_actor = activity.object().clone().single_xsd_any_uri();
+    // If activity is something like Undo/Block, we need to access activity.object.object
+    if object_actor.is_none() {
+      object_actor = activity
+        .object()
+        .as_one()
+        .map(|a| ActorAndObject::from_any_base(a.to_owned()).ok())
+        .flatten()
+        .flatten()
+        .map(|a: ActorAndObject<CommunityValidTypes>| a.object().to_owned().single_xsd_any_uri())
+        .flatten();
+    }
     to_community
-      .send_announce(activity.into_any_base()?, context)
+      .send_announce(activity.into_any_base()?, object_actor, context)
       .await?;
   }
 
diff --git a/crates/apub_receive/src/inbox/person_inbox.rs b/crates/apub_receive/src/inbox/person_inbox.rs
index 42567ab6..66b6c95a 100644
--- a/crates/apub_receive/src/inbox/person_inbox.rs
+++ b/crates/apub_receive/src/inbox/person_inbox.rs
@@ -25,6 +25,7 @@ use crate::{
     is_addressed_to_local_person,
     receive_for_community::{
       receive_add_for_community,
+      receive_block_user_for_community,
       receive_create_for_community,
       receive_delete_for_community,
       receive_dislike_for_community,
@@ -276,6 +277,7 @@ enum AnnouncableActivities {
   Remove,
   Undo,
   Add,
+  Block,
 }
 
 /// Takes an announce and passes the inner activity to the appropriate handler.
@@ -352,6 +354,10 @@ pub async fn receive_announce(
     Some(Add) => {
       receive_add_for_community(context, inner_activity, Some(announce), request_counter).await
     }
+    Some(Block) => {
+      receive_block_user_for_community(context, inner_activity, Some(announce), request_counter)
+        .await
+    }
     _ => receive_unhandled_activity(inner_activity),
   }
 }
diff --git a/crates/apub_receive/src/inbox/receive_for_community.rs b/crates/apub_receive/src/inbox/receive_for_community.rs
index 181a86d1..2fcd17af 100644
--- a/crates/apub_receive/src/inbox/receive_for_community.rs
+++ b/crates/apub_receive/src/inbox/receive_for_community.rs
@@ -38,6 +38,7 @@ use activitystreams::{
     ActorAndObjectRef,
     Add,
     Announce,
+    Block,
     Create,
     Delete,
     Dislike,
@@ -64,10 +65,25 @@ use lemmy_apub::{
   CommunityType,
   PostOrComment,
 };
-use lemmy_db_queries::{source::community::CommunityModerator_, ApubObject, Crud, Joinable};
+use lemmy_db_queries::{
+  source::community::CommunityModerator_,
+  ApubObject,
+  Bannable,
+  Crud,
+  Followable,
+  Joinable,
+};
 use lemmy_db_schema::{
   source::{
-    community::{Community, CommunityModerator, CommunityModeratorForm},
+    community::{
+      Community,
+      CommunityFollower,
+      CommunityFollowerForm,
+      CommunityModerator,
+      CommunityModeratorForm,
+      CommunityPersonBan,
+      CommunityPersonBanForm,
+    },
     person::Person,
     site::Site,
   },
@@ -244,7 +260,13 @@ pub(in crate::inbox) async fn receive_remove_for_community(
       CommunityModerator::leave(conn, &form)
     })
     .await??;
-    community.send_announce(remove_any_base, context).await?;
+    community
+      .send_announce(
+        remove_any_base,
+        remove.object().clone().single_xsd_any_uri(),
+        context,
+      )
+      .await?;
     // TODO: send websocket notification about removed mod
     Ok(())
   }
@@ -271,6 +293,7 @@ enum UndoableActivities {
   Remove,
   Like,
   Dislike,
+  Block,
 }
 
 /// A post/comment action being reverted (either a delete, remove, upvote or downvote)
@@ -301,6 +324,16 @@ pub(in crate::inbox) async fn receive_undo_for_community(
     Some(Dislike) => {
       receive_undo_dislike_for_community(context, undo, expected_domain, request_counter).await
     }
+    Some(Block) => {
+      receive_undo_block_user_for_community(
+        context,
+        undo,
+        announce,
+        expected_domain,
+        request_counter,
+      )
+      .await
+    }
     _ => receive_unhandled_activity(undo),
   }
 }
@@ -419,7 +452,13 @@ pub(in crate::inbox) async fn receive_add_for_community(
     .await??;
   }
   if community.local {
-    community.send_announce(add_any_base, context).await?;
+    community
+      .send_announce(
+        add_any_base,
+        add.object().clone().single_xsd_any_uri(),
+        context,
+      )
+      .await?;
   }
   // TODO: send websocket notification about added mod
   Ok(())
@@ -451,6 +490,85 @@ pub(in crate::inbox) async fn receive_undo_dislike_for_community(
   }
 }
 
+pub(crate) async fn receive_block_user_for_community(
+  context: &LemmyContext,
+  block_any_base: AnyBase,
+  announce: Option<Announce>,
+  request_counter: &mut i32,
+) -> Result<(), LemmyError> {
+  let block = Block::from_any_base(block_any_base.to_owned())?.context(location_info!())?;
+  let community = extract_community_from_cc(&block, context).await?;
+
+  verify_mod_activity(&block, announce, &community, context).await?;
+  verify_is_addressed_to_public(&block)?;
+
+  let blocked_user = block
+    .object()
+    .as_single_xsd_any_uri()
+    .context(location_info!())?;
+  let blocked_user =
+    get_or_fetch_and_upsert_person(&blocked_user, context, request_counter).await?;
+
+  let community_user_ban_form = CommunityPersonBanForm {
+    community_id: community.id,
+    person_id: blocked_user.id,
+  };
+
+  blocking(context.pool(), move |conn: &'_ _| {
+    CommunityPersonBan::ban(conn, &community_user_ban_form)
+  })
+  .await??;
+
+  // Also unsubscribe them from the community, if they are subscribed
+  let community_follower_form = CommunityFollowerForm {
+    community_id: community.id,
+    person_id: blocked_user.id,
+    pending: false,
+  };
+  blocking(context.pool(), move |conn: &'_ _| {
+    CommunityFollower::unfollow(conn, &community_follower_form)
+  })
+  .await?
+  .ok();
+
+  Ok(())
+}
+
+pub(crate) async fn receive_undo_block_user_for_community(
+  context: &LemmyContext,
+  undo: Undo,
+  announce: Option<Announce>,
+  expected_domain: &Url,
+  request_counter: &mut i32,
+) -> Result<(), LemmyError> {
+  let object = undo.object().clone().one().context(location_info!())?;
+  let block = Block::from_any_base(object)?.context(location_info!())?;
+  let community = extract_community_from_cc(&block, context).await?;
+
+  verify_activity_domains_valid(&block, &expected_domain, false)?;
+  verify_is_addressed_to_public(&block)?;
+  verify_undo_remove_actor_instance(&undo, &block, &announce, context).await?;
+
+  let blocked_user = block
+    .object()
+    .as_single_xsd_any_uri()
+    .context(location_info!())?;
+  let blocked_user =
+    get_or_fetch_and_upsert_person(&blocked_user, context, request_counter).await?;
+
+  let community_user_ban_form = CommunityPersonBanForm {
+    community_id: community.id,
+    person_id: blocked_user.id,
+  };
+
+  blocking(context.pool(), move |conn: &'_ _| {
+    CommunityPersonBan::unban(conn, &community_user_ban_form)
+  })
+  .await??;
+
+  Ok(())
+}
+
 async fn fetch_post_or_comment_by_id(
   apub_id: &Url,
   context: &LemmyContext,
diff --git a/crates/apub_receive/src/inbox/shared_inbox.rs b/crates/apub_receive/src/inbox/shared_inbox.rs
index cd4ae145..db060247 100644
--- a/crates/apub_receive/src/inbox/shared_inbox.rs
+++ b/crates/apub_receive/src/inbox/shared_inbox.rs
@@ -34,6 +34,7 @@ pub enum ValidTypes {
   Remove,
   Announce,
   Add,
+  Block,
 }
 
 // TODO: this isnt entirely correct, cause some of these receive are not ActorAndObject,