From 9172eff65a368300d48e1ace69092a4788721ba7 Mon Sep 17 00:00:00 2001 From: Felix Ableitner <me@nutomic.com> Date: Mon, 8 Mar 2021 17:34:54 +0100 Subject: [PATCH] Implemented receiving activities to add/remove remote mods --- crates/apub/src/inbox/community_inbox.rs | 14 +- .../apub/src/inbox/receive_for_community.rs | 165 ++++++++++++++---- crates/apub/src/inbox/user_inbox.rs | 4 +- 3 files changed, 148 insertions(+), 35 deletions(-) diff --git a/crates/apub/src/inbox/community_inbox.rs b/crates/apub/src/inbox/community_inbox.rs index f003fb16..43072c51 100644 --- a/crates/apub/src/inbox/community_inbox.rs +++ b/crates/apub/src/inbox/community_inbox.rs @@ -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 } }; diff --git a/crates/apub/src/inbox/receive_for_community.rs b/crates/apub/src/inbox/receive_for_community.rs index a3ffbf11..aab17840 100644 --- a/crates/apub/src/inbox/receive_for_community.rs +++ b/crates/apub/src/inbox/receive_for_community.rs @@ -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!())?; - - let object = remove - .object() - .to_owned() - .single_xsd_any_uri() - .context(location_info!())?; - - // 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(()), + // Remove a moderator from community + if remove.target().is_some() { + let community = verify_actor_is_community_mod(&remove, context).await?; + + 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!())?; + + let object = remove + .object() + .to_owned() + .single_xsd_any_uri() + .context(location_info!())?; + + // 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) +} diff --git a/crates/apub/src/inbox/user_inbox.rs b/crates/apub/src/inbox/user_inbox.rs index 1a906d62..6e047674 100644 --- a/crates/apub/src/inbox/user_inbox.rs +++ b/crates/apub/src/inbox/user_inbox.rs @@ -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 } -- 2.44.1