mirror of https://github.com/LemmyNet/lemmy.git
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 * ciNutomic-patch-15
parent
4ca63c5641
commit
e3b715002b
|
@ -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",
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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"
|
||||||
|
}
|
|
@ -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"
|
||||||
|
}
|
||||||
|
}
|
|
@ -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"]
|
||||||
|
}
|
|
@ -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?;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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> {
|
||||||
|
|
|
@ -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(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue