]> Untitled Git - lemmy.git/commitdiff
Federation: dont overwrite local object from Announce activity (#2232)
authorNutomic <me@nutomic.com>
Mon, 25 Apr 2022 21:11:34 +0000 (23:11 +0200)
committerGitHub <noreply@github.com>
Mon, 25 Apr 2022 21:11:34 +0000 (21:11 +0000)
* Federation: dont overwrite local object from Announce activity (fixes #2143)

* add missing form fields

* refactoring

* add ap_id, updated fields

* fix

crates/apub/src/objects/comment.rs
crates/apub/src/objects/mod.rs
crates/apub/src/objects/post.rs
crates/apub/src/protocol/objects/page.rs

index 23a645f71ba6119f9442538038c47d302e397a5a..40aef9197f38dbd7bb7aa27af1025cefa5a5f6ee 100644 (file)
@@ -2,7 +2,7 @@ use crate::{
   activities::{verify_is_public, verify_person_in_community},
   check_is_apub_id_valid,
   mentions::collect_non_local_mentions,
-  objects::read_from_string_or_source,
+  objects::{read_from_string_or_source, verify_is_remote_object},
   protocol::{
     objects::{note::Note, tombstone::Tombstone},
     Source,
@@ -149,6 +149,7 @@ impl ApubObject for ApubComment {
     })
     .await??;
     check_is_apub_id_valid(note.id.inner(), community.local, &context.settings())?;
+    verify_is_remote_object(note.id.inner())?;
     verify_person_in_community(
       &note.attributed_to,
       &community.into(),
index 78013b0cc39e01a1a6249aa6c155f4530b2d2a26..e7155b4b14d144c7d2e8d7ecc07f074cb79f7843 100644 (file)
@@ -1,7 +1,8 @@
 use crate::protocol::{ImageObject, Source};
+use anyhow::anyhow;
 use html2md::parse_html;
 use lemmy_apub_lib::verify::verify_domains_match;
-use lemmy_utils::LemmyError;
+use lemmy_utils::{settings::structs::Settings, LemmyError};
 use url::Url;
 
 pub mod comment;
@@ -30,7 +31,10 @@ pub(crate) fn read_from_string_or_source_opt(
   }
 }
 
-pub fn verify_image_domain_matches(a: &Url, b: &Option<ImageObject>) -> Result<(), LemmyError> {
+pub(crate) fn verify_image_domain_matches(
+  a: &Url,
+  b: &Option<ImageObject>,
+) -> Result<(), LemmyError> {
   if let Some(b) = b {
     verify_domains_match(a, &b.url)
   } else {
@@ -38,6 +42,19 @@ pub fn verify_image_domain_matches(a: &Url, b: &Option<ImageObject>) -> Result<(
   }
 }
 
+/// When for example a Post is made in a remote community, the community will send it back,
+/// wrapped in Announce. If we simply receive this like any other federated object, overwrite the
+/// existing, local Post. In particular, it will set the field local = false, so that the object
+/// can't be fetched from the Activitypub HTTP endpoint anymore (which only serves local objects).
+pub(crate) fn verify_is_remote_object(id: &Url) -> Result<(), LemmyError> {
+  let local_domain = Settings::get().get_hostname_without_port()?;
+  if id.domain() == Some(&local_domain) {
+    Err(anyhow!("cant accept local object from remote instance").into())
+  } else {
+    Ok(())
+  }
+}
+
 #[cfg(test)]
 pub(crate) mod tests {
   use actix::Actor;
index 1e59c339edd3cb84a976082cd74d7a37bb831a11..88ca1ceb03c8ab471729d7a42db9c1cfadcb6cbd 100644 (file)
@@ -1,7 +1,7 @@
 use crate::{
   activities::{verify_is_public, verify_person_in_community},
   check_is_apub_id_valid,
-  objects::read_from_string_or_source_opt,
+  objects::{read_from_string_or_source_opt, verify_is_remote_object},
   protocol::{
     objects::{
       page::{Attachment, Page, PageType},
@@ -139,6 +139,7 @@ impl ApubObject for ApubPost {
     // instance from the post author.
     if !page.is_mod_action(context).await? {
       verify_domains_match(page.id.inner(), expected_domain)?;
+      verify_is_remote_object(page.id.inner())?;
     };
 
     let community = page.extract_community(context, request_counter).await?;
@@ -162,42 +163,56 @@ impl ApubObject for ApubPost {
       .await?;
     let community = page.extract_community(context, request_counter).await?;
 
-    let url = if let Some(attachment) = page.attachment.first() {
-      Some(attachment.href.clone())
-    } else {
-      page.url
-    };
-    let thumbnail_url: Option<Url> = page.image.map(|i| i.url);
-    let (metadata_res, pictrs_thumbnail) = if let Some(url) = &url {
-      fetch_site_data(context.client(), &context.settings(), Some(url)).await
-    } else {
-      (None, thumbnail_url)
-    };
-    let (embed_title, embed_description, embed_html) = metadata_res
-      .map(|u| (u.title, u.description, u.html))
-      .unwrap_or((None, None, None));
-    let body_slurs_removed = read_from_string_or_source_opt(&page.content, &page.source)
-      .map(|s| remove_slurs(&s, &context.settings().slur_regex()));
+    let form = if !page.is_mod_action(context).await? {
+      let url = if let Some(attachment) = page.attachment.first() {
+        Some(attachment.href.clone())
+      } else {
+        page.url
+      };
+      let thumbnail_url: Option<Url> = page.image.map(|i| i.url);
+      let (metadata_res, pictrs_thumbnail) = if let Some(url) = &url {
+        fetch_site_data(context.client(), &context.settings(), Some(url)).await
+      } else {
+        (None, thumbnail_url)
+      };
+      let (embed_title, embed_description, embed_html) = metadata_res
+        .map(|u| (u.title, u.description, u.html))
+        .unwrap_or((None, None, None));
+      let body_slurs_removed = read_from_string_or_source_opt(&page.content, &page.source)
+        .map(|s| remove_slurs(&s, &context.settings().slur_regex()));
 
-    let form = PostForm {
-      name: page.name.clone(),
-      url: url.map(Into::into),
-      body: body_slurs_removed,
-      creator_id: creator.id,
-      community_id: community.id,
-      removed: None,
-      locked: page.comments_enabled.map(|e| !e),
-      published: page.published.map(|u| u.naive_local()),
-      updated: page.updated.map(|u| u.naive_local()),
-      deleted: None,
-      nsfw: page.sensitive,
-      stickied: page.stickied,
-      embed_title,
-      embed_description,
-      embed_html,
-      thumbnail_url: pictrs_thumbnail.map(|u| u.into()),
-      ap_id: Some(page.id.clone().into()),
-      local: Some(false),
+      PostForm {
+        name: page.name.clone(),
+        url: url.map(Into::into),
+        body: body_slurs_removed,
+        creator_id: creator.id,
+        community_id: community.id,
+        removed: None,
+        locked: page.comments_enabled.map(|e| !e),
+        published: page.published.map(|u| u.naive_local()),
+        updated: page.updated.map(|u| u.naive_local()),
+        deleted: None,
+        nsfw: page.sensitive,
+        stickied: page.stickied,
+        embed_title,
+        embed_description,
+        embed_html,
+        thumbnail_url: pictrs_thumbnail.map(|u| u.into()),
+        ap_id: Some(page.id.clone().into()),
+        local: Some(false),
+      }
+    } else {
+      // if is mod action, only update locked/stickied fields, nothing else
+      PostForm {
+        name: page.name.clone(),
+        creator_id: creator.id,
+        community_id: community.id,
+        locked: page.comments_enabled.map(|e| !e),
+        stickied: page.stickied,
+        updated: page.updated.map(|u| u.naive_local()),
+        ap_id: Some(page.id.clone().into()),
+        ..Default::default()
+      }
     };
 
     // read existing, local post if any (for generating mod log)
index 8b66073081aa09859f7d37045197a95398753eb3..c799c5209b3442f5da4dca318210de957a3c386a 100644 (file)
@@ -75,9 +75,9 @@ impl Page {
       .dereference_local(context)
       .await;
 
-    let is_mod_action = Page::is_stickied_changed(&old_post, &self.stickied)
-      || Page::is_locked_changed(&old_post, &self.comments_enabled);
-    Ok(is_mod_action)
+    let stickied_changed = Page::is_stickied_changed(&old_post, &self.stickied);
+    let locked_changed = Page::is_locked_changed(&old_post, &self.comments_enabled);
+    Ok(stickied_changed || locked_changed)
   }
 
   pub(crate) fn is_stickied_changed<E>(