From 8708abd9cd8cc12b3bd732f3213bbb0dc31ca8f0 Mon Sep 17 00:00:00 2001 From: silverpill Date: Wed, 5 Apr 2023 20:58:09 +0000 Subject: [PATCH] Reject unsolicited public posts --- CHANGELOG.md | 1 + mitra-models/src/relationships/queries.rs | 28 ++++++++++++++++++++- src/activitypub/handlers/create.rs | 30 ++++++++++++++++++++++- 3 files changed, 57 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2e350b4..ba2745c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - Populate `alsoKnownAs` property on actor object with declared aliases. - Support account migration from Mastodon. - Created API endpoint for managing client configurations. +- Reject unsolicited public posts. ### Changed diff --git a/mitra-models/src/relationships/queries.rs b/mitra-models/src/relationships/queries.rs index 15fdd2f..d07285c 100644 --- a/mitra-models/src/relationships/queries.rs +++ b/mitra-models/src/relationships/queries.rs @@ -348,6 +348,24 @@ pub async fn get_followers_paginated( Ok(related_profiles) } +pub async fn has_local_followers( + db_client: &impl DatabaseClient, + actor_id: &str, +) -> Result { + let maybe_row = db_client.query_opt( + " + SELECT 1 + FROM relationship + JOIN actor_profile ON (relationship.target_id = actor_profile.id) + WHERE + actor_profile.actor_id = $1 + AND relationship_type = $2 + ", + &[&actor_id, &RelationshipType::Follow] + ).await?; + Ok(maybe_row.is_some()) +} + pub async fn get_following( db_client: &impl DatabaseClient, profile_id: &Uuid, @@ -581,10 +599,14 @@ mod tests { ..Default::default() }; let source = create_user(db_client, source_data).await.unwrap(); + let target_actor_id = "https://example.org/users/1"; let target_data = ProfileCreateData { username: "followed".to_string(), hostname: Some("example.org".to_string()), - actor_json: Some(DbActor::default()), + actor_json: Some(DbActor { + id: target_actor_id.to_string(), + ..Default::default() + }), ..Default::default() }; let target = create_profile(db_client, target_data).await.unwrap(); @@ -604,6 +626,10 @@ mod tests { assert_eq!(follow_request.request_status, FollowRequestStatus::Accepted); let following = get_following(db_client, &source.id).await.unwrap(); assert_eq!(following[0].id, target.id); + let target_has_followers = + has_local_followers(db_client, target_actor_id).await.unwrap(); + assert_eq!(target_has_followers, true); + // Unfollow let follow_request_id = unfollow(db_client, &source.id, &target.id) .await.unwrap().unwrap(); diff --git a/src/activitypub/handlers/create.rs b/src/activitypub/handlers/create.rs index 0568e93..15b92e8 100644 --- a/src/activitypub/handlers/create.rs +++ b/src/activitypub/handlers/create.rs @@ -19,6 +19,7 @@ use mitra_models::{ types::{Post, PostCreateData, Visibility}, }, profiles::types::DbActorProfile, + relationships::queries::has_local_followers, users::queries::get_user_by_name, }; use mitra_utils::{ @@ -655,6 +656,26 @@ pub async fn handle_note( Ok(post) } +async fn is_unsolicited_message( + db_client: &impl DatabaseClient, + instance_url: &str, + object: &Object, +) -> Result { + let author_id = get_object_attributed_to(object)?; + let author_has_followers = + has_local_followers(db_client, &author_id).await?; + let audience = get_audience(object)?; + let has_local_recipients = audience.iter().any(|actor_id| { + parse_local_actor_id(instance_url, actor_id).is_ok() + }); + let result = + object.in_reply_to.is_none() && + is_public_object(&audience) && + !has_local_recipients && + !author_has_followers; + Ok(result) +} + pub async fn handle_create( config: &Config, db_client: &mut impl DatabaseClient, @@ -663,6 +684,13 @@ pub async fn handle_create( ) -> HandlerResult { let object: Object = serde_json::from_value(activity["object"].to_owned()) .map_err(|_| ValidationError("invalid object"))?; + let instance = config.instance(); + + if is_unsolicited_message(db_client, &instance.url(), &object).await? { + log::warn!("unsolicited message rejected: {}", object.id); + return Ok(None); + }; + let object_id = object.id.clone(); let object_received = if is_authenticated { Some(object) @@ -673,7 +701,7 @@ pub async fn handle_create( }; import_post( db_client, - &config.instance(), + &instance, &MediaStorage::from(config), object_id, object_received,