convert remaining activity receivers

This commit is contained in:
Felix Ableitner 2021-06-29 03:58:19 +02:00
parent e1e54672c6
commit 8a071b7b07
52 changed files with 456 additions and 1012 deletions

View file

@ -1,5 +1,5 @@
use crate::{
activities_new::comment::{get_notif_recipients, send_websocket_message},
activities::comment::{get_notif_recipients, send_websocket_message},
inbox::new_inbox_routing::Activity,
};
use activitystreams::{activity::kind::CreateType, base::BaseExt};
@ -10,7 +10,7 @@ use lemmy_utils::LemmyError;
use lemmy_websocket::{LemmyContext, UserOperationCrud};
use url::Url;
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
#[derive(Debug, serde::Deserialize, serde::Serialize)]
#[serde(rename_all = "camelCase")]
pub struct CreateComment {
actor: Url,

View file

@ -1,4 +1,4 @@
use crate::{activities_new::comment::send_websocket_message, inbox::new_inbox_routing::Activity};
use crate::{activities::comment::send_websocket_message, inbox::new_inbox_routing::Activity};
use activitystreams::activity::kind::DeleteType;
use lemmy_api_common::blocking;
use lemmy_apub::{check_is_apub_id_valid, fetcher::objects::get_or_fetch_and_insert_comment};
@ -9,12 +9,12 @@ use lemmy_utils::LemmyError;
use lemmy_websocket::{LemmyContext, UserOperationCrud};
use url::Url;
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
#[derive(Debug, serde::Deserialize, serde::Serialize)]
#[serde(rename_all = "camelCase")]
pub struct DeleteComment {
pub(in crate::activities_new::comment) actor: Url,
pub(in crate::activities::comment) actor: Url,
to: PublicUrl,
pub(in crate::activities_new::comment) object: Url,
pub(in crate::activities::comment) object: Url,
cc: [Url; 1],
#[serde(rename = "type")]
kind: DeleteType,

View file

@ -1,4 +1,4 @@
use crate::{activities_new::comment::like_or_dislike_comment, inbox::new_inbox_routing::Activity};
use crate::{activities::comment::like_or_dislike_comment, inbox::new_inbox_routing::Activity};
use activitystreams::activity::kind::DislikeType;
use lemmy_apub::check_is_apub_id_valid;
use lemmy_apub_lib::{verify_domains_match, PublicUrl, ReceiveActivity, VerifyActivity};
@ -6,12 +6,12 @@ use lemmy_utils::LemmyError;
use lemmy_websocket::LemmyContext;
use url::Url;
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
#[derive(Debug, serde::Deserialize, serde::Serialize)]
#[serde(rename_all = "camelCase")]
pub struct DislikeComment {
actor: Url,
to: PublicUrl,
pub(in crate::activities_new::comment) object: Url,
pub(in crate::activities::comment) object: Url,
cc: [Url; 1],
#[serde(rename = "type")]
kind: DislikeType,

View file

@ -1,4 +1,4 @@
use crate::{activities_new::comment::like_or_dislike_comment, inbox::new_inbox_routing::Activity};
use crate::{activities::comment::like_or_dislike_comment, inbox::new_inbox_routing::Activity};
use activitystreams::activity::kind::LikeType;
use lemmy_apub::check_is_apub_id_valid;
use lemmy_apub_lib::{verify_domains_match, PublicUrl, ReceiveActivity, VerifyActivity};
@ -6,12 +6,12 @@ use lemmy_utils::LemmyError;
use lemmy_websocket::LemmyContext;
use url::Url;
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
#[derive(Debug, serde::Deserialize, serde::Serialize)]
#[serde(rename_all = "camelCase")]
pub struct LikeComment {
actor: Url,
to: PublicUrl,
pub(in crate::activities_new::comment) object: Url,
pub(in crate::activities::comment) object: Url,
cc: [Url; 1],
#[serde(rename = "type")]
kind: LikeType,

View file

@ -1,5 +1,5 @@
use crate::{
activities_new::{comment::send_websocket_message, verify_mod_action},
activities::{comment::send_websocket_message, verify_mod_action},
inbox::new_inbox_routing::Activity,
};
use activitystreams::activity::kind::RemoveType;
@ -12,12 +12,12 @@ use lemmy_utils::LemmyError;
use lemmy_websocket::{LemmyContext, UserOperationCrud};
use url::Url;
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
#[derive(Debug, serde::Deserialize, serde::Serialize)]
#[serde(rename_all = "camelCase")]
pub struct RemoveComment {
actor: Url,
to: PublicUrl,
pub(in crate::activities_new::comment) object: Url,
pub(in crate::activities::comment) object: Url,
cc: [Url; 1],
#[serde(rename = "type")]
kind: RemoveType,

View file

@ -1,5 +1,5 @@
use crate::{
activities_new::comment::{delete::DeleteComment, send_websocket_message},
activities::comment::{delete::DeleteComment, send_websocket_message},
inbox::new_inbox_routing::Activity,
};
use activitystreams::activity::kind::UndoType;
@ -12,7 +12,7 @@ use lemmy_utils::LemmyError;
use lemmy_websocket::{LemmyContext, UserOperationCrud};
use url::Url;
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
#[derive(Debug, serde::Deserialize, serde::Serialize)]
#[serde(rename_all = "camelCase")]
pub struct UndoDeleteComment {
actor: Url,

View file

@ -1,5 +1,5 @@
use crate::{
activities_new::comment::{dislike::DislikeComment, undo_like_or_dislike_comment},
activities::comment::{dislike::DislikeComment, undo_like_or_dislike_comment},
inbox::new_inbox_routing::Activity,
};
use activitystreams::activity::kind::UndoType;
@ -9,7 +9,7 @@ use lemmy_utils::LemmyError;
use lemmy_websocket::LemmyContext;
use url::Url;
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
#[derive(Debug, serde::Deserialize, serde::Serialize)]
#[serde(rename_all = "camelCase")]
pub struct UndoDislikeComment {
actor: Url,

View file

@ -1,5 +1,5 @@
use crate::{
activities_new::comment::{like::LikeComment, undo_like_or_dislike_comment},
activities::comment::{like::LikeComment, undo_like_or_dislike_comment},
inbox::new_inbox_routing::Activity,
};
use activitystreams::activity::kind::UndoType;
@ -9,7 +9,7 @@ use lemmy_utils::LemmyError;
use lemmy_websocket::LemmyContext;
use url::Url;
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
#[derive(Debug, serde::Deserialize, serde::Serialize)]
#[serde(rename_all = "camelCase")]
pub struct UndoLikeComment {
actor: Url,

View file

@ -1,5 +1,5 @@
use crate::{
activities_new::{
activities::{
comment::{remove::RemoveComment, send_websocket_message},
verify_mod_action,
},
@ -15,7 +15,7 @@ use lemmy_utils::LemmyError;
use lemmy_websocket::{LemmyContext, UserOperationCrud};
use url::Url;
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
#[derive(Debug, serde::Deserialize, serde::Serialize)]
#[serde(rename_all = "camelCase")]
pub struct UndoRemoveComment {
actor: Url,

View file

@ -1,5 +1,5 @@
use crate::{
activities_new::comment::{get_notif_recipients, send_websocket_message},
activities::comment::{get_notif_recipients, send_websocket_message},
inbox::new_inbox_routing::Activity,
};
use activitystreams::{activity::kind::UpdateType, base::BaseExt};
@ -10,7 +10,7 @@ use lemmy_utils::LemmyError;
use lemmy_websocket::{LemmyContext, UserOperationCrud};
use url::Url;
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
#[derive(Debug, serde::Deserialize, serde::Serialize)]
#[serde(rename_all = "camelCase")]
pub struct UpdateComment {
actor: Url,

View file

@ -0,0 +1,80 @@
use crate::{
activities::{community::verify_add_remove_moderator_target, verify_mod_action},
inbox::new_inbox_routing::Activity,
};
use activitystreams::{activity::kind::AddType, base::AnyBase};
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},
CommunityType,
};
use lemmy_apub_lib::{verify_domains_match, PublicUrl, ReceiveActivity, VerifyActivity};
use lemmy_db_queries::{source::community::CommunityModerator_, Joinable};
use lemmy_db_schema::source::community::{CommunityModerator, CommunityModeratorForm};
use lemmy_utils::LemmyError;
use lemmy_websocket::LemmyContext;
use url::Url;
#[derive(Debug, serde::Deserialize, serde::Serialize)]
#[serde(rename_all = "camelCase")]
pub struct AddModToCommunity {
actor: Url,
to: PublicUrl,
object: Url,
target: Url,
cc: [Url; 1],
#[serde(rename = "type")]
kind: AddType,
}
#[async_trait::async_trait(?Send)]
impl VerifyActivity for Activity<AddModToCommunity> {
async fn verify(&self, context: &LemmyContext) -> Result<(), LemmyError> {
verify_domains_match(&self.inner.actor, self.id_unchecked())?;
verify_domains_match(&self.inner.target, &self.inner.cc[0])?;
check_is_apub_id_valid(&self.inner.actor, false)?;
verify_mod_action(self.inner.actor.clone(), self.inner.cc[0].clone(), context).await?;
verify_add_remove_moderator_target(&self.inner.target, self.inner.cc[0].clone())
}
}
#[async_trait::async_trait(?Send)]
impl ReceiveActivity for Activity<AddModToCommunity> {
async fn receive(
&self,
context: &LemmyContext,
request_counter: &mut i32,
) -> Result<(), LemmyError> {
let community =
get_or_fetch_and_upsert_community(&self.inner.cc[0], context, request_counter).await?;
let new_mod =
get_or_fetch_and_upsert_person(&self.inner.object, context, request_counter).await?;
// If we had to refetch the community while parsing the activity, then the new mod has already
// been added. Skip it here as it would result in a duplicate key error.
let new_mod_id = new_mod.id;
let moderated_communities = blocking(context.pool(), move |conn| {
CommunityModerator::get_person_moderated_communities(conn, new_mod_id)
})
.await??;
if !moderated_communities.contains(&community.id) {
let form = CommunityModeratorForm {
community_id: community.id,
person_id: new_mod.id,
};
blocking(context.pool(), move |conn| {
CommunityModerator::join(conn, &form)
})
.await??;
}
if community.local {
let anybase = AnyBase::from_arbitrary_json(serde_json::to_string(self)?)?;
community
.send_announce(anybase, Some(self.inner.object.clone()), context)
.await?;
}
// TODO: send websocket notification about added mod
Ok(())
}
}

View file

@ -0,0 +1,134 @@
use crate::{
activities::{
comment::{
create::CreateComment,
delete::DeleteComment,
dislike::DislikeComment,
like::LikeComment,
remove::RemoveComment,
undo_delete::UndoDeleteComment,
undo_dislike::UndoDislikeComment,
undo_like::UndoLikeComment,
undo_remove::UndoRemoveComment,
update::UpdateComment,
},
community::{
block_user::BlockUserFromCommunity,
delete::DeleteCommunity,
remove::RemoveCommunity,
undo_block_user::UndoBlockUserFromCommunity,
undo_delete::UndoDeleteCommunity,
undo_remove::UndoRemoveCommunity,
update::UpdateCommunity,
},
post::{
create::CreatePost,
delete::DeletePost,
dislike::DislikePost,
like::LikePost,
remove::RemovePost,
undo_delete::UndoDeletePost,
undo_dislike::UndoDislikePost,
undo_like::UndoLikePost,
undo_remove::UndoRemovePost,
update::UpdatePost,
},
},
inbox::{is_activity_already_known, new_inbox_routing::Activity},
};
use activitystreams::activity::kind::RemoveType;
use lemmy_apub::check_is_apub_id_valid;
use lemmy_apub_lib::{verify_domains_match, PublicUrl, ReceiveActivity, VerifyActivity};
use lemmy_utils::LemmyError;
use lemmy_websocket::LemmyContext;
use url::Url;
#[derive(Debug, serde::Deserialize, serde::Serialize)]
pub enum AnnouncableActivities {
CreateComment(CreateComment),
UpdateComment(UpdateComment),
LikeComment(LikeComment),
DislikeComment(DislikeComment),
UndoLikeComment(UndoLikeComment),
UndoDislikeComment(UndoDislikeComment),
DeleteComment(DeleteComment),
UndoDeleteComment(UndoDeleteComment),
RemoveComment(RemoveComment),
UndoRemoveComment(UndoRemoveComment),
CreatePost(CreatePost),
UpdatePost(UpdatePost),
LikePost(LikePost),
DislikePost(DislikePost),
DeletePost(DeletePost),
UndoDeletePost(UndoDeletePost),
RemovePost(RemovePost),
UndoRemovePost(UndoRemovePost),
UndoLikePost(UndoLikePost),
UndoDislikePost(UndoDislikePost),
// TODO: which of these get announced?
UpdateCommunity(UpdateCommunity),
DeleteCommunity(DeleteCommunity),
RemoveCommunity(RemoveCommunity),
UndoDeleteCommunity(UndoDeleteCommunity),
UndoRemoveCommunity(UndoRemoveCommunity),
BlockUserFromCommunity(BlockUserFromCommunity),
UndoBlockUserFromCommunity(UndoBlockUserFromCommunity),
}
#[async_trait::async_trait(?Send)]
impl VerifyActivity for AnnouncableActivities {
async fn verify(&self, context: &LemmyContext) -> Result<(), LemmyError> {
self.verify(context).await
}
}
#[async_trait::async_trait(?Send)]
impl ReceiveActivity for AnnouncableActivities {
async fn receive(
&self,
context: &LemmyContext,
request_counter: &mut i32,
) -> Result<(), LemmyError> {
self.receive(context, request_counter).await
}
}
#[derive(Debug, serde::Deserialize, serde::Serialize)]
#[serde(rename_all = "camelCase")]
pub struct AnnounceActivity {
actor: Url,
to: PublicUrl,
object: Activity<AnnouncableActivities>,
cc: [Url; 1],
#[serde(rename = "type")]
kind: RemoveType,
}
#[async_trait::async_trait(?Send)]
impl VerifyActivity for Activity<AnnounceActivity> {
async fn verify(&self, context: &LemmyContext) -> Result<(), LemmyError> {
verify_domains_match(&self.inner.actor, self.id_unchecked())?;
verify_domains_match(&self.inner.actor, &self.inner.cc[0])?;
check_is_apub_id_valid(&self.inner.actor, false)?;
self.inner.object.inner.verify(context).await
}
}
#[async_trait::async_trait(?Send)]
impl ReceiveActivity for Activity<AnnounceActivity> {
async fn receive(
&self,
context: &LemmyContext,
request_counter: &mut i32,
) -> Result<(), LemmyError> {
if is_activity_already_known(context.pool(), &self.inner.object.id_unchecked()).await? {
return Ok(());
}
self
.inner
.object
.inner
.receive(context, request_counter)
.await
}
}

View file

@ -1,4 +1,4 @@
use crate::{activities_new::verify_mod_action, inbox::new_inbox_routing::Activity};
use crate::{activities::verify_mod_action, inbox::new_inbox_routing::Activity};
use activitystreams::activity::kind::BlockType;
use lemmy_api_common::blocking;
use lemmy_apub::{
@ -17,12 +17,12 @@ use lemmy_utils::LemmyError;
use lemmy_websocket::LemmyContext;
use url::Url;
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
#[derive(Debug, serde::Deserialize, serde::Serialize)]
#[serde(rename_all = "camelCase")]
pub struct BlockUserFromCommunity {
actor: Url,
to: PublicUrl,
pub(in crate::activities_new::community) object: Url,
pub(in crate::activities::community) object: Url,
cc: [Url; 1],
#[serde(rename = "type")]
kind: BlockType,

View file

@ -1,5 +1,5 @@
use crate::{
activities_new::community::{send_websocket_message, verify_is_community_mod},
activities::community::{send_websocket_message, verify_is_community_mod},
inbox::new_inbox_routing::Activity,
};
use activitystreams::activity::kind::DeleteType;
@ -20,12 +20,12 @@ 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)]
#[derive(Debug, serde::Deserialize, serde::Serialize)]
#[serde(rename_all = "camelCase")]
pub struct DeleteCommunity {
actor: Url,
to: PublicUrl,
pub(in crate::activities_new::community) object: Url,
pub(in crate::activities::community) object: Url,
cc: [Url; 1],
#[serde(rename = "type")]
kind: DeleteType,

View file

@ -1,5 +1,6 @@
use anyhow::anyhow;
use lemmy_api_common::{blocking, community::CommunityResponse};
use lemmy_apub::generate_moderators_url;
use lemmy_db_queries::ApubObject;
use lemmy_db_schema::{
source::{community::Community, person::Person},
@ -10,9 +11,12 @@ use lemmy_utils::LemmyError;
use lemmy_websocket::{messages::SendCommunityRoomMessage, LemmyContext};
use url::Url;
pub mod add_mod;
pub mod announce;
pub mod block_user;
pub mod delete;
pub mod remove;
pub mod remove_mod;
pub mod undo_block_user;
pub mod undo_delete;
pub mod undo_remove;
@ -63,3 +67,12 @@ async fn verify_is_community_mod(
}
Ok(())
}
/// For Add/Remove community moderator activities, check that the target field actually contains
/// /c/community/moderators. Any different values are unsupported.
fn verify_add_remove_moderator_target(target: &Url, community: Url) -> Result<(), LemmyError> {
if target != &generate_moderators_url(&community.into())?.into_inner() {
return Err(anyhow!("Unkown target url").into());
}
Ok(())
}

View file

@ -1,7 +1,4 @@
use crate::{
activities_new::community::send_websocket_message,
inbox::new_inbox_routing::Activity,
};
use crate::{activities::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;
@ -12,12 +9,12 @@ use lemmy_utils::LemmyError;
use lemmy_websocket::{LemmyContext, UserOperationCrud};
use url::Url;
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
#[derive(Debug, serde::Deserialize, serde::Serialize)]
#[serde(rename_all = "camelCase")]
pub struct RemoveCommunity {
actor: Url,
to: PublicUrl,
pub(in crate::activities_new::community) object: Url,
pub(in crate::activities::community) object: Url,
cc: [Url; 1],
#[serde(rename = "type")]
kind: RemoveType,

View file

@ -0,0 +1,69 @@
use crate::{
activities::{community::verify_add_remove_moderator_target, verify_mod_action},
inbox::new_inbox_routing::Activity,
};
use activitystreams::{activity::kind::RemoveType, base::AnyBase};
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},
CommunityType,
};
use lemmy_apub_lib::{verify_domains_match, PublicUrl, ReceiveActivity, VerifyActivity};
use lemmy_db_queries::Joinable;
use lemmy_db_schema::source::community::{CommunityModerator, CommunityModeratorForm};
use lemmy_utils::LemmyError;
use lemmy_websocket::LemmyContext;
use url::Url;
#[derive(Debug, serde::Deserialize, serde::Serialize)]
#[serde(rename_all = "camelCase")]
pub struct RemoveModToCommunity {
actor: Url,
to: PublicUrl,
object: Url,
target: Url,
cc: [Url; 1],
#[serde(rename = "type")]
kind: RemoveType,
}
#[async_trait::async_trait(?Send)]
impl VerifyActivity for Activity<RemoveModToCommunity> {
async fn verify(&self, context: &LemmyContext) -> Result<(), LemmyError> {
verify_domains_match(&self.inner.actor, self.id_unchecked())?;
verify_domains_match(&self.inner.target, &self.inner.cc[0])?;
check_is_apub_id_valid(&self.inner.actor, false)?;
verify_mod_action(self.inner.actor.clone(), self.inner.cc[0].clone(), context).await?;
verify_add_remove_moderator_target(&self.inner.target, self.inner.cc[0].clone())
}
}
#[async_trait::async_trait(?Send)]
impl ReceiveActivity for Activity<RemoveModToCommunity> {
async fn receive(
&self,
context: &LemmyContext,
request_counter: &mut i32,
) -> Result<(), LemmyError> {
let community =
get_or_fetch_and_upsert_community(&self.inner.cc[0], context, request_counter).await?;
let add_mod =
get_or_fetch_and_upsert_person(&self.inner.object, context, request_counter).await?;
let form = CommunityModeratorForm {
community_id: community.id,
person_id: add_mod.id,
};
blocking(context.pool(), move |conn| {
CommunityModerator::leave(conn, &form)
})
.await??;
let anybase = AnyBase::from_arbitrary_json(serde_json::to_string(self)?)?;
community
.send_announce(anybase, Some(self.inner.object.clone()), context)
.await?;
// TODO: send websocket notification about removed mod
Ok(())
}
}

View file

@ -1,5 +1,5 @@
use crate::{
activities_new::{community::block_user::BlockUserFromCommunity, verify_mod_action},
activities::{community::block_user::BlockUserFromCommunity, verify_mod_action},
inbox::new_inbox_routing::Activity,
};
use activitystreams::activity::kind::BlockType;
@ -15,7 +15,7 @@ use lemmy_utils::LemmyError;
use lemmy_websocket::LemmyContext;
use url::Url;
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
#[derive(Debug, serde::Deserialize, serde::Serialize)]
#[serde(rename_all = "camelCase")]
pub struct UndoBlockUserFromCommunity {
actor: Url,

View file

@ -1,5 +1,5 @@
use crate::{
activities_new::community::{
activities::community::{
delete::DeleteCommunity,
send_websocket_message,
verify_is_community_mod,
@ -24,7 +24,7 @@ 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)]
#[derive(Debug, serde::Deserialize, serde::Serialize)]
#[serde(rename_all = "camelCase")]
pub struct UndoDeleteCommunity {
actor: Url,

View file

@ -1,5 +1,5 @@
use crate::{
activities_new::community::{remove::RemoveCommunity, send_websocket_message},
activities::community::{remove::RemoveCommunity, send_websocket_message},
inbox::new_inbox_routing::Activity,
};
use activitystreams::activity::kind::RemoveType;
@ -12,7 +12,7 @@ use lemmy_utils::LemmyError;
use lemmy_websocket::{LemmyContext, UserOperationCrud};
use url::Url;
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
#[derive(Debug, serde::Deserialize, serde::Serialize)]
#[serde(rename_all = "camelCase")]
pub struct UndoRemoveCommunity {
actor: Url,

View file

@ -1,5 +1,5 @@
use crate::{
activities_new::community::{send_websocket_message, verify_is_community_mod},
activities::community::{send_websocket_message, verify_is_community_mod},
inbox::new_inbox_routing::Activity,
};
use activitystreams::{activity::kind::UpdateType, base::BaseExt};
@ -14,7 +14,7 @@ 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)]
#[derive(Debug, serde::Deserialize, serde::Serialize)]
#[serde(rename_all = "camelCase")]
pub struct UpdateCommunity {
actor: Url,

View file

@ -1,4 +1,4 @@
use crate::{activities_new::follow::follow::FollowCommunity, inbox::new_inbox_routing::Activity};
use crate::{activities::follow::follow::FollowCommunity, inbox::new_inbox_routing::Activity};
use activitystreams::activity::kind::AcceptType;
use lemmy_api_common::blocking;
use lemmy_apub::{
@ -12,7 +12,7 @@ use lemmy_utils::LemmyError;
use lemmy_websocket::LemmyContext;
use url::Url;
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
#[derive(Debug, serde::Deserialize, serde::Serialize)]
#[serde(rename_all = "camelCase")]
pub struct AcceptFollowCommunity {
actor: Url,

View file

@ -17,12 +17,12 @@ use lemmy_utils::{location_info, LemmyError};
use lemmy_websocket::LemmyContext;
use url::Url;
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
#[derive(Debug, serde::Deserialize, serde::Serialize)]
#[serde(rename_all = "camelCase")]
pub struct FollowCommunity {
actor: Url,
to: Url,
pub(in crate::activities_new::follow) object: Url,
pub(in crate::activities::follow) object: Url,
#[serde(rename = "type")]
kind: FollowType,
}

View file

@ -1,4 +1,4 @@
use crate::{activities_new::follow::follow::FollowCommunity, inbox::new_inbox_routing::Activity};
use crate::{activities::follow::follow::FollowCommunity, inbox::new_inbox_routing::Activity};
use activitystreams::activity::kind::UndoType;
use lemmy_api_common::blocking;
use lemmy_apub::{
@ -12,7 +12,7 @@ use lemmy_utils::LemmyError;
use lemmy_websocket::LemmyContext;
use url::Url;
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
#[derive(Debug, serde::Deserialize, serde::Serialize)]
#[serde(rename_all = "camelCase")]
pub struct UndoFollowCommunity {
actor: Url,

View file

@ -1 +1,45 @@
pub(crate) mod receive;
use anyhow::anyhow;
use lemmy_api_common::blocking;
use lemmy_db_queries::ApubObject;
use lemmy_db_schema::source::{community::Community, person::Person};
use lemmy_db_views_actor::community_view::CommunityView;
use lemmy_utils::LemmyError;
use lemmy_websocket::LemmyContext;
use url::Url;
pub mod comment;
pub mod community;
pub mod follow;
pub mod post;
pub mod private_message;
async fn verify_mod_action(
actor_id: Url,
activity_cc: Url,
context: &LemmyContext,
) -> Result<(), LemmyError> {
let community = blocking(context.pool(), move |conn| {
Community::read_from_apub_id(conn, &activity_cc.into())
})
.await??;
if community.local {
let actor = blocking(&context.pool(), move |conn| {
Person::read_from_apub_id(&conn, &actor_id.clone().into())
})
.await??;
// Note: this will also return true for admins in addition to mods, but as we dont know about
// remote admins, it doesnt make any difference.
let community_id = community.id;
let actor_id = actor.id;
let is_mod_or_admin = blocking(context.pool(), move |conn| {
CommunityView::is_mod_or_admin(conn, actor_id, community_id)
})
.await?;
if !is_mod_or_admin {
return Err(anyhow!("Not a mod").into());
}
}
Ok(())
}

View file

@ -1,4 +1,4 @@
use crate::{activities_new::post::send_websocket_message, inbox::new_inbox_routing::Activity};
use crate::{activities::post::send_websocket_message, inbox::new_inbox_routing::Activity};
use activitystreams::{activity::kind::CreateType, base::BaseExt};
use lemmy_apub::{
check_is_apub_id_valid,
@ -13,7 +13,7 @@ use lemmy_utils::LemmyError;
use lemmy_websocket::{LemmyContext, UserOperationCrud};
use url::Url;
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
#[derive(Debug, serde::Deserialize, serde::Serialize)]
#[serde(rename_all = "camelCase")]
pub struct CreatePost {
actor: Url,

View file

@ -1,4 +1,4 @@
use crate::{activities_new::post::send_websocket_message, inbox::new_inbox_routing::Activity};
use crate::{activities::post::send_websocket_message, inbox::new_inbox_routing::Activity};
use activitystreams::activity::kind::DeleteType;
use lemmy_api_common::blocking;
use lemmy_apub::{check_is_apub_id_valid, fetcher::objects::get_or_fetch_and_insert_post};
@ -9,12 +9,12 @@ use lemmy_utils::LemmyError;
use lemmy_websocket::{LemmyContext, UserOperationCrud};
use url::Url;
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
#[derive(Debug, serde::Deserialize, serde::Serialize)]
#[serde(rename_all = "camelCase")]
pub struct DeletePost {
pub(in crate::activities_new::post) actor: Url,
pub(in crate::activities::post) actor: Url,
to: PublicUrl,
pub(in crate::activities_new::post) object: Url,
pub(in crate::activities::post) object: Url,
cc: [Url; 1],
#[serde(rename = "type")]
kind: DeleteType,

View file

@ -1,4 +1,4 @@
use crate::{activities_new::post::like_or_dislike_post, inbox::new_inbox_routing::Activity};
use crate::{activities::post::like_or_dislike_post, inbox::new_inbox_routing::Activity};
use activitystreams::activity::kind::DislikeType;
use lemmy_apub::check_is_apub_id_valid;
use lemmy_apub_lib::{verify_domains_match, PublicUrl, ReceiveActivity, VerifyActivity};
@ -6,12 +6,12 @@ use lemmy_utils::LemmyError;
use lemmy_websocket::LemmyContext;
use url::Url;
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
#[derive(Debug, serde::Deserialize, serde::Serialize)]
#[serde(rename_all = "camelCase")]
pub struct DislikePost {
actor: Url,
to: PublicUrl,
pub(in crate::activities_new::post) object: Url,
pub(in crate::activities::post) object: Url,
cc: [Url; 1],
#[serde(rename = "type")]
kind: DislikeType,

View file

@ -1,4 +1,4 @@
use crate::{activities_new::post::like_or_dislike_post, inbox::new_inbox_routing::Activity};
use crate::{activities::post::like_or_dislike_post, inbox::new_inbox_routing::Activity};
use activitystreams::activity::kind::LikeType;
use lemmy_apub::check_is_apub_id_valid;
use lemmy_apub_lib::{verify_domains_match, PublicUrl, ReceiveActivity, VerifyActivity};
@ -6,12 +6,12 @@ use lemmy_utils::LemmyError;
use lemmy_websocket::LemmyContext;
use url::Url;
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
#[derive(Debug, serde::Deserialize, serde::Serialize)]
#[serde(rename_all = "camelCase")]
pub struct LikePost {
actor: Url,
to: PublicUrl,
pub(in crate::activities_new::post) object: Url,
pub(in crate::activities::post) object: Url,
cc: [Url; 1],
#[serde(rename = "type")]
kind: LikeType,

View file

@ -1,5 +1,5 @@
use crate::{
activities_new::{post::send_websocket_message, verify_mod_action},
activities::{post::send_websocket_message, verify_mod_action},
inbox::new_inbox_routing::Activity,
};
use activitystreams::activity::kind::RemoveType;
@ -12,12 +12,12 @@ use lemmy_utils::LemmyError;
use lemmy_websocket::{LemmyContext, UserOperationCrud};
use url::Url;
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
#[derive(Debug, serde::Deserialize, serde::Serialize)]
#[serde(rename_all = "camelCase")]
pub struct RemovePost {
actor: Url,
to: PublicUrl,
pub(in crate::activities_new::post) object: Url,
pub(in crate::activities::post) object: Url,
cc: [Url; 1],
#[serde(rename = "type")]
kind: RemoveType,

View file

@ -1,5 +1,5 @@
use crate::{
activities_new::post::{delete::DeletePost, send_websocket_message},
activities::post::{delete::DeletePost, send_websocket_message},
inbox::new_inbox_routing::Activity,
};
use activitystreams::activity::kind::UndoType;
@ -12,7 +12,7 @@ use lemmy_utils::LemmyError;
use lemmy_websocket::{LemmyContext, UserOperationCrud};
use url::Url;
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
#[derive(Debug, serde::Deserialize, serde::Serialize)]
#[serde(rename_all = "camelCase")]
pub struct UndoDeletePost {
actor: Url,

View file

@ -1,5 +1,5 @@
use crate::{
activities_new::post::{dislike::DislikePost, undo_like_or_dislike_post},
activities::post::{dislike::DislikePost, undo_like_or_dislike_post},
inbox::new_inbox_routing::Activity,
};
use activitystreams::activity::kind::UndoType;
@ -9,7 +9,7 @@ use lemmy_utils::LemmyError;
use lemmy_websocket::LemmyContext;
use url::Url;
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
#[derive(Debug, serde::Deserialize, serde::Serialize)]
#[serde(rename_all = "camelCase")]
pub struct UndoDislikePost {
actor: Url,

View file

@ -1,5 +1,5 @@
use crate::{
activities_new::post::{like::LikePost, undo_like_or_dislike_post},
activities::post::{like::LikePost, undo_like_or_dislike_post},
inbox::new_inbox_routing::Activity,
};
use activitystreams::activity::kind::UndoType;
@ -9,7 +9,7 @@ use lemmy_utils::LemmyError;
use lemmy_websocket::LemmyContext;
use url::Url;
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
#[derive(Debug, serde::Deserialize, serde::Serialize)]
#[serde(rename_all = "camelCase")]
pub struct UndoLikePost {
actor: Url,

View file

@ -1,5 +1,5 @@
use crate::{
activities_new::{
activities::{
post::{remove::RemovePost, send_websocket_message},
verify_mod_action,
},
@ -15,7 +15,7 @@ use lemmy_utils::LemmyError;
use lemmy_websocket::{LemmyContext, UserOperationCrud};
use url::Url;
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
#[derive(Debug, serde::Deserialize, serde::Serialize)]
#[serde(rename_all = "camelCase")]
pub struct UndoRemovePost {
actor: Url,

View file

@ -1,4 +1,4 @@
use crate::{activities_new::post::send_websocket_message, inbox::new_inbox_routing::Activity};
use crate::{activities::post::send_websocket_message, inbox::new_inbox_routing::Activity};
use activitystreams::{activity::kind::UpdateType, base::BaseExt};
use anyhow::Context;
use lemmy_api_common::blocking;
@ -22,7 +22,7 @@ use lemmy_utils::{location_info, LemmyError};
use lemmy_websocket::{LemmyContext, UserOperationCrud};
use url::Url;
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
#[derive(Debug, serde::Deserialize, serde::Serialize)]
#[serde(rename_all = "camelCase")]
pub struct UpdatePost {
actor: Url,

View file

@ -1,5 +1,5 @@
use crate::{
activities_new::private_message::send_websocket_message,
activities::private_message::send_websocket_message,
inbox::new_inbox_routing::Activity,
};
use activitystreams::{activity::kind::CreateType, base::BaseExt};
@ -10,7 +10,7 @@ use lemmy_utils::LemmyError;
use lemmy_websocket::{LemmyContext, UserOperationCrud};
use url::Url;
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
#[derive(Debug, serde::Deserialize, serde::Serialize)]
#[serde(rename_all = "camelCase")]
pub struct CreatePrivateMessage {
actor: Url,

View file

@ -1,5 +1,5 @@
use crate::{
activities_new::private_message::send_websocket_message,
activities::private_message::send_websocket_message,
inbox::new_inbox_routing::Activity,
};
use activitystreams::activity::kind::DeleteType;
@ -12,12 +12,12 @@ use lemmy_utils::LemmyError;
use lemmy_websocket::{LemmyContext, UserOperationCrud};
use url::Url;
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
#[derive(Debug, serde::Deserialize, serde::Serialize)]
#[serde(rename_all = "camelCase")]
pub struct DeletePrivateMessage {
actor: Url,
to: Url,
pub(in crate::activities_new::private_message) object: Url,
pub(in crate::activities::private_message) object: Url,
#[serde(rename = "type")]
kind: DeleteType,
}

View file

@ -1,5 +1,5 @@
use crate::{
activities_new::private_message::{delete::DeletePrivateMessage, send_websocket_message},
activities::private_message::{delete::DeletePrivateMessage, send_websocket_message},
inbox::new_inbox_routing::Activity,
};
use activitystreams::activity::kind::UndoType;
@ -12,7 +12,7 @@ use lemmy_utils::LemmyError;
use lemmy_websocket::{LemmyContext, UserOperationCrud};
use url::Url;
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
#[derive(Debug, serde::Deserialize, serde::Serialize)]
#[serde(rename_all = "camelCase")]
pub struct UndoDeletePrivateMessage {
actor: Url,

View file

@ -1,5 +1,5 @@
use crate::{
activities_new::private_message::send_websocket_message,
activities::private_message::send_websocket_message,
inbox::new_inbox_routing::Activity,
};
use activitystreams::{activity::kind::UpdateType, base::BaseExt};
@ -10,7 +10,7 @@ use lemmy_utils::LemmyError;
use lemmy_websocket::{LemmyContext, UserOperationCrud};
use url::Url;
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
#[derive(Debug, serde::Deserialize, serde::Serialize)]
#[serde(rename_all = "camelCase")]
pub struct UpdatePrivateMessage {
actor: Url,

View file

@ -1,57 +0,0 @@
use activitystreams::{
activity::{ActorAndObjectRef, ActorAndObjectRefExt},
base::{AsBase, BaseExt},
error::DomainError,
};
use anyhow::{anyhow, Context};
use lemmy_utils::{location_info, LemmyError};
use log::debug;
use std::fmt::Debug;
use url::Url;
/// Return HTTP 501 for unsupported activities in inbox.
pub(crate) fn receive_unhandled_activity<A>(activity: A) -> Result<(), LemmyError>
where
A: Debug,
{
debug!("received unhandled activity type: {:?}", activity);
Err(anyhow!("Activity not supported").into())
}
/// Ensure that the ID of an incoming activity comes from the same domain as the actor. Optionally
/// also checks the ID of the inner object.
///
/// The reason that this starts with the actor ID is that it was already confirmed as correct by the
/// HTTP signature.
pub(crate) fn verify_activity_domains_valid<T, Kind>(
activity: &T,
actor_id: &Url,
object_domain_must_match: bool,
) -> Result<(), LemmyError>
where
T: AsBase<Kind> + ActorAndObjectRef,
{
let expected_domain = actor_id.domain().context(location_info!())?;
activity.id(expected_domain)?;
let object_id = match activity.object().to_owned().single_xsd_any_uri() {
// object is just an ID
Some(id) => id,
// object is something like an activity, a comment or a post
None => activity
.object()
.to_owned()
.one()
.context(location_info!())?
.id()
.context(location_info!())?
.to_owned(),
};
if object_domain_must_match && object_id.domain() != Some(expected_domain) {
return Err(DomainError.into());
}
Ok(())
}

View file

@ -1,45 +0,0 @@
use anyhow::anyhow;
use lemmy_api_common::blocking;
use lemmy_db_queries::ApubObject;
use lemmy_db_schema::source::{community::Community, person::Person};
use lemmy_db_views_actor::community_view::CommunityView;
use lemmy_utils::LemmyError;
use lemmy_websocket::LemmyContext;
use url::Url;
pub mod comment;
pub mod community;
pub mod follow;
pub mod post;
pub mod private_message;
async fn verify_mod_action(
actor_id: Url,
activity_cc: Url,
context: &LemmyContext,
) -> Result<(), LemmyError> {
let community = blocking(context.pool(), move |conn| {
Community::read_from_apub_id(conn, &activity_cc.into())
})
.await??;
if community.local {
let actor = blocking(&context.pool(), move |conn| {
Person::read_from_apub_id(&conn, &actor_id.clone().into())
})
.await??;
// Note: this will also return true for admins in addition to mods, but as we dont know about
// remote admins, it doesnt make any difference.
let community_id = community.id;
let actor_id = actor.id;
let is_mod_or_admin = blocking(context.pool(), move |conn| {
CommunityView::is_mod_or_admin(conn, actor_id, community_id)
})
.await?;
if !is_mod_or_admin {
return Err(anyhow!("Not a mod").into());
}
}
Ok(())
}

View file

@ -1,160 +1,14 @@
use crate::inbox::{
assert_activity_not_local,
get_activity_id,
inbox_verify_http_signature,
is_activity_already_known,
receive_for_community::receive_add_for_community,
verify_is_addressed_to_public,
};
use activitystreams::{activity::ActorAndObject, prelude::*};
use crate::inbox::new_inbox_routing::{Activity, SharedInboxActivities};
use actix_web::{web, HttpRequest, HttpResponse};
use anyhow::{anyhow, Context};
use lemmy_api_common::blocking;
use lemmy_apub::{
check_community_or_site_ban,
get_activity_to_and_cc,
insert_activity,
ActorType,
CommunityType,
};
use lemmy_db_queries::{source::community::Community_, ApubObject};
use lemmy_db_schema::source::{community::Community, person::Person};
use lemmy_utils::{location_info, LemmyError};
use lemmy_utils::LemmyError;
use lemmy_websocket::LemmyContext;
use log::info;
use serde::{Deserialize, Serialize};
use std::fmt::Debug;
/// Allowed activities for community inbox.
#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd, Deserialize, Serialize)]
#[serde(rename_all = "PascalCase")]
pub enum CommunityValidTypes {
Follow, // follow request from a person
Undo, // unfollow from a person
Create, // create post or comment
Update, // update post or comment
Like, // upvote post or comment
Dislike, // downvote post or comment
Delete, // post or comment deleted by creator
Remove, // post or comment removed by mod or admin, or mod removed from community
Add, // mod added to community
Block, // user blocked by community
}
pub type CommunityAcceptedActivities = ActorAndObject<CommunityValidTypes>;
/// Handler for all incoming receive to community inboxes.
pub async fn community_inbox(
request: HttpRequest,
input: web::Json<CommunityAcceptedActivities>,
path: web::Path<String>,
context: web::Data<LemmyContext>,
_request: HttpRequest,
_input: web::Json<Activity<Activity<SharedInboxActivities>>>,
_path: web::Path<String>,
_context: web::Data<LemmyContext>,
) -> Result<HttpResponse, LemmyError> {
let activity = input.into_inner();
// First of all check the http signature
let request_counter = &mut 0;
let actor = inbox_verify_http_signature(&activity, &context, request, request_counter).await?;
// Do nothing if we received the same activity before
let activity_id = get_activity_id(&activity, &actor.actor_id())?;
if is_activity_already_known(context.pool(), &activity_id).await? {
return Ok(HttpResponse::Ok().finish());
}
// Check if the activity is actually meant for us
let path = path.into_inner();
let community = blocking(&context.pool(), move |conn| {
Community::read_from_name(&conn, &path)
})
.await??;
let to_and_cc = get_activity_to_and_cc(&activity);
if !to_and_cc.contains(&&community.actor_id()) {
return Err(anyhow!("Activity delivered to wrong community").into());
}
assert_activity_not_local(&activity)?;
insert_activity(&activity_id, activity.clone(), false, true, context.pool()).await?;
community_receive_message(
activity.clone(),
community.clone(),
actor.as_ref(),
&context,
request_counter,
)
.await
}
/// Receives Follow, Undo/Follow, post actions, comment actions (including votes)
pub(crate) async fn community_receive_message(
activity: CommunityAcceptedActivities,
to_community: Community,
actor: &dyn ActorType,
context: &LemmyContext,
request_counter: &mut i32,
) -> Result<HttpResponse, LemmyError> {
// Only persons can send activities to the community, so we can get the actor as person
// unconditionally.
let actor_id = actor.actor_id();
let person = blocking(&context.pool(), move |conn| {
Person::read_from_apub_id(&conn, &actor_id.into())
})
.await??;
check_community_or_site_ban(&person, to_community.id, context.pool()).await?;
info!(
"Community {} received activity {} from {}",
to_community.name,
&activity
.id_unchecked()
.context(location_info!())?
.to_string(),
&person.actor_id().to_string()
);
let any_base = activity.clone().into_any_base()?;
let activity_kind = activity.kind().context(location_info!())?;
let do_announce = match activity_kind {
CommunityValidTypes::Follow => todo!(),
CommunityValidTypes::Undo => todo!(),
CommunityValidTypes::Create => todo!(),
CommunityValidTypes::Update => todo!(),
CommunityValidTypes::Like => todo!(),
CommunityValidTypes::Dislike => todo!(),
CommunityValidTypes::Delete => todo!(),
CommunityValidTypes::Add => {
Box::pin(receive_add_for_community(
context,
any_base.clone(),
None,
request_counter,
))
.await?;
true
}
CommunityValidTypes::Remove => todo!(),
CommunityValidTypes::Block => todo!(),
};
if do_announce {
// Check again that the activity is public, just to be sure
verify_is_addressed_to_public(&activity)?;
let mut object_actor = activity.object().clone().single_xsd_any_uri();
// If activity is something like Undo/Block, we need to access activity.object.object
if object_actor.is_none() {
object_actor = activity
.object()
.as_one()
.map(|a| ActorAndObject::from_any_base(a.to_owned()).ok())
.flatten()
.flatten()
.map(|a: ActorAndObject<CommunityValidTypes>| a.object().to_owned().single_xsd_any_uri())
.flatten();
}
to_community
.send_announce(activity.into_any_base()?, object_actor, context)
.await?;
}
Ok(HttpResponse::Ok().finish())
todo!()
}

View file

@ -1,48 +1,29 @@
use activitystreams::{
activity::ActorAndObjectRefExt,
base::{AsBase, BaseExt, Extends},
base::{AsBase, Extends},
object::AsObject,
public,
};
use actix_web::HttpRequest;
use anyhow::{anyhow, Context};
use anyhow::Context;
use lemmy_api_common::blocking;
use lemmy_apub::{
check_is_apub_id_valid,
extensions::signatures::verify_signature,
fetcher::get_or_fetch_and_upsert_actor,
get_activity_to_and_cc,
ActorType,
};
use lemmy_db_queries::{
source::{activity::Activity_, community::Community_},
ApubObject,
DbPool,
};
use lemmy_db_schema::source::{activity::Activity, community::Community, person::Person};
use lemmy_utils::{location_info, settings::structs::Settings, LemmyError};
use lemmy_db_queries::{source::activity::Activity_, DbPool};
use lemmy_db_schema::source::activity::Activity;
use lemmy_utils::{location_info, LemmyError};
use lemmy_websocket::LemmyContext;
use serde::Serialize;
use std::fmt::Debug;
use url::Url;
pub mod community_inbox;
pub mod new_inbox_routing;
pub mod person_inbox;
pub(crate) mod receive_for_community;
pub mod shared_inbox;
pub(crate) fn get_activity_id<T, Kind>(activity: &T, creator_uri: &Url) -> Result<Url, LemmyError>
where
T: BaseExt<Kind> + Extends<Kind> + Debug,
Kind: Serialize,
<T as Extends<Kind>>::Error: From<serde_json::Error> + Send + Sync + 'static,
{
let creator_domain = creator_uri.host_str().context(location_info!())?;
let activity_id = activity.id(creator_domain)?;
Ok(activity_id.context(location_info!())?.to_owned())
}
pub(crate) async fn is_activity_already_known(
pool: &DbPool,
activity_id: &Url,
@ -58,18 +39,6 @@ pub(crate) async fn is_activity_already_known(
}
}
pub(crate) fn verify_is_addressed_to_public<T, Kind>(activity: &T) -> Result<(), LemmyError>
where
T: AsBase<Kind> + AsObject<Kind> + ActorAndObjectRefExt,
{
let to_and_cc = get_activity_to_and_cc(activity);
if to_and_cc.contains(&public()) {
Ok(())
} else {
Err(anyhow!("Activity is not addressed to public").into())
}
}
pub(crate) async fn inbox_verify_http_signature<T, Kind>(
activity: &T,
context: &LemmyContext,
@ -91,64 +60,3 @@ where
verify_signature(&request, actor.as_ref())?;
Ok(actor)
}
/// Returns true if `to_and_cc` contains at least one local user.
pub(crate) async fn is_addressed_to_local_person(
to_and_cc: &[Url],
pool: &DbPool,
) -> Result<bool, LemmyError> {
for url in to_and_cc {
let url = url.to_owned();
let person = blocking(&pool, move |conn| {
Person::read_from_apub_id(&conn, &url.into())
})
.await?;
if let Ok(u) = person {
if u.local {
return Ok(true);
}
}
}
Ok(false)
}
/// If `to_and_cc` contains the followers collection of a remote community, returns this community
/// (like `https://example.com/c/main/followers`)
pub(crate) async fn is_addressed_to_community_followers(
to_and_cc: &[Url],
pool: &DbPool,
) -> Result<Option<Community>, LemmyError> {
for url in to_and_cc {
let url = url.to_owned().into();
let community = blocking(&pool, move |conn| {
// ignore errors here, because the current url might not actually be a followers url
Community::read_from_followers_url(&conn, &url).ok()
})
.await?;
if let Some(c) = community {
if !c.local {
return Ok(Some(c));
}
}
}
Ok(None)
}
pub(in crate::inbox) fn assert_activity_not_local<T, Kind>(activity: &T) -> Result<(), LemmyError>
where
T: BaseExt<Kind> + Debug,
{
let id = activity.id_unchecked().context(location_info!())?;
let activity_domain = id.domain().context(location_info!())?;
if activity_domain == Settings::get().hostname() {
return Err(
anyhow!(
"Error: received activity which was sent by local instance: {:?}",
activity
)
.into(),
);
}
Ok(())
}

View file

@ -1,4 +1,4 @@
use crate::activities_new::{
use crate::activities::{
comment::{
create::CreateComment,
delete::DeleteComment,
@ -12,8 +12,11 @@ use crate::activities_new::{
update::UpdateComment,
},
community::{
announce::AnnounceActivity,
block_user::BlockUserFromCommunity,
delete::DeleteCommunity,
remove::RemoveCommunity,
undo_block_user::UndoBlockUserFromCommunity,
undo_delete::UndoDeleteCommunity,
undo_remove::UndoRemoveCommunity,
update::UpdateCommunity,
@ -68,7 +71,7 @@ impl<Kind> Activity<Kind> {
}
}
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
#[derive(Debug, serde::Deserialize, serde::Serialize)]
pub enum SharedInboxActivities {
FollowCommunity(FollowCommunity),
AcceptFollowCommunity(AcceptFollowCommunity),
@ -97,11 +100,14 @@ pub enum SharedInboxActivities {
UndoRemovePost(UndoRemovePost),
UndoLikePost(UndoLikePost),
UndoDislikePost(UndoDislikePost),
AnnounceActivity(AnnounceActivity),
UpdateCommunity(UpdateCommunity),
DeleteCommunity(DeleteCommunity),
RemoveCommunity(RemoveCommunity),
UndoDeleteCommunity(UndoDeleteCommunity),
UndoRemoveCommunity(UndoRemoveCommunity),
BlockUserFromCommunity(BlockUserFromCommunity),
UndoBlockUserFromCommunity(UndoBlockUserFromCommunity),
}
// todo: can probably get rid of these?

View file

@ -1,223 +1,13 @@
use crate::{
activities::receive::{receive_unhandled_activity, verify_activity_domains_valid},
inbox::{
is_activity_already_known,
is_addressed_to_community_followers,
is_addressed_to_local_person,
new_inbox_routing::{Activity, SharedInboxActivities},
receive_for_community::receive_add_for_community,
verify_is_addressed_to_public,
},
};
use activitystreams::{
activity::{ActorAndObject, Announce},
base::AnyBase,
prelude::*,
};
use crate::inbox::new_inbox_routing::{Activity, SharedInboxActivities};
use actix_web::{web, HttpRequest, HttpResponse};
use anyhow::{anyhow, Context};
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::Followable;
use lemmy_db_schema::source::{community::CommunityFollower, person::Person};
use lemmy_utils::{location_info, LemmyError};
use lemmy_utils::LemmyError;
use lemmy_websocket::LemmyContext;
use log::info;
use serde::{Deserialize, Serialize};
use std::fmt::Debug;
use strum_macros::EnumString;
/// Allowed activities for person inbox.
#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd, Deserialize, Serialize)]
#[serde(rename_all = "PascalCase")]
pub enum PersonValidTypes {
Accept, // community accepted our follow request
Create, // create private message
Update, // edit private message
Delete, // private message or community deleted by creator
Undo, // private message or community restored
Remove, // community removed by admin
Announce, // post, comment or vote in community
}
pub type PersonAcceptedActivities = ActorAndObject<PersonValidTypes>;
/// Handler for all incoming activities to person inboxes.
pub async fn person_inbox(
_request: HttpRequest,
input: web::Json<Activity<SharedInboxActivities>>,
_input: web::Json<Activity<Activity<SharedInboxActivities>>>,
_path: web::Path<String>,
context: web::Data<LemmyContext>,
_context: web::Data<LemmyContext>,
) -> Result<HttpResponse, LemmyError> {
let activity = input.into_inner();
activity.inner.verify(&context).await?;
let request_counter = &mut 0;
activity.inner.receive(&context, request_counter).await?;
todo!()
/*
// First of all check the http signature
let request_counter = &mut 0;
let actor = inbox_verify_http_signature(&activity, &context, request, request_counter).await?;
// Do nothing if we received the same activity before
let activity_id = get_activity_id(&activity, &actor.actor_id())?;
if is_activity_already_known(context.pool(), &activity_id).await? {
return Ok(HttpResponse::Ok().finish());
}
// Check if the activity is actually meant for us
let username = path.into_inner();
let person = blocking(&context.pool(), move |conn| {
Person::find_by_name(&conn, &username)
})
.await??;
let to_and_cc = get_activity_to_and_cc(&activity);
// TODO: we should also accept activities that are sent to community followers
if !to_and_cc.contains(&&person.actor_id()) {
return Err(anyhow!("Activity delivered to wrong person").into());
}
assert_activity_not_local(&activity)?;
insert_activity(&activity_id, activity.clone(), false, true, context.pool()).await?;
person_receive_message(
activity.clone(),
Some(person.clone()),
actor.as_ref(),
&context,
request_counter,
)
.await
*/
}
/// Receives Accept/Follow, Announce, private messages and community (undo) remove, (undo) delete
pub(crate) async fn person_receive_message(
activity: PersonAcceptedActivities,
_to_person: Option<Person>,
actor: &dyn ActorType,
context: &LemmyContext,
request_counter: &mut i32,
) -> Result<HttpResponse, LemmyError> {
is_for_person_inbox(context, &activity).await?;
info!(
"User received activity {:?} from {}",
&activity
.id_unchecked()
.context(location_info!())?
.to_string(),
&actor.actor_id().to_string()
);
let any_base = activity.clone().into_any_base()?;
let kind = activity.kind().context(location_info!())?;
match kind {
PersonValidTypes::Accept => {}
PersonValidTypes::Announce => {
Box::pin(receive_announce(&context, any_base, actor, request_counter)).await?
}
PersonValidTypes::Create => {}
PersonValidTypes::Update => {}
PersonValidTypes::Delete => todo!(),
PersonValidTypes::Undo => todo!(),
PersonValidTypes::Remove => todo!(),
};
// TODO: would be logical to move websocket notification code here
Ok(HttpResponse::Ok().finish())
}
/// Returns true if the activity is addressed directly to one or more local persons, or if it is
/// addressed to the followers collection of a remote community, and at least one local person follows
/// it.
async fn is_for_person_inbox(
context: &LemmyContext,
activity: &PersonAcceptedActivities,
) -> Result<(), LemmyError> {
let to_and_cc = get_activity_to_and_cc(activity);
// Check if it is addressed directly to any local person
if is_addressed_to_local_person(&to_and_cc, context.pool()).await? {
return Ok(());
}
// Check if it is addressed to any followers collection of a remote community, and that the
// community has local followers.
let community = is_addressed_to_community_followers(&to_and_cc, context.pool()).await?;
if let Some(c) = community {
let community_id = c.id;
let has_local_followers = blocking(&context.pool(), move |conn| {
CommunityFollower::has_local_followers(conn, community_id)
})
.await??;
if c.local {
return Err(
anyhow!("Remote activity cant be addressed to followers of local community").into(),
);
}
if has_local_followers {
return Ok(());
}
}
Err(anyhow!("Not addressed for any local person").into())
}
#[derive(EnumString)]
enum AnnouncableActivities {
Create,
Update,
Like,
Dislike,
Delete,
Remove,
Undo,
Add,
Block,
}
/// Takes an announce and passes the inner activity to the appropriate handler.
pub async fn receive_announce(
context: &LemmyContext,
activity: AnyBase,
actor: &dyn ActorType,
request_counter: &mut i32,
) -> Result<(), LemmyError> {
let announce = Announce::from_any_base(activity)?.context(location_info!())?;
verify_activity_domains_valid(&announce, &actor.actor_id(), false)?;
verify_is_addressed_to_public(&announce)?;
let kind = announce
.object()
.as_single_kind_str()
.and_then(|s| s.parse().ok());
let inner_activity = announce
.object()
.to_owned()
.one()
.context(location_info!())?;
let inner_id = inner_activity.id().context(location_info!())?.to_owned();
check_is_apub_id_valid(&inner_id, false)?;
if is_activity_already_known(context.pool(), &inner_id).await? {
return Ok(());
}
use AnnouncableActivities::*;
match kind {
Some(Create) => todo!(),
Some(Update) => todo!(),
Some(Like) => todo!(),
Some(Dislike) => todo!(),
Some(Delete) => todo!(),
Some(Remove) => todo!(),
Some(Undo) => todo!(),
Some(Add) => {
receive_add_for_community(context, inner_activity, Some(announce), request_counter).await
}
Some(Block) => todo!(),
_ => receive_unhandled_activity(inner_activity),
}
}

View file

@ -1,214 +0,0 @@
use crate::{
activities::receive::verify_activity_domains_valid,
inbox::verify_is_addressed_to_public,
};
use activitystreams::{
activity::{ActorAndObjectRef, Add, Announce, OptTargetRef},
base::AnyBase,
object::AsObject,
prelude::*,
};
use anyhow::{anyhow, Context};
use lemmy_api_common::blocking;
use lemmy_apub::{
fetcher::person::get_or_fetch_and_upsert_person,
generate_moderators_url,
CommunityType,
};
use lemmy_db_queries::{source::community::CommunityModerator_, ApubObject, Joinable};
use lemmy_db_schema::{
source::{
community::{Community, CommunityModerator, CommunityModeratorForm},
person::Person,
},
DbUrl,
};
use lemmy_db_views_actor::community_view::CommunityView;
use lemmy_utils::{location_info, LemmyError};
use lemmy_websocket::LemmyContext;
use strum_macros::EnumString;
#[derive(EnumString)]
enum PageOrNote {
Page,
Note,
}
#[derive(EnumString)]
enum ObjectTypes {
Page,
Note,
Group,
Person,
}
#[derive(EnumString)]
enum UndoableActivities {
Delete,
Remove,
Like,
Dislike,
Block,
}
/// Add a new mod to the community (can only be done by an existing mod).
pub(in crate::inbox) async fn receive_add_for_community(
context: &LemmyContext,
add_any_base: AnyBase,
announce: Option<Announce>,
request_counter: &mut i32,
) -> Result<(), LemmyError> {
let add = Add::from_any_base(add_any_base.to_owned())?.context(location_info!())?;
let community = extract_community_from_cc(&add, context).await?;
verify_mod_activity(&add, announce, &community, context).await?;
verify_is_addressed_to_public(&add)?;
verify_add_remove_moderator_target(&add, &community)?;
let new_mod = add
.object()
.as_single_xsd_any_uri()
.context(location_info!())?;
let new_mod = get_or_fetch_and_upsert_person(&new_mod, context, request_counter).await?;
// If we had to refetch the community while parsing the activity, then the new mod has already
// been added. Skip it here as it would result in a duplicate key error.
let new_mod_id = new_mod.id;
let moderated_communities = blocking(context.pool(), move |conn| {
CommunityModerator::get_person_moderated_communities(conn, new_mod_id)
})
.await??;
if !moderated_communities.contains(&community.id) {
let form = CommunityModeratorForm {
community_id: community.id,
person_id: new_mod.id,
};
blocking(context.pool(), move |conn| {
CommunityModerator::join(conn, &form)
})
.await??;
}
if community.local {
community
.send_announce(
add_any_base,
add.object().clone().single_xsd_any_uri(),
context,
)
.await?;
}
// TODO: send websocket notification about added mod
Ok(())
}
/// Searches the activity's cc field for a Community ID, and returns the community.
async fn extract_community_from_cc<T, Kind>(
activity: &T,
context: &LemmyContext,
) -> Result<Community, LemmyError>
where
T: AsObject<Kind>,
{
let cc = activity
.cc()
.map(|c| c.as_many())
.flatten()
.context(location_info!())?;
let community_id = cc
.first()
.map(|c| c.as_xsd_any_uri())
.flatten()
.context(location_info!())?;
let community_id: DbUrl = community_id.to_owned().into();
let community = blocking(&context.pool(), move |conn| {
Community::read_from_apub_id(&conn, &community_id)
})
.await??;
Ok(community)
}
/// Checks that a moderation activity was sent by a user who is listed as mod for the community.
/// This is only used in the case of remote mods, as local mod actions don't go through the
/// community inbox.
///
/// This method should only be used for activities received by the community, not for activities
/// used by community followers.
pub(crate) async fn verify_actor_is_community_mod<T, Kind>(
activity: &T,
community: &Community,
context: &LemmyContext,
) -> Result<(), LemmyError>
where
T: ActorAndObjectRef + BaseExt<Kind>,
{
let actor = activity
.actor()?
.as_single_xsd_any_uri()
.context(location_info!())?
.to_owned();
let actor = blocking(&context.pool(), move |conn| {
Person::read_from_apub_id(&conn, &actor.into())
})
.await??;
// Note: this will also return true for admins in addition to mods, but as we dont know about
// remote admins, it doesnt make any difference.
let community_id = community.id;
let actor_id = actor.id;
let is_mod_or_admin = blocking(context.pool(), move |conn| {
CommunityView::is_mod_or_admin(conn, actor_id, community_id)
})
.await?;
if !is_mod_or_admin {
return Err(anyhow!("Not a mod").into());
}
Ok(())
}
/// This method behaves differently, depending if it is called via community inbox (activity
/// received by community from a remote user), or via user inbox (activity received by user from
/// community). We distinguish the cases by checking if the activity is wrapper in an announce
/// (only true when sent from user to community).
///
/// In the first case, we check that the actor is listed as community mod. In the second case, we
/// only check that the announce comes from the same domain as the activity. We trust the
/// community's instance to have validated the inner activity correctly. We can't do this validation
/// here, because we don't know who the instance admins are. Plus this allows for compatibility with
/// software that uses different rules for mod actions.
pub(crate) async fn verify_mod_activity<T, Kind>(
mod_action: &T,
announce: Option<Announce>,
community: &Community,
context: &LemmyContext,
) -> Result<(), LemmyError>
where
T: ActorAndObjectRef + BaseExt<Kind>,
{
match announce {
None => verify_actor_is_community_mod(mod_action, community, context).await?,
Some(a) => verify_activity_domains_valid(&a, &community.actor_id.to_owned().into(), false)?,
}
Ok(())
}
/// For Add/Remove community moderator activities, check that the target field actually contains
/// /c/community/moderators. Any different values are unsupported.
fn verify_add_remove_moderator_target<T, Kind>(
activity: &T,
community: &Community,
) -> Result<(), LemmyError>
where
T: ActorAndObjectRef + BaseExt<Kind> + OptTargetRef,
{
let target = activity
.target()
.map(|t| t.as_single_xsd_any_uri())
.flatten()
.context(location_info!())?;
if target != &generate_moderators_url(&community.actor_id)?.into_inner() {
return Err(anyhow!("Unkown target url").into());
}
Ok(())
}

View file

@ -1,151 +1,17 @@
use crate::inbox::{
assert_activity_not_local,
community_inbox::{community_receive_message, CommunityAcceptedActivities},
get_activity_id,
inbox_verify_http_signature,
is_activity_already_known,
is_addressed_to_community_followers,
is_addressed_to_local_person,
person_inbox::{person_receive_message, PersonAcceptedActivities},
};
use activitystreams::{activity::ActorAndObject, prelude::*};
use crate::inbox::new_inbox_routing::{Activity, SharedInboxActivities};
use actix_web::{web, HttpRequest, HttpResponse};
use anyhow::Context;
use lemmy_api_common::blocking;
use lemmy_apub::{get_activity_to_and_cc, insert_activity};
use lemmy_db_queries::{ApubObject, DbPool};
use lemmy_db_schema::source::community::Community;
use lemmy_utils::{location_info, LemmyError};
use lemmy_apub_lib::{ReceiveActivity, VerifyActivity};
use lemmy_utils::LemmyError;
use lemmy_websocket::LemmyContext;
use serde::{Deserialize, Serialize};
use std::fmt::Debug;
use url::Url;
/// Allowed activity types for shared inbox.
#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd, Deserialize, Serialize)]
#[serde(rename_all = "PascalCase")]
pub enum ValidTypes {
Create,
Update,
Like,
Dislike,
Delete,
Undo,
Remove,
Announce,
Add,
Block,
}
// TODO: this isnt entirely correct, cause some of these receive are not ActorAndObject,
// but it still works due to the anybase conversion
pub type AcceptedActivities = ActorAndObject<ValidTypes>;
/// Handler for all incoming requests to shared inbox.
pub async fn shared_inbox(
request: HttpRequest,
input: web::Json<AcceptedActivities>,
_request: HttpRequest,
input: web::Json<Activity<SharedInboxActivities>>,
context: web::Data<LemmyContext>,
) -> Result<HttpResponse, LemmyError> {
let activity = input.into_inner();
// First of all check the http signature
activity.inner.verify(&context).await?;
let request_counter = &mut 0;
let actor = inbox_verify_http_signature(&activity, &context, request, request_counter).await?;
// Do nothing if we received the same activity before
let actor_id = actor.actor_id();
let activity_id = get_activity_id(&activity, &actor_id)?;
if is_activity_already_known(context.pool(), &activity_id).await? {
return Ok(HttpResponse::Ok().finish());
}
assert_activity_not_local(&activity)?;
// Log the activity, so we avoid receiving and parsing it twice. Note that this could still happen
// if we receive the same activity twice in very quick succession.
insert_activity(&activity_id, activity.clone(), false, true, context.pool()).await?;
let activity_any_base = activity.clone().into_any_base()?;
let mut res: Option<HttpResponse> = None;
let to_and_cc = get_activity_to_and_cc(&activity);
// Handle community first, so in case the sender is banned by the community, it will error out.
// If we handled the person receive first, the activity would be inserted to the database before the
// community could check for bans.
// Note that an activity can be addressed to a community and to a person (or multiple persons) at the
// same time. In this case we still only handle it once, to avoid duplicate websocket
// notifications.
let community = extract_local_community_from_destinations(&to_and_cc, context.pool()).await?;
if let Some(community) = community {
let community_activity = CommunityAcceptedActivities::from_any_base(activity_any_base.clone())?
.context(location_info!())?;
res = Some(
Box::pin(community_receive_message(
community_activity,
community,
actor.as_ref(),
&context,
request_counter,
))
.await?,
);
} else if is_addressed_to_local_person(&to_and_cc, context.pool()).await? {
let person_activity = PersonAcceptedActivities::from_any_base(activity_any_base.clone())?
.context(location_info!())?;
// `to_person` is only used for follow activities (which we dont receive here), so no need to pass
// it in
Box::pin(person_receive_message(
person_activity,
None,
actor.as_ref(),
&context,
request_counter,
))
.await?;
} else if is_addressed_to_community_followers(&to_and_cc, context.pool())
.await?
.is_some()
{
let person_activity = PersonAcceptedActivities::from_any_base(activity_any_base.clone())?
.context(location_info!())?;
res = Some(
Box::pin(person_receive_message(
person_activity,
None,
actor.as_ref(),
&context,
request_counter,
))
.await?,
);
}
// If none of those, throw an error
if let Some(r) = res {
Ok(r)
} else {
Ok(HttpResponse::NotImplemented().finish())
}
}
/// If `to_and_cc` contains the ID of a local community, return that community, otherwise return
/// None.
///
/// This doesnt handle the case where an activity is addressed to multiple communities (because
/// Lemmy doesnt generate such activities).
async fn extract_local_community_from_destinations(
to_and_cc: &[Url],
pool: &DbPool,
) -> Result<Option<Community>, LemmyError> {
for url in to_and_cc {
let url = url.to_owned();
let community = blocking(&pool, move |conn| {
Community::read_from_apub_id(&conn, &url.into())
})
.await?;
if let Ok(c) = community {
if c.local {
return Ok(Some(c));
}
}
}
Ok(None)
activity.inner.receive(&context, request_counter).await?;
todo!()
}

View file

@ -1,5 +1,4 @@
mod activities;
pub mod activities_new;
pub mod activities;
mod http;
mod inbox;
pub mod routes;