Reject unsolicited public posts

This commit is contained in:
silverpill 2023-04-05 20:58:09 +00:00
parent fc82c83421
commit 8708abd9cd
3 changed files with 57 additions and 2 deletions

View file

@ -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

View file

@ -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<bool, DatabaseError> {
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();

View file

@ -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<bool, HandlerError> {
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,