Merge different delete activities for better compatibility (fixes #2066) (#2073)

This commit is contained in:
Nutomic 2022-02-14 15:14:24 +00:00 committed by GitHub
parent dd865c5af5
commit 788924d7ff
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
38 changed files with 452 additions and 568 deletions

View File

@ -7,7 +7,7 @@ use lemmy_api_common::{
get_local_user_view_from_jwt,
is_mod_or_admin,
};
use lemmy_apub::activities::deletion::{send_apub_delete, send_apub_remove, DeletableObjects};
use lemmy_apub::activities::deletion::{send_apub_delete_in_community, DeletableObjects};
use lemmy_db_schema::{
source::{
comment::Comment,
@ -83,21 +83,7 @@ impl PerformCrud for DeleteComment {
)
.await?;
// Send the apub message
let community = blocking(context.pool(), move |conn| {
Community::read(conn, orig_comment.post.community_id)
})
.await??;
send_apub_delete(
&local_user_view.person.clone().into(),
&community.clone().into(),
DeletableObjects::Comment(Box::new(updated_comment.into())),
deleted,
context,
)
.await?;
send_comment_ws_message(
let res = send_comment_ws_message(
data.comment_id,
UserOperationCrud::DeleteComment,
websocket_id,
@ -106,7 +92,25 @@ impl PerformCrud for DeleteComment {
recipient_ids,
context,
)
.await
.await?;
// Send the apub message
let community = blocking(context.pool(), move |conn| {
Community::read(conn, orig_comment.post.community_id)
})
.await??;
let deletable = DeletableObjects::Comment(Box::new(updated_comment.clone().into()));
send_apub_delete_in_community(
local_user_view.person,
community,
deletable,
None,
deleted,
context,
)
.await?;
Ok(res)
}
}
@ -178,22 +182,7 @@ impl PerformCrud for RemoveComment {
)
.await?;
// Send the apub message
let community = blocking(context.pool(), move |conn| {
Community::read(conn, orig_comment.post.community_id)
})
.await??;
send_apub_remove(
&local_user_view.person.clone().into(),
&community.into(),
DeletableObjects::Comment(Box::new(updated_comment.into())),
data.reason.clone().unwrap_or_else(|| "".to_string()),
removed,
context,
)
.await?;
send_comment_ws_message(
let res = send_comment_ws_message(
data.comment_id,
UserOperationCrud::RemoveComment,
websocket_id,
@ -202,6 +191,24 @@ impl PerformCrud for RemoveComment {
recipient_ids,
context,
)
.await
.await?;
// Send the apub message
let community = blocking(context.pool(), move |conn| {
Community::read(conn, orig_comment.post.community_id)
})
.await??;
let deletable = DeletableObjects::Comment(Box::new(updated_comment.clone().into()));
send_apub_delete_in_community(
local_user_view.person,
community,
deletable,
data.reason.clone().or_else(|| Some("".to_string())),
removed,
context,
)
.await?;
Ok(res)
}
}

View File

@ -1,7 +1,7 @@
use crate::PerformCrud;
use actix_web::web::Data;
use lemmy_api_common::{blocking, community::*, get_local_user_view_from_jwt, is_admin};
use lemmy_apub::activities::deletion::{send_apub_delete, send_apub_remove, DeletableObjects};
use lemmy_apub::activities::deletion::{send_apub_delete_in_community, DeletableObjects};
use lemmy_db_schema::{
source::{
community::Community,
@ -49,24 +49,28 @@ impl PerformCrud for DeleteCommunity {
.map_err(LemmyError::from)
.map_err(|e| e.with_message("couldnt_update_community"))?;
// Send apub messages
send_apub_delete(
&local_user_view.person.clone().into(),
&updated_community.clone().into(),
DeletableObjects::Community(Box::new(updated_community.into())),
deleted,
context,
)
.await?;
send_community_ws_message(
let res = send_community_ws_message(
data.community_id,
UserOperationCrud::DeleteCommunity,
websocket_id,
Some(local_user_view.person.id),
context,
)
.await
.await?;
// Send apub messages
let deletable = DeletableObjects::Community(Box::new(updated_community.clone().into()));
send_apub_delete_in_community(
local_user_view.person,
updated_community,
deletable,
None,
deleted,
context,
)
.await?;
Ok(res)
}
}
@ -111,24 +115,26 @@ impl PerformCrud for RemoveCommunity {
})
.await??;
// Apub messages
send_apub_remove(
&local_user_view.person.clone().into(),
&updated_community.clone().into(),
DeletableObjects::Community(Box::new(updated_community.into())),
data.reason.clone().unwrap_or_else(|| "".to_string()),
removed,
context,
)
.await?;
send_community_ws_message(
let res = send_community_ws_message(
data.community_id,
UserOperationCrud::RemoveCommunity,
websocket_id,
Some(local_user_view.person.id),
context,
)
.await
.await?;
// Apub messages
let deletable = DeletableObjects::Community(Box::new(updated_community.clone().into()));
send_apub_delete_in_community(
local_user_view.person,
updated_community,
deletable,
data.reason.clone().or_else(|| Some("".to_string())),
removed,
context,
)
.await?;
Ok(res)
}
}

View File

@ -8,7 +8,7 @@ use lemmy_api_common::{
is_mod_or_admin,
post::*,
};
use lemmy_apub::activities::deletion::{send_apub_delete, send_apub_remove, DeletableObjects};
use lemmy_apub::activities::deletion::{send_apub_delete_in_community, DeletableObjects};
use lemmy_db_schema::{
source::{
community::Community,
@ -63,28 +63,31 @@ impl PerformCrud for DeletePost {
})
.await??;
// apub updates
let community = blocking(context.pool(), move |conn| {
Community::read(conn, orig_post.community_id)
})
.await??;
send_apub_delete(
&local_user_view.person.clone().into(),
&community.into(),
DeletableObjects::Post(Box::new(updated_post.into())),
deleted,
context,
)
.await?;
send_post_ws_message(
let res = send_post_ws_message(
data.post_id,
UserOperationCrud::DeletePost,
websocket_id,
Some(local_user_view.person.id),
context,
)
.await
.await?;
// apub updates
let community = blocking(context.pool(), move |conn| {
Community::read(conn, orig_post.community_id)
})
.await??;
let deletable = DeletableObjects::Post(Box::new(updated_post.into()));
send_apub_delete_in_community(
local_user_view.person,
community,
deletable,
None,
deleted,
context,
)
.await?;
Ok(res)
}
}
@ -140,28 +143,30 @@ impl PerformCrud for RemovePost {
})
.await??;
// apub updates
let community = blocking(context.pool(), move |conn| {
Community::read(conn, orig_post.community_id)
})
.await??;
send_apub_remove(
&local_user_view.person.clone().into(),
&community.into(),
DeletableObjects::Post(Box::new(updated_post.into())),
data.reason.clone().unwrap_or_else(|| "".to_string()),
removed,
context,
)
.await?;
send_post_ws_message(
let res = send_post_ws_message(
data.post_id,
UserOperationCrud::RemovePost,
websocket_id,
Some(local_user_view.person.id),
context,
)
.await
.await?;
// apub updates
let community = blocking(context.pool(), move |conn| {
Community::read(conn, orig_post.community_id)
})
.await??;
let deletable = DeletableObjects::Post(Box::new(updated_post.into()));
send_apub_delete_in_community(
local_user_view.person,
community,
deletable,
data.reason.clone().or_else(|| Some("".to_string())),
removed,
context,
)
.await?;
Ok(res)
}
}

View File

@ -10,7 +10,7 @@ use lemmy_api_common::{
use lemmy_apub::{
generate_local_apub_endpoint,
protocol::activities::{
private_message::create_or_update::CreateOrUpdatePrivateMessage,
create_or_update::private_message::CreateOrUpdatePrivateMessage,
CreateOrUpdateType,
},
EndpointType,

View File

@ -5,14 +5,8 @@ use lemmy_api_common::{
get_local_user_view_from_jwt,
person::{DeletePrivateMessage, PrivateMessageResponse},
};
use lemmy_apub::protocol::activities::private_message::{
delete::DeletePrivateMessage as DeletePrivateMessageApub,
undo_delete::UndoDeletePrivateMessage,
};
use lemmy_db_schema::{
source::private_message::PrivateMessage,
traits::{Crud, DeleteableOrRemoveable},
};
use lemmy_apub::activities::deletion::send_apub_delete_private_message;
use lemmy_db_schema::{source::private_message::PrivateMessage, traits::Crud};
use lemmy_utils::{ConnectionId, LemmyError};
use lemmy_websocket::{send::send_pm_ws_message, LemmyContext, UserOperationCrud};
@ -51,23 +45,13 @@ impl PerformCrud for DeletePrivateMessage {
.map_err(|e| e.with_message("couldnt_update_private_message"))?;
// Send the apub update
if data.deleted {
DeletePrivateMessageApub::send(
send_apub_delete_private_message(
&local_user_view.person.into(),
&updated_private_message
.blank_out_deleted_or_removed_info()
.into(),
updated_private_message,
data.deleted,
context,
)
.await?;
} else {
UndoDeletePrivateMessage::send(
&local_user_view.person.into(),
&updated_private_message.into(),
context,
)
.await?;
}
let op = UserOperationCrud::DeletePrivateMessage;
send_pm_ws_message(data.private_message_id, op, websocket_id, context).await

View File

@ -6,7 +6,7 @@ use lemmy_api_common::{
person::{EditPrivateMessage, PrivateMessageResponse},
};
use lemmy_apub::protocol::activities::{
private_message::create_or_update::CreateOrUpdatePrivateMessage,
create_or_update::private_message::CreateOrUpdatePrivateMessage,
CreateOrUpdateType,
};
use lemmy_db_schema::{source::private_message::PrivateMessage, traits::Crud};

View File

@ -3,10 +3,7 @@
"to": [
"https://www.w3.org/ns/activitystreams#Public"
],
"object": {
"id": "http://ds9.lemmy.ml/post/1",
"type": "Tombstone"
},
"object": "http://ds9.lemmy.ml/post/1",
"cc": [
"http://enterprise.lemmy.ml/c/main"
],

View File

@ -3,10 +3,7 @@
"to": [
"https://www.w3.org/ns/activitystreams#Public"
],
"object": {
"id": "http://ds9.lemmy.ml/comment/1",
"type": "Tombstone"
},
"object": "http://ds9.lemmy.ml/comment/1",
"cc": [
"http://enterprise.lemmy.ml/c/main"
],

View File

@ -8,10 +8,7 @@
"to": [
"https://www.w3.org/ns/activitystreams#Public"
],
"object": {
"id": "http://ds9.lemmy.ml/post/1",
"type": "Tombstone"
},
"object": "http://ds9.lemmy.ml/post/1",
"cc": [
"http://enterprise.lemmy.ml/c/main"
],

View File

@ -8,10 +8,7 @@
"to": [
"https://www.w3.org/ns/activitystreams#Public"
],
"object": {
"id": "http://ds9.lemmy.ml/comment/1",
"type": "Tombstone"
},
"object": "http://ds9.lemmy.ml/comment/1",
"cc": [
"http://enterprise.lemmy.ml/c/main"
],

View File

@ -0,0 +1,26 @@
{
"@context": [
"https://www.w3.org/ns/activitystreams",
{
"ostatus": "http://ostatus.org#",
"atomUri": "ostatus:atomUri"
}
],
"id": "https://mastodon.madrid/users/felix/statuses/107773559874184870#delete",
"type": "Delete",
"actor": "https://mastodon.madrid/users/felix",
"to": [
"https://www.w3.org/ns/activitystreams#Public"
],
"object": {
"id": "https://mastodon.madrid/users/felix/statuses/107773559874184870",
"type": "Tombstone",
"atomUri": "https://mastodon.madrid/users/felix/statuses/107773559874184870"
},
"signature": {
"type": "RsaSignature2017",
"creator": "https://mastodon.madrid/users/felix#main-key",
"created": "2022-02-10T11:54:18Z",
"signatureValue": "NjGnbkvouSP/cSusR7+sz39iEYxWXCu6nFmBXU3t8ETPkmbpMF5ASeJixXvpTOqbOfkMoWfXncw+jDsbqZ3ELaHGG1gZ5wHWym7mk7YCjQokpF3oPhTWmlEJCVKgewXMrfI4Ok8GGsUMGzuki9EyBDGc/UNBMEAhcxV5Huu7QSQDowcbIwxS3ImxFmtKFceh6mv/kMiXUerCgkYSm6rYZeXZGMTUpvcn9gP6X6Ed6UsrLjCSb3Fj0Naz7LHtzZXRSZDZF/SX2Vw/xKJIgEGzSCv+LKZGvEEkK8PPfMJJhi8cBJebkqOnBGtE6gYK2z2cm/oGorZtXU2L05pXmLAlYQ=="
}
}

View File

@ -0,0 +1,35 @@
{
"@context": [
"https://www.w3.org/ns/activitystreams",
"https://greenish.red/schemas/litepub-0.1.jsonld",
{
"@language": "und"
}
],
"actor": "https://greenish.red/users/vpzom",
"attachment": [],
"attributedTo": "https://greenish.red/users/vpzom",
"cc": [],
"conversation": null,
"id": "https://greenish.red/activities/52f0b259-596e-429f-8a1b-c0b455f8932b",
"object": "https://greenish.red/objects/38e2b983-ebf5-4387-9bc2-3b80305469c9",
"tag": [
{
"href": "https://voyager.lemmy.ml/c/main",
"name": "@main@voyager.lemmy.ml",
"type": "Mention"
},
{
"href": "https://voyager.lemmy.ml/u/dess_voy_41u2",
"name": "@dess_voy_41u2@voyager.lemmy.ml",
"type": "Mention"
}
],
"to": [
"https://greenish.red/users/vpzom/followers",
"https://voyager.lemmy.ml/c/main",
"https://voyager.lemmy.ml/u/dess_voy_41u2",
"https://www.w3.org/ns/activitystreams#Public"
],
"type": "Delete"
}

View File

@ -1,8 +1,8 @@
use crate::{
activities::{
check_community_deleted_or_removed,
comment::get_notif_recipients,
community::{announce::GetCommunity, send_activity_in_community},
create_or_update::get_comment_notif_recipients,
generate_activity_id,
verify_activity,
verify_is_public,
@ -114,7 +114,7 @@ impl ActivityHandler for CreateOrUpdateComment {
) -> Result<(), LemmyError> {
let comment = ApubComment::from_apub(self.object, context, request_counter).await?;
let do_send_email = self.kind == CreateOrUpdateType::Create;
let recipients = get_notif_recipients(
let recipients = get_comment_notif_recipients(
&self.actor,
&comment,
do_send_email,

View File

@ -9,10 +9,12 @@ use lemmy_db_schema::{
use lemmy_utils::{utils::scrape_text_for_mentions, LemmyError};
use lemmy_websocket::{send::send_local_notifs, LemmyContext};
pub mod create_or_update;
pub mod comment;
pub mod post;
pub mod private_message;
#[tracing::instrument(skip_all)]
async fn get_notif_recipients(
async fn get_comment_notif_recipients(
actor: &ObjectId<ApubPerson>,
comment: &Comment,
do_send_email: bool,

View File

@ -2,7 +2,7 @@ use crate::{
activities::{generate_activity_id, send_lemmy_activity, verify_activity, verify_person},
objects::{person::ApubPerson, private_message::ApubPrivateMessage},
protocol::activities::{
private_message::create_or_update::CreateOrUpdatePrivateMessage,
create_or_update::private_message::CreateOrUpdatePrivateMessage,
CreateOrUpdateType,
},
};

View File

@ -1,22 +1,17 @@
use crate::{
activities::{
community::{announce::GetCommunity, send_activity_in_community},
community::announce::GetCommunity,
deletion::{receive_delete_action, verify_delete_activity, DeletableObjects},
generate_activity_id,
verify_activity,
verify_is_public,
},
activity_lists::AnnouncableActivities,
objects::{community::ApubCommunity, person::ApubPerson},
protocol::activities::deletion::delete::Delete,
protocol::activities::deletion::delete::{Delete, IdOrNestedObject},
};
use activitystreams_kinds::{activity::DeleteType, public};
use activitystreams_kinds::activity::DeleteType;
use anyhow::anyhow;
use lemmy_api_common::blocking;
use lemmy_apub_lib::{
data::Data,
object_id::ObjectId,
traits::{ActivityHandler, ActorType},
};
use lemmy_apub_lib::{data::Data, object_id::ObjectId, traits::ActivityHandler};
use lemmy_db_schema::{
source::{
comment::Comment,
@ -29,6 +24,7 @@ use lemmy_db_schema::{
ModRemovePost,
ModRemovePostForm,
},
person::Person,
post::Post,
},
traits::Crud,
@ -51,18 +47,8 @@ impl ActivityHandler for Delete {
context: &Data<LemmyContext>,
request_counter: &mut i32,
) -> Result<(), LemmyError> {
verify_is_public(&self.to, &[])?;
verify_activity(&self.id, self.actor.inner(), &context.settings())?;
let community = self.get_community(context, request_counter).await?;
verify_delete_activity(
&self.object.id,
&self.actor,
&community,
self.summary.is_some(),
context,
request_counter,
)
.await?;
verify_delete_activity(self, self.summary.is_some(), context, request_counter).await?;
Ok(())
}
@ -82,53 +68,50 @@ impl ActivityHandler for Delete {
};
receive_remove_action(
&self.actor,
&self.object.id,
self.object.id(),
reason,
context,
request_counter,
)
.await
} else {
receive_delete_action(&self.object.id, &self.actor, true, context, request_counter).await
receive_delete_action(
self.object.id(),
&self.actor,
true,
context,
request_counter,
)
.await
}
}
}
impl Delete {
pub(in crate::activities::deletion) fn new(
actor: &ApubPerson,
actor: &Person,
object: DeletableObjects,
to: Url,
community: Option<&Community>,
summary: Option<String>,
context: &LemmyContext,
) -> Result<Delete, LemmyError> {
Ok(Delete {
actor: ObjectId::new(actor.actor_id()),
to: vec![public()],
object: object.to_tombstone()?,
kind: DeleteType::Delete,
summary,
id: generate_activity_id(
let id = generate_activity_id(
DeleteType::Delete,
&context.settings().get_protocol_and_hostname(),
)?,
)?;
let cc: Option<Url> = community.map(|c| c.actor_id.clone().into());
Ok(Delete {
actor: ObjectId::new(actor.actor_id.clone()),
to: vec![to],
object: IdOrNestedObject::Id(object.id()),
cc: cc.into_iter().collect(),
kind: DeleteType::Delete,
summary,
id,
unparsed: Default::default(),
})
}
#[tracing::instrument(skip_all)]
pub(in crate::activities::deletion) async fn send(
actor: &ApubPerson,
community: &ApubCommunity,
object: DeletableObjects,
summary: Option<String>,
context: &LemmyContext,
) -> Result<(), LemmyError> {
let delete = Delete::new(actor, object, summary, context)?;
let delete_id = delete.id.clone();
let activity = AnnouncableActivities::Delete(delete);
send_activity_in_community(activity, &delete_id, actor, community, vec![], context).await
}
}
#[tracing::instrument(skip_all)]
@ -204,6 +187,7 @@ pub(in crate::activities) async fn receive_remove_action(
send_comment_ws_message_simple(removed_comment.id, RemoveComment, context).await?;
}
DeletableObjects::PrivateMessage(_) => unimplemented!(),
}
Ok(())
}
@ -216,13 +200,16 @@ impl GetCommunity for Delete {
context: &LemmyContext,
_request_counter: &mut i32,
) -> Result<ApubCommunity, LemmyError> {
let community_id = match DeletableObjects::read_from_db(&self.object.id, context).await? {
let community_id = match DeletableObjects::read_from_db(self.object.id(), context).await? {
DeletableObjects::Community(c) => c.id,
DeletableObjects::Comment(c) => {
let post = blocking(context.pool(), move |conn| Post::read(conn, c.post_id)).await??;
post.community_id
}
DeletableObjects::Post(p) => p.community_id,
DeletableObjects::PrivateMessage(_) => {
return Err(anyhow!("Private message is not part of community").into())
}
};
let community = blocking(context.pool(), move |conn| {
Community::read(conn, community_id)

View File

@ -1,62 +1,117 @@
use crate::{
activities::{verify_mod_action, verify_person_in_community},
objects::{comment::ApubComment, community::ApubCommunity, person::ApubPerson, post::ApubPost},
protocol::{
activities::deletion::{delete::Delete, undo_delete::UndoDelete},
objects::tombstone::Tombstone,
activities::{
community::{announce::GetCommunity, send_activity_in_community},
send_lemmy_activity,
verify_is_public,
verify_mod_action,
verify_person,
verify_person_in_community,
},
activity_lists::AnnouncableActivities,
objects::{
comment::ApubComment,
community::ApubCommunity,
person::ApubPerson,
post::ApubPost,
private_message::ApubPrivateMessage,
},
protocol::activities::deletion::{delete::Delete, undo_delete::UndoDelete},
};
use activitystreams_kinds::public;
use lemmy_api_common::blocking;
use lemmy_apub_lib::{object_id::ObjectId, traits::ApubObject, verify::verify_domains_match};
use lemmy_db_schema::source::{comment::Comment, community::Community, post::Post};
use lemmy_apub_lib::{
object_id::ObjectId,
traits::{ActorType, ApubObject},
verify::verify_domains_match,
};
use lemmy_db_schema::{
source::{
comment::Comment,
community::Community,
person::Person,
post::Post,
private_message::PrivateMessage,
},
traits::Crud,
};
use lemmy_utils::LemmyError;
use lemmy_websocket::{
send::{send_comment_ws_message_simple, send_community_ws_message, send_post_ws_message},
send::{
send_comment_ws_message_simple,
send_community_ws_message,
send_pm_ws_message,
send_post_ws_message,
},
LemmyContext,
UserOperationCrud,
};
use std::ops::Deref;
use url::Url;
pub mod delete;
pub mod undo_delete;
/// Parameter `reason` being set indicates that this is a removal by a mod. If its unset, this
/// action was done by a normal user.
#[tracing::instrument(skip_all)]
pub async fn send_apub_delete(
actor: &ApubPerson,
community: &ApubCommunity,
pub async fn send_apub_delete_in_community(
actor: Person,
community: Community,
object: DeletableObjects,
reason: Option<String>,
deleted: bool,
context: &LemmyContext,
) -> Result<(), LemmyError> {
if deleted {
Delete::send(actor, community, object, None, context).await
let (id, activity) = if deleted {
let delete = Delete::new(&actor, object, public(), Some(&community), reason, context)?;
(delete.id.clone(), AnnouncableActivities::Delete(delete))
} else {
UndoDelete::send(actor, community, object, None, context).await
}
let undo = UndoDelete::new(&actor, object, public(), Some(&community), reason, context)?;
(undo.id.clone(), AnnouncableActivities::UndoDelete(undo))
};
send_activity_in_community(
activity,
&id,
&ApubPerson::from(actor),
&community.into(),
vec![],
context,
)
.await
}
// TODO: remove reason is actually optional in lemmy. we set an empty string in that case, but its
// ugly
#[tracing::instrument(skip_all)]
pub async fn send_apub_remove(
pub async fn send_apub_delete_private_message(
actor: &ApubPerson,
community: &ApubCommunity,
object: DeletableObjects,
reason: String,
removed: bool,
pm: PrivateMessage,
deleted: bool,
context: &LemmyContext,
) -> Result<(), LemmyError> {
if removed {
Delete::send(actor, community, object, Some(reason), context).await
let recipient_id = pm.recipient_id;
let recipient: ApubPerson =
blocking(context.pool(), move |conn| Person::read(conn, recipient_id))
.await??
.into();
let deletable = DeletableObjects::PrivateMessage(Box::new(pm.into()));
let inbox = vec![recipient.shared_inbox_or_inbox_url()];
if deleted {
let delete = Delete::new(actor, deletable, recipient.actor_id(), None, None, context)?;
let id = delete.id.clone();
send_lemmy_activity(context, &delete, &id, actor, inbox, true).await?;
} else {
UndoDelete::send(actor, community, object, Some(reason), context).await
}
let undo = UndoDelete::new(actor, deletable, recipient.actor_id(), None, None, context)?;
let id = undo.id.clone();
send_lemmy_activity(context, &undo, &id, actor, inbox, true).await?;
};
Ok(())
}
pub enum DeletableObjects {
Community(Box<ApubCommunity>),
Comment(Box<ApubComment>),
Post(Box<ApubPost>),
PrivateMessage(Box<ApubPrivateMessage>),
}
impl DeletableObjects {
@ -74,43 +129,47 @@ impl DeletableObjects {
if let Some(c) = ApubComment::read_from_apub_id(ap_id.clone(), context).await? {
return Ok(DeletableObjects::Comment(Box::new(c)));
}
if let Some(p) = ApubPrivateMessage::read_from_apub_id(ap_id.clone(), context).await? {
return Ok(DeletableObjects::PrivateMessage(Box::new(p)));
}
Err(diesel::NotFound.into())
}
pub(crate) fn to_tombstone(&self) -> Result<Tombstone, LemmyError> {
pub(crate) fn id(&self) -> Url {
match self {
DeletableObjects::Community(c) => c.to_tombstone(),
DeletableObjects::Comment(c) => c.to_tombstone(),
DeletableObjects::Post(p) => p.to_tombstone(),
DeletableObjects::Community(c) => c.actor_id(),
DeletableObjects::Comment(c) => c.ap_id.clone().into(),
DeletableObjects::Post(p) => p.ap_id.clone().into(),
DeletableObjects::PrivateMessage(p) => p.ap_id.clone().into(),
}
}
}
#[tracing::instrument(skip_all)]
pub(in crate::activities) async fn verify_delete_activity(
object: &Url,
actor: &ObjectId<ApubPerson>,
community: &ApubCommunity,
activity: &Delete,
is_mod_action: bool,
context: &LemmyContext,
request_counter: &mut i32,
) -> Result<(), LemmyError> {
let object = DeletableObjects::read_from_db(object, context).await?;
let object = DeletableObjects::read_from_db(activity.object.id(), context).await?;
match object {
DeletableObjects::Community(community) => {
verify_is_public(&activity.to, &[])?;
if community.local {
// can only do this check for local community, in remote case it would try to fetch the
// deleted community (which fails)
verify_person_in_community(actor, &community, context, request_counter).await?;
verify_person_in_community(&activity.actor, &community, context, request_counter).await?;
}
// community deletion is always a mod (or admin) action
verify_mod_action(actor, &community, context, request_counter).await?;
verify_mod_action(&activity.actor, &community, context, request_counter).await?;
}
DeletableObjects::Post(p) => {
verify_delete_activity_post_or_comment(
actor,
verify_is_public(&activity.to, &[])?;
verify_delete_post_or_comment(
&activity.actor,
&p.ap_id.clone().into(),
community,
&activity.get_community(context, request_counter).await?,
is_mod_action,
context,
request_counter,
@ -118,22 +177,27 @@ pub(in crate::activities) async fn verify_delete_activity(
.await?;
}
DeletableObjects::Comment(c) => {
verify_delete_activity_post_or_comment(
actor,
verify_is_public(&activity.to, &[])?;
verify_delete_post_or_comment(
&activity.actor,
&c.ap_id.clone().into(),
community,
&activity.get_community(context, request_counter).await?,
is_mod_action,
context,
request_counter,
)
.await?;
}
DeletableObjects::PrivateMessage(_) => {
verify_person(&activity.actor, context, request_counter).await?;
verify_domains_match(activity.actor.inner(), activity.object.id())?;
}
}
Ok(())
}
#[tracing::instrument(skip_all)]
async fn verify_delete_activity_post_or_comment(
async fn verify_delete_post_or_comment(
actor: &ObjectId<ApubPerson>,
object_id: &Url,
community: &ApubCommunity,
@ -152,8 +216,6 @@ async fn verify_delete_activity_post_or_comment(
}
/// Write deletion or restoring of an object to the database, and send websocket message.
/// TODO: we should do something similar for receive_remove_action(), but its much more complicated
/// because of the mod log
#[tracing::instrument(skip_all)]
async fn receive_delete_action(
object: &Url,
@ -165,11 +227,14 @@ async fn receive_delete_action(
match DeletableObjects::read_from_db(object, context).await? {
DeletableObjects::Community(community) => {
if community.local {
let mod_ = actor
let mod_: Person = actor
.dereference(context, context.client(), request_counter)
.await?;
.await?
.deref()
.clone();
let object = DeletableObjects::Community(community.clone());
send_apub_delete(&mod_, &community.clone(), object, true, context).await?;
let c: Community = community.deref().deref().clone();
send_apub_delete_in_community(mod_, c, object, None, true, context).await?;
}
let community = blocking(context.pool(), move |conn| {
@ -215,6 +280,20 @@ async fn receive_delete_action(
.await?;
}
}
DeletableObjects::PrivateMessage(pm) => {
let deleted_private_message = blocking(context.pool(), move |conn| {
PrivateMessage::update_deleted(conn, pm.id, deleted)
})
.await??;
send_pm_ws_message(
deleted_private_message.id,
UserOperationCrud::DeletePrivateMessage,
None,
context,
)
.await?;
}
}
Ok(())
}

View File

@ -1,23 +1,17 @@
use crate::{
activities::{
community::{announce::GetCommunity, send_activity_in_community},
community::announce::GetCommunity,
deletion::{receive_delete_action, verify_delete_activity, DeletableObjects},
generate_activity_id,
verify_activity,
verify_is_public,
},
activity_lists::AnnouncableActivities,
objects::{community::ApubCommunity, person::ApubPerson},
objects::community::ApubCommunity,
protocol::activities::deletion::{delete::Delete, undo_delete::UndoDelete},
};
use activitystreams_kinds::{activity::UndoType, public};
use activitystreams_kinds::activity::UndoType;
use lemmy_api_common::blocking;
use lemmy_apub_lib::{
data::Data,
object_id::ObjectId,
traits::{ActivityHandler, ActorType},
};
use lemmy_db_schema::source::{comment::Comment, community::Community, post::Post};
use lemmy_apub_lib::{data::Data, object_id::ObjectId, traits::ActivityHandler};
use lemmy_db_schema::source::{comment::Comment, community::Community, person::Person, post::Post};
use lemmy_utils::LemmyError;
use lemmy_websocket::{
send::{send_comment_ws_message_simple, send_community_ws_message, send_post_ws_message},
@ -36,14 +30,10 @@ impl ActivityHandler for UndoDelete {
context: &Data<LemmyContext>,
request_counter: &mut i32,
) -> Result<(), LemmyError> {
verify_is_public(&self.to, &self.cc)?;
verify_activity(&self.id, self.actor.inner(), &context.settings())?;
self.object.verify(context, request_counter).await?;
let community = self.get_community(context, request_counter).await?;
verify_delete_activity(
&self.object.object.id,
&self.actor,
&community,
&self.object,
self.object.summary.is_some(),
context,
request_counter,
@ -59,10 +49,10 @@ impl ActivityHandler for UndoDelete {
request_counter: &mut i32,
) -> Result<(), LemmyError> {
if self.object.summary.is_some() {
UndoDelete::receive_undo_remove_action(&self.object.object.id, context).await
UndoDelete::receive_undo_remove_action(self.object.object.id(), context).await
} else {
receive_delete_action(
&self.object.object.id,
self.object.object.id(),
&self.actor,
false,
context,
@ -75,31 +65,30 @@ impl ActivityHandler for UndoDelete {
impl UndoDelete {
#[tracing::instrument(skip_all)]
pub(in crate::activities::deletion) async fn send(
actor: &ApubPerson,
community: &ApubCommunity,
pub(in crate::activities::deletion) fn new(
actor: &Person,
object: DeletableObjects,
to: Url,
community: Option<&Community>,
summary: Option<String>,
context: &LemmyContext,
) -> Result<(), LemmyError> {
let object = Delete::new(actor, object, summary, context)?;
) -> Result<UndoDelete, LemmyError> {
let object = Delete::new(actor, object, to.clone(), community, summary, context)?;
let id = generate_activity_id(
UndoType::Undo,
&context.settings().get_protocol_and_hostname(),
)?;
let undo = UndoDelete {
actor: ObjectId::new(actor.actor_id()),
to: vec![public()],
let cc: Option<Url> = community.map(|c| c.actor_id.clone().into());
Ok(UndoDelete {
actor: ObjectId::new(actor.actor_id.clone()),
to: vec![to],
object,
cc: vec![community.actor_id()],
cc: cc.into_iter().collect(),
kind: UndoType::Undo,
id: id.clone(),
id,
unparsed: Default::default(),
};
let activity = AnnouncableActivities::UndoDelete(undo);
send_activity_in_community(activity, &id, actor, community, vec![], context).await
})
}
#[tracing::instrument(skip_all)]
@ -135,6 +124,7 @@ impl UndoDelete {
.await??;
send_comment_ws_message_simple(removed_comment.id, EditComment, context).await?;
}
DeletableObjects::PrivateMessage(_) => unimplemented!(),
}
Ok(())
}

View File

@ -26,12 +26,10 @@ use url::{ParseError, Url};
use uuid::Uuid;
pub mod block;
pub mod comment;
pub mod community;
pub mod create_or_update;
pub mod deletion;
pub mod following;
pub mod post;
pub mod private_message;
pub mod voting;
/// Checks that the specified Url actually identifies a Person (by fetching it), and that the person

View File

@ -1 +0,0 @@
pub mod create_or_update;

View File

@ -1,97 +0,0 @@
use crate::{
activities::{generate_activity_id, send_lemmy_activity, verify_activity, verify_person},
objects::{person::ApubPerson, private_message::ApubPrivateMessage},
protocol::activities::private_message::delete::DeletePrivateMessage,
};
use activitystreams_kinds::activity::DeleteType;
use lemmy_api_common::blocking;
use lemmy_apub_lib::{
data::Data,
object_id::ObjectId,
traits::{ActivityHandler, ActorType},
verify::verify_domains_match,
};
use lemmy_db_schema::{
source::{person::Person, private_message::PrivateMessage},
traits::Crud,
};
use lemmy_utils::LemmyError;
use lemmy_websocket::{send::send_pm_ws_message, LemmyContext, UserOperationCrud};
impl DeletePrivateMessage {
pub(in crate::activities::private_message) fn new(
actor: &ApubPerson,
pm: &PrivateMessage,
context: &LemmyContext,
) -> Result<DeletePrivateMessage, LemmyError> {
Ok(DeletePrivateMessage {
actor: ObjectId::new(actor.actor_id()),
to: [ObjectId::new(actor.actor_id())],
object: ObjectId::new(pm.ap_id.clone()),
kind: DeleteType::Delete,
id: generate_activity_id(
DeleteType::Delete,
&context.settings().get_protocol_and_hostname(),
)?,
unparsed: Default::default(),
})
}
#[tracing::instrument(skip_all)]
pub async fn send(
actor: &ApubPerson,
pm: &ApubPrivateMessage,
context: &LemmyContext,
) -> Result<(), LemmyError> {
let delete = DeletePrivateMessage::new(actor, pm, context)?;
let delete_id = delete.id.clone();
let recipient_id = pm.recipient_id;
let recipient: ApubPerson =
blocking(context.pool(), move |conn| Person::read(conn, recipient_id))
.await??
.into();
let inbox = vec![recipient.shared_inbox_or_inbox_url()];
send_lemmy_activity(context, &delete, &delete_id, actor, inbox, true).await
}
}
#[async_trait::async_trait(?Send)]
impl ActivityHandler for DeletePrivateMessage {
type DataType = LemmyContext;
#[tracing::instrument(skip_all)]
async fn verify(
&self,
context: &Data<LemmyContext>,
request_counter: &mut i32,
) -> Result<(), LemmyError> {
verify_activity(&self.id, self.actor.inner(), &context.settings())?;
verify_person(&self.actor, context, request_counter).await?;
verify_domains_match(self.actor.inner(), self.object.inner())?;
Ok(())
}
#[tracing::instrument(skip_all)]
async fn receive(
self,
context: &Data<LemmyContext>,
_request_counter: &mut i32,
) -> Result<(), LemmyError> {
let private_message = self.object.dereference_local(context).await?;
let deleted_private_message = blocking(context.pool(), move |conn| {
PrivateMessage::update_deleted(conn, private_message.id, true)
})
.await??;
send_pm_ws_message(
deleted_private_message.id,
UserOperationCrud::DeletePrivateMessage,
None,
context,
)
.await?;
Ok(())
}
}

View File

@ -1,3 +0,0 @@
pub mod create_or_update;
pub mod delete;
pub mod undo_delete;

View File

@ -1,97 +0,0 @@
use crate::{
activities::{generate_activity_id, send_lemmy_activity, verify_activity, verify_person},
objects::{person::ApubPerson, private_message::ApubPrivateMessage},
protocol::activities::private_message::{
delete::DeletePrivateMessage,
undo_delete::UndoDeletePrivateMessage,
},
};
use activitystreams_kinds::activity::UndoType;
use lemmy_api_common::blocking;
use lemmy_apub_lib::{
data::Data,
object_id::ObjectId,
traits::{ActivityHandler, ActorType},
verify::{verify_domains_match, verify_urls_match},
};
use lemmy_db_schema::{
source::{person::Person, private_message::PrivateMessage},
traits::Crud,
};
use lemmy_utils::LemmyError;
use lemmy_websocket::{send::send_pm_ws_message, LemmyContext, UserOperationCrud};
impl UndoDeletePrivateMessage {
#[tracing::instrument(skip_all)]
pub async fn send(
actor: &ApubPerson,
pm: &ApubPrivateMessage,
context: &LemmyContext,
) -> Result<(), LemmyError> {
let recipient_id = pm.recipient_id;
let recipient: ApubPerson =
blocking(context.pool(), move |conn| Person::read(conn, recipient_id))
.await??
.into();
let object = DeletePrivateMessage::new(actor, pm, context)?;
let id = generate_activity_id(
UndoType::Undo,
&context.settings().get_protocol_and_hostname(),
)?;
let undo = UndoDeletePrivateMessage {
actor: ObjectId::new(actor.actor_id()),
to: [ObjectId::new(recipient.actor_id())],
object,
kind: UndoType::Undo,
id: id.clone(),
unparsed: Default::default(),
};
let inbox = vec![recipient.shared_inbox_or_inbox_url()];
send_lemmy_activity(context, &undo, &id, actor, inbox, true).await
}
}
#[async_trait::async_trait(?Send)]
impl ActivityHandler for UndoDeletePrivateMessage {
type DataType = LemmyContext;
#[tracing::instrument(skip_all)]
async fn verify(
&self,
context: &Data<LemmyContext>,
request_counter: &mut i32,
) -> Result<(), LemmyError> {
verify_activity(&self.id, self.actor.inner(), &context.settings())?;
verify_person(&self.actor, context, request_counter).await?;
verify_urls_match(self.actor.inner(), self.object.actor.inner())?;
verify_domains_match(self.actor.inner(), self.object.object.inner())?;
self.object.verify(context, request_counter).await?;
Ok(())
}
#[tracing::instrument(skip_all)]
async fn receive(
self,
context: &Data<LemmyContext>,
_request_counter: &mut i32,
) -> Result<(), LemmyError> {
let ap_id = self.object.object.clone();
let private_message = ap_id.dereference_local(context).await?;
let deleted_private_message = blocking(context.pool(), move |conn| {
PrivateMessage::update_deleted(conn, private_message.id, false)
})
.await??;
send_pm_ws_message(
deleted_private_message.id,
UserOperationCrud::EditPrivateMessage,
None,
context,
)
.await?;
Ok(())
}
}

View File

@ -11,18 +11,17 @@ use crate::{
report::Report,
update::UpdateCommunity,
},
create_or_update::{comment::CreateOrUpdateComment, post::CreateOrUpdatePost},
create_or_update::{
comment::CreateOrUpdateComment,
post::CreateOrUpdatePost,
private_message::CreateOrUpdatePrivateMessage,
},
deletion::{delete::Delete, undo_delete::UndoDelete},
following::{
accept::AcceptFollowCommunity,
follow::FollowCommunity,
undo_follow::UndoFollowCommunity,
},
private_message::{
create_or_update::CreateOrUpdatePrivateMessage,
delete::DeletePrivateMessage,
undo_delete::UndoDeletePrivateMessage,
},
voting::{undo_vote::UndoVote, vote::Vote},
},
objects::page::Page,
@ -61,8 +60,8 @@ pub enum PersonInboxActivities {
/// Some activities can also be sent from user to user, eg a comment with mentions
AnnouncableActivities(AnnouncableActivities),
CreateOrUpdatePrivateMessage(CreateOrUpdatePrivateMessage),
DeletePrivateMessage(DeletePrivateMessage),
UndoDeletePrivateMessage(UndoDeletePrivateMessage),
Delete(Delete),
UndoDelete(UndoDelete),
AnnounceActivity(AnnounceActivity),
}

View File

@ -84,12 +84,16 @@ pub(in crate::http) async fn receive_group_inbox(
let res = receive_activity(request, activity.clone(), activity_data, context).await?;
if let GroupInboxActivities::AnnouncableActivities(announcable) = activity {
let community = announcable.get_community(context, &mut 0).await?;
verify_person_in_community(&actor_id, &community, context, &mut 0).await?;
// Ignore failures in get_community(). those happen because Delete/PrivateMessage is not in a
// community, but looks identical to Delete/Post or Delete/Comment which are in a community.
let community = announcable.get_community(context, &mut 0).await;
if let Ok(community) = community {
if community.local {
verify_person_in_community(&actor_id, &community, context, &mut 0).await?;
AnnounceActivity::send(*announcable, &community, context).await?;
}
}
}
Ok(res)
}

View File

@ -1,5 +1,6 @@
pub mod comment;
pub mod post;
pub mod private_message;
#[cfg(test)]
mod tests {
@ -7,7 +8,11 @@ mod tests {
context::WithContext,
objects::tests::file_to_json_object,
protocol::{
activities::create_or_update::{comment::CreateOrUpdateComment, post::CreateOrUpdatePost},
activities::create_or_update::{
comment::CreateOrUpdateComment,
post::CreateOrUpdatePost,
private_message::CreateOrUpdatePrivateMessage,
},
tests::test_parse_lemmy_item,
},
};
@ -26,6 +31,10 @@ mod tests {
"assets/lemmy/activities/create_or_update/create_note.json",
)
.unwrap();
test_parse_lemmy_item::<CreateOrUpdatePrivateMessage>(
"assets/lemmy/activities/create_or_update/create_private_message.json",
)
.unwrap();
file_to_json_object::<WithContext<CreateOrUpdateComment>>(
"assets/pleroma/activities/create_note.json",

View File

@ -1,7 +1,4 @@
use crate::{
objects::person::ApubPerson,
protocol::{objects::tombstone::Tombstone, Unparsed},
};
use crate::{objects::person::ApubPerson, protocol::Unparsed};
use activitystreams_kinds::activity::DeleteType;
use lemmy_apub_lib::object_id::ObjectId;
use serde::{Deserialize, Serialize};
@ -15,7 +12,11 @@ pub struct Delete {
pub(crate) actor: ObjectId<ApubPerson>,
#[serde(deserialize_with = "crate::deserialize_one_or_many")]
pub(crate) to: Vec<Url>,
pub(crate) object: Tombstone,
pub(crate) object: IdOrNestedObject,
#[serde(deserialize_with = "crate::deserialize_one_or_many")]
#[serde(default)]
#[serde(skip_serializing_if = "Vec::is_empty")]
pub(crate) cc: Vec<Url>,
#[serde(rename = "type")]
pub(crate) kind: DeleteType,
/// If summary is present, this is a mod action (Remove in Lemmy terms). Otherwise, its a user
@ -25,3 +26,26 @@ pub struct Delete {
#[serde(flatten)]
pub(crate) unparsed: Unparsed,
}
/// Instead of a simple ID string as object, Mastodon sends a nested tombstone for some reason,
/// so we need to handle that as well.
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(untagged)]
pub(crate) enum IdOrNestedObject {
Id(Url),
NestedObject(NestedObject),
}
#[derive(Clone, Debug, Deserialize, Serialize)]
pub(crate) struct NestedObject {
id: Url,
}
impl IdOrNestedObject {
pub(crate) fn id(&self) -> &Url {
match self {
IdOrNestedObject::Id(i) => i,
IdOrNestedObject::NestedObject(n) => &n.id,
}
}
}

View File

@ -3,13 +3,17 @@ pub mod undo_delete;
#[cfg(test)]
mod tests {
use crate::protocol::{
use crate::{
context::WithContext,
objects::tests::file_to_json_object,
protocol::{
activities::deletion::{delete::Delete, undo_delete::UndoDelete},
tests::test_parse_lemmy_item,
},
};
#[actix_rt::test]
async fn test_parse_lemmy_deletion() {
async fn test_parse_deletion() {
test_parse_lemmy_item::<Delete>("assets/lemmy/activities/deletion/remove_note.json").unwrap();
test_parse_lemmy_item::<Delete>("assets/lemmy/activities/deletion/delete_page.json").unwrap();
@ -17,5 +21,14 @@ mod tests {
.unwrap();
test_parse_lemmy_item::<UndoDelete>("assets/lemmy/activities/deletion/undo_delete_page.json")
.unwrap();
test_parse_lemmy_item::<Delete>("assets/lemmy/activities/deletion/delete_private_message.json")
.unwrap();
test_parse_lemmy_item::<UndoDelete>(
"assets/lemmy/activities/deletion/undo_delete_private_message.json",
)
.unwrap();
file_to_json_object::<WithContext<Delete>>("assets/pleroma/activities/delete.json").unwrap();
file_to_json_object::<WithContext<Delete>>("assets/mastodon/activities/delete.json").unwrap();
}
}

View File

@ -15,6 +15,8 @@ pub struct UndoDelete {
pub(crate) to: Vec<Url>,
pub(crate) object: Delete,
#[serde(deserialize_with = "crate::deserialize_one_or_many")]
#[serde(default)]
#[serde(skip_serializing_if = "Vec::is_empty")]
pub(crate) cc: Vec<Url>,
#[serde(rename = "type")]
pub(crate) kind: UndoType,

View File

@ -6,7 +6,6 @@ pub mod community;
pub mod create_or_update;
pub mod deletion;
pub mod following;
pub mod private_message;
pub mod voting;
#[derive(Clone, Debug, Display, Deserialize, Serialize, PartialEq)]

View File

@ -1,22 +0,0 @@
use crate::{
objects::{person::ApubPerson, private_message::ApubPrivateMessage},
protocol::Unparsed,
};
use activitystreams_kinds::activity::DeleteType;
use lemmy_apub_lib::object_id::ObjectId;
use serde::{Deserialize, Serialize};
use url::Url;
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct DeletePrivateMessage {
pub(crate) actor: ObjectId<ApubPerson>,
#[serde(deserialize_with = "crate::deserialize_one")]
pub(crate) to: [ObjectId<ApubPerson>; 1],
pub(crate) object: ObjectId<ApubPrivateMessage>,
#[serde(rename = "type")]
pub(crate) kind: DeleteType,
pub(crate) id: Url,
#[serde(flatten)]
pub(crate) unparsed: Unparsed,
}

View File

@ -1,31 +0,0 @@
pub mod create_or_update;
pub mod delete;
pub mod undo_delete;
#[cfg(test)]
mod tests {
use crate::protocol::{
activities::private_message::{
create_or_update::CreateOrUpdatePrivateMessage,
delete::DeletePrivateMessage,
undo_delete::UndoDeletePrivateMessage,
},
tests::test_parse_lemmy_item,
};
#[actix_rt::test]
async fn test_parse_lemmy_private_message() {
test_parse_lemmy_item::<CreateOrUpdatePrivateMessage>(
"assets/lemmy/activities/private_message/create.json",
)
.unwrap();
test_parse_lemmy_item::<DeletePrivateMessage>(
"assets/lemmy/activities/private_message/delete.json",
)
.unwrap();
test_parse_lemmy_item::<UndoDeletePrivateMessage>(
"assets/lemmy/activities/private_message/undo_delete.json",
)
.unwrap();
}
}

View File

@ -1,22 +0,0 @@
use crate::{
objects::person::ApubPerson,
protocol::{activities::private_message::delete::DeletePrivateMessage, Unparsed},
};
use activitystreams_kinds::activity::UndoType;
use lemmy_apub_lib::object_id::ObjectId;
use serde::{Deserialize, Serialize};
use url::Url;
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct UndoDeletePrivateMessage {
pub(crate) actor: ObjectId<ApubPerson>,
#[serde(deserialize_with = "crate::deserialize_one")]
pub(crate) to: [ObjectId<ApubPerson>; 1],
pub(crate) object: DeletePrivateMessage,
#[serde(rename = "type")]
pub(crate) kind: UndoType,
pub(crate) id: Url,
#[serde(flatten)]
pub(crate) unparsed: Unparsed,
}