Skip to content

Receive and store remote site bans (fixes #3399) #5515

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 35 commits into from
Mar 28, 2025
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
08a324c
Receive and store remote site bans (fixes #3399)
Nutomic Mar 17, 2025
a47e48d
db schema changes, handly expiration
Nutomic Mar 17, 2025
900eb1d
partial federation changes
Nutomic Mar 17, 2025
aa8767c
add column `mod_ban.instance_id`
Nutomic Mar 18, 2025
858621a
remove unused
Nutomic Mar 18, 2025
71e1e19
wip: remove person.banned
Nutomic Mar 18, 2025
f60f6fd
fix down migration
Nutomic Mar 18, 2025
2ad4caf
fmt, keep banned status
Nutomic Mar 18, 2025
11d69db
fixes
Nutomic Mar 18, 2025
0eac34e
simplify
Nutomic Mar 18, 2025
2302931
update post removed
Nutomic Mar 18, 2025
d19200f
Merge branch 'main' into site-person-ban
Nutomic Mar 18, 2025
49f7636
Merge branch 'main' into site-person-ban
Nutomic Mar 19, 2025
da732c0
fix api tests
Nutomic Mar 19, 2025
44ed5f2
ban from local instance
Nutomic Mar 20, 2025
cbde6c5
banned() helpers
Nutomic Mar 20, 2025
34ef0cb
cleanup undo_block_user
Nutomic Mar 20, 2025
e65ee24
remove order by
Nutomic Mar 20, 2025
e6690b0
cache SiteView::read_local, add instance
Nutomic Mar 25, 2025
5879013
use local instance id for PersonView
Nutomic Mar 25, 2025
ac07ba7
add helper function person_with_instance_actions
Nutomic Mar 25, 2025
00a6554
use exists
Nutomic Mar 25, 2025
81503dc
comments update removed
Nutomic Mar 25, 2025
02e79c2
remove method is_mod_or_admin
Nutomic Mar 25, 2025
a943060
fix tests
Nutomic Mar 25, 2025
d1473c2
no unwrap
Nutomic Mar 25, 2025
564c7da
change local_instance_person_join
Nutomic Mar 25, 2025
afcdb56
remove LocalSite::read
Nutomic Mar 26, 2025
b805891
wip: home_instance_actions
Nutomic Mar 26, 2025
d79a934
add home_instance_actions to all structs
Nutomic Mar 26, 2025
7acc461
fixes
Nutomic Mar 27, 2025
1e6e1b9
fix tests
Nutomic Mar 27, 2025
27f1eb9
fmt
Nutomic Mar 27, 2025
f538d83
Merge branch 'main' into site-person-ban
Nutomic Mar 28, 2025
22131e2
Merge branch 'main' into site-person-ban
dessalines Mar 28, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions crates/api/src/comment/distinguish.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ pub async fn distinguish_comment(
.await?;

check_community_user_action(
&local_user_view.person,
&local_user_view,
&orig_comment.community,
&mut context.pool(),
)
Expand All @@ -37,7 +37,7 @@ pub async fn distinguish_comment(

// Verify that only a mod or admin can distinguish a comment
check_community_mod_action(
&local_user_view.person,
&local_user_view,
&orig_comment.community,
false,
&mut context.pool(),
Expand Down
2 changes: 1 addition & 1 deletion crates/api/src/comment/like.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ pub async fn like_comment(
.await?;

check_community_user_action(
&local_user_view.person,
&local_user_view,
&orig_comment.community,
&mut context.pool(),
)
Expand Down
2 changes: 1 addition & 1 deletion crates/api/src/comment/list_comment_likes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ pub async fn list_comment_likes(

is_mod_or_admin(
&mut context.pool(),
&local_user_view.person,
&local_user_view,
comment_view.community.id,
)
.await?;
Expand Down
8 changes: 1 addition & 7 deletions crates/api/src/community/add_mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,7 @@ pub async fn add_mod_to_community(
) -> LemmyResult<Json<AddModToCommunityResponse>> {
let community = Community::read(&mut context.pool(), data.community_id).await?;
// Verify that only mods or admins can add mod
check_community_mod_action(
&local_user_view.person,
&community,
false,
&mut context.pool(),
)
.await?;
check_community_mod_action(&local_user_view, &community, false, &mut context.pool()).await?;

// If its a mod removal, also check that you're a higher mod.
if !data.added {
Expand Down
8 changes: 1 addition & 7 deletions crates/api/src/community/ban.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,7 @@ pub async fn ban_from_community(
let community = Community::read(&mut context.pool(), data.community_id).await?;

// Verify that only mods or admins can ban
check_community_mod_action(
&local_user_view.person,
&community,
false,
&mut context.pool(),
)
.await?;
check_community_mod_action(&local_user_view, &community, false, &mut context.pool()).await?;

LocalUser::is_higher_mod_or_admin_check(
&mut context.pool(),
Expand Down
4 changes: 2 additions & 2 deletions crates/api/src/community/follow.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use lemmy_api_common::{
community::{CommunityResponse, FollowCommunity},
context::LemmyContext,
send_activity::{ActivityChannel, SendActivityData},
utils::{check_community_deleted_removed, check_user_valid},
utils::{check_community_deleted_removed, check_local_user_valid},
};
use lemmy_db_schema::{
source::{
Expand All @@ -22,7 +22,7 @@ pub async fn follow_community(
context: Data<LemmyContext>,
local_user_view: LocalUserView,
) -> LemmyResult<Json<CommunityResponse>> {
check_user_valid(&local_user_view.person)?;
check_local_user_valid(&local_user_view)?;
let community = Community::read(&mut context.pool(), data.community_id).await?;
let person_id = local_user_view.person.id;

Expand Down
7 changes: 1 addition & 6 deletions crates/api/src/community/pending_follows/approve.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,7 @@ pub async fn post_pending_follows_approve(
context: Data<LemmyContext>,
local_user_view: LocalUserView,
) -> LemmyResult<Json<SuccessResponse>> {
is_mod_or_admin(
&mut context.pool(),
&local_user_view.person,
data.community_id,
)
.await?;
is_mod_or_admin(&mut context.pool(), &local_user_view, data.community_id).await?;

let activity_data = if data.approve {
CommunityActions::approve_follower(
Expand Down
7 changes: 1 addition & 6 deletions crates/api/src/community/pending_follows/count.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,7 @@ pub async fn get_pending_follows_count(
context: Data<LemmyContext>,
local_user_view: LocalUserView,
) -> LemmyResult<Json<GetCommunityPendingFollowsCountResponse>> {
is_mod_or_admin(
&mut context.pool(),
&local_user_view.person,
data.community_id,
)
.await?;
is_mod_or_admin(&mut context.pool(), &local_user_view, data.community_id).await?;
let count =
CommunityFollowerView::count_approval_required(&mut context.pool(), data.community_id).await?;
Ok(Json(GetCommunityPendingFollowsCountResponse { count }))
Expand Down
2 changes: 1 addition & 1 deletion crates/api/src/community/transfer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ pub async fn transfer_community(
let mut community_mods =
CommunityModeratorView::for_community(&mut context.pool(), community.id).await?;

check_community_user_action(&local_user_view.person, &community, &mut context.pool()).await?;
check_community_user_action(&local_user_view, &community, &mut context.pool()).await?;

// Make sure transferrer is either the top community mod, or an admin
if !(is_top_mod(&local_user_view, &community_mods).is_ok() || is_admin(&local_user_view).is_ok())
Expand Down
98 changes: 0 additions & 98 deletions crates/api/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,20 +1,5 @@
use activitypub_federation::config::Data;
use base64::{engine::general_purpose::STANDARD_NO_PAD as base64, Engine};
use captcha::Captcha;
use lemmy_api_common::{
community::BanFromCommunity,
context::LemmyContext,
send_activity::{ActivityChannel, SendActivityData},
utils::check_expire_time,
};
use lemmy_db_schema::{
source::{
community::{CommunityActions, CommunityPersonBanForm},
mod_log::moderator::{ModBanFromCommunity, ModBanFromCommunityForm},
person::Person,
},
traits::{Bannable, Crud, Followable},
};
use lemmy_db_views::structs::LocalUserView;
use lemmy_utils::{
error::{LemmyErrorExt, LemmyErrorType, LemmyResult},
Expand Down Expand Up @@ -132,89 +117,6 @@ fn build_totp_2fa(hostname: &str, username: &str, secret: &str) -> LemmyResult<T
.with_lemmy_type(LemmyErrorType::CouldntGenerateTotp)
}

/// 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
pub(crate) async fn ban_nonlocal_user_from_local_communities(
local_user_view: &LocalUserView,
target: &Person,
ban: bool,
reason: &Option<String>,
remove_or_restore_data: &Option<bool>,
expires: &Option<i64>,
context: &Data<LemmyContext>,
) -> LemmyResult<()> {
// Only run this code for federated users
if !target.local {
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 {
ban_expires: Some(expires_dt),
..CommunityPersonBanForm::new(community_id, target.id)
};

if ban {
// Ignore all errors for these
CommunityActions::ban(&mut context.pool(), &community_user_ban_form)
.await
.ok();

// Also unsubscribe them from the community, if they are subscribed

CommunityActions::unfollow(&mut context.pool(), target.id, community_id)
.await
.ok();
} else {
CommunityActions::unban(&mut context.pool(), &community_user_ban_form)
.await
.ok();
}

// 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,
reason: reason.clone(),
remove_or_restore_data: *remove_or_restore_data,
expires: *expires,
};

ActivityChannel::submit_activity(
SendActivityData::BanFromCommunity {
moderator: local_user_view.person.clone(),
community_id,
target: target.clone(),
data: ban_from_community,
},
context,
)?;
}
}

Ok(())
}

#[cfg(test)]
mod tests {

Expand Down
42 changes: 17 additions & 25 deletions crates/api/src/local_user/ban_person.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use crate::ban_nonlocal_user_from_local_communities;
use activitypub_federation::config::Data;
use actix_web::web::Json;
use chrono::Utc;
use lemmy_api_common::{
context::LemmyContext,
person::{BanPerson, BanPersonResponse},
Expand All @@ -9,16 +9,17 @@ use lemmy_api_common::{
};
use lemmy_db_schema::{
source::{
instance::{InstanceActions, InstanceBanForm},
local_user::LocalUser,
login_token::LoginToken,
mod_log::moderator::{ModBan, ModBanForm},
person::{Person, PersonUpdateForm},
person::Person,
},
traits::Crud,
traits::{Bannable, Crud},
};
use lemmy_db_views::structs::{LocalUserView, PersonView};
use lemmy_utils::{
error::{LemmyErrorExt, LemmyErrorType, LemmyResult},
error::{LemmyErrorExt2, LemmyErrorType, LemmyResult},
utils::validation::is_valid_body_field,
};

Expand All @@ -44,20 +45,21 @@ pub async fn ban_from_site(

let expires = check_expire_time(data.expires)?;

let person = Person::update(
let target_person = Person::read(&mut context.pool(), data.person_id).await?;
InstanceActions::ban(
&mut context.pool(),
data.person_id,
&PersonUpdateForm {
banned: Some(data.ban),
ban_expires: Some(expires),
..Default::default()
&InstanceBanForm {
person_id: target_person.id,
instance_id: target_person.instance_id,
received_ban: Utc::now(),
ban_expires: expires,
},
)
.await
.with_lemmy_type(LemmyErrorType::CouldntUpdateUser)?;

// if its a local user, invalidate logins
let local_user = LocalUserView::read_person(&mut context.pool(), person.id).await;
let local_user = LocalUserView::read_person(&mut context.pool(), target_person.id).await;
if let Ok(local_user) = local_user {
LoginToken::invalidate_all(&mut context.pool(), local_user.local_user.id).await?;
}
Expand All @@ -67,7 +69,7 @@ pub async fn ban_from_site(
let removed = data.ban;
remove_or_restore_user_data(
local_user_view.person.id,
person.id,
target_person.id,
removed,
&data.reason,
&context,
Expand All @@ -78,26 +80,16 @@ pub async fn ban_from_site(
// Mod tables
let form = ModBanForm {
mod_person_id: local_user_view.person.id,
other_person_id: person.id,
other_person_id: target_person.id,
reason: data.reason.clone(),
banned: Some(data.ban),
expires,
instance_id: local_user_view.person.instance_id,
};

ModBan::create(&mut context.pool(), &form).await?;

let person_view = PersonView::read(&mut context.pool(), person.id, false).await?;

ban_nonlocal_user_from_local_communities(
&local_user_view,
&person,
data.ban,
&data.reason,
&data.remove_or_restore_data,
&data.expires,
&context,
)
.await?;
let person_view = PersonView::read(&mut context.pool(), target_person.id, false).await?;

ActivityChannel::submit_activity(
SendActivityData::BanFromSite {
Expand Down
4 changes: 2 additions & 2 deletions crates/api/src/local_user/login.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use lemmy_api_common::{
claims::Claims,
context::LemmyContext,
person::{Login, LoginResponse},
utils::{check_email_verified, check_registration_application, check_user_valid},
utils::{check_email_verified, check_local_user_valid, check_registration_application},
};
use lemmy_db_views::structs::{LocalUserView, SiteView};
use lemmy_utils::error::{LemmyErrorType, LemmyResult};
Expand All @@ -35,7 +35,7 @@ pub async fn login(
if !valid {
Err(LemmyErrorType::IncorrectLogin)?
}
check_user_valid(&local_user_view.person)?;
check_local_user_valid(&local_user_view)?;
check_email_verified(&local_user_view, &site_view)?;

check_registration_application(&local_user_view, &site_view.local_site, &mut context.pool())
Expand Down
8 changes: 1 addition & 7 deletions crates/api/src/post/feature.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,7 @@ pub async fn feature_post(
let orig_post = Post::read(&mut context.pool(), post_id).await?;

let community = Community::read(&mut context.pool(), orig_post.community_id).await?;
check_community_mod_action(
&local_user_view.person,
&community,
false,
&mut context.pool(),
)
.await?;
check_community_mod_action(&local_user_view, &community, false, &mut context.pool()).await?;

if data.feature_type == PostFeatureType::Local {
is_admin(&local_user_view)?;
Expand Down
7 changes: 1 addition & 6 deletions crates/api/src/post/like.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,12 +40,7 @@ pub async fn like_post(
// Check for a community ban
let post = PostView::read(&mut context.pool(), post_id, None, false).await?;

check_community_user_action(
&local_user_view.person,
&post.community,
&mut context.pool(),
)
.await?;
check_community_user_action(&local_user_view, &post.community, &mut context.pool()).await?;

let like_form = PostLikeForm::new(data.post_id, local_user_view.person.id, data.score);

Expand Down
7 changes: 1 addition & 6 deletions crates/api/src/post/list_post_likes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,7 @@ pub async fn list_post_likes(
local_user_view: LocalUserView,
) -> LemmyResult<Json<ListPostLikesResponse>> {
let post = Post::read(&mut context.pool(), data.post_id).await?;
is_mod_or_admin(
&mut context.pool(),
&local_user_view.person,
post.community_id,
)
.await?;
is_mod_or_admin(&mut context.pool(), &local_user_view, post.community_id).await?;

let post_likes =
VoteView::list_for_post(&mut context.pool(), data.post_id, data.page, data.limit).await?;
Expand Down
2 changes: 1 addition & 1 deletion crates/api/src/post/lock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ pub async fn lock_post(
let orig_post = PostView::read(&mut context.pool(), post_id, None, false).await?;

check_community_mod_action(
&local_user_view.person,
&local_user_view,
&orig_post.community,
false,
&mut context.pool(),
Expand Down
Loading