Skip to content

Commit

Permalink
Implement federated bans (fixes #1298) (#1553)
Browse files Browse the repository at this point in the history
* 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...
  • Loading branch information
Nutomic authored Apr 9, 2021
1 parent 89dd0f2 commit aa79c51
Show file tree
Hide file tree
Showing 12 changed files with 335 additions and 113 deletions.
22 changes: 12 additions & 10 deletions api_tests/src/post.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -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();
});
16 changes: 16 additions & 0 deletions crates/api/src/community.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand All @@ -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
Expand Down
18 changes: 9 additions & 9 deletions crates/apub/src/activities/send/comment.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(())
}
Expand Down Expand Up @@ -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(())
}
Expand All @@ -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(())
}

Expand Down Expand Up @@ -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(())
}

Expand All @@ -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(())
}

Expand Down Expand Up @@ -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(())
}
}
Expand All @@ -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(())
}

Expand All @@ -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(())
}

Expand Down Expand Up @@ -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(())
}
}
Expand Down
117 changes: 92 additions & 25 deletions crates/apub/src/activities/send/community.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,29 @@ 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,
CommunityType,
};
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,
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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(())
}

Expand All @@ -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(())
}

Expand All @@ -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(())
}

Expand All @@ -140,17 +154,17 @@ 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()?);
undo
.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(())
}

Expand All @@ -160,24 +174,41 @@ 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!())?;
if inner_id.domain() == Some(&Settings::get().get_hostname_without_port()?) {
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(())
}
Expand Down Expand Up @@ -209,18 +240,15 @@ 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)?)
.set_to(public())
.set_many_ccs(vec![self.actor_id()])
.set_target(generate_moderators_url(&self.actor_id)?.into_inner());

send_to_community(add, actor, self, context).await?;
send_to_community(add, actor, self, Some(added_mod.actor_id()), context).await?;
Ok(())
}

Expand All @@ -230,18 +258,57 @@ 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)?)
.set_to(public())
.set_many_ccs(vec![self.actor_id()])
.set_target(generate_moderators_url(&self.actor_id)?.into_inner());

send_to_community(remove, &actor, self, 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(())
}
}
Loading

0 comments on commit aa79c51

Please sign in to comment.