convert community receivers

This commit is contained in:
Felix Ableitner 2021-06-26 03:20:24 +02:00
parent fb0932e0e6
commit 27c12ce411
13 changed files with 483 additions and 548 deletions

View file

@ -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(())
}
}

View file

@ -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.

View file

@ -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<DeleteCommunity> {
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<DeleteCommunity> {
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
}
}

View file

@ -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<OP: ToString + Send + lemmy_websocket::OperationType + 'static>(
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(())
}

View file

@ -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<RemoveCommunity> {
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<RemoveCommunity> {
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
}
}

View file

@ -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<DeleteCommunity>,
cc: Url,
#[serde(rename = "type")]
kind: DeleteType,
}
#[async_trait::async_trait(?Send)]
impl VerifyActivity for Activity<UndoDeleteCommunity> {
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<UndoDeleteCommunity> {
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
}
}

View file

@ -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<RemoveCommunity>,
cc: Url,
#[serde(rename = "type")]
kind: RemoveType,
}
#[async_trait::async_trait(?Send)]
impl VerifyActivity for Activity<UndoRemoveCommunity> {
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<UndoRemoveCommunity> {
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
}
}

View file

@ -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<UpdateCommunity> {
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<UpdateCommunity> {
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
}
}

View file

@ -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;

View file

@ -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,

View file

@ -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?

View file

@ -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<CommunityOrPrivateMessage, LemmyError> {
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())
}

View file

@ -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<Announce>,
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<Announce>,
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<T, Kind>(
activity: &T,
announce: &Option<Announce>,
context: &LemmyContext,
request_counter: &mut i32,
) -> Result<(), LemmyError>
where
T: ActorAndObjectRef + BaseExt<Kind> + AsObject<Kind>,
{
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<T, Kind>(
undo: &Undo,
inner: &T,