From 8d91543a13c753827f221acc700cc41e541cadf2 Mon Sep 17 00:00:00 2001
From: Nutomic <me@nutomic.com>
Date: Thu, 12 Dec 2024 15:06:38 +0000
Subject: [PATCH] Allow admins to view deleted users (fixes #5249) (#5258)

* Allow admins to view deleted users (fixes #5249)

* remove check
---
 crates/api/src/community/ban.rs             |  2 +-
 crates/api/src/local_user/ban_person.rs     |  2 +-
 crates/api/src/local_user/block.rs          |  2 +-
 crates/api_common/src/utils.rs              |  2 --
 crates/apub/src/api/read_person.rs          |  8 +++--
 crates/apub/src/api/resolve_object.rs       |  2 +-
 crates/db_views_actor/src/community_view.rs |  4 +--
 crates/db_views_actor/src/person_view.rs    | 34 +++++++++++++--------
 crates/utils/src/error.rs                   |  5 ++-
 9 files changed, 36 insertions(+), 25 deletions(-)

diff --git a/crates/api/src/community/ban.rs b/crates/api/src/community/ban.rs
index 8689d25636..547838fa78 100644
--- a/crates/api/src/community/ban.rs
+++ b/crates/api/src/community/ban.rs
@@ -110,7 +110,7 @@ pub async fn ban_from_community(
 
   ModBanFromCommunity::create(&mut context.pool(), &form).await?;
 
-  let person_view = PersonView::read(&mut context.pool(), data.person_id).await?;
+  let person_view = PersonView::read(&mut context.pool(), data.person_id, false).await?;
 
   ActivityChannel::submit_activity(
     SendActivityData::BanFromCommunity {
diff --git a/crates/api/src/local_user/ban_person.rs b/crates/api/src/local_user/ban_person.rs
index f929433f02..715bd206df 100644
--- a/crates/api/src/local_user/ban_person.rs
+++ b/crates/api/src/local_user/ban_person.rs
@@ -88,7 +88,7 @@ pub async fn ban_from_site(
 
   ModBan::create(&mut context.pool(), &form).await?;
 
-  let person_view = PersonView::read(&mut context.pool(), person.id).await?;
+  let person_view = PersonView::read(&mut context.pool(), person.id, false).await?;
 
   ban_nonlocal_user_from_local_communities(
     &local_user_view,
diff --git a/crates/api/src/local_user/block.rs b/crates/api/src/local_user/block.rs
index 80532e897a..3aee554d46 100644
--- a/crates/api/src/local_user/block.rs
+++ b/crates/api/src/local_user/block.rs
@@ -48,7 +48,7 @@ pub async fn user_block_person(
       .with_lemmy_type(LemmyErrorType::PersonBlockAlreadyExists)?;
   }
 
-  let person_view = PersonView::read(&mut context.pool(), target_id).await?;
+  let person_view = PersonView::read(&mut context.pool(), target_id, false).await?;
   Ok(Json(BlockPersonResponse {
     person_view,
     blocked: data.block,
diff --git a/crates/api_common/src/utils.rs b/crates/api_common/src/utils.rs
index 21154c823c..80f559edb8 100644
--- a/crates/api_common/src/utils.rs
+++ b/crates/api_common/src/utils.rs
@@ -123,8 +123,6 @@ pub fn is_admin(local_user_view: &LocalUserView) -> LemmyResult<()> {
   check_user_valid(&local_user_view.person)?;
   if !local_user_view.local_user.admin {
     Err(LemmyErrorType::NotAnAdmin)?
-  } else if local_user_view.person.banned {
-    Err(LemmyErrorType::Banned)?
   } else {
     Ok(())
   }
diff --git a/crates/apub/src/api/read_person.rs b/crates/apub/src/api/read_person.rs
index fac68cd63b..72dce81400 100644
--- a/crates/apub/src/api/read_person.rs
+++ b/crates/apub/src/api/read_person.rs
@@ -4,7 +4,7 @@ use actix_web::web::{Json, Query};
 use lemmy_api_common::{
   context::LemmyContext,
   person::{GetPersonDetails, GetPersonDetailsResponse},
-  utils::{check_private_instance, read_site_for_actor},
+  utils::{check_private_instance, is_admin, read_site_for_actor},
 };
 use lemmy_db_schema::{source::person::Person, utils::post_to_comment_sort_type};
 use lemmy_db_views::{
@@ -45,7 +45,11 @@ pub async fn read_person(
 
   // You don't need to return settings for the user, since this comes back with GetSite
   // `my_user`
-  let person_view = PersonView::read(&mut context.pool(), person_details_id).await?;
+  let is_admin = local_user_view
+    .as_ref()
+    .map(|l| is_admin(l).is_ok())
+    .unwrap_or_default();
+  let person_view = PersonView::read(&mut context.pool(), person_details_id, is_admin).await?;
 
   let sort = data.sort;
   let page = data.page;
diff --git a/crates/apub/src/api/resolve_object.rs b/crates/apub/src/api/resolve_object.rs
index 04d4895920..8d2cd384f0 100644
--- a/crates/apub/src/api/resolve_object.rs
+++ b/crates/apub/src/api/resolve_object.rs
@@ -60,7 +60,7 @@ async fn convert_response(
       }
     },
     SearchableObjects::PersonOrCommunity(pc) => match *pc {
-      UserOrCommunity::User(u) => res.person = Some(PersonView::read(pool, u.id).await?),
+      UserOrCommunity::User(u) => res.person = Some(PersonView::read(pool, u.id, is_admin).await?),
       UserOrCommunity::Community(c) => {
         res.community = Some(CommunityView::read(pool, c.id, local_user.as_ref(), is_admin).await?)
       }
diff --git a/crates/db_views_actor/src/community_view.rs b/crates/db_views_actor/src/community_view.rs
index f6ce82d370..8bcf50ba39 100644
--- a/crates/db_views_actor/src/community_view.rs
+++ b/crates/db_views_actor/src/community_view.rs
@@ -188,7 +188,7 @@ impl CommunityView {
     let is_mod =
       CommunityModeratorView::check_is_community_moderator(pool, community_id, person_id).await;
     if is_mod.is_ok()
-      || PersonView::read(pool, person_id)
+      || PersonView::read(pool, person_id, false)
         .await
         .is_ok_and(|t| t.is_admin)
     {
@@ -206,7 +206,7 @@ impl CommunityView {
     let is_mod_of_any =
       CommunityModeratorView::is_community_moderator_of_any(pool, person_id).await;
     if is_mod_of_any.is_ok()
-      || PersonView::read(pool, person_id)
+      || PersonView::read(pool, person_id, false)
         .await
         .is_ok_and(|t| t.is_admin)
     {
diff --git a/crates/db_views_actor/src/person_view.rs b/crates/db_views_actor/src/person_view.rs
index 39d1ac27c6..b90ab78115 100644
--- a/crates/db_views_actor/src/person_view.rs
+++ b/crates/db_views_actor/src/person_view.rs
@@ -58,12 +58,11 @@ fn post_to_person_sort_type(sort: PostSortType) -> PersonSortType {
 }
 
 fn queries<'a>(
-) -> Queries<impl ReadFn<'a, PersonView, PersonId>, impl ListFn<'a, PersonView, ListMode>> {
+) -> Queries<impl ReadFn<'a, PersonView, (PersonId, bool)>, impl ListFn<'a, PersonView, ListMode>> {
   let all_joins = move |query: person::BoxedQuery<'a, Pg>| {
     query
       .inner_join(person_aggregates::table)
       .left_join(local_user::table)
-      .filter(person::deleted.eq(false))
       .select((
         person::all_columns,
         person_aggregates::all_columns,
@@ -71,14 +70,17 @@ fn queries<'a>(
       ))
   };
 
-  let read = move |mut conn: DbConn<'a>, person_id: PersonId| async move {
-    all_joins(person::table.find(person_id).into_boxed())
-      .first(&mut conn)
-      .await
+  let read = move |mut conn: DbConn<'a>, params: (PersonId, bool)| async move {
+    let (person_id, is_admin) = params;
+    let mut query = all_joins(person::table.find(person_id).into_boxed());
+    if !is_admin {
+      query = query.filter(person::deleted.eq(false));
+    }
+    query.first(&mut conn).await
   };
 
   let list = move |mut conn: DbConn<'a>, mode: ListMode| async move {
-    let mut query = all_joins(person::table.into_boxed());
+    let mut query = all_joins(person::table.into_boxed()).filter(person::deleted.eq(false));
     match mode {
       ListMode::Admins => {
         query = query
@@ -135,8 +137,12 @@ fn queries<'a>(
 }
 
 impl PersonView {
-  pub async fn read(pool: &mut DbPool<'_>, person_id: PersonId) -> Result<Self, Error> {
-    queries().read(pool, person_id).await
+  pub async fn read(
+    pool: &mut DbPool<'_>,
+    person_id: PersonId,
+    is_admin: bool,
+  ) -> Result<Self, Error> {
+    queries().read(pool, (person_id, is_admin)).await
   }
 
   pub async fn admins(pool: &mut DbPool<'_>) -> Result<Vec<Self>, Error> {
@@ -243,9 +249,13 @@ mod tests {
     )
     .await?;
 
-    let read = PersonView::read(pool, data.alice.id).await;
+    let read = PersonView::read(pool, data.alice.id, false).await;
     assert!(read.is_err());
 
+    // only admin can view deleted users
+    let read = PersonView::read(pool, data.alice.id, true).await;
+    assert!(read.is_ok());
+
     let list = PersonQuery {
       sort: Some(PostSortType::New),
       ..Default::default()
@@ -303,10 +313,10 @@ mod tests {
     assert_length!(1, list);
     assert_eq!(list[0].person.id, data.alice.id);
 
-    let is_admin = PersonView::read(pool, data.alice.id).await?.is_admin;
+    let is_admin = PersonView::read(pool, data.alice.id, false).await?.is_admin;
     assert!(is_admin);
 
-    let is_admin = PersonView::read(pool, data.bob.id).await?.is_admin;
+    let is_admin = PersonView::read(pool, data.bob.id, false).await?.is_admin;
     assert!(!is_admin);
 
     cleanup(data, pool).await
diff --git a/crates/utils/src/error.rs b/crates/utils/src/error.rs
index f45bc271f6..4f28aaa32e 100644
--- a/crates/utils/src/error.rs
+++ b/crates/utils/src/error.rs
@@ -113,7 +113,6 @@ pub enum LemmyErrorType {
   SystemErrLogin,
   CouldntSetAllRegistrationsAccepted,
   CouldntSetAllEmailVerified,
-  Banned,
   BlockedUrl,
   CouldntGetComments,
   CouldntGetPosts,
@@ -328,9 +327,9 @@ cfg_if! {
 
       #[test]
       fn deserializes_no_message() -> LemmyResult<()> {
-        let err = LemmyError::from(LemmyErrorType::Banned).error_response();
+        let err = LemmyError::from(LemmyErrorType::BlockedUrl).error_response();
         let json = String::from_utf8(err.into_body().try_into_bytes().unwrap_or_default().to_vec())?;
-        assert_eq!(&json, "{\"error\":\"banned\"}");
+        assert_eq!(&json, "{\"error\":\"blocked_url\"}");
 
         Ok(())
       }