Add local community ban, and nonlocal person check.

content_removal_remote_users
Dessalines 2024-02-27 10:55:24 -05:00
parent eba42de4bd
commit 0caaac193d
7 changed files with 112 additions and 72 deletions

1
Cargo.lock generated
View File

@ -2555,7 +2555,6 @@ dependencies = [
"captcha",
"chrono",
"elementtree",
"itertools 0.12.0",
"lemmy_api_common",
"lemmy_db_schema",
"lemmy_db_views",

View File

@ -33,7 +33,6 @@ anyhow = { workspace = true }
tracing = { workspace = true }
chrono = { workspace = true }
url = { workspace = true }
itertools = { workspace = true }
wav = "1.0.0"
sitemap-rs = "0.2.0"
totp-rs = { version = "5.5.1", features = ["gen_secret", "otpauth"] }

View File

@ -3,19 +3,26 @@ use actix_web::{http::header::Header, HttpRequest};
use actix_web_httpauth::headers::authorization::{Authorization, Bearer};
use base64::{engine::general_purpose::STANDARD_NO_PAD as base64, Engine};
use captcha::Captcha;
use itertools::Itertools;
use lemmy_api_common::{
claims::Claims,
community::BanFromCommunity,
context::LemmyContext,
send_activity::{ActivityChannel, SendActivityData},
utils::{check_user_valid, local_site_to_slur_regex, AUTH_COOKIE_NAME},
utils::{check_expire_time, check_user_valid, local_site_to_slur_regex, AUTH_COOKIE_NAME},
};
use lemmy_db_schema::source::{
comment::Comment,
local_site::LocalSite,
person::Person,
post::Post,
use lemmy_db_schema::{
source::{
community::{
CommunityFollower,
CommunityFollowerForm,
CommunityPersonBan,
CommunityPersonBanForm,
},
local_site::LocalSite,
moderator::{ModBanFromCommunity, ModBanFromCommunityForm},
person::Person,
},
traits::{Bannable, Crud, Followable},
};
use lemmy_db_views::structs::LocalUserView;
use lemmy_utils::{
@ -150,35 +157,77 @@ pub(crate) fn build_totp_2fa(
.with_lemmy_type(LemmyErrorType::CouldntGenerateTotp)
}
/// Since removals and bans are only federated for local users,
/// you also need to send bans for their content to local communities.
/// Site bans are only federated for local users.
/// This is a problem, because site-banning non-local users will still leave content
/// they've posted to our local communities, on other servers.
///
/// So when doing a site ban for a non-local user, you need to federate/send a
/// community ban for every local community they've participated in.
/// See https://github.com/LemmyNet/lemmy/issues/4118
#[tracing::instrument(skip_all)]
pub(crate) async fn send_bans_and_removals_to_local_communities(
pub(crate) async fn ban_nonlocal_user_from_local_communities(
local_user_view: &LocalUserView,
target: &Person,
ban: bool,
reason: &Option<String>,
remove_data: &Option<bool>,
expires: &Option<i64>,
context: &Data<LemmyContext>,
) -> LemmyResult<()> {
let posts_community_ids =
Post::list_creators_local_community_ids(&mut context.pool(), target.id).await?;
let comments_community_ids =
Comment::list_creators_local_community_ids(&mut context.pool(), target.id).await?;
let ids = [posts_community_ids, comments_community_ids]
.concat()
.into_iter()
.unique();
let ids = Person::list_local_community_ids(&mut context.pool(), target.id).await?;
for community_id in ids {
let expires_dt = check_expire_time(*expires)?;
// Ban / unban them from our local communities
let community_user_ban_form = CommunityPersonBanForm {
community_id,
person_id: target.id,
expires: Some(expires_dt),
};
if ban {
CommunityPersonBan::ban(&mut context.pool(), &community_user_ban_form)
.await
.with_lemmy_type(LemmyErrorType::CommunityUserAlreadyBanned)
.ok();
// Also unsubscribe them from the community, if they are subscribed
let community_follower_form = CommunityFollowerForm {
community_id,
person_id: target.id,
pending: false,
};
CommunityFollower::unfollow(&mut context.pool(), &community_follower_form)
.await
.ok();
} else {
CommunityPersonBan::unban(&mut context.pool(), &community_user_ban_form)
.await
.with_lemmy_type(LemmyErrorType::CommunityUserAlreadyBanned)?;
}
// Mod tables
let form = ModBanFromCommunityForm {
mod_person_id: local_user_view.person.id,
other_person_id: target.id,
community_id,
reason: reason.clone(),
banned: Some(ban),
expires: expires_dt,
};
ModBanFromCommunity::create(&mut context.pool(), &form).await?;
// Federate the ban from community
let ban_from_community = BanFromCommunity {
community_id,
person_id: target.id,
ban: true,
remove_data: *remove_data,
ban,
reason: reason.clone(),
expires: None,
remove_data: *remove_data,
expires: *expires,
};
ActivityChannel::submit_activity(

View File

@ -1,4 +1,4 @@
use crate::send_bans_and_removals_to_local_communities;
use crate::ban_nonlocal_user_from_local_communities;
use activitypub_federation::config::Data;
use actix_web::web::Json;
use lemmy_api_common::{
@ -72,14 +72,18 @@ pub async fn ban_from_site(
let person_view = PersonView::read(&mut context.pool(), person.id).await?;
send_bans_and_removals_to_local_communities(
&local_user_view,
&person,
&data.reason,
&data.remove_data,
&context,
)
.await?;
if !person.local {
ban_nonlocal_user_from_local_communities(
&local_user_view,
&person,
data.ban,
&data.reason,
&data.remove_data,
&data.expires,
&context,
)
.await?;
}
ActivityChannel::submit_activity(
SendActivityData::BanFromSite {

View File

@ -1,6 +1,6 @@
use crate::{
newtypes::{CommentId, CommunityId, DbUrl, PersonId},
schema::{comment, community, post},
newtypes::{CommentId, DbUrl, PersonId},
schema::comment,
source::comment::{
Comment,
CommentInsertForm,
@ -17,7 +17,6 @@ use diesel::{
dsl::{insert_into, sql_query},
result::Error,
ExpressionMethods,
JoinOnDsl,
QueryDsl,
};
use diesel_async::RunQueryDsl;
@ -56,23 +55,6 @@ impl Comment {
.await
}
/// Lists local community ids for all comments for a given creator.
pub async fn list_creators_local_community_ids(
pool: &mut DbPool<'_>,
for_creator_id: PersonId,
) -> Result<Vec<CommunityId>, Error> {
let conn = &mut get_conn(pool).await?;
comment::table
.inner_join(post::table)
.inner_join(community::table.on(post::community_id.eq(community::id)))
.filter(community::local.eq(true))
.filter(comment::creator_id.eq(for_creator_id))
.select(community::id)
.distinct()
.load::<CommunityId>(conn)
.await
}
pub async fn create(
pool: &mut DbPool<'_>,
comment_form: &CommentInsertForm,

View File

@ -1,6 +1,6 @@
use crate::{
newtypes::{CommunityId, DbUrl, InstanceId, PersonId},
schema::{instance, local_user, person, person_follower},
schema::{comment, community, instance, local_user, person, person_follower, post},
source::person::{
Person,
PersonFollower,
@ -11,7 +11,7 @@ use crate::{
traits::{ApubActor, Crud, Followable},
utils::{functions::lower, get_conn, naive_now, DbPool},
};
use diesel::{dsl::insert_into, result::Error, ExpressionMethods, JoinOnDsl, QueryDsl};
use diesel::{dsl::insert_into, result::Error, CombineDsl, ExpressionMethods, JoinOnDsl, QueryDsl};
use diesel_async::RunQueryDsl;
#[async_trait]
@ -84,6 +84,29 @@ impl Person {
.get_result::<Self>(conn)
.await
}
/// Lists local community ids for all posts and comments for a given creator.
pub async fn list_local_community_ids(
pool: &mut DbPool<'_>,
for_creator_id: PersonId,
) -> Result<Vec<CommunityId>, Error> {
let conn = &mut get_conn(pool).await?;
comment::table
.inner_join(post::table)
.inner_join(community::table.on(post::community_id.eq(community::id)))
.filter(community::local.eq(true))
.filter(comment::creator_id.eq(for_creator_id))
.select(community::id)
.union(
post::table
.inner_join(community::table)
.filter(community::local.eq(true))
.filter(post::creator_id.eq(for_creator_id))
.select(community::id),
)
.load::<CommunityId>(conn)
.await
}
}
impl PersonInsertForm {

View File

@ -1,6 +1,6 @@
use crate::{
newtypes::{CommunityId, DbUrl, PersonId, PostId},
schema::{community, post},
schema::post,
source::post::{
Post,
PostInsertForm,
@ -236,22 +236,6 @@ impl Post {
.get_results::<Self>(conn)
.await
}
/// Lists local community ids for all posts for a given creator.
pub async fn list_creators_local_community_ids(
pool: &mut DbPool<'_>,
for_creator_id: PersonId,
) -> Result<Vec<CommunityId>, Error> {
let conn = &mut get_conn(pool).await?;
post::table
.inner_join(community::table)
.filter(community::local.eq(true))
.filter(post::creator_id.eq(for_creator_id))
.select(community::id)
.distinct()
.load::<CommunityId>(conn)
.await
}
}
#[async_trait]