Allow adding remote users as community mods (ref #1061)

This commit is contained in:
Felix Ableitner 2021-03-09 18:13:08 +01:00
parent dcf40db225
commit 3ffae1f5b8
7 changed files with 121 additions and 23 deletions

View file

@ -10,6 +10,7 @@ use actix_web::web::Data;
use anyhow::Context; use anyhow::Context;
use lemmy_api_structs::{blocking, community::*}; use lemmy_api_structs::{blocking, community::*};
use lemmy_apub::{ use lemmy_apub::{
activities::send::community::{send_add_mod, send_remove_mod},
generate_apub_endpoint, generate_apub_endpoint,
generate_followers_url, generate_followers_url,
generate_inbox_url, generate_inbox_url,
@ -34,7 +35,7 @@ use lemmy_db_queries::{
}; };
use lemmy_db_schema::{ use lemmy_db_schema::{
naive_now, naive_now,
source::{comment::Comment, community::*, moderator::*, post::Post, site::*}, source::{comment::Comment, community::*, moderator::*, post::Post, site::*, user::User_},
}; };
use lemmy_db_views::comment_view::CommentQueryBuilder; use lemmy_db_views::comment_view::CommentQueryBuilder;
use lemmy_db_views_actor::{ use lemmy_db_views_actor::{
@ -699,16 +700,16 @@ impl Perform for AddModToCommunity {
let data: &AddModToCommunity = &self; let data: &AddModToCommunity = &self;
let user = get_user_from_jwt(&data.auth, context.pool()).await?; let user = get_user_from_jwt(&data.auth, context.pool()).await?;
let community_moderator_form = CommunityModeratorForm {
community_id: data.community_id,
user_id: data.user_id,
};
let community_id = data.community_id; let community_id = data.community_id;
// Verify that only mods or admins can add mod // Verify that only mods or admins can add mod
is_mod_or_admin(context.pool(), user.id, community_id).await?; is_mod_or_admin(context.pool(), user.id, community_id).await?;
// Update in local database
let community_moderator_form = CommunityModeratorForm {
community_id: data.community_id,
user_id: data.user_id,
};
if data.added { if data.added {
let join = move |conn: &'_ _| CommunityModerator::join(conn, &community_moderator_form); let join = move |conn: &'_ _| CommunityModerator::join(conn, &community_moderator_form);
if blocking(context.pool(), join).await?.is_err() { if blocking(context.pool(), join).await?.is_err() {
@ -733,6 +734,25 @@ impl Perform for AddModToCommunity {
}) })
.await??; .await??;
// Send to federated instances
let updated_mod_id = data.user_id;
let updated_mod = blocking(context.pool(), move |conn| {
User_::read(conn, updated_mod_id)
})
.await??;
let community = blocking(context.pool(), move |conn| {
Community::read(conn, community_id)
})
.await??;
dbg!(data.added);
if data.added {
send_add_mod(user, updated_mod, community, context).await?;
} else {
send_remove_mod(user, updated_mod, community, context).await?;
}
// Note: in case a remote mod is added, this returns the old moderators list, it will only get
// updated once we receive an activity from the community (like `Announce/Add/Moderator`)
let community_id = data.community_id; let community_id = data.community_id;
let moderators = blocking(context.pool(), move |conn| { let moderators = blocking(context.pool(), move |conn| {
CommunityModeratorView::for_community(conn, community_id) CommunityModeratorView::for_community(conn, community_id)
@ -740,18 +760,18 @@ impl Perform for AddModToCommunity {
.await??; .await??;
let res = AddModToCommunityResponse { moderators }; let res = AddModToCommunityResponse { moderators };
context.chat_server().do_send(SendCommunityRoomMessage { context.chat_server().do_send(SendCommunityRoomMessage {
op: UserOperation::AddModToCommunity, op: UserOperation::AddModToCommunity,
response: res.clone(), response: res.clone(),
community_id, community_id,
websocket_id, websocket_id,
}); });
Ok(res) Ok(res)
} }
} }
// TODO: we dont do anything for federation here, it should be updated the next time the community
// gets fetched. i hope we can get rid of the community creator role soon.
#[async_trait::async_trait(?Send)] #[async_trait::async_trait(?Send)]
impl Perform for TransferCommunity { impl Perform for TransferCommunity {
type Response = GetCommunityResponse; type Response = GetCommunityResponse;

View file

@ -1,2 +1,2 @@
pub(crate) mod receive; pub(crate) mod receive;
pub(crate) mod send; pub mod send;

View file

@ -1,19 +1,22 @@
use crate::{ use crate::{
activities::send::generate_activity_id, activities::send::generate_activity_id,
activity_queue::{send_activity_single_dest, send_to_community_followers}, activity_queue::{send_activity_single_dest, send_to_community, send_to_community_followers},
check_is_apub_id_valid, check_is_apub_id_valid,
extensions::context::lemmy_context, extensions::context::lemmy_context,
fetcher::user::get_or_fetch_and_upsert_user, fetcher::user::get_or_fetch_and_upsert_user,
generate_moderators_url,
ActorType, ActorType,
}; };
use activitystreams::{ use activitystreams::{
activity::{ activity::{
kind::{AcceptType, AnnounceType, DeleteType, LikeType, RemoveType, UndoType}, kind::{AcceptType, AddType, AnnounceType, DeleteType, LikeType, RemoveType, UndoType},
Accept, Accept,
ActorAndObjectRefExt, ActorAndObjectRefExt,
Add,
Announce, Announce,
Delete, Delete,
Follow, Follow,
OptTargetRefExt,
Remove, Remove,
Undo, Undo,
}, },
@ -25,7 +28,7 @@ use anyhow::Context;
use itertools::Itertools; use itertools::Itertools;
use lemmy_api_structs::blocking; use lemmy_api_structs::blocking;
use lemmy_db_queries::DbPool; use lemmy_db_queries::DbPool;
use lemmy_db_schema::source::community::Community; use lemmy_db_schema::source::{community::Community, user::User_};
use lemmy_db_views_actor::community_follower_view::CommunityFollowerView; use lemmy_db_views_actor::community_follower_view::CommunityFollowerView;
use lemmy_utils::{location_info, LemmyError}; use lemmy_utils::{location_info, LemmyError};
use lemmy_websocket::LemmyContext; use lemmy_websocket::LemmyContext;
@ -202,3 +205,55 @@ impl ActorType for Community {
Ok(inboxes) Ok(inboxes)
} }
} }
pub async fn send_add_mod(
actor: User_,
added_mod: User_,
community: Community,
context: &LemmyContext,
) -> Result<(), LemmyError> {
let mut add = Add::new(
actor.actor_id.clone().into_inner(),
added_mod.actor_id.into_inner(),
);
add
.set_many_contexts(lemmy_context()?)
.set_id(generate_activity_id(AddType::Add)?)
.set_many_tos(vec![community.actor_id.to_owned().into_inner(), public()])
.set_target(generate_moderators_url(&community.actor_id)?.into_inner());
if community.local {
community
.send_announce(add.into_any_base()?, context)
.await?;
} else {
send_to_community(add, &actor, &community, context).await?;
}
Ok(())
}
pub async fn send_remove_mod(
actor: User_,
removed_mod: User_,
community: Community,
context: &LemmyContext,
) -> Result<(), LemmyError> {
let mut remove = Remove::new(
actor.actor_id.clone().into_inner(),
removed_mod.actor_id.into_inner(),
);
remove
.set_many_contexts(lemmy_context()?)
.set_id(generate_activity_id(RemoveType::Remove)?)
.set_many_tos(vec![community.actor_id.to_owned().into_inner(), public()])
.set_target(generate_moderators_url(&community.actor_id)?.into_inner());
if community.local {
community
.send_announce(remove.into_any_base()?, context)
.await?;
} else {
send_to_community(remove, &actor, &community, context).await?;
}
Ok(())
}

View file

@ -3,7 +3,7 @@ use url::{ParseError, Url};
use uuid::Uuid; use uuid::Uuid;
pub(crate) mod comment; pub(crate) mod comment;
pub(crate) mod community; pub mod community;
pub(crate) mod post; pub(crate) mod post;
pub(crate) mod private_message; pub(crate) mod private_message;
pub(crate) mod user; pub(crate) mod user;

View file

@ -37,6 +37,7 @@ use crate::{
}, },
find_post_or_comment_by_id, find_post_or_comment_by_id,
inbox::is_addressed_to_public, inbox::is_addressed_to_public,
ActorType,
PostOrComment, PostOrComment,
}; };
use activitystreams::{ use activitystreams::{
@ -58,7 +59,7 @@ use activitystreams::{
use anyhow::{anyhow, Context}; use anyhow::{anyhow, Context};
use diesel::result::Error::NotFound; use diesel::result::Error::NotFound;
use lemmy_api_structs::blocking; use lemmy_api_structs::blocking;
use lemmy_db_queries::{ApubObject, Crud, Joinable}; use lemmy_db_queries::{source::community::CommunityModerator_, ApubObject, Crud, Joinable};
use lemmy_db_schema::{ use lemmy_db_schema::{
source::{ source::{
community::{Community, CommunityModerator, CommunityModeratorForm}, community::{Community, CommunityModerator, CommunityModeratorForm},
@ -213,7 +214,7 @@ pub(in crate::inbox) async fn receive_remove_for_community(
expected_domain: &Url, expected_domain: &Url,
request_counter: &mut i32, request_counter: &mut i32,
) -> Result<(), LemmyError> { ) -> Result<(), LemmyError> {
let remove = Remove::from_any_base(activity)?.context(location_info!())?; let remove = Remove::from_any_base(activity.to_owned())?.context(location_info!())?;
verify_activity_domains_valid(&remove, &expected_domain, false)?; verify_activity_domains_valid(&remove, &expected_domain, false)?;
is_addressed_to_public(&remove)?; is_addressed_to_public(&remove)?;
@ -234,6 +235,8 @@ pub(in crate::inbox) async fn receive_remove_for_community(
CommunityModerator::leave(conn, &form) CommunityModerator::leave(conn, &form)
}) })
.await??; .await??;
community.send_announce(activity, context).await?;
// TODO: send websocket notification about removed mod
Ok(()) Ok(())
} }
// Remove a post or comment // Remove a post or comment
@ -385,7 +388,7 @@ pub(in crate::inbox) async fn receive_add_for_community(
expected_domain: &Url, expected_domain: &Url,
request_counter: &mut i32, request_counter: &mut i32,
) -> Result<(), LemmyError> { ) -> Result<(), LemmyError> {
let add = Add::from_any_base(activity)?.context(location_info!())?; let add = Add::from_any_base(activity.to_owned())?.context(location_info!())?;
verify_activity_domains_valid(&add, &expected_domain, false)?; verify_activity_domains_valid(&add, &expected_domain, false)?;
is_addressed_to_public(&add)?; is_addressed_to_public(&add)?;
let community = verify_actor_is_community_mod(&add, context).await?; let community = verify_actor_is_community_mod(&add, context).await?;
@ -395,14 +398,28 @@ pub(in crate::inbox) async fn receive_add_for_community(
.as_single_xsd_any_uri() .as_single_xsd_any_uri()
.context(location_info!())?; .context(location_info!())?;
let new_mod = get_or_fetch_and_upsert_user(&new_mod, context, request_counter).await?; let new_mod = get_or_fetch_and_upsert_user(&new_mod, context, request_counter).await?;
let form = CommunityModeratorForm {
community_id: community.id, // If we had to refetch the community while parsing the activity, then the new mod has already
user_id: new_mod.id, // been added. Skip it here as it would result in a duplicate key error.
}; let new_mod_id = new_mod.id;
blocking(context.pool(), move |conn| { let moderated_communities = blocking(context.pool(), move |conn| {
CommunityModerator::join(conn, &form) CommunityModerator::get_user_moderated_communities(conn, new_mod_id)
}) })
.await??; .await??;
if moderated_communities.contains(&community.id) {
let form = CommunityModeratorForm {
community_id: community.id,
user_id: new_mod.id,
};
blocking(context.pool(), move |conn| {
CommunityModerator::join(conn, &form)
})
.await??;
}
if community.local {
community.send_announce(activity, context).await?;
}
// TODO: send websocket notification about added mod
Ok(()) Ok(())
} }
@ -458,7 +475,7 @@ where
// should be the moderators collection of a local community // should be the moderators collection of a local community
let target = activity let target = activity
.target() .target()
.map(|t| t.as_single_xsd_string()) .map(|t| t.as_single_xsd_any_uri())
.flatten() .flatten()
.context(location_info!())?; .context(location_info!())?;
// TODO: very hacky, we should probably store the moderators url in db // TODO: very hacky, we should probably store the moderators url in db

View file

@ -36,6 +36,7 @@ pub enum ValidTypes {
Undo, Undo,
Remove, Remove,
Announce, Announce,
Add,
} }
// TODO: this isnt entirely correct, cause some of these receive are not ActorAndObject, // TODO: this isnt entirely correct, cause some of these receive are not ActorAndObject,

View file

@ -28,6 +28,7 @@ use crate::{
is_addressed_to_local_user, is_addressed_to_local_user,
is_addressed_to_public, is_addressed_to_public,
receive_for_community::{ receive_for_community::{
receive_add_for_community,
receive_create_for_community, receive_create_for_community,
receive_delete_for_community, receive_delete_for_community,
receive_dislike_for_community, receive_dislike_for_community,
@ -252,6 +253,7 @@ enum AnnouncableActivities {
Delete, Delete,
Remove, Remove,
Undo, Undo,
Add,
} }
/// Takes an announce and passes the inner activity to the appropriate handler. /// Takes an announce and passes the inner activity to the appropriate handler.
@ -302,6 +304,9 @@ pub async fn receive_announce(
Some(Undo) => { Some(Undo) => {
receive_undo_for_community(context, inner_activity, &inner_id, request_counter).await receive_undo_for_community(context, inner_activity, &inner_id, request_counter).await
} }
Some(Add) => {
receive_add_for_community(context, inner_activity, &inner_id, request_counter).await
}
_ => receive_unhandled_activity(inner_activity), _ => receive_unhandled_activity(inner_activity),
} }
} }