diff --git a/Cargo.lock b/Cargo.lock index d3bcf04360..eef05660cb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3114,6 +3114,7 @@ dependencies = [ "captcha", "chrono", "diesel-async", + "either", "elementtree", "hound", "lemmy_api_common", @@ -3142,6 +3143,7 @@ dependencies = [ "actix-web-httpauth", "anyhow", "chrono", + "either", "encoding_rs", "enum-map", "extism", diff --git a/Cargo.toml b/Cargo.toml index 4ffc6ba7ed..08aeb28d0f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -92,7 +92,7 @@ lemmy_routes = { version = "=1.0.0-alpha.4", path = "./crates/routes" } lemmy_db_views = { version = "=1.0.0-alpha.4", path = "./crates/db_views" } lemmy_federate = { version = "=1.0.0-alpha.4", path = "./crates/federate" } lemmy_email = { version = "=1.0.0-alpha.4", path = "./crates/email" } -activitypub_federation = { version = "0.6.3", default-features = false, features = [ +activitypub_federation = { version = "0.6.4", default-features = false, features = [ "actix-web", ] } diesel = { version = "2.2.7", features = [ @@ -166,6 +166,7 @@ clap = { version = "4.5.29", features = ["derive", "env"] } pretty_assertions = "1.4.1" derive-new = "0.7.0" tuplex = "0.1.2" +either = "1.15.0" [dependencies] lemmy_api = { workspace = true } diff --git a/api_tests/src/shared.ts b/api_tests/src/shared.ts index 0998e47c15..b7be9a59e3 100644 --- a/api_tests/src/shared.ts +++ b/api_tests/src/shared.ts @@ -80,6 +80,8 @@ import { PostReportResponse } from "lemmy-js-client/dist/types/PostReportRespons import { CreatePostReport } from "lemmy-js-client/dist/types/CreatePostReport"; import { CommentReportResponse } from "lemmy-js-client/dist/types/CommentReportResponse"; import { CreateCommentReport } from "lemmy-js-client/dist/types/CreateCommentReport"; +import { CommunityReportResponse } from "lemmy-js-client/dist/types/CommunityReportResponse"; +import { CreateCommunityReport } from "lemmy-js-client/dist/types/CreateCommunityReport"; import { GetPostsResponse } from "lemmy-js-client/dist/types/GetPostsResponse"; import { GetPosts } from "lemmy-js-client/dist/types/GetPosts"; import { GetPersonDetailsResponse } from "lemmy-js-client/dist/types/GetPersonDetailsResponse"; @@ -797,6 +799,18 @@ export async function reportPost( return api.createPostReport(form); } +export async function reportCommunity( + api: LemmyHttp, + community_id: number, + reason: string, +): Promise { + let form: CreateCommunityReport = { + community_id, + reason, + }; + return api.createCommunityReport(form); +} + export async function listReports( api: LemmyHttp, show_community_rule_violations: boolean = false, diff --git a/crates/api/Cargo.toml b/crates/api/Cargo.toml index 252a7e7cc8..c10b65c5a2 100644 --- a/crates/api/Cargo.toml +++ b/crates/api/Cargo.toml @@ -38,6 +38,7 @@ hound = "3.5.1" sitemap-rs = "0.2.2" totp-rs = { version = "5.6.0", features = ["gen_secret", "otpauth"] } diesel-async = { workspace = true, features = ["deadpool", "postgres"] } +either = { workspace = true } [dev-dependencies] serial_test = { workspace = true } diff --git a/crates/api/src/reports/comment_report/create.rs b/crates/api/src/reports/comment_report/create.rs index 49af35c4f0..0dc0fcffaf 100644 --- a/crates/api/src/reports/comment_report/create.rs +++ b/crates/api/src/reports/comment_report/create.rs @@ -1,6 +1,7 @@ use crate::check_report_reason; use activitypub_federation::config::Data; use actix_web::web::Json; +use either::Either; use lemmy_api_common::{ context::LemmyContext, reports::comment::{CommentReportResponse, CreateCommentReport}, @@ -75,7 +76,7 @@ pub async fn create_comment_report( SendActivityData::CreateReport { object_id: comment_view.comment.ap_id.inner().clone(), actor: local_user_view.person, - community: comment_view.community, + receiver: Either::Right(comment_view.community), reason: data.reason.clone(), }, &context, diff --git a/crates/api/src/reports/comment_report/resolve.rs b/crates/api/src/reports/comment_report/resolve.rs index 64965e83d0..218c5e9b9e 100644 --- a/crates/api/src/reports/comment_report/resolve.rs +++ b/crates/api/src/reports/comment_report/resolve.rs @@ -1,5 +1,6 @@ use activitypub_federation::config::Data; use actix_web::web::Json; +use either::Either; use lemmy_api_common::{ context::LemmyContext, reports::comment::{CommentReportResponse, ResolveCommentReport}, @@ -44,7 +45,7 @@ pub async fn resolve_comment_report( object_id: comment_report_view.comment.ap_id.inner().clone(), actor: local_user_view.person, report_creator: report.creator, - community: comment_report_view.community.clone(), + receiver: Either::Right(comment_report_view.community.clone()), }, &context, )?; diff --git a/crates/api/src/reports/community_report/create.rs b/crates/api/src/reports/community_report/create.rs index c74a6dd689..c1fcefdf10 100644 --- a/crates/api/src/reports/community_report/create.rs +++ b/crates/api/src/reports/community_report/create.rs @@ -1,14 +1,18 @@ use crate::check_report_reason; -use actix_web::web::{Data, Json}; +use activitypub_federation::config::Data; +use actix_web::web::Json; +use either::Either; use lemmy_api_common::{ context::LemmyContext, reports::community::{CommunityReportResponse, CreateCommunityReport}, + send_activity::{ActivityChannel, SendActivityData}, utils::slur_regex, }; use lemmy_db_schema::{ source::{ community::Community, community_report::{CommunityReport, CommunityReportForm}, + site::Site, }, traits::{Crud, Reportable}, }; @@ -28,6 +32,7 @@ pub async fn create_community_report( let person_id = local_user_view.person.id; let community_id = data.community_id; let community = Community::read(&mut context.pool(), community_id).await?; + let site = Site::read_from_instance_id(&mut context.pool(), community.instance_id).await?; let report_form = CommunityReportForm { creator_id: person_id, @@ -61,7 +66,15 @@ pub async fn create_community_report( .await?; } - // TODO: consider federating this + ActivityChannel::submit_activity( + SendActivityData::CreateReport { + object_id: community.ap_id.inner().clone(), + actor: local_user_view.person, + receiver: Either::Left(site), + reason: data.reason.clone(), + }, + &context, + )?; Ok(Json(CommunityReportResponse { community_report_view, diff --git a/crates/api/src/reports/community_report/resolve.rs b/crates/api/src/reports/community_report/resolve.rs index 89d05fb257..1f7f8a1851 100644 --- a/crates/api/src/reports/community_report/resolve.rs +++ b/crates/api/src/reports/community_report/resolve.rs @@ -1,10 +1,16 @@ -use actix_web::web::{Data, Json}; +use activitypub_federation::config::Data; +use actix_web::web::Json; +use either::Either; use lemmy_api_common::{ context::LemmyContext, reports::community::{CommunityReportResponse, ResolveCommunityReport}, + send_activity::{ActivityChannel, SendActivityData}, utils::is_admin, }; -use lemmy_db_schema::{source::community_report::CommunityReport, traits::Reportable}; +use lemmy_db_schema::{ + source::{community_report::CommunityReport, site::Site}, + traits::Reportable, +}; use lemmy_db_views::structs::{CommunityReportView, LocalUserView}; use lemmy_utils::error::LemmyResult; @@ -25,6 +31,21 @@ pub async fn resolve_community_report( let community_report_view = CommunityReportView::read(&mut context.pool(), report_id, person_id).await?; + let site = Site::read_from_instance_id( + &mut context.pool(), + community_report_view.community.instance_id, + ) + .await?; + + ActivityChannel::submit_activity( + SendActivityData::SendResolveReport { + object_id: community_report_view.community.ap_id.inner().clone(), + actor: local_user_view.person, + report_creator: community_report_view.creator.clone(), + receiver: Either::Left(site), + }, + &context, + )?; Ok(Json(CommunityReportResponse { community_report_view, diff --git a/crates/api/src/reports/post_report/create.rs b/crates/api/src/reports/post_report/create.rs index 992e6ea520..d8989e429c 100644 --- a/crates/api/src/reports/post_report/create.rs +++ b/crates/api/src/reports/post_report/create.rs @@ -1,6 +1,7 @@ use crate::check_report_reason; use activitypub_federation::config::Data; use actix_web::web::Json; +use either::Either; use lemmy_api_common::{ context::LemmyContext, reports::post::{CreatePostReport, PostReportResponse}, @@ -65,7 +66,7 @@ pub async fn create_post_report( SendActivityData::CreateReport { object_id: post_view.post.ap_id.inner().clone(), actor: local_user_view.person, - community: post_view.community, + receiver: Either::Right(post_view.community), reason: data.reason.clone(), }, &context, diff --git a/crates/api/src/reports/post_report/resolve.rs b/crates/api/src/reports/post_report/resolve.rs index 24dfdf7179..3e81d9a5a1 100644 --- a/crates/api/src/reports/post_report/resolve.rs +++ b/crates/api/src/reports/post_report/resolve.rs @@ -1,5 +1,6 @@ use activitypub_federation::config::Data; use actix_web::web::Json; +use either::Either; use lemmy_api_common::{ context::LemmyContext, reports::post::{PostReportResponse, ResolvePostReport}, @@ -42,7 +43,7 @@ pub async fn resolve_post_report( object_id: post_report_view.post.ap_id.inner().clone(), actor: local_user_view.person, report_creator: report.creator, - community: post_report_view.community.clone(), + receiver: Either::Right(post_report_view.community.clone()), }, &context, )?; diff --git a/crates/api_common/Cargo.toml b/crates/api_common/Cargo.toml index 98ef7b503d..34cdda6213 100644 --- a/crates/api_common/Cargo.toml +++ b/crates/api_common/Cargo.toml @@ -80,6 +80,7 @@ webmention = { version = "0.6.0", optional = true } extism = { git = "https://github.com/extism/extism.git", branch = "pool", optional = true } extism-convert = { git = "https://github.com/extism/extism.git", branch = "pool", optional = true } once_cell = { version = "1.21.0", optional = true } +either = { workspace = true } [dev-dependencies] serial_test = { workspace = true } diff --git a/crates/api_common/src/send_activity.rs b/crates/api_common/src/send_activity.rs index fdefbaac0f..abfe63f107 100644 --- a/crates/api_common/src/send_activity.rs +++ b/crates/api_common/src/send_activity.rs @@ -1,5 +1,6 @@ use crate::{community::BanFromCommunity, context::LemmyContext, post::DeletePost}; use activitypub_federation::config::Data; +use either::Either; use futures::future::BoxFuture; use lemmy_db_schema::{ newtypes::{CommunityId, DbUrl, PersonId}, @@ -9,6 +10,7 @@ use lemmy_db_schema::{ person::Person, post::Post, private_message::PrivateMessage, + site::Site, }, }; use lemmy_db_views::structs::PrivateMessageView; @@ -96,14 +98,14 @@ pub enum SendActivityData { CreateReport { object_id: Url, actor: Person, - community: Community, + receiver: Either, reason: String, }, SendResolveReport { object_id: Url, actor: Person, report_creator: Person, - community: Community, + receiver: Either, }, } diff --git a/crates/apub/Cargo.toml b/crates/apub/Cargo.toml index 66fcca390b..e9adaaa024 100644 --- a/crates/apub/Cargo.toml +++ b/crates/apub/Cargo.toml @@ -46,7 +46,7 @@ html2text = "0.14.0" stringreader = "0.1.1" enum_delegate = "0.2.0" semver = "1.0.25" -either = "1.15.0" +either = { workspace = true } [dev-dependencies] serial_test = { workspace = true } diff --git a/crates/apub/src/activities/community/mod.rs b/crates/apub/src/activities/community/mod.rs index 7d6d3e2556..17989d8f85 100644 --- a/crates/apub/src/activities/community/mod.rs +++ b/crates/apub/src/activities/community/mod.rs @@ -1,11 +1,12 @@ use crate::{ activities::send_lemmy_activity, activity_lists::AnnouncableActivities, - fetcher::PostOrComment, + fetcher::{PostOrComment, ReportableObjects}, objects::{community::ApubCommunity, instance::ApubSite, person::ApubPerson}, protocol::activities::community::announce::AnnounceActivity, }; use activitypub_federation::{config::Data, fetch::object_id::ObjectId, traits::Actor}; +use either::Either; use lemmy_api_common::context::LemmyContext; use lemmy_db_schema::{ source::{ @@ -79,14 +80,14 @@ pub(crate) async fn send_activity_in_community( } async fn report_inboxes( - object_id: ObjectId, - community: &ApubCommunity, + object_id: ObjectId, + receiver: &Either, context: &Data, ) -> LemmyResult { // send report to the community where object was posted - let mut inboxes = ActivitySendTargets::to_inbox(community.shared_inbox_or_inbox()); + let mut inboxes = ActivitySendTargets::to_inbox(receiver.shared_inbox_or_inbox()); - if community.local { + if let Some(community) = local_community(receiver) { // send to all moderators let moderators = CommunityModeratorView::for_community(&mut context.pool(), community.id).await?; @@ -96,8 +97,9 @@ async fn report_inboxes( // also send report to user's home instance if possible let object_creator_id = match object_id.dereference_local(context).await? { - PostOrComment::Left(p) => p.creator_id, - PostOrComment::Right(c) => c.creator_id, + ReportableObjects::Left(PostOrComment::Left(p)) => p.creator_id, + ReportableObjects::Left(PostOrComment::Right(c)) => c.creator_id, + _ => return Ok(inboxes), }; let object_creator = Person::read(&mut context.pool(), object_creator_id).await?; let object_creator_site: Option = @@ -111,3 +113,10 @@ async fn report_inboxes( } Ok(inboxes) } + +fn local_community(site_or_community: &Either) -> Option<&ApubCommunity> { + match site_or_community { + Either::Right(c) if c.local => Some(c), + _ => None, + } +} diff --git a/crates/apub/src/activities/community/report.rs b/crates/apub/src/activities/community/report.rs index 4e37db967e..0930677587 100644 --- a/crates/apub/src/activities/community/report.rs +++ b/crates/apub/src/activities/community/report.rs @@ -1,15 +1,13 @@ -use super::report_inboxes; +use super::{local_community, report_inboxes}; use crate::{ - activities::{generate_activity_id, send_lemmy_activity, verify_person_in_community}, + activities::{generate_activity_id, send_lemmy_activity, verify_person_in_site_or_community}, activity_lists::AnnouncableActivities, + fetcher::ReportableObjects, insert_received_activity, - objects::{community::ApubCommunity, person::ApubPerson}, - protocol::{ - activities::community::{ - announce::AnnounceActivity, - report::{Report, ReportObject}, - }, - InCommunity, + objects::{community::ApubCommunity, instance::ApubSite, person::ApubPerson}, + protocol::activities::community::{ + announce::AnnounceActivity, + report::{Report, ReportObject}, }, PostOrComment, }; @@ -19,13 +17,19 @@ use activitypub_federation::{ kinds::activity::FlagType, traits::{ActivityHandler, Actor}, }; +use either::Either; use lemmy_api_common::{ context::LemmyContext, - utils::{check_comment_deleted_or_removed, check_post_deleted_or_removed}, + utils::{ + check_comment_deleted_or_removed, + check_community_deleted_removed, + check_post_deleted_or_removed, + }, }; use lemmy_db_schema::{ source::{ comment_report::{CommentReport, CommentReportForm}, + community_report::{CommunityReport, CommunityReportForm}, post_report::{PostReport, PostReportForm}, }, traits::Reportable, @@ -35,9 +39,9 @@ use url::Url; impl Report { pub(crate) fn new( - object_id: &ObjectId, + object_id: &ObjectId, actor: &ApubPerson, - community: &ApubCommunity, + receiver: &Either, reason: Option, context: &Data, ) -> LemmyResult { @@ -48,7 +52,7 @@ impl Report { )?; Ok(Report { actor: actor.id().into(), - to: [community.id().into()], + to: [receiver.id().into()], object: ReportObject::Lemmy(object_id.clone()), summary: reason, content: None, @@ -58,14 +62,14 @@ impl Report { } pub(crate) async fn send( - object_id: ObjectId, + object_id: ObjectId, actor: &ApubPerson, - community: &ApubCommunity, + receiver: &Either, reason: String, context: Data, ) -> LemmyResult<()> { - let report = Self::new(&object_id, actor, community, Some(reason), &context)?; - let inboxes = report_inboxes(object_id, community, &context).await?; + let report = Self::new(&object_id, actor, receiver, Some(reason), &context)?; + let inboxes = report_inboxes(object_id, receiver, &context).await?; send_lemmy_activity(&context, report, actor, inboxes, false).await } @@ -85,8 +89,8 @@ impl ActivityHandler for Report { } async fn verify(&self, context: &Data) -> LemmyResult<()> { - let community = self.community(context).await?; - verify_person_in_community(&self.actor, &community, context).await?; + let receiver = self.receiver(context).await?; + verify_person_in_site_or_community(&self.actor, &receiver, context).await?; Ok(()) } @@ -95,7 +99,7 @@ impl ActivityHandler for Report { let actor = self.actor.dereference(context).await?; let reason = self.reason()?; match self.object.dereference(context).await? { - PostOrComment::Left(post) => { + ReportableObjects::Left(PostOrComment::Left(post)) => { check_post_deleted_or_removed(&post)?; let report_form = PostReportForm { @@ -109,7 +113,7 @@ impl ActivityHandler for Report { }; PostReport::report(&mut context.pool(), &report_form).await?; } - PostOrComment::Right(comment) => { + ReportableObjects::Left(PostOrComment::Right(comment)) => { check_comment_deleted_or_removed(&comment)?; let report_form = CommentReportForm { @@ -121,16 +125,31 @@ impl ActivityHandler for Report { }; CommentReport::report(&mut context.pool(), &report_form).await?; } + ReportableObjects::Right(community) => { + check_community_deleted_removed(&community)?; + let report_form = CommunityReportForm { + creator_id: actor.id, + community_id: community.id, + reason, + original_community_name: community.name.clone(), + original_community_title: community.title.clone(), + original_community_banner: community.banner.clone(), + original_community_icon: community.icon.clone(), + original_community_description: community.description.clone(), + original_community_sidebar: community.sidebar.clone(), + }; + CommunityReport::report(&mut context.pool(), &report_form).await?; + } }; - let community = self.community(context).await?; - if community.local { + let receiver = self.receiver(context).await?; + if let Some(community) = local_community(&receiver) { // forward to remote mods let object_id = self.object.object_id(context).await?; let announce = AnnouncableActivities::Report(self); - let announce = AnnounceActivity::new(announce.try_into()?, &community, context)?; - let inboxes = report_inboxes(object_id, &community, context).await?; - send_lemmy_activity(context, announce, &community, inboxes.clone(), false).await?; + let announce = AnnounceActivity::new(announce.try_into()?, community, context)?; + let inboxes = report_inboxes(object_id, &receiver, context).await?; + send_lemmy_activity(context, announce, community, inboxes.clone(), false).await?; } Ok(()) diff --git a/crates/apub/src/activities/community/resolve_report.rs b/crates/apub/src/activities/community/resolve_report.rs index 0e9ecbd46c..d70cc523f4 100644 --- a/crates/apub/src/activities/community/resolve_report.rs +++ b/crates/apub/src/activities/community/resolve_report.rs @@ -1,21 +1,19 @@ -use super::report_inboxes; +use super::{local_community, report_inboxes}; use crate::{ activities::{ generate_activity_id, send_lemmy_activity, - verify_mod_action, - verify_person_in_community, + verify_mod_or_admin_action, + verify_person_in_site_or_community, }, activity_lists::AnnouncableActivities, + fetcher::ReportableObjects, insert_received_activity, - objects::{community::ApubCommunity, person::ApubPerson}, - protocol::{ - activities::community::{ - announce::AnnounceActivity, - report::Report, - resolve_report::{ResolveReport, ResolveType}, - }, - InCommunity, + objects::{community::ApubCommunity, instance::ApubSite, person::ApubPerson}, + protocol::activities::community::{ + announce::AnnounceActivity, + report::Report, + resolve_report::{ResolveReport, ResolveType}, }, PostOrComment, }; @@ -25,9 +23,14 @@ use activitypub_federation::{ protocol::verification::verify_urls_match, traits::{ActivityHandler, Actor}, }; +use either::Either; use lemmy_api_common::context::LemmyContext; use lemmy_db_schema::{ - source::{comment_report::CommentReport, post_report::PostReport}, + source::{ + comment_report::CommentReport, + community_report::CommunityReport, + post_report::PostReport, + }, traits::Reportable, }; use lemmy_utils::error::{LemmyError, LemmyResult}; @@ -35,10 +38,10 @@ use url::Url; impl ResolveReport { pub(crate) async fn send( - object_id: ObjectId, + object_id: ObjectId, actor: &ApubPerson, report_creator: &ApubPerson, - community: &ApubCommunity, + receiver: &Either, context: Data, ) -> LemmyResult<()> { let kind = ResolveType::Resolve; @@ -46,15 +49,15 @@ impl ResolveReport { kind.clone(), &context.settings().get_protocol_and_hostname(), )?; - let object = Report::new(&object_id, report_creator, community, None, &context)?; + let object = Report::new(&object_id, report_creator, receiver, None, &context)?; let resolve = ResolveReport { actor: actor.id().into(), - to: [community.id().into()], + to: [receiver.id().into()], object, kind, id: id.clone(), }; - let inboxes = report_inboxes(object_id, community, &context).await?; + let inboxes = report_inboxes(object_id, receiver, &context).await?; send_lemmy_activity(&context, resolve, actor, inboxes, false).await } @@ -75,10 +78,10 @@ impl ActivityHandler for ResolveReport { async fn verify(&self, context: &Data) -> LemmyResult<()> { self.object.verify(context).await?; - let community = self.community(context).await?; - verify_person_in_community(&self.actor, &community, context).await?; + let receiver = self.object.receiver(context).await?; + verify_person_in_site_or_community(&self.actor, &receiver, context).await?; verify_urls_match(self.to[0].inner(), self.object.to[0].inner())?; - verify_mod_action(&self.actor, &community, context).await?; + verify_mod_or_admin_action(&self.actor, &receiver, context).await?; Ok(()) } @@ -87,22 +90,26 @@ impl ActivityHandler for ResolveReport { let reporter = self.object.actor.dereference(context).await?; let actor = self.actor.dereference(context).await?; match self.object.object.dereference(context).await? { - PostOrComment::Left(post) => { + ReportableObjects::Left(PostOrComment::Left(post)) => { PostReport::resolve_apub(&mut context.pool(), post.id, reporter.id, actor.id).await?; } - PostOrComment::Right(comment) => { + ReportableObjects::Left(PostOrComment::Right(comment)) => { CommentReport::resolve_apub(&mut context.pool(), comment.id, reporter.id, actor.id).await?; } + ReportableObjects::Right(community) => { + CommunityReport::resolve_apub(&mut context.pool(), community.id, reporter.id, actor.id) + .await?; + } }; - let community = self.community(context).await?; - if community.local { + let receiver = self.object.receiver(context).await?; + if let Some(community) = local_community(&receiver) { // forward to remote mods let object_id = self.object.object.object_id(context).await?; let announce = AnnouncableActivities::ResolveReport(self); - let announce = AnnounceActivity::new(announce.try_into()?, &community, context)?; - let inboxes = report_inboxes(object_id, &community, context).await?; - send_lemmy_activity(context, announce, &community, inboxes.clone(), false).await?; + let announce = AnnounceActivity::new(announce.try_into()?, community, context)?; + let inboxes = report_inboxes(object_id, &receiver, context).await?; + send_lemmy_activity(context, announce, community, inboxes.clone(), false).await?; } Ok(()) diff --git a/crates/apub/src/activities/mod.rs b/crates/apub/src/activities/mod.rs index b88cd235bd..8482766558 100644 --- a/crates/apub/src/activities/mod.rs +++ b/crates/apub/src/activities/mod.rs @@ -16,7 +16,7 @@ use crate::{ }, voting::send_like_activity, }, - objects::{community::ApubCommunity, person::ApubPerson}, + objects::{community::ApubCommunity, instance::ApubSite, person::ApubPerson}, protocol::activities::{ community::{report::Report, resolve_report::ResolveReport}, create_or_update::{note::CreateOrUpdateNote, page::CreateOrUpdatePage}, @@ -29,21 +29,24 @@ use activitypub_federation::{ kinds::{activity::AnnounceType, public}, traits::{ActivityHandler, Actor}, }; +use either::Either; use following::send_accept_or_reject_follow; use lemmy_api_common::{ context::LemmyContext, send_activity::{ActivityChannel, SendActivityData}, + utils::is_admin, }; use lemmy_db_schema::{ source::{ activity::{ActivitySendTargets, SentActivity, SentActivityForm}, community::Community, instance::InstanceActions, + site::Site, }, traits::Crud, }; use lemmy_db_schema_file::enums::{ActorType, CommunityVisibility}; -use lemmy_db_views::structs::{CommunityPersonBanView, CommunityView, SiteView}; +use lemmy_db_views::structs::{CommunityPersonBanView, CommunityView, LocalUserView, SiteView}; use lemmy_utils::error::{FederationError, LemmyError, LemmyResult}; use serde::Serialize; use tracing::info; @@ -82,6 +85,23 @@ pub(crate) async fn verify_person_in_community( CommunityPersonBanView::check(&mut context.pool(), person_id, community_id).await } +/// Fetches the person and community or site to verify their type, then checks if person is banned +/// from local site or community. +pub(crate) async fn verify_person_in_site_or_community( + person_id: &ObjectId, + site_or_community: &Either, + context: &Data, +) -> LemmyResult<()> { + let person = person_id.dereference(context).await?; + InstanceActions::check_ban(&mut context.pool(), person.id, person.instance_id).await?; + if let Either::Right(community) = site_or_community { + let person_id = person.id; + let community_id = community.id; + CommunityPersonBanView::check(&mut context.pool(), person_id, community_id).await?; + } + Ok(()) +} + /// Verify that mod action in community was performed by a moderator. /// /// * `mod_id` - Activitypub ID of the mod or admin who performed the action @@ -112,6 +132,38 @@ pub(crate) async fn verify_mod_action( .await } +/// Verify that admin action was performed by an admin. +/// +/// * `mod_id` - Activitypub ID of the admin who performed the action +/// * `site` - The site that the person should be an admin of +pub(crate) async fn verify_admin_action( + admin_id: &ObjectId, + site: &Site, + context: &Data, +) -> LemmyResult<()> { + // admin action comes from the correct instance, so it was presumably done + // by an instance admin. + // TODO: federate instance admin status and check it here + if admin_id.inner().domain() == site.ap_id.domain() { + return Ok(()); + } + + let admin = admin_id.dereference(context).await?; + let local_user_view = LocalUserView::read_person(&mut context.pool(), admin.id).await?; + is_admin(&local_user_view) +} + +pub(crate) async fn verify_mod_or_admin_action( + person_id: &ObjectId, + site_or_community: &Either, + context: &Data, +) -> LemmyResult<()> { + match site_or_community { + Either::Left(site) => verify_admin_action(person_id, site, context).await, + Either::Right(community) => verify_mod_action(person_id, community, context).await, + } +} + pub(crate) fn verify_is_public(to: &[Url], cc: &[Url]) -> LemmyResult<()> { if ![to, cc].iter().any(|set| set.contains(&public())) { Err(FederationError::ObjectIsNotPublic)? @@ -379,13 +431,13 @@ pub async fn match_outgoing_activities( CreateReport { object_id, actor, - community, + receiver, reason, } => { Report::send( ObjectId::from(object_id), &actor.into(), - &community.into(), + &receiver.map_either(Into::into, Into::into), reason, context, ) @@ -395,13 +447,13 @@ pub async fn match_outgoing_activities( object_id, actor, report_creator, - community, + receiver, } => { ResolveReport::send( ObjectId::from(object_id), &actor.into(), &report_creator.into(), - &community.into(), + &receiver.map_either(Into::into, Into::into), context, ) .await diff --git a/crates/apub/src/activity_lists.rs b/crates/apub/src/activity_lists.rs index 849e27fb6b..9e4f58a5f5 100644 --- a/crates/apub/src/activity_lists.rs +++ b/crates/apub/src/activity_lists.rs @@ -93,7 +93,7 @@ impl InCommunity for AnnouncableActivities { LockPost(a) => a.community(context).await, UndoLockPost(a) => a.community(context).await, Report(a) => a.community(context).await, - ResolveReport(a) => a.community(context).await, + ResolveReport(a) => a.object.community(context).await, Page(_) => Err(LemmyErrorType::NotFound.into()), } } diff --git a/crates/apub/src/fetcher/mod.rs b/crates/apub/src/fetcher/mod.rs index 900edd33a9..91d984ad81 100644 --- a/crates/apub/src/fetcher/mod.rs +++ b/crates/apub/src/fetcher/mod.rs @@ -23,6 +23,8 @@ pub mod search; pub(crate) type PostOrComment = Either; +pub(crate) type ReportableObjects = Either; + pub type SiteOrCommunityOrUser = Either; pub type UserOrCommunity = Either; diff --git a/crates/apub/src/protocol/activities/community/report.rs b/crates/apub/src/protocol/activities/community/report.rs index 27842cc319..637e089384 100644 --- a/crates/apub/src/protocol/activities/community/report.rs +++ b/crates/apub/src/protocol/activities/community/report.rs @@ -1,6 +1,6 @@ use crate::{ - fetcher::PostOrComment, - objects::{community::ApubCommunity, person::ApubPerson}, + fetcher::ReportableObjects, + objects::{community::ApubCommunity, instance::ApubSite, person::ApubPerson}, protocol::InCommunity, }; use activitypub_federation::{ @@ -9,6 +9,7 @@ use activitypub_federation::{ kinds::activity::FlagType, protocol::helpers::deserialize_one, }; +use either::Either; use lemmy_api_common::context::LemmyContext; use lemmy_utils::error::{LemmyErrorType, LemmyResult}; use serde::{Deserialize, Serialize}; @@ -19,9 +20,9 @@ use url::Url; pub struct Report { pub(crate) actor: ObjectId, #[serde(deserialize_with = "deserialize_one")] - pub(crate) to: [ObjectId; 1], + pub(crate) to: [ObjectId>; 1], pub(crate) object: ReportObject, - /// Report reason as sent by Lemmy + /// Report reason as sent by Lemmy{ pub(crate) summary: Option, /// Report reason as sent by Mastodon pub(crate) content: Option, @@ -38,12 +39,20 @@ impl Report { .or(self.content.clone()) .ok_or(LemmyErrorType::NotFound.into()) } + + pub(crate) async fn receiver( + &self, + context: &Data, + ) -> LemmyResult> { + let receiver = self.to[0].dereference(context).await?; + Ok(receiver) + } } #[derive(Clone, Debug, Deserialize, Serialize)] #[serde(untagged)] pub(crate) enum ReportObject { - Lemmy(ObjectId), + Lemmy(ObjectId), /// Mastodon sends an array containing user id and one or more post ids Mastodon(Vec), } @@ -52,7 +61,7 @@ impl ReportObject { pub(crate) async fn dereference( &self, context: &Data, - ) -> LemmyResult { + ) -> LemmyResult { match self { ReportObject::Lemmy(l) => l.dereference(context).await, ReportObject::Mastodon(objects) => { @@ -72,13 +81,13 @@ impl ReportObject { pub(crate) async fn object_id( &self, context: &Data, - ) -> LemmyResult> { + ) -> LemmyResult> { match self { ReportObject::Lemmy(l) => Ok(l.clone()), ReportObject::Mastodon(objects) => { for o in objects { // Same logic as above, but return the ID and not the object itself. - let deref = ObjectId::::from(o.clone()) + let deref = ObjectId::::from(o.clone()) .dereference(context) .await; if deref.is_ok() { @@ -93,7 +102,9 @@ impl ReportObject { impl InCommunity for Report { async fn community(&self, context: &Data) -> LemmyResult { - let community = self.to[0].dereference(context).await?; - Ok(community) + match self.receiver(context).await? { + Either::Left(_) => Err(LemmyErrorType::NotFound.into()), + Either::Right(c) => Ok(c), + } } } diff --git a/crates/apub/src/protocol/activities/community/resolve_report.rs b/crates/apub/src/protocol/activities/community/resolve_report.rs index c15753476c..3a756b7884 100644 --- a/crates/apub/src/protocol/activities/community/resolve_report.rs +++ b/crates/apub/src/protocol/activities/community/resolve_report.rs @@ -1,15 +1,7 @@ use super::report::Report; -use crate::{ - objects::{community::ApubCommunity, person::ApubPerson}, - protocol::InCommunity, -}; -use activitypub_federation::{ - config::Data, - fetch::object_id::ObjectId, - protocol::helpers::deserialize_one, -}; -use lemmy_api_common::context::LemmyContext; -use lemmy_utils::error::LemmyResult; +use crate::objects::{community::ApubCommunity, instance::ApubSite, person::ApubPerson}; +use activitypub_federation::{fetch::object_id::ObjectId, protocol::helpers::deserialize_one}; +use either::Either; use serde::{Deserialize, Serialize}; use strum::Display; use url::Url; @@ -24,15 +16,9 @@ pub enum ResolveType { pub struct ResolveReport { pub(crate) actor: ObjectId, #[serde(deserialize_with = "deserialize_one")] - pub(crate) to: [ObjectId; 1], + pub(crate) to: [ObjectId>; 1], pub(crate) object: Report, #[serde(rename = "type")] pub(crate) kind: ResolveType, pub(crate) id: Url, } - -impl InCommunity for ResolveReport { - async fn community(&self, context: &Data) -> LemmyResult { - self.object.community(context).await - } -}