diff --git a/crates/apub_receive/src/activities/receive/community.rs b/crates/apub_receive/src/activities/receive/community.rs deleted file mode 100644 index 18ebbed3..00000000 --- a/crates/apub_receive/src/activities/receive/community.rs +++ /dev/null @@ -1,232 +0,0 @@ -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_apub::{ - get_community_from_to_or_cc, - objects::FromApubToForm, - 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}; - -/// 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( - context: &LemmyContext, - community: Community, -) -> Result<(), LemmyError> { - let deleted_community = blocking(context.pool(), move |conn| { - Community::update_deleted(conn, community.id, true) - }) - .await??; - - let community_id = deleted_community.id; - let res = CommunityResponse { - community_view: blocking(context.pool(), move |conn| { - CommunityView::read(conn, community_id, None) - }) - .await??, - }; - - let community_id = res.community_view.community.id; - context.chat_server().do_send(SendCommunityRoomMessage { - op: UserOperationCrud::EditCommunity, - response: res, - community_id, - websocket_id: None, - }); - - Ok(()) -} - -pub(crate) async fn receive_remove_community( - context: &LemmyContext, - community: Community, -) -> Result<(), LemmyError> { - let removed_community = blocking(context.pool(), move |conn| { - Community::update_removed(conn, community.id, true) - }) - .await??; - - let community_id = removed_community.id; - let res = CommunityResponse { - community_view: blocking(context.pool(), move |conn| { - CommunityView::read(conn, community_id, None) - }) - .await??, - }; - - let community_id = res.community_view.community.id; - context.chat_server().do_send(SendCommunityRoomMessage { - op: UserOperationCrud::EditCommunity, - response: res, - community_id, - websocket_id: None, - }); - - 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( - context: &LemmyContext, - community: Community, -) -> Result<(), LemmyError> { - let deleted_community = blocking(context.pool(), move |conn| { - Community::update_deleted(conn, community.id, false) - }) - .await??; - - let community_id = deleted_community.id; - let res = CommunityResponse { - community_view: blocking(context.pool(), move |conn| { - CommunityView::read(conn, community_id, None) - }) - .await??, - }; - - let community_id = res.community_view.community.id; - context.chat_server().do_send(SendCommunityRoomMessage { - op: UserOperationCrud::EditCommunity, - response: res, - community_id, - websocket_id: None, - }); - - Ok(()) -} - -pub(crate) async fn receive_undo_remove_community( - context: &LemmyContext, - community: Community, -) -> Result<(), LemmyError> { - let removed_community = blocking(context.pool(), move |conn| { - Community::update_removed(conn, community.id, false) - }) - .await??; - - let community_id = removed_community.id; - let res = CommunityResponse { - community_view: blocking(context.pool(), move |conn| { - CommunityView::read(conn, community_id, None) - }) - .await??, - }; - - let community_id = res.community_view.community.id; - - context.chat_server().do_send(SendCommunityRoomMessage { - op: UserOperationCrud::EditCommunity, - response: res, - community_id, - websocket_id: None, - }); - - 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(()) - } -} diff --git a/crates/apub_receive/src/activities/receive/mod.rs b/crates/apub_receive/src/activities/receive/mod.rs index 01e3f3b5..ba275831 100644 --- a/crates/apub_receive/src/activities/receive/mod.rs +++ b/crates/apub_receive/src/activities/receive/mod.rs @@ -13,7 +13,6 @@ use std::fmt::Debug; use url::Url; pub(crate) mod comment_undo; -pub(crate) mod community; pub(crate) mod post_undo; /// Return HTTP 501 for unsupported activities in inbox. diff --git a/crates/apub_receive/src/activities_new/community/delete.rs b/crates/apub_receive/src/activities_new/community/delete.rs new file mode 100644 index 00000000..0229ee0e --- /dev/null +++ b/crates/apub_receive/src/activities_new/community/delete.rs @@ -0,0 +1,95 @@ +use crate::{ + activities_new::community::{send_websocket_message, verify_is_community_mod}, + inbox::new_inbox_routing::Activity, +}; +use activitystreams::activity::kind::DeleteType; +use lemmy_api_common::blocking; +use lemmy_apub::{ + check_is_apub_id_valid, + fetcher::{community::get_or_fetch_and_upsert_community, person::get_or_fetch_and_upsert_person}, + ActorType, + CommunityType, +}; +use lemmy_apub_lib::{verify_domains_match, PublicUrl, ReceiveActivity, VerifyActivity}; +use lemmy_db_queries::{source::community::Community_, ApubObject}; +use lemmy_db_schema::source::community::Community; +use lemmy_utils::LemmyError; +use lemmy_websocket::{LemmyContext, UserOperationCrud}; +use url::Url; + +// We have two possibilities which need to be handled: +// 1. actor is remote mod, community id in object +// 2. actor is community, cc is followers collection +#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)] +#[serde(rename_all = "camelCase")] +pub struct DeleteCommunity { + actor: Url, + to: PublicUrl, + pub(in crate::activities_new::community) object: Url, + cc: Url, + #[serde(rename = "type")] + kind: DeleteType, +} + +#[async_trait::async_trait(?Send)] +impl VerifyActivity for Activity { + async fn verify(&self, context: &LemmyContext) -> Result<(), LemmyError> { + let object = self.inner.object.clone(); + let community = blocking(context.pool(), move |conn| { + Community::read_from_apub_id(conn, &object.into()) + }) + .await?; + // remote mod action on local community + if let Ok(c) = community { + verify_domains_match(&self.inner.object, &self.inner.cc)?; + check_is_apub_id_valid(&self.inner.actor, false)?; + verify_is_community_mod(self.inner.actor.clone(), c.actor_id(), context).await + } + // community action sent to followers + else { + verify_domains_match(&self.inner.actor, &self.inner.object)?; + verify_domains_match(&self.inner.actor, &self.inner.cc) + } + } +} + +#[async_trait::async_trait(?Send)] +impl ReceiveActivity for Activity { + async fn receive( + &self, + context: &LemmyContext, + request_counter: &mut i32, + ) -> Result<(), LemmyError> { + let actor = self.inner.object.clone(); + let community = blocking(context.pool(), move |conn| { + Community::read_from_apub_id(conn, &actor.into()) + }) + .await?; + let community_id = match community { + Ok(c) => { + // remote mod sent delete to local community, forward it to followers + let actor = + get_or_fetch_and_upsert_person(&self.inner.actor, context, request_counter).await?; + c.send_delete(actor, context).await?; + c.id + } + Err(_) => { + // refetch the remote community + let community = + get_or_fetch_and_upsert_community(&self.inner.object, context, request_counter).await?; + community.id + } + }; + let deleted_community = blocking(context.pool(), move |conn| { + Community::update_deleted(conn, community_id, true) + }) + .await??; + + send_websocket_message( + deleted_community.id, + UserOperationCrud::DeleteCommunity, + context, + ) + .await + } +} diff --git a/crates/apub_receive/src/activities_new/community/mod.rs b/crates/apub_receive/src/activities_new/community/mod.rs new file mode 100644 index 00000000..951a29e7 --- /dev/null +++ b/crates/apub_receive/src/activities_new/community/mod.rs @@ -0,0 +1,62 @@ +use anyhow::anyhow; +use lemmy_api_common::{blocking, community::CommunityResponse}; +use lemmy_db_queries::ApubObject; +use lemmy_db_schema::{ + source::{community::Community, person::Person}, + CommunityId, +}; +use lemmy_db_views_actor::community_view::CommunityView; +use lemmy_utils::LemmyError; +use lemmy_websocket::{messages::SendCommunityRoomMessage, LemmyContext}; +use url::Url; + +pub mod delete; +pub mod remove; +pub mod undo_delete; +pub mod undo_remove; +pub mod update; + +async fn send_websocket_message( + community_id: CommunityId, + op: OP, + context: &LemmyContext, +) -> Result<(), LemmyError> { + let community_view = blocking(context.pool(), move |conn| { + CommunityView::read(conn, community_id, None) + }) + .await??; + + let res = CommunityResponse { community_view }; + + context.chat_server().do_send(SendCommunityRoomMessage { + op, + response: res, + community_id, + websocket_id: None, + }); + + Ok(()) +} + +async fn verify_is_community_mod( + actor: Url, + community: Url, + context: &LemmyContext, +) -> Result<(), LemmyError> { + let actor = blocking(context.pool(), move |conn| { + Person::read_from_apub_id(conn, &actor.into()) + }) + .await??; + let community = blocking(context.pool(), move |conn| { + Community::read_from_apub_id(conn, &community.into()) + }) + .await??; + 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()); + } + Ok(()) +} diff --git a/crates/apub_receive/src/activities_new/community/remove.rs b/crates/apub_receive/src/activities_new/community/remove.rs new file mode 100644 index 00000000..ba27984d --- /dev/null +++ b/crates/apub_receive/src/activities_new/community/remove.rs @@ -0,0 +1,60 @@ +use crate::{ + activities_new::community::send_websocket_message, + inbox::new_inbox_routing::Activity, +}; +use activitystreams::activity::kind::RemoveType; +use lemmy_api_common::blocking; +use lemmy_apub::check_is_apub_id_valid; +use lemmy_apub_lib::{verify_domains_match, PublicUrl, ReceiveActivity, VerifyActivity}; +use lemmy_db_queries::{source::community::Community_, ApubObject}; +use lemmy_db_schema::source::community::Community; +use lemmy_utils::LemmyError; +use lemmy_websocket::{LemmyContext, UserOperationCrud}; +use url::Url; + +#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)] +#[serde(rename_all = "camelCase")] +pub struct RemoveCommunity { + actor: Url, + to: PublicUrl, + pub(in crate::activities_new::community) object: Url, + cc: Url, + #[serde(rename = "type")] + kind: RemoveType, +} + +#[async_trait::async_trait(?Send)] +impl VerifyActivity for Activity { + async fn verify(&self, _context: &LemmyContext) -> Result<(), LemmyError> { + check_is_apub_id_valid(&self.inner.actor, false)?; + verify_domains_match(&self.inner.actor, &self.inner.object)?; + verify_domains_match(&self.inner.actor, &self.inner.cc) + } +} + +#[async_trait::async_trait(?Send)] +impl ReceiveActivity for Activity { + async fn receive( + &self, + context: &LemmyContext, + _request_counter: &mut i32, + ) -> Result<(), LemmyError> { + let object = self.inner.object.clone(); + // only search in local database, there is no reason to fetch something thats deleted + let community = blocking(context.pool(), move |conn| { + Community::read_from_apub_id(conn, &object.into()) + }) + .await??; + let removed_community = blocking(context.pool(), move |conn| { + Community::update_removed(conn, community.id, true) + }) + .await??; + + send_websocket_message( + removed_community.id, + UserOperationCrud::RemoveCommunity, + context, + ) + .await + } +} diff --git a/crates/apub_receive/src/activities_new/community/undo_delete.rs b/crates/apub_receive/src/activities_new/community/undo_delete.rs new file mode 100644 index 00000000..71d4c78b --- /dev/null +++ b/crates/apub_receive/src/activities_new/community/undo_delete.rs @@ -0,0 +1,104 @@ +use crate::{ + activities_new::community::{ + delete::DeleteCommunity, + send_websocket_message, + verify_is_community_mod, + }, + inbox::new_inbox_routing::Activity, +}; +use activitystreams::activity::kind::DeleteType; +use lemmy_api_common::blocking; +use lemmy_apub::{ + check_is_apub_id_valid, + fetcher::{community::get_or_fetch_and_upsert_community, person::get_or_fetch_and_upsert_person}, + ActorType, + CommunityType, +}; +use lemmy_apub_lib::{verify_domains_match, PublicUrl, ReceiveActivity, VerifyActivity}; +use lemmy_db_queries::{source::community::Community_, ApubObject}; +use lemmy_db_schema::source::community::Community; +use lemmy_utils::LemmyError; +use lemmy_websocket::{LemmyContext, UserOperationCrud}; +use url::Url; + +// We have two possibilities which need to be handled: +// 1. actor is remote mod, community id in object +// 2. actor is community, cc is followers collection +#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)] +#[serde(rename_all = "camelCase")] +pub struct UndoDeleteCommunity { + actor: Url, + to: PublicUrl, + object: Activity, + cc: Url, + #[serde(rename = "type")] + kind: DeleteType, +} + +#[async_trait::async_trait(?Send)] +impl VerifyActivity for Activity { + async fn verify(&self, context: &LemmyContext) -> Result<(), LemmyError> { + let object = self.inner.object.inner.object.clone(); + let community = blocking(context.pool(), move |conn| { + Community::read_from_apub_id(conn, &object.into()) + }) + .await?; + // remote mod action on local community + if let Ok(c) = community { + verify_domains_match(&self.inner.object.inner.object, &self.inner.cc)?; + check_is_apub_id_valid(&self.inner.actor, false)?; + verify_is_community_mod(self.inner.actor.clone(), c.actor_id(), context).await?; + } + // community action sent to followers + else { + verify_domains_match(&self.inner.actor, &self.inner.object.inner.object)?; + verify_domains_match(&self.inner.actor, &self.inner.cc)?; + } + self.inner.object.verify(context).await + } +} + +#[async_trait::async_trait(?Send)] +impl ReceiveActivity for Activity { + async fn receive( + &self, + context: &LemmyContext, + request_counter: &mut i32, + ) -> Result<(), LemmyError> { + let actor = self.inner.object.inner.object.clone(); + let community = blocking(context.pool(), move |conn| { + Community::read_from_apub_id(conn, &actor.into()) + }) + .await?; + let community_id = match community { + Ok(c) => { + // remote mod sent undo to local community, forward it to followers + let actor = + get_or_fetch_and_upsert_person(&self.inner.actor, context, request_counter).await?; + c.send_delete(actor, context).await?; + c.id + } + Err(_) => { + // refetch the remote community + let community = get_or_fetch_and_upsert_community( + &self.inner.object.inner.object, + context, + request_counter, + ) + .await?; + community.id + } + }; + let restored_community = blocking(context.pool(), move |conn| { + Community::update_deleted(conn, community_id, false) + }) + .await??; + + send_websocket_message( + restored_community.id, + UserOperationCrud::EditCommunity, + context, + ) + .await + } +} diff --git a/crates/apub_receive/src/activities_new/community/undo_remove.rs b/crates/apub_receive/src/activities_new/community/undo_remove.rs new file mode 100644 index 00000000..7478b44e --- /dev/null +++ b/crates/apub_receive/src/activities_new/community/undo_remove.rs @@ -0,0 +1,59 @@ +use crate::{ + activities_new::community::{remove::RemoveCommunity, send_websocket_message}, + inbox::new_inbox_routing::Activity, +}; +use activitystreams::activity::kind::RemoveType; +use lemmy_api_common::blocking; +use lemmy_apub::{check_is_apub_id_valid, fetcher::community::get_or_fetch_and_upsert_community}; +use lemmy_apub_lib::{verify_domains_match, PublicUrl, ReceiveActivity, VerifyActivity}; +use lemmy_db_queries::source::community::Community_; +use lemmy_db_schema::source::community::Community; +use lemmy_utils::LemmyError; +use lemmy_websocket::{LemmyContext, UserOperationCrud}; +use url::Url; + +#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)] +#[serde(rename_all = "camelCase")] +pub struct UndoRemoveCommunity { + actor: Url, + to: PublicUrl, + object: Activity, + cc: Url, + #[serde(rename = "type")] + kind: RemoveType, +} + +#[async_trait::async_trait(?Send)] +impl VerifyActivity for Activity { + async fn verify(&self, context: &LemmyContext) -> Result<(), LemmyError> { + check_is_apub_id_valid(&self.inner.actor, false)?; + verify_domains_match(&self.inner.actor, &self.inner.object.inner.object)?; + verify_domains_match(&self.inner.actor, &self.inner.cc)?; + self.inner.object.verify(context).await + } +} + +#[async_trait::async_trait(?Send)] +impl ReceiveActivity for Activity { + async fn receive( + &self, + context: &LemmyContext, + request_counter: &mut i32, + ) -> Result<(), LemmyError> { + let community_id = self.inner.object.inner.object.clone(); + let community = + get_or_fetch_and_upsert_community(&community_id, context, request_counter).await?; + + let restored_community = blocking(context.pool(), move |conn| { + Community::update_removed(conn, community.id, false) + }) + .await??; + + send_websocket_message( + restored_community.id, + UserOperationCrud::EditCommunity, + context, + ) + .await + } +} diff --git a/crates/apub_receive/src/activities_new/community/update.rs b/crates/apub_receive/src/activities_new/community/update.rs new file mode 100644 index 00000000..f9343039 --- /dev/null +++ b/crates/apub_receive/src/activities_new/community/update.rs @@ -0,0 +1,80 @@ +use crate::{ + activities_new::community::{send_websocket_message, verify_is_community_mod}, + inbox::new_inbox_routing::Activity, +}; +use activitystreams::{activity::kind::UpdateType, base::BaseExt}; +use lemmy_api_common::blocking; +use lemmy_apub::{check_is_apub_id_valid, objects::FromApubToForm, GroupExt}; +use lemmy_apub_lib::{PublicUrl, ReceiveActivity, VerifyActivity}; +use lemmy_db_queries::{ApubObject, Crud}; +use lemmy_db_schema::source::community::{Community, CommunityForm}; +use lemmy_utils::LemmyError; +use lemmy_websocket::{LemmyContext, UserOperationCrud}; +use url::Url; + +/// This activity is received from a remote community mod, and updates the description or other +/// fields of a local community. +#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)] +#[serde(rename_all = "camelCase")] +pub struct UpdateCommunity { + actor: Url, + to: PublicUrl, + object: GroupExt, + cc: Url, + #[serde(rename = "type")] + kind: UpdateType, +} + +#[async_trait::async_trait(?Send)] +impl VerifyActivity for Activity { + async fn verify(&self, context: &LemmyContext) -> Result<(), LemmyError> { + self.inner.object.id(self.inner.cc.as_str())?; + check_is_apub_id_valid(&self.inner.actor, false)?; + verify_is_community_mod(self.inner.actor.clone(), self.inner.cc.clone(), context).await + } +} + +#[async_trait::async_trait(?Send)] +impl ReceiveActivity for Activity { + async fn receive( + &self, + context: &LemmyContext, + request_counter: &mut i32, + ) -> Result<(), LemmyError> { + let cc = self.inner.cc.clone().into(); + let community = blocking(context.pool(), move |conn| { + Community::read_from_apub_id(conn, &cc) + }) + .await??; + + let updated_community = CommunityForm::from_apub( + &self.inner.object, + context, + community.actor_id.clone().into(), + 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() + }; + let updated_community = blocking(context.pool(), move |conn| { + Community::update(conn, community.id, &cf) + }) + .await??; + + send_websocket_message( + updated_community.id, + UserOperationCrud::EditCommunity, + context, + ) + .await + } +} diff --git a/crates/apub_receive/src/activities_new/mod.rs b/crates/apub_receive/src/activities_new/mod.rs index 95cd394a..a6f9eebb 100644 --- a/crates/apub_receive/src/activities_new/mod.rs +++ b/crates/apub_receive/src/activities_new/mod.rs @@ -8,6 +8,7 @@ use lemmy_websocket::LemmyContext; use url::Url; pub mod comment; +pub mod community; pub mod follow; pub mod post; pub mod private_message; diff --git a/crates/apub_receive/src/inbox/community_inbox.rs b/crates/apub_receive/src/inbox/community_inbox.rs index 3e2bb73a..c0368cb7 100644 --- a/crates/apub_receive/src/inbox/community_inbox.rs +++ b/crates/apub_receive/src/inbox/community_inbox.rs @@ -8,10 +8,8 @@ use crate::{ receive_for_community::{ receive_add_for_community, receive_block_user_for_community, - receive_delete_for_community, receive_remove_for_community, receive_undo_for_community, - receive_update_for_community, }, verify_is_addressed_to_public, }, @@ -155,30 +153,10 @@ pub(crate) async fn community_receive_message( .await? } CommunityValidTypes::Create => todo!(), - CommunityValidTypes::Update => { - Box::pin(receive_update_for_community( - context, - any_base.clone(), - None, - &actor_url, - request_counter, - )) - .await?; - true - } + CommunityValidTypes::Update => todo!(), CommunityValidTypes::Like => todo!(), CommunityValidTypes::Dislike => todo!(), - CommunityValidTypes::Delete => { - Box::pin(receive_delete_for_community( - context, - any_base.clone(), - None, - &actor_url, - request_counter, - )) - .await?; - true - } + CommunityValidTypes::Delete => todo!(), CommunityValidTypes::Add => { Box::pin(receive_add_for_community( context, diff --git a/crates/apub_receive/src/inbox/new_inbox_routing.rs b/crates/apub_receive/src/inbox/new_inbox_routing.rs index c70af9d9..7fcfff14 100644 --- a/crates/apub_receive/src/inbox/new_inbox_routing.rs +++ b/crates/apub_receive/src/inbox/new_inbox_routing.rs @@ -7,6 +7,7 @@ use crate::activities_new::{ remove::RemoveComment, update::UpdateComment, }, + community::{delete::DeleteCommunity, update::UpdateCommunity}, follow::AcceptFollowCommunity, post::{ create::CreatePost, @@ -28,9 +29,9 @@ use lemmy_apub_lib::{ReceiveActivity, VerifyActivity}; use lemmy_utils::LemmyError; use lemmy_websocket::LemmyContext; use url::Url; - -// TODO: add security checks for received activities back in -// mainly check that domain of actor and id are identical (and object/object.id where applicable) +use crate::activities_new::community::remove::RemoveCommunity; +use crate::activities_new::community::undo_remove::UndoRemoveCommunity; +use crate::activities_new::community::undo_delete::UndoDeleteCommunity; // TODO: would be nice if we could move this to lemmy_apub_lib crate. doing that gives error: // "only traits defined in the current crate can be implemented for arbitrary types" @@ -75,6 +76,11 @@ pub enum PersonAcceptedActivitiesNew { DislikePost(DislikePost), DeletePost(DeletePost), RemovePost(RemovePost), + UpdateCommunity(UpdateCommunity), + DeleteCommunity(DeleteCommunity), + RemoveCommunity(RemoveCommunity), + UndoDeleteCommunity(UndoDeleteCommunity), + UndoRemoveCommunity(UndoRemoveCommunity), } // todo: can probably get rid of these? diff --git a/crates/apub_receive/src/inbox/person_inbox.rs b/crates/apub_receive/src/inbox/person_inbox.rs index 3ed365de..c0ae9a55 100644 --- a/crates/apub_receive/src/inbox/person_inbox.rs +++ b/crates/apub_receive/src/inbox/person_inbox.rs @@ -1,14 +1,5 @@ use crate::{ - activities::receive::{ - community::{ - receive_delete_community, - receive_remove_community, - receive_undo_delete_community, - receive_undo_remove_community, - }, - receive_unhandled_activity, - verify_activity_domains_valid, - }, + activities::receive::{receive_unhandled_activity, verify_activity_domains_valid}, inbox::{ is_activity_already_known, is_addressed_to_community_followers, @@ -17,38 +8,30 @@ use crate::{ receive_for_community::{ receive_add_for_community, receive_block_user_for_community, - receive_delete_for_community, receive_remove_for_community, receive_undo_for_community, - receive_update_for_community, }, verify_is_addressed_to_public, }, }; use activitystreams::{ - activity::{ActorAndObject, Announce, Delete, Remove, Undo}, + activity::{ActorAndObject, Announce}, base::AnyBase, prelude::*, }; use actix_web::{web, HttpRequest, HttpResponse}; use anyhow::{anyhow, Context}; -use diesel::NotFound; use lemmy_api_common::blocking; use lemmy_apub::{check_is_apub_id_valid, get_activity_to_and_cc, ActorType}; use lemmy_apub_lib::{ReceiveActivity, VerifyActivity}; -use lemmy_db_queries::{ApubObject, Followable}; -use lemmy_db_schema::source::{ - community::{Community, CommunityFollower}, - person::Person, - private_message::PrivateMessage, -}; +use lemmy_db_queries::Followable; +use lemmy_db_schema::source::{community::CommunityFollower, person::Person}; use lemmy_utils::{location_info, LemmyError}; use lemmy_websocket::LemmyContext; use log::info; use serde::{Deserialize, Serialize}; use std::fmt::Debug; use strum_macros::EnumString; -use url::Url; /// Allowed activities for person inbox. #[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd, Deserialize, Serialize)] @@ -135,7 +118,6 @@ pub(crate) async fn person_receive_message( let any_base = activity.clone().into_any_base()?; let kind = activity.kind().context(location_info!())?; - let actor_url = actor.actor_id(); match kind { PersonValidTypes::Accept => {} PersonValidTypes::Announce => { @@ -143,19 +125,9 @@ pub(crate) async fn person_receive_message( } PersonValidTypes::Create => {} PersonValidTypes::Update => {} - PersonValidTypes::Delete => { - Box::pin(receive_delete( - context, - any_base, - &actor_url, - request_counter, - )) - .await? - } - PersonValidTypes::Undo => { - Box::pin(receive_undo(context, any_base, &actor_url, request_counter)).await? - } - PersonValidTypes::Remove => Box::pin(receive_remove(context, any_base, &actor_url)).await?, + PersonValidTypes::Delete => todo!(), + PersonValidTypes::Undo => todo!(), + PersonValidTypes::Remove => todo!(), }; // TODO: would be logical to move websocket notification code here @@ -241,28 +213,10 @@ pub async fn receive_announce( use AnnouncableActivities::*; match kind { Some(Create) => todo!(), - Some(Update) => { - receive_update_for_community( - context, - inner_activity, - Some(announce), - &inner_id, - request_counter, - ) - .await - } + Some(Update) => todo!(), Some(Like) => todo!(), Some(Dislike) => todo!(), - Some(Delete) => { - receive_delete_for_community( - context, - inner_activity, - Some(announce), - &inner_id, - request_counter, - ) - .await - } + Some(Delete) => todo!(), Some(Remove) => { receive_remove_for_community(context, inner_activity, Some(announce), request_counter).await } @@ -286,118 +240,3 @@ pub async fn receive_announce( _ => receive_unhandled_activity(inner_activity), } } - -async fn receive_delete( - context: &LemmyContext, - any_base: AnyBase, - expected_domain: &Url, - _request_counter: &mut i32, -) -> Result<(), LemmyError> { - use CommunityOrPrivateMessage::*; - - let delete = Delete::from_any_base(any_base.clone())?.context(location_info!())?; - verify_activity_domains_valid(&delete, expected_domain, true)?; - let object_uri = delete - .object() - .to_owned() - .single_xsd_any_uri() - .context(location_info!())?; - - match find_community_or_private_message_by_id(context, object_uri).await? { - Community(c) => receive_delete_community(context, c).await, - PrivateMessage(_) => todo!(), - } -} - -async fn receive_remove( - context: &LemmyContext, - any_base: AnyBase, - expected_domain: &Url, -) -> Result<(), LemmyError> { - let remove = Remove::from_any_base(any_base.clone())?.context(location_info!())?; - verify_activity_domains_valid(&remove, expected_domain, true)?; - let object_uri = remove - .object() - .to_owned() - .single_xsd_any_uri() - .context(location_info!())?; - let community = blocking(context.pool(), move |conn| { - Community::read_from_apub_id(conn, &object_uri.into()) - }) - .await??; - receive_remove_community(&context, community).await -} - -async fn receive_undo( - context: &LemmyContext, - any_base: AnyBase, - expected_domain: &Url, - _request_counter: &mut i32, -) -> Result<(), LemmyError> { - let undo = Undo::from_any_base(any_base)?.context(location_info!())?; - verify_activity_domains_valid(&undo, expected_domain, true)?; - - let inner_activity = undo.object().to_owned().one().context(location_info!())?; - let kind = inner_activity.kind_str(); - match kind { - Some("Delete") => { - let delete = Delete::from_any_base(inner_activity)?.context(location_info!())?; - verify_activity_domains_valid(&delete, expected_domain, true)?; - let object_uri = delete - .object() - .to_owned() - .single_xsd_any_uri() - .context(location_info!())?; - use CommunityOrPrivateMessage::*; - match find_community_or_private_message_by_id(context, object_uri).await? { - Community(c) => receive_undo_delete_community(context, c).await, - PrivateMessage(_) => { - todo!() - } - } - } - Some("Remove") => { - let remove = Remove::from_any_base(inner_activity)?.context(location_info!())?; - let object_uri = remove - .object() - .to_owned() - .single_xsd_any_uri() - .context(location_info!())?; - let community = blocking(context.pool(), move |conn| { - Community::read_from_apub_id(conn, &object_uri.into()) - }) - .await??; - receive_undo_remove_community(context, community).await - } - _ => receive_unhandled_activity(undo), - } -} -enum CommunityOrPrivateMessage { - Community(Community), - PrivateMessage(PrivateMessage), -} - -async fn find_community_or_private_message_by_id( - context: &LemmyContext, - apub_id: Url, -) -> Result { - let ap_id = apub_id.to_owned(); - let community = blocking(context.pool(), move |conn| { - Community::read_from_apub_id(conn, &ap_id.into()) - }) - .await?; - if let Ok(c) = community { - return Ok(CommunityOrPrivateMessage::Community(c)); - } - - let ap_id = apub_id.to_owned(); - let private_message = blocking(context.pool(), move |conn| { - PrivateMessage::read_from_apub_id(conn, &ap_id.into()) - }) - .await?; - if let Ok(p) = private_message { - return Ok(CommunityOrPrivateMessage::PrivateMessage(p)); - } - - Err(NotFound.into()) -} diff --git a/crates/apub_receive/src/inbox/receive_for_community.rs b/crates/apub_receive/src/inbox/receive_for_community.rs index cbfe2e6e..7b406f9e 100644 --- a/crates/apub_receive/src/inbox/receive_for_community.rs +++ b/crates/apub_receive/src/inbox/receive_for_community.rs @@ -6,11 +6,6 @@ use crate::{ receive_undo_like_comment, receive_undo_remove_comment, }, - community::{ - receive_remote_mod_delete_community, - receive_remote_mod_undo_delete_community, - receive_remote_mod_update_community, - }, post_undo::{ receive_undo_delete_post, receive_undo_dislike_post, @@ -34,7 +29,6 @@ use activitystreams::{ OptTargetRef, Remove, Undo, - Update, }, base::AnyBase, object::AsObject, @@ -51,7 +45,6 @@ use lemmy_apub::{ find_object_by_id, find_post_or_comment_by_id, generate_moderators_url, - ActorType, CommunityType, Object, PostOrComment, @@ -101,66 +94,6 @@ enum ObjectTypes { /// 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. -/// A post or comment being edited -pub(in crate::inbox) async fn receive_update_for_community( - context: &LemmyContext, - activity: AnyBase, - announce: Option, - expected_domain: &Url, - request_counter: &mut i32, -) -> Result<(), LemmyError> { - let update = Update::from_any_base(activity.to_owned())?.context(location_info!())?; - verify_activity_domains_valid(&update, &expected_domain, false)?; - verify_is_addressed_to_public(&update)?; - verify_modification_actor_instance(&update, &announce, context, request_counter).await?; - - let kind = update - .object() - .as_single_kind_str() - .and_then(|s| s.parse().ok()); - match kind { - Some(ObjectTypes::Page) => todo!(), - Some(ObjectTypes::Note) => todo!(), - Some(ObjectTypes::Group) => { - receive_remote_mod_update_community(update, context, request_counter).await - } - _ => receive_unhandled_activity(update), - } -} - -/// A post or comment being deleted by its creator -pub(in crate::inbox) async fn receive_delete_for_community( - context: &LemmyContext, - activity: AnyBase, - announce: Option, - expected_domain: &Url, - request_counter: &mut i32, -) -> Result<(), LemmyError> { - let delete = Delete::from_any_base(activity)?.context(location_info!())?; - // TODO: skip this check if action is done by remote mod - verify_is_addressed_to_public(&delete)?; - verify_modification_actor_instance(&delete, &announce, context, request_counter).await?; - - let object = delete - .object() - .to_owned() - .single_xsd_any_uri() - .context(location_info!())?; - - match find_object_by_id(context, object).await { - Ok(Object::Post(_)) => todo!(), - Ok(Object::Comment(_)) => { - verify_activity_domains_valid(&delete, &expected_domain, true)?; - todo!() - } - 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(()), - } -} - /// A post or comment being removed by a mod/admin pub(in crate::inbox) async fn receive_remove_for_community( context: &LemmyContext, @@ -258,7 +191,7 @@ pub(in crate::inbox) async fn receive_undo_delete_for_community( context: &LemmyContext, undo: Undo, expected_domain: &Url, - request_counter: &mut i32, + _request_counter: &mut i32, ) -> Result<(), LemmyError> { let delete = Delete::from_any_base(undo.object().to_owned().one().context(location_info!())?)? .context(location_info!())?; @@ -278,10 +211,7 @@ pub(in crate::inbox) async fn receive_undo_delete_for_community( 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 - } + Ok(Object::Community(_)) => todo!(), // if we dont have the object or dont support its deletion, no need to do anything _ => Ok(()), } @@ -622,52 +552,6 @@ where Ok(()) } -/// For activities like Update, Delete or Remove, check that the actor is from the same instance -/// as the original object itself (or is a remote mod). -/// -/// Note: This is only needed for mod actions. Normal user actions (edit post, undo vote etc) are -/// already verified with `expected_domain`, so this serves as an additional check. -async fn verify_modification_actor_instance( - activity: &T, - announce: &Option, - context: &LemmyContext, - request_counter: &mut i32, -) -> Result<(), LemmyError> -where - T: ActorAndObjectRef + BaseExt + AsObject, -{ - let actor_id = activity - .actor()? - .to_owned() - .single_xsd_any_uri() - .context(location_info!())?; - let object_id = activity - .object() - .as_one() - .map(|o| o.id()) - .flatten() - .context(location_info!())?; - let original_id = match fetch_post_or_comment_by_id(object_id, context, request_counter).await { - Ok(PostOrComment::Post(p)) => p.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() { - let community = extract_community_from_cc(activity, context).await?; - verify_mod_activity(activity, announce.to_owned(), &community, context).await?; - } - - Ok(()) -} - pub(crate) async fn verify_undo_remove_actor_instance( undo: &Undo, inner: &T,