Implemented receiving activities to add/remove remote mods

This commit is contained in:
Felix Ableitner 2021-03-08 17:34:54 +01:00
parent 0c484e8c76
commit 9172eff65a
3 changed files with 145 additions and 32 deletions

View file

@ -8,10 +8,12 @@ use crate::{
is_activity_already_known,
is_addressed_to_public,
receive_for_community::{
receive_add_for_community,
receive_create_for_community,
receive_delete_for_community,
receive_dislike_for_community,
receive_like_for_community,
receive_remove_for_community,
receive_undo_for_community,
receive_update_for_community,
},
@ -51,7 +53,8 @@ pub enum CommunityValidTypes {
Like, // upvote post or comment
Dislike, // downvote post or comment
Delete, // post or comment deleted by creator
Remove, // post or comment removed by mod or admin
Remove, // post or comment removed by mod or admin, or mod removed from community
Add, // mod added to community
}
pub type CommunityAcceptedActivities = ActorAndObject<CommunityValidTypes>;
@ -160,10 +163,13 @@ pub(crate) async fn community_receive_message(
receive_delete_for_community(context, any_base.clone(), &actor_url).await?;
true
}
CommunityValidTypes::Add => {
receive_add_for_community(context, any_base.clone(), &actor_url, request_counter).await?;
true
}
CommunityValidTypes::Remove => {
// TODO: we dont support remote mods, so this is ignored for now
//receive_remove_for_community(context, any_base.clone(), &user_url).await?
false
receive_remove_for_community(context, any_base.clone(), &actor_url, request_counter).await?;
true
}
};

View file

@ -31,21 +31,32 @@ use crate::{
receive_unhandled_activity,
verify_activity_domains_valid,
},
fetcher::objects::{get_or_fetch_and_insert_comment, get_or_fetch_and_insert_post},
fetcher::{
objects::{get_or_fetch_and_insert_comment, get_or_fetch_and_insert_post},
user::get_or_fetch_and_upsert_user,
},
find_post_or_comment_by_id,
inbox::is_addressed_to_public,
PostOrComment,
};
use activitystreams::{
activity::{Create, Delete, Dislike, Like, Remove, Undo, Update},
activity::{ActorAndObjectRef, Add, Create, Delete, Dislike, Like, Remove, Undo, Update},
base::AnyBase,
prelude::*,
};
use anyhow::Context;
use anyhow::{anyhow, Context};
use diesel::result::Error::NotFound;
use lemmy_api_structs::blocking;
use lemmy_db_queries::Crud;
use lemmy_db_schema::source::site::Site;
use lemmy_db_queries::{ApubObject, Crud, Joinable};
use lemmy_db_schema::{
source::{
community::{Community, CommunityModerator, CommunityModeratorForm},
site::Site,
user::User_,
},
DbUrl,
};
use lemmy_db_views_actor::community_view::CommunityView;
use lemmy_utils::{location_info, LemmyError};
use lemmy_websocket::LemmyContext;
use strum_macros::EnumString;
@ -189,36 +200,59 @@ pub(in crate::inbox) async fn receive_remove_for_community(
context: &LemmyContext,
activity: AnyBase,
expected_domain: &Url,
request_counter: &mut i32,
) -> Result<(), LemmyError> {
let remove = Remove::from_any_base(activity)?.context(location_info!())?;
verify_activity_domains_valid(&remove, &expected_domain, false)?;
is_addressed_to_public(&remove)?;
let cc = remove
.cc()
.map(|c| c.as_many())
.flatten()
.context(location_info!())?;
let community_id = cc
.first()
.map(|c| c.as_xsd_any_uri())
.flatten()
.context(location_info!())?;
// Remove a moderator from community
if remove.target().is_some() {
let community = verify_actor_is_community_mod(&remove, context).await?;
let object = remove
.object()
.to_owned()
.single_xsd_any_uri()
.context(location_info!())?;
let remove_mod = remove
.object()
.as_single_xsd_any_uri()
.context(location_info!())?;
let remove_mod = get_or_fetch_and_upsert_user(&remove_mod, context, request_counter).await?;
let form = CommunityModeratorForm {
community_id: community.id,
user_id: remove_mod.id,
};
blocking(context.pool(), move |conn| {
CommunityModerator::leave(conn, &form)
})
.await??;
Ok(())
}
// Remove a post or comment
else {
let cc = remove
.cc()
.map(|c| c.as_many())
.flatten()
.context(location_info!())?;
let community_id = cc
.first()
.map(|c| c.as_xsd_any_uri())
.flatten()
.context(location_info!())?;
// Ensure that remove activity comes from the same domain as the community
remove.id(community_id.domain().context(location_info!())?)?;
let object = remove
.object()
.to_owned()
.single_xsd_any_uri()
.context(location_info!())?;
match find_post_or_comment_by_id(context, object).await {
Ok(PostOrComment::Post(p)) => receive_remove_post(context, remove, *p).await,
Ok(PostOrComment::Comment(c)) => receive_remove_comment(context, remove, *c).await,
// if we dont have the object, no need to do anything
Err(_) => Ok(()),
// Ensure that remove activity comes from the same domain as the community
remove.id(community_id.domain().context(location_info!())?)?;
match find_post_or_comment_by_id(context, object).await {
Ok(PostOrComment::Post(p)) => receive_remove_post(context, remove, *p).await,
Ok(PostOrComment::Comment(c)) => receive_remove_comment(context, remove, *c).await,
// if we dont have the object, no need to do anything
Err(_) => Ok(()),
}
}
}
@ -333,6 +367,34 @@ pub(in crate::inbox) async fn receive_undo_like_for_community(
}
}
/// Add a new mod to the community (can only be done by an existing mod).
pub(in crate::inbox) async fn receive_add_for_community(
context: &LemmyContext,
activity: AnyBase,
expected_domain: &Url,
request_counter: &mut i32,
) -> Result<(), LemmyError> {
let add = Add::from_any_base(activity)?.context(location_info!())?;
verify_activity_domains_valid(&add, &expected_domain, false)?;
is_addressed_to_public(&add)?;
let community = verify_actor_is_community_mod(&add, context).await?;
let new_mod = add
.object()
.as_single_xsd_any_uri()
.context(location_info!())?;
let new_mod = get_or_fetch_and_upsert_user(&new_mod, context, request_counter).await?;
let form = CommunityModeratorForm {
community_id: community.id,
user_id: new_mod.id,
};
blocking(context.pool(), move |conn| {
CommunityModerator::join(conn, &form)
})
.await??;
Ok(())
}
/// A post or comment downvote being reverted
pub(in crate::inbox) async fn receive_undo_dislike_for_community(
context: &LemmyContext,
@ -374,3 +436,46 @@ async fn fetch_post_or_comment_by_id(
Err(NotFound.into())
}
async fn verify_actor_is_community_mod<T, Kind>(
activity: &T,
context: &LemmyContext,
) -> Result<Community, LemmyError>
where
T: ActorAndObjectRef + BaseExt<Kind>,
{
// should be the moderators collection of a local community
// TODO: not compiling, seems to be a bug in activitystreams crate
let target = Url::parse("")?; //activity.target().as_single_xsd_any_uri().context(location_info!())?;
// TODO: very hacky
let community_id: DbUrl = Url::parse(&target.to_string().replace("/moderators", ""))?.into();
let community = blocking(&context.pool(), move |conn| {
Community::read_from_apub_id(&conn, &community_id)
})
.await??;
let actor = activity
.actor()?
.as_single_xsd_any_uri()
.context(location_info!())?
.to_owned();
let actor = blocking(&context.pool(), move |conn| {
User_::read_from_apub_id(&conn, &actor.into())
})
.await??;
// Note: this will also return true for admins in addition to mods, but as we dont know about
// remote admins, it doesnt make any difference.
let community_id = community.id;
let actor_id = actor.id;
let is_mod_or_admin = blocking(context.pool(), move |conn| {
CommunityView::is_mod_or_admin(conn, actor_id, community_id)
})
.await?;
if !is_mod_or_admin {
return Err(anyhow!("Not a mod").into());
}
// TODO: the function name doesnt make sense if we return the community
Ok(community)
}

View file

@ -296,7 +296,9 @@ pub async fn receive_announce(
receive_dislike_for_community(context, inner_activity, &inner_id, request_counter).await
}
Some(Delete) => receive_delete_for_community(context, inner_activity, &inner_id).await,
Some(Remove) => receive_remove_for_community(context, inner_activity, &inner_id).await,
Some(Remove) => {
receive_remove_for_community(context, inner_activity, &inner_id, request_counter).await
}
Some(Undo) => {
receive_undo_for_community(context, inner_activity, &inner_id, request_counter).await
}