Merge pull request #1572 from LemmyNet/feature/federated-mod-community-updates

Remote mods can update/delete/undelete communities
This commit is contained in:
Dessalines 2021-04-14 11:02:36 -04:00 committed by GitHub
commit b0cef3c76d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 327 additions and 94 deletions

View file

@ -52,9 +52,13 @@ impl PerformCrud for DeleteCommunity {
// Send apub messages // Send apub messages
if deleted { if deleted {
updated_community.send_delete(context).await?; updated_community
.send_delete(local_user_view.person.to_owned(), context)
.await?;
} else { } else {
updated_community.send_undo_delete(context).await?; updated_community
.send_undo_delete(local_user_view.person.to_owned(), context)
.await?;
} }
let community_id = data.community_id; let community_id = data.community_id;

View file

@ -5,6 +5,7 @@ use lemmy_api_common::{
community::{CommunityResponse, EditCommunity}, community::{CommunityResponse, EditCommunity},
get_local_user_view_from_jwt, get_local_user_view_from_jwt,
}; };
use lemmy_apub::CommunityType;
use lemmy_db_queries::{diesel_option_overwrite_to_url, Crud}; use lemmy_db_queries::{diesel_option_overwrite_to_url, Crud};
use lemmy_db_schema::{ use lemmy_db_schema::{
naive_now, naive_now,
@ -70,17 +71,15 @@ impl PerformCrud for EditCommunity {
}; };
let community_id = data.community_id; let community_id = data.community_id;
match blocking(context.pool(), move |conn| { let updated_community = blocking(context.pool(), move |conn| {
Community::update(conn, community_id, &community_form) Community::update(conn, community_id, &community_form)
}) })
.await? .await?
{ .map_err(|_| ApiError::err("couldnt_update_community"))?;
Ok(community) => community,
Err(_e) => return Err(ApiError::err("couldnt_update_community").into()),
};
// TODO there needs to be some kind of an apub update updated_community
// process for communities and users .send_update(local_user_view.person.to_owned(), context)
.await?;
let community_id = data.community_id; let community_id = data.community_id;
let person_id = local_user_view.person.id; let person_id = local_user_view.person.id;

View file

@ -6,6 +6,7 @@ use crate::{
fetcher::{get_or_fetch_and_upsert_actor, person::get_or_fetch_and_upsert_person}, fetcher::{get_or_fetch_and_upsert_actor, person::get_or_fetch_and_upsert_person},
generate_moderators_url, generate_moderators_url,
insert_activity, insert_activity,
objects::ToApub,
ActorType, ActorType,
CommunityType, CommunityType,
}; };
@ -20,6 +21,7 @@ use activitystreams::{
LikeType, LikeType,
RemoveType, RemoveType,
UndoType, UndoType,
UpdateType,
}, },
Accept, Accept,
ActorAndObjectRefExt, ActorAndObjectRefExt,
@ -31,6 +33,7 @@ use activitystreams::{
OptTargetRefExt, OptTargetRefExt,
Remove, Remove,
Undo, Undo,
Update,
}, },
base::{AnyBase, BaseExt, ExtendsExt}, base::{AnyBase, BaseExt, ExtendsExt},
object::ObjectExt, object::ObjectExt,
@ -101,8 +104,32 @@ impl CommunityType for Community {
Ok(()) Ok(())
} }
/// If a remote community is updated by a local mod, send the updated info to the community's
/// instance.
async fn send_update(&self, mod_: Person, context: &LemmyContext) -> Result<(), LemmyError> {
if self.local {
// Do nothing, other instances will automatically refetch the community
} else {
let mut update = Update::new(
mod_.actor_id(),
self.to_apub(context.pool()).await?.into_any_base()?,
);
update
.set_many_contexts(lemmy_context()?)
.set_id(generate_activity_id(UpdateType::Update)?)
.set_to(public())
.set_many_ccs(vec![self.actor_id()]);
send_to_community(update, &mod_, self, None, context).await?;
}
Ok(())
}
/// If the creator of a community deletes the community, send this to all followers. /// If the creator of a community deletes the community, send this to all followers.
async fn send_delete(&self, context: &LemmyContext) -> Result<(), LemmyError> { ///
/// We need to handle deletion by a remote mod separately.
async fn send_delete(&self, mod_: Person, context: &LemmyContext) -> Result<(), LemmyError> {
// Local mod, send directly from community to followers
if self.local {
let mut delete = Delete::new(self.actor_id(), self.actor_id()); let mut delete = Delete::new(self.actor_id(), self.actor_id());
delete delete
.set_many_contexts(lemmy_context()?) .set_many_contexts(lemmy_context()?)
@ -111,11 +138,27 @@ impl CommunityType for Community {
.set_many_ccs(vec![self.followers_url()]); .set_many_ccs(vec![self.followers_url()]);
send_to_community_followers(delete, self, None, context).await?; send_to_community_followers(delete, self, None, context).await?;
}
// Remote mod, send from mod to community
else {
let mut delete = Delete::new(mod_.actor_id(), self.actor_id());
delete
.set_many_contexts(lemmy_context()?)
.set_id(generate_activity_id(DeleteType::Delete)?)
.set_to(public())
.set_many_ccs(vec![self.actor_id()]);
send_to_community(delete, &mod_, self, None, context).await?;
}
Ok(()) Ok(())
} }
/// If the creator of a community reverts the deletion of a community, send this to all followers. /// If the creator of a community reverts the deletion of a community, send this to all followers.
async fn send_undo_delete(&self, context: &LemmyContext) -> Result<(), LemmyError> { ///
/// We need to handle undelete by a remote mod separately.
async fn send_undo_delete(&self, mod_: Person, context: &LemmyContext) -> Result<(), LemmyError> {
// Local mod, send directly from community to followers
if self.local {
let mut delete = Delete::new(self.actor_id(), self.actor_id()); let mut delete = Delete::new(self.actor_id(), self.actor_id());
delete delete
.set_many_contexts(lemmy_context()?) .set_many_contexts(lemmy_context()?)
@ -131,6 +174,25 @@ impl CommunityType for Community {
.set_many_ccs(vec![self.followers_url()]); .set_many_ccs(vec![self.followers_url()]);
send_to_community_followers(undo, self, None, context).await?; send_to_community_followers(undo, self, None, context).await?;
}
// Remote mod, send from mod to community
else {
let mut delete = Delete::new(mod_.actor_id(), self.actor_id());
delete
.set_many_contexts(lemmy_context()?)
.set_id(generate_activity_id(DeleteType::Delete)?)
.set_to(public())
.set_many_ccs(vec![self.actor_id()]);
let mut undo = Undo::new(mod_.actor_id(), delete.into_any_base()?);
undo
.set_many_contexts(lemmy_context()?)
.set_id(generate_activity_id(UndoType::Undo)?)
.set_to(public())
.set_many_ccs(vec![self.actor_id()]);
send_to_community(undo, &mod_, self, None, context).await?;
}
Ok(()) Ok(())
} }

View file

@ -7,11 +7,14 @@ pub mod extensions;
pub mod fetcher; pub mod fetcher;
pub mod objects; pub mod objects;
use crate::extensions::{ use crate::{
extensions::{
group_extension::GroupExtension, group_extension::GroupExtension,
page_extension::PageExtension, page_extension::PageExtension,
person_extension::PersonExtension, person_extension::PersonExtension,
signatures::{PublicKey, PublicKeyExtension}, signatures::{PublicKey, PublicKeyExtension},
},
fetcher::community::get_or_fetch_and_upsert_community,
}; };
use activitystreams::{ use activitystreams::{
activity::Follow, activity::Follow,
@ -44,7 +47,8 @@ use std::net::IpAddr;
use url::{ParseError, Url}; use url::{ParseError, Url};
/// Activitystreams type for community /// Activitystreams type for community
type GroupExt = Ext2<actor::ApActor<ApObject<actor::Group>>, GroupExtension, PublicKeyExtension>; pub type GroupExt =
Ext2<actor::ApActor<ApObject<actor::Group>>, GroupExtension, PublicKeyExtension>;
/// Activitystreams type for person /// Activitystreams type for person
type PersonExt = Ext2<actor::ApActor<ApObject<actor::Person>>, PersonExtension, PublicKeyExtension>; type PersonExt = Ext2<actor::ApActor<ApObject<actor::Person>>, PersonExtension, PublicKeyExtension>;
/// Activitystreams type for post /// Activitystreams type for post
@ -171,9 +175,11 @@ pub trait ActorType {
/// Outbox URL is not generally used by Lemmy, so it can be generated on the fly (but only for /// Outbox URL is not generally used by Lemmy, so it can be generated on the fly (but only for
/// local actors). /// local actors).
fn get_outbox_url(&self) -> Result<Url, LemmyError> { fn get_outbox_url(&self) -> Result<Url, LemmyError> {
/* TODO
if !self.is_local() { if !self.is_local() {
return Err(anyhow!("get_outbox_url() called for remote actor").into()); return Err(anyhow!("get_outbox_url() called for remote actor").into());
} }
*/
Ok(Url::parse(&format!("{}/outbox", &self.actor_id()))?) Ok(Url::parse(&format!("{}/outbox", &self.actor_id()))?)
} }
@ -199,8 +205,9 @@ pub trait CommunityType {
context: &LemmyContext, context: &LemmyContext,
) -> Result<(), LemmyError>; ) -> Result<(), LemmyError>;
async fn send_delete(&self, context: &LemmyContext) -> Result<(), LemmyError>; async fn send_update(&self, mod_: Person, context: &LemmyContext) -> Result<(), LemmyError>;
async fn send_undo_delete(&self, context: &LemmyContext) -> Result<(), LemmyError>; async fn send_delete(&self, mod_: Person, context: &LemmyContext) -> Result<(), LemmyError>;
async fn send_undo_delete(&self, mod_: Person, context: &LemmyContext) -> Result<(), LemmyError>;
async fn send_remove(&self, context: &LemmyContext) -> Result<(), LemmyError>; async fn send_remove(&self, context: &LemmyContext) -> Result<(), LemmyError>;
async fn send_undo_remove(&self, context: &LemmyContext) -> Result<(), LemmyError>; async fn send_undo_remove(&self, context: &LemmyContext) -> Result<(), LemmyError>;
@ -366,7 +373,7 @@ pub async fn find_post_or_comment_by_id(
} }
#[derive(Debug)] #[derive(Debug)]
pub(crate) enum Object { pub enum Object {
Comment(Box<Comment>), Comment(Box<Comment>),
Post(Box<Post>), Post(Box<Post>),
Community(Box<Community>), Community(Box<Community>),
@ -374,10 +381,7 @@ pub(crate) enum Object {
PrivateMessage(Box<PrivateMessage>), PrivateMessage(Box<PrivateMessage>),
} }
pub(crate) async fn find_object_by_id( pub async fn find_object_by_id(context: &LemmyContext, apub_id: Url) -> Result<Object, LemmyError> {
context: &LemmyContext,
apub_id: Url,
) -> Result<Object, LemmyError> {
let ap_id = apub_id.clone(); let ap_id = apub_id.clone();
if let Ok(pc) = find_post_or_comment_by_id(context, ap_id.to_owned()).await { if let Ok(pc) = find_post_or_comment_by_id(context, ap_id.to_owned()).await {
return Ok(match pc { return Ok(match pc {
@ -460,3 +464,20 @@ where
} }
to_and_cc to_and_cc
} }
pub async fn get_community_from_to_or_cc<T, Kind>(
activity: &T,
context: &LemmyContext,
request_counter: &mut i32,
) -> Result<Community, LemmyError>
where
T: AsObject<Kind>,
{
for cid in get_activity_to_and_cc(activity) {
let community = get_or_fetch_and_upsert_community(&cid, context, request_counter).await;
if community.is_ok() {
return community;
}
}
Err(NotFound.into())
}

View file

@ -1,9 +1,7 @@
use crate::{ use crate::{
check_community_or_site_ban, check_community_or_site_ban,
check_is_apub_id_valid, check_is_apub_id_valid,
fetcher::{community::get_or_fetch_and_upsert_community, person::get_or_fetch_and_upsert_person}, fetcher::person::get_or_fetch_and_upsert_person,
get_activity_to_and_cc,
PageExt,
}; };
use activitystreams::{ use activitystreams::{
base::{AsBase, BaseExt, ExtendsExt}, base::{AsBase, BaseExt, ExtendsExt},
@ -13,10 +11,9 @@ use activitystreams::{
}; };
use anyhow::{anyhow, Context}; use anyhow::{anyhow, Context};
use chrono::NaiveDateTime; use chrono::NaiveDateTime;
use diesel::result::Error::NotFound;
use lemmy_api_common::blocking; use lemmy_api_common::blocking;
use lemmy_db_queries::{ApubObject, Crud, DbPool}; use lemmy_db_queries::{ApubObject, Crud, DbPool};
use lemmy_db_schema::{source::community::Community, CommunityId, DbUrl}; use lemmy_db_schema::{CommunityId, DbUrl};
use lemmy_utils::{ use lemmy_utils::{
location_info, location_info,
settings::structs::Settings, settings::structs::Settings,
@ -61,7 +58,7 @@ pub trait FromApub {
} }
#[async_trait::async_trait(?Send)] #[async_trait::async_trait(?Send)]
pub(in crate::objects) trait FromApubToForm<ApubType> { pub trait FromApubToForm<ApubType> {
async fn from_apub( async fn from_apub(
apub: &ApubType, apub: &ApubType,
context: &LemmyContext, context: &LemmyContext,
@ -175,7 +172,7 @@ pub(in crate::objects) fn check_is_markdown(mime: Option<&Mime>) -> Result<(), L
/// Converts an ActivityPub object (eg `Note`) to a database object (eg `Comment`). If an object /// Converts an ActivityPub object (eg `Note`) to a database object (eg `Comment`). If an object
/// with the same ActivityPub ID already exists in the database, it is returned directly. Otherwise /// with the same ActivityPub ID already exists in the database, it is returned directly. Otherwise
/// the apub object is parsed, inserted and returned. /// the apub object is parsed, inserted and returned.
pub(in crate::objects) async fn get_object_from_apub<From, Kind, To, ToForm, IdType>( pub async fn get_object_from_apub<From, Kind, To, ToForm, IdType>(
from: &From, from: &From,
context: &LemmyContext, context: &LemmyContext,
expected_domain: Url, expected_domain: Url,
@ -231,17 +228,3 @@ where
let person = get_or_fetch_and_upsert_person(person_id, context, request_counter).await?; let person = get_or_fetch_and_upsert_person(person_id, context, request_counter).await?;
check_community_or_site_ban(&person, community_id, context.pool()).await check_community_or_site_ban(&person, community_id, context.pool()).await
} }
pub(in crate::objects) async fn get_community_from_to_or_cc(
page: &PageExt,
context: &LemmyContext,
request_counter: &mut i32,
) -> Result<Community, LemmyError> {
for cid in get_activity_to_and_cc(page) {
let community = get_or_fetch_and_upsert_community(&cid, context, request_counter).await;
if community.is_ok() {
return community;
}
}
Err(NotFound.into())
}

View file

@ -2,11 +2,11 @@ use crate::{
check_is_apub_id_valid, check_is_apub_id_valid,
extensions::{context::lemmy_context, page_extension::PageExtension}, extensions::{context::lemmy_context, page_extension::PageExtension},
fetcher::person::get_or_fetch_and_upsert_person, fetcher::person::get_or_fetch_and_upsert_person,
get_community_from_to_or_cc,
objects::{ objects::{
check_object_domain, check_object_domain,
check_object_for_community_or_site_ban, check_object_for_community_or_site_ban,
create_tombstone, create_tombstone,
get_community_from_to_or_cc,
get_object_from_apub, get_object_from_apub,
get_source_markdown_value, get_source_markdown_value,
set_content_and_source, set_content_and_source,

View file

@ -1,10 +1,86 @@
use crate::{
activities::receive::get_actor_as_person,
inbox::receive_for_community::verify_actor_is_community_mod,
};
use activitystreams::{
activity::{ActorAndObjectRefExt, Delete, Undo, Update},
base::ExtendsExt,
};
use anyhow::{anyhow, Context};
use lemmy_api_common::{blocking, community::CommunityResponse}; use lemmy_api_common::{blocking, community::CommunityResponse};
use lemmy_db_queries::source::community::Community_; use lemmy_apub::{
use lemmy_db_schema::source::community::Community; get_community_from_to_or_cc,
use lemmy_db_views_actor::community_view::CommunityView; objects::FromApubToForm,
use lemmy_utils::LemmyError; ActorType,
CommunityType,
GroupExt,
};
use lemmy_db_queries::{source::community::Community_, Crud};
use lemmy_db_schema::source::{
community::{Community, CommunityForm},
person::Person,
};
use lemmy_db_views_actor::{
community_moderator_view::CommunityModeratorView,
community_view::CommunityView,
};
use lemmy_utils::{location_info, LemmyError};
use lemmy_websocket::{messages::SendCommunityRoomMessage, LemmyContext, UserOperationCrud}; use lemmy_websocket::{messages::SendCommunityRoomMessage, LemmyContext, UserOperationCrud};
/// This activity is received from a remote community mod, and updates the description or other
/// fields of a local community.
pub(crate) async fn receive_remote_mod_update_community(
update: Update,
context: &LemmyContext,
request_counter: &mut i32,
) -> Result<(), LemmyError> {
let community = get_community_from_to_or_cc(&update, context, request_counter).await?;
verify_actor_is_community_mod(&update, &community, context).await?;
let group = GroupExt::from_any_base(update.object().to_owned().one().context(location_info!())?)?
.context(location_info!())?;
let updated_community = CommunityForm::from_apub(
&group,
context,
community.actor_id(),
request_counter,
false,
)
.await?;
let cf = CommunityForm {
name: updated_community.name,
title: updated_community.title,
description: updated_community.description,
nsfw: updated_community.nsfw,
// TODO: icon and banner would be hosted on the other instance, ideally we would copy it to ours
icon: updated_community.icon,
banner: updated_community.banner,
..CommunityForm::default()
};
blocking(context.pool(), move |conn| {
Community::update(conn, community.id, &cf)
})
.await??;
Ok(())
}
pub(crate) async fn receive_remote_mod_delete_community(
delete: Delete,
community: Community,
context: &LemmyContext,
request_counter: &mut i32,
) -> Result<(), LemmyError> {
verify_actor_is_community_mod(&delete, &community, context).await?;
let actor = get_actor_as_person(&delete, context, request_counter).await?;
verify_is_remote_community_creator(&actor, &community, context).await?;
let community_id = community.id;
blocking(context.pool(), move |conn| {
Community::update_deleted(conn, community_id, true)
})
.await??;
community.send_delete(actor, context).await
}
pub(crate) async fn receive_delete_community( pub(crate) async fn receive_delete_community(
context: &LemmyContext, context: &LemmyContext,
community: Community, community: Community,
@ -61,6 +137,23 @@ pub(crate) async fn receive_remove_community(
Ok(()) Ok(())
} }
pub(crate) async fn receive_remote_mod_undo_delete_community(
undo: Undo,
community: Community,
context: &LemmyContext,
request_counter: &mut i32,
) -> Result<(), LemmyError> {
verify_actor_is_community_mod(&undo, &community, context).await?;
let actor = get_actor_as_person(&undo, context, request_counter).await?;
verify_is_remote_community_creator(&actor, &community, context).await?;
let community_id = community.id;
blocking(context.pool(), move |conn| {
Community::update_deleted(conn, community_id, false)
})
.await??;
community.send_undo_delete(actor, context).await
}
pub(crate) async fn receive_undo_delete_community( pub(crate) async fn receive_undo_delete_community(
context: &LemmyContext, context: &LemmyContext,
community: Community, community: Community,
@ -117,3 +210,23 @@ pub(crate) async fn receive_undo_remove_community(
Ok(()) Ok(())
} }
/// Checks if the remote user is creator of the local community. This can only happen if a community
/// is created by a local user, and then transferred to a remote user.
async fn verify_is_remote_community_creator(
user: &Person,
community: &Community,
context: &LemmyContext,
) -> Result<(), LemmyError> {
let community_id = community.id;
let community_mods = blocking(context.pool(), move |conn| {
CommunityModeratorView::for_community(conn, community_id)
})
.await??;
if user.id != community_mods[0].moderator.id {
Err(anyhow!("Actor is not community creator").into())
} else {
Ok(())
}
}

View file

@ -14,6 +14,11 @@ use crate::{
receive_undo_like_comment, receive_undo_like_comment,
receive_undo_remove_comment, receive_undo_remove_comment,
}, },
community::{
receive_remote_mod_delete_community,
receive_remote_mod_undo_delete_community,
receive_remote_mod_update_community,
},
post::{ post::{
receive_create_post, receive_create_post,
receive_delete_post, receive_delete_post,
@ -60,9 +65,12 @@ use lemmy_apub::{
objects::{get_or_fetch_and_insert_comment, get_or_fetch_and_insert_post}, objects::{get_or_fetch_and_insert_comment, get_or_fetch_and_insert_post},
person::get_or_fetch_and_upsert_person, person::get_or_fetch_and_upsert_person,
}, },
find_object_by_id,
find_post_or_comment_by_id, find_post_or_comment_by_id,
generate_moderators_url, generate_moderators_url,
ActorType,
CommunityType, CommunityType,
Object,
PostOrComment, PostOrComment,
}; };
use lemmy_db_queries::{ use lemmy_db_queries::{
@ -101,6 +109,14 @@ enum PageOrNote {
Note, Note,
} }
#[derive(EnumString)]
enum ObjectTypes {
Page,
Note,
Group,
Person,
}
/// This file is for post/comment activities received by the community, and for post/comment /// This file is for post/comment activities received by the community, and for post/comment
/// activities announced by the community and received by the person. /// activities announced by the community and received by the person.
@ -120,8 +136,8 @@ pub(in crate::inbox) async fn receive_create_for_community(
.as_single_kind_str() .as_single_kind_str()
.and_then(|s| s.parse().ok()); .and_then(|s| s.parse().ok());
match kind { match kind {
Some(PageOrNote::Page) => receive_create_post(create, context, request_counter).await, Some(ObjectTypes::Page) => receive_create_post(create, context, request_counter).await,
Some(PageOrNote::Note) => receive_create_comment(create, context, request_counter).await, Some(ObjectTypes::Note) => receive_create_comment(create, context, request_counter).await,
_ => receive_unhandled_activity(create), _ => receive_unhandled_activity(create),
} }
} }
@ -134,7 +150,7 @@ pub(in crate::inbox) async fn receive_update_for_community(
expected_domain: &Url, expected_domain: &Url,
request_counter: &mut i32, request_counter: &mut i32,
) -> Result<(), LemmyError> { ) -> Result<(), LemmyError> {
let update = Update::from_any_base(activity)?.context(location_info!())?; let update = Update::from_any_base(activity.to_owned())?.context(location_info!())?;
verify_activity_domains_valid(&update, &expected_domain, false)?; verify_activity_domains_valid(&update, &expected_domain, false)?;
verify_is_addressed_to_public(&update)?; verify_is_addressed_to_public(&update)?;
verify_modification_actor_instance(&update, &announce, context, request_counter).await?; verify_modification_actor_instance(&update, &announce, context, request_counter).await?;
@ -144,8 +160,13 @@ pub(in crate::inbox) async fn receive_update_for_community(
.as_single_kind_str() .as_single_kind_str()
.and_then(|s| s.parse().ok()); .and_then(|s| s.parse().ok());
match kind { match kind {
Some(PageOrNote::Page) => receive_update_post(update, announce, context, request_counter).await, Some(ObjectTypes::Page) => {
Some(PageOrNote::Note) => receive_update_comment(update, context, request_counter).await, receive_update_post(update, announce, context, request_counter).await
}
Some(ObjectTypes::Note) => receive_update_comment(update, context, request_counter).await,
Some(ObjectTypes::Group) => {
receive_remote_mod_update_community(update, context, request_counter).await
}
_ => receive_unhandled_activity(update), _ => receive_unhandled_activity(update),
} }
} }
@ -215,7 +236,7 @@ pub(in crate::inbox) async fn receive_delete_for_community(
request_counter: &mut i32, request_counter: &mut i32,
) -> Result<(), LemmyError> { ) -> Result<(), LemmyError> {
let delete = Delete::from_any_base(activity)?.context(location_info!())?; let delete = Delete::from_any_base(activity)?.context(location_info!())?;
verify_activity_domains_valid(&delete, &expected_domain, true)?; // TODO: skip this check if action is done by remote mod
verify_is_addressed_to_public(&delete)?; verify_is_addressed_to_public(&delete)?;
verify_modification_actor_instance(&delete, &announce, context, request_counter).await?; verify_modification_actor_instance(&delete, &announce, context, request_counter).await?;
@ -225,11 +246,20 @@ pub(in crate::inbox) async fn receive_delete_for_community(
.single_xsd_any_uri() .single_xsd_any_uri()
.context(location_info!())?; .context(location_info!())?;
match find_post_or_comment_by_id(context, object).await { match find_object_by_id(context, object).await {
Ok(PostOrComment::Post(p)) => receive_delete_post(context, *p).await, Ok(Object::Post(p)) => {
Ok(PostOrComment::Comment(c)) => receive_delete_comment(context, *c).await, verify_activity_domains_valid(&delete, &expected_domain, true)?;
// if we dont have the object, no need to do anything receive_delete_post(context, *p).await
Err(_) => Ok(()), }
Ok(Object::Comment(c)) => {
verify_activity_domains_valid(&delete, &expected_domain, true)?;
receive_delete_comment(context, *c).await
}
Ok(Object::Community(c)) => {
receive_remote_mod_delete_community(delete, *c, context, request_counter).await
}
// if we dont have the object or dont support its deletion, no need to do anything
_ => Ok(()),
} }
} }
@ -314,7 +344,9 @@ pub(in crate::inbox) async fn receive_undo_for_community(
.as_single_kind_str() .as_single_kind_str()
.and_then(|s| s.parse().ok()) .and_then(|s| s.parse().ok())
{ {
Some(Delete) => receive_undo_delete_for_community(context, undo, expected_domain).await, Some(Delete) => {
receive_undo_delete_for_community(context, undo, expected_domain, request_counter).await
}
Some(Remove) => { Some(Remove) => {
receive_undo_remove_for_community(context, undo, announce, expected_domain).await receive_undo_remove_for_community(context, undo, announce, expected_domain).await
} }
@ -338,15 +370,15 @@ pub(in crate::inbox) async fn receive_undo_for_community(
} }
} }
/// A post or comment deletion being reverted /// A post, comment or community deletion being reverted
pub(in crate::inbox) async fn receive_undo_delete_for_community( pub(in crate::inbox) async fn receive_undo_delete_for_community(
context: &LemmyContext, context: &LemmyContext,
undo: Undo, undo: Undo,
expected_domain: &Url, expected_domain: &Url,
request_counter: &mut i32,
) -> Result<(), LemmyError> { ) -> Result<(), LemmyError> {
let delete = Delete::from_any_base(undo.object().to_owned().one().context(location_info!())?)? let delete = Delete::from_any_base(undo.object().to_owned().one().context(location_info!())?)?
.context(location_info!())?; .context(location_info!())?;
verify_activity_domains_valid(&delete, &expected_domain, true)?;
verify_is_addressed_to_public(&delete)?; verify_is_addressed_to_public(&delete)?;
let object = delete let object = delete
@ -354,11 +386,21 @@ pub(in crate::inbox) async fn receive_undo_delete_for_community(
.to_owned() .to_owned()
.single_xsd_any_uri() .single_xsd_any_uri()
.context(location_info!())?; .context(location_info!())?;
match find_post_or_comment_by_id(context, object).await { match find_object_by_id(context, object).await {
Ok(PostOrComment::Post(p)) => receive_undo_delete_post(context, *p).await, Ok(Object::Post(p)) => {
Ok(PostOrComment::Comment(c)) => receive_undo_delete_comment(context, *c).await, verify_activity_domains_valid(&delete, &expected_domain, true)?;
// if we dont have the object, no need to do anything receive_undo_delete_post(context, *p).await
Err(_) => Ok(()), }
Ok(Object::Comment(c)) => {
verify_activity_domains_valid(&delete, &expected_domain, true)?;
receive_undo_delete_comment(context, *c).await
}
Ok(Object::Community(c)) => {
verify_actor_is_community_mod(&undo, &c, context).await?;
receive_remote_mod_undo_delete_community(undo, *c, context, request_counter).await
}
// if we dont have the object or dont support its deletion, no need to do anything
_ => Ok(()),
} }
} }
@ -617,7 +659,7 @@ where
/// ///
/// This method should only be used for activities received by the community, not for activities /// This method should only be used for activities received by the community, not for activities
/// used by community followers. /// used by community followers.
async fn verify_actor_is_community_mod<T, Kind>( pub(crate) async fn verify_actor_is_community_mod<T, Kind>(
activity: &T, activity: &T,
community: &Community, community: &Community,
context: &LemmyContext, context: &LemmyContext,
@ -722,9 +764,18 @@ where
.map(|o| o.id()) .map(|o| o.id())
.flatten() .flatten()
.context(location_info!())?; .context(location_info!())?;
let original_id = match fetch_post_or_comment_by_id(object_id, context, request_counter).await? { let original_id = match fetch_post_or_comment_by_id(object_id, context, request_counter).await {
PostOrComment::Post(p) => p.ap_id.into_inner(), Ok(PostOrComment::Post(p)) => p.ap_id.into_inner(),
PostOrComment::Comment(c) => c.ap_id.into_inner(), Ok(PostOrComment::Comment(c)) => c.ap_id.into_inner(),
Err(_) => {
// We can also receive Update activity from remote mod for local activity
let object_id = object_id.to_owned().into();
blocking(context.pool(), move |conn| {
Community::read_from_apub_id(conn, &object_id)
})
.await??
.actor_id()
}
}; };
if actor_id.domain() != original_id.domain() { if actor_id.domain() != original_id.domain() {
let community = extract_community_from_cc(activity, context).await?; let community = extract_community_from_cc(activity, context).await?;