Handle federated reports from Mastodon, Kbin (#4323)

* Test Kbin/Mbin federation

* Handle reports from Mastodon/Kbin (fixes #4217)

* prettier

* revert

* add mastodon activity

* ci

* revert

* ci
Nutomic-patch-15
Nutomic 2024-01-05 17:03:13 +01:00 committed by GitHub
parent 4ca63c5641
commit e3b715002b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 102 additions and 13 deletions

4
Cargo.lock generated
View File

@ -10,9 +10,9 @@ checksum = "fe438c63458706e03479442743baae6c88256498e6431708f6dfc520a26515d3"
[[package]] [[package]]
name = "activitypub_federation" name = "activitypub_federation"
version = "0.5.0" version = "0.5.1-beta.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8bac58c1d61b6e2358adbd043c78ba853428102b489acb7b6cb74ee6f2ae668f" checksum = "866db431760d14a7360f12e75ad48f3265b5b89cd2303e548a02bcc8983e4fcd"
dependencies = [ dependencies = [
"activitystreams-kinds", "activitystreams-kinds",
"actix-web", "actix-web",

View File

@ -95,7 +95,7 @@ lemmy_routes = { version = "=0.19.2-rc.2", path = "./crates/routes" }
lemmy_db_views = { version = "=0.19.2-rc.2", path = "./crates/db_views" } lemmy_db_views = { version = "=0.19.2-rc.2", path = "./crates/db_views" }
lemmy_db_views_actor = { version = "=0.19.2-rc.2", path = "./crates/db_views_actor" } lemmy_db_views_actor = { version = "=0.19.2-rc.2", path = "./crates/db_views_actor" }
lemmy_db_views_moderator = { version = "=0.19.2-rc.2", path = "./crates/db_views_moderator" } lemmy_db_views_moderator = { version = "=0.19.2-rc.2", path = "./crates/db_views_moderator" }
activitypub_federation = { version = "0.5.0", default-features = false, features = [ activitypub_federation = { version = "0.5.1-beta.1", default-features = false, features = [
"actix-web", "actix-web",
] } ] }
diesel = "2.1.4" diesel = "2.1.4"

View File

@ -0,0 +1,13 @@
{
"@context": "https://www.w3.org/ns/activitystreams",
"id": "https://mastodon.example/ccb4f39a-506a-490e-9a8c-71831c7713a4",
"type": "Flag",
"actor": "https://mastodon.example/actor",
"content": "Please take a look at this user and their posts",
"object": [
"https://example.com/users/1",
"https://example.com/posts/380590",
"https://example.com/posts/380591"
],
"to": "https://example.com/users/1"
}

View File

@ -0,0 +1,12 @@
{
"@context": "https://www.w3.org/ns/activitystreams",
"id": "https://some-mbin.instance/f/object/2721ffc3-f8a9-417e-a124-af057434a3af#accept",
"type": "Accept",
"actor": "https://some-mbin.instance/m/someMag",
"object": {
"id": "https://some-other.instance/f/object/c51ea652-e594-4920-a989-f5350f0cec05",
"type": "Follow",
"actor": "https://some-other.instance/u/someUser",
"object": "https://some-mbin.instance/m/someMag"
}
}

View File

@ -0,0 +1,11 @@
{
"@context": ["https://www.w3.org/ns/activitystreams"],
"id": "https://mbin-test1/reports/45f8a01d-a73e-4575-bffa-c9f24c61f458",
"type": "Flag",
"actor": "https://mbin-test1/u/BentiGorlich",
"object": ["https://lemmy-test/post/4", "https://lemmy-test/u/BentiGorlich"],
"audience": "https://lemmy-test/c/test_mag",
"summary": "dikjhgasdpas dsaü",
"content": "dikjhgasdpas dsaü",
"to": ["https://lemmy-test/c/test_mag"]
}

View File

@ -2,7 +2,10 @@ use crate::{
activities::{generate_activity_id, send_lemmy_activity, verify_person_in_community}, activities::{generate_activity_id, send_lemmy_activity, verify_person_in_community},
insert_received_activity, insert_received_activity,
objects::{community::ApubCommunity, instance::ApubSite, person::ApubPerson}, objects::{community::ApubCommunity, instance::ApubSite, person::ApubPerson},
protocol::{activities::community::report::Report, InCommunity}, protocol::{
activities::community::report::{Report, ReportObject},
InCommunity,
},
PostOrComment, PostOrComment,
}; };
use activitypub_federation::{ use activitypub_federation::{
@ -45,8 +48,9 @@ impl Report {
let report = Report { let report = Report {
actor: actor.id().into(), actor: actor.id().into(),
to: [community.id().into()], to: [community.id().into()],
object: object_id.clone(), object: ReportObject::Lemmy(object_id.clone()),
summary: reason, summary: Some(reason),
content: None,
kind, kind,
id: id.clone(), id: id.clone(),
audience: Some(community.id().into()), audience: Some(community.id().into()),
@ -97,6 +101,7 @@ impl ActivityHandler for Report {
#[tracing::instrument(skip_all)] #[tracing::instrument(skip_all)]
async fn receive(self, context: &Data<Self::DataType>) -> Result<(), LemmyError> { async fn receive(self, context: &Data<Self::DataType>) -> Result<(), LemmyError> {
let actor = self.actor.dereference(context).await?; let actor = self.actor.dereference(context).await?;
let reason = self.reason()?;
match self.object.dereference(context).await? { match self.object.dereference(context).await? {
PostOrComment::Post(post) => { PostOrComment::Post(post) => {
let report_form = PostReportForm { let report_form = PostReportForm {
@ -104,7 +109,7 @@ impl ActivityHandler for Report {
post_id: post.id, post_id: post.id,
original_post_name: post.name.clone(), original_post_name: post.name.clone(),
original_post_url: post.url.clone(), original_post_url: post.url.clone(),
reason: self.summary.clone(), reason,
original_post_body: post.body.clone(), original_post_body: post.body.clone(),
}; };
PostReport::report(&mut context.pool(), &report_form).await?; PostReport::report(&mut context.pool(), &report_form).await?;
@ -114,7 +119,7 @@ impl ActivityHandler for Report {
creator_id: actor.id, creator_id: actor.id,
comment_id: comment.id, comment_id: comment.id,
original_comment_text: comment.content.clone(), original_comment_text: comment.content.clone(),
reason: self.summary.clone(), reason,
}; };
CommentReport::report(&mut context.pool(), &report_form).await?; CommentReport::report(&mut context.pool(), &report_form).await?;
} }

View File

@ -11,7 +11,7 @@ use activitypub_federation::{
protocol::helpers::deserialize_one, protocol::helpers::deserialize_one,
}; };
use lemmy_api_common::context::LemmyContext; use lemmy_api_common::context::LemmyContext;
use lemmy_utils::error::LemmyError; use lemmy_utils::error::{LemmyError, LemmyErrorType, LemmyResult};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use url::Url; use url::Url;
@ -21,14 +21,54 @@ pub struct Report {
pub(crate) actor: ObjectId<ApubPerson>, pub(crate) actor: ObjectId<ApubPerson>,
#[serde(deserialize_with = "deserialize_one")] #[serde(deserialize_with = "deserialize_one")]
pub(crate) to: [ObjectId<ApubCommunity>; 1], pub(crate) to: [ObjectId<ApubCommunity>; 1],
pub(crate) object: ObjectId<PostOrComment>, pub(crate) object: ReportObject,
pub(crate) summary: String, /// Report reason as sent by Lemmy
pub(crate) summary: Option<String>,
/// Report reason as sent by Mastodon
pub(crate) content: Option<String>,
#[serde(rename = "type")] #[serde(rename = "type")]
pub(crate) kind: FlagType, pub(crate) kind: FlagType,
pub(crate) id: Url, pub(crate) id: Url,
pub(crate) audience: Option<ObjectId<ApubCommunity>>, pub(crate) audience: Option<ObjectId<ApubCommunity>>,
} }
impl Report {
pub fn reason(&self) -> LemmyResult<String> {
self
.summary
.clone()
.or(self.content.clone())
.ok_or(LemmyErrorType::CouldntFindObject.into())
}
}
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(untagged)]
pub(crate) enum ReportObject {
Lemmy(ObjectId<PostOrComment>),
/// Mastodon sends an array containing user id and one or more post ids
Mastodon(Vec<Url>),
}
impl ReportObject {
pub async fn dereference(self, context: &Data<LemmyContext>) -> LemmyResult<PostOrComment> {
match self {
ReportObject::Lemmy(l) => l.dereference(context).await,
ReportObject::Mastodon(objects) => {
for o in objects {
// Find the first reported item which can be dereferenced as post or comment (Lemmy can
// only handle one item per report).
let deref = ObjectId::from(o).dereference(context).await;
if deref.is_ok() {
return deref;
}
}
Err(LemmyErrorType::CouldntFindObject.into())
}
}
}
}
#[async_trait::async_trait] #[async_trait::async_trait]
impl InCommunity for Report { impl InCommunity for Report {
async fn community(&self, context: &Data<LemmyContext>) -> Result<ApubCommunity, LemmyError> { async fn community(&self, context: &Data<LemmyContext>) -> Result<ApubCommunity, LemmyError> {

View File

@ -18,10 +18,10 @@ pub enum CreateOrUpdateType {
mod tests { mod tests {
use crate::protocol::{ use crate::protocol::{
activities::{ activities::{
community::announce::AnnounceActivity, community::{announce::AnnounceActivity, report::Report},
create_or_update::{note::CreateOrUpdateNote, page::CreateOrUpdatePage}, create_or_update::{note::CreateOrUpdateNote, page::CreateOrUpdatePage},
deletion::delete::Delete, deletion::delete::Delete,
following::{follow::Follow, undo_follow::UndoFollow}, following::{accept::AcceptFollow, follow::Follow, undo_follow::UndoFollow},
voting::{undo_vote::UndoVote, vote::Vote}, voting::{undo_vote::UndoVote, vote::Vote},
}, },
tests::test_json, tests::test_json,
@ -50,6 +50,7 @@ mod tests {
test_json::<UndoFollow>("assets/mastodon/activities/undo_follow.json")?; test_json::<UndoFollow>("assets/mastodon/activities/undo_follow.json")?;
test_json::<Vote>("assets/mastodon/activities/like_page.json")?; test_json::<Vote>("assets/mastodon/activities/like_page.json")?;
test_json::<UndoVote>("assets/mastodon/activities/undo_like_page.json")?; test_json::<UndoVote>("assets/mastodon/activities/undo_like_page.json")?;
test_json::<Report>("assets/mastodon/activities/flag.json")?;
Ok(()) Ok(())
} }
@ -88,4 +89,11 @@ mod tests {
test_json::<AnnounceActivity>("assets/peertube/activities/announce_video.json")?; test_json::<AnnounceActivity>("assets/peertube/activities/announce_video.json")?;
Ok(()) Ok(())
} }
#[test]
fn test_parse_mbin_activities() -> LemmyResult<()> {
test_json::<AcceptFollow>("assets/mbin/activities/accept.json")?;
test_json::<Report>("assets/mbin/activities/flag.json")?;
Ok(())
}
} }