From aa79c5131f0c09ef30ff180d51f63ef05a2fd082 Mon Sep 17 00:00:00 2001 From: Nutomic Date: Fri, 9 Apr 2021 15:01:26 +0000 Subject: [PATCH] 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... --- api_tests/src/post.spec.ts | 22 +-- crates/api/src/community.rs | 16 +++ crates/apub/src/activities/send/comment.rs | 18 +-- crates/apub/src/activities/send/community.rs | 117 ++++++++++++---- crates/apub/src/activities/send/person.rs | 4 +- crates/apub/src/activities/send/post.rs | 18 +-- crates/apub/src/activity_queue.rs | 79 ++++------- crates/apub/src/lib.rs | 15 +++ .../apub_receive/src/inbox/community_inbox.rs | 26 +++- crates/apub_receive/src/inbox/person_inbox.rs | 6 + .../src/inbox/receive_for_community.rs | 126 +++++++++++++++++- crates/apub_receive/src/inbox/shared_inbox.rs | 1 + 12 files changed, 335 insertions(+), 113 deletions(-) diff --git a/api_tests/src/post.spec.ts b/api_tests/src/post.spec.ts index db5f3edee..36e5ac3fa 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 3e6ae3e47..c451cbef7 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 76371b997..b27255755 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 f31eb260a..5b93cbb2b 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, 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 = 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 c034f5935..286778c78 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 9f8be1e1e..0af7369ac 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 b1913b4b1..924b3dd61 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( activity: T, community: &Community, + extra_inbox: Option, context: &LemmyContext, ) -> Result<(), LemmyError> where @@ -84,31 +74,25 @@ where Kind: Serialize, >::Error: From + Send + Sync + 'static, { - let follower_inboxes: Vec = 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 = 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).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( activity: T, creator: &Person, community: &Community, + object_actor: Option, 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( - activity_sender: &QueueHandle, + context: &LemmyContext, activity: T, actor: &dyn ActorType, inboxes: Vec, - 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::(message)?; + context.activity_queue.queue::(message)?; } } diff --git a/crates/apub/src/lib.rs b/crates/apub/src/lib.rs index 8dd35ea3d..37e959014 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, LemmyError>; async fn send_accept_follow( &self, @@ -207,6 +208,7 @@ pub trait CommunityType { async fn send_announce( &self, activity: AnyBase, + object: Option, 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 486636caa..84b016991 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; @@ -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| 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 42567ab63..66b6c95ac 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 181a86d12..2fcd17af0 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, + 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, + 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 cd4ae145a..db060247c 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,