From a2707e1c56e994f93c22f6134bae70b7bbfbe8fb Mon Sep 17 00:00:00 2001
From: Felix Ableitner <me@nutomic.com>
Date: Thu, 18 Nov 2021 18:04:28 +0100
Subject: [PATCH] Community outbox should only contain activities sent by
 community (fixes #1916)

---
 .../lemmy/collections/group_outbox.json       | 188 ++----------------
 .../apub/src/activities/community/announce.rs |  19 +-
 .../apub/src/collections/community_outbox.rs  |  30 ++-
 .../src/protocol/collections/group_outbox.rs  |   4 +-
 crates/apub/src/protocol/objects/page.rs      |  21 +-
 5 files changed, 63 insertions(+), 199 deletions(-)

diff --git a/crates/apub/assets/lemmy/collections/group_outbox.json b/crates/apub/assets/lemmy/collections/group_outbox.json
index cf68742f..14789177 100644
--- a/crates/apub/assets/lemmy/collections/group_outbox.json
+++ b/crates/apub/assets/lemmy/collections/group_outbox.json
@@ -1,209 +1,61 @@
 {
   "type": "OrderedCollection",
-  "id": "https://ds9.lemmy.ml/c/main/outbox",
-  "totalItems": 7,
+  "id": "https://ds9.lemmy.ml/c/testcom/outbox",
+  "totalItems": 2,
   "orderedItems": [
     {
-      "actor": "https://ds9.lemmy.ml/u/dess_ds9",
+      "actor": "https://ds9.lemmy.ml/c/testcom",
       "to": [
         "https://www.w3.org/ns/activitystreams#Public"
       ],
       "object": {
         "type": "Page",
-        "id": "https://ds9.lemmy.ml/post/1685",
-        "attributedTo": "https://ds9.lemmy.ml/u/dess_ds9",
-        "to": [
-          "https://ds9.lemmy.ml/c/main",
-          "https://www.w3.org/ns/activitystreams#Public"
-        ],
-        "name": "Test post",
-        "mediaType": "text/html",
-        "commentsEnabled": true,
-        "sensitive": false,
-        "stickied": false,
-        "published": "2021-09-30T16:37:58.425718+00:00",
-        "updated": "2021-09-30T16:39:50.934055+00:00"
-      },
-      "cc": [
-        "https://ds9.lemmy.ml/c/main"
-      ],
-      "type": "Create",
-      "id": "https://ds9.lemmy.ml/activities/create/157bc329-05cb-4dc3-ad9e-5110fde3f3aa"
-    },
-    {
-      "actor": "https://ds9.lemmy.ml/u/nutomic",
-      "to": [
-        "https://www.w3.org/ns/activitystreams#Public"
-      ],
-      "object": {
-        "type": "Page",
-        "id": "https://ds9.lemmy.ml/post/1665",
+        "id": "https://ds9.lemmy.ml/post/2328",
         "attributedTo": "https://ds9.lemmy.ml/u/nutomic",
         "to": [
-          "https://ds9.lemmy.ml/c/main",
+          "https://ds9.lemmy.ml/c/testcom",
           "https://www.w3.org/ns/activitystreams#Public"
         ],
-        "name": "another webmention test",
+        "cc": [],
+        "name": "another outbox test",
         "mediaType": "text/html",
-        "url": "https://webmention.rocks/test/1",
         "commentsEnabled": true,
         "sensitive": false,
         "stickied": false,
-        "published": "2021-09-17T13:22:15.026912+00:00"
+        "published": "2021-11-18T17:19:45.895163+00:00"
       },
       "cc": [
-        "https://ds9.lemmy.ml/c/main"
+        "https://ds9.lemmy.ml/c/testcom/followers"
       ],
-      "type": "Create",
-      "id": "https://ds9.lemmy.ml/activities/create/c54e4509-16ac-42bf-b3b4-0bf8516f8152"
+      "type": "Announce",
+      "id": "https://ds9.lemmy.ml/activities/announce/b204fe9f-b13d-4af2-9d22-239ac2d892e6"
     },
     {
-      "actor": "https://ds9.lemmy.ml/u/nutomic",
+      "actor": "https://ds9.lemmy.ml/c/testcom",
       "to": [
         "https://www.w3.org/ns/activitystreams#Public"
       ],
       "object": {
         "type": "Page",
-        "id": "https://ds9.lemmy.ml/post/1664",
+        "id": "https://ds9.lemmy.ml/post/2327",
         "attributedTo": "https://ds9.lemmy.ml/u/nutomic",
         "to": [
-          "https://ds9.lemmy.ml/c/main",
-          "https://www.w3.org/ns/activitystreams#Public"
-        ],
-        "name": "another test",
-        "mediaType": "text/html",
-        "url": "https://webmention.rocks/test/1",
-        "commentsEnabled": true,
-        "sensitive": false,
-        "stickied": false,
-        "published": "2021-09-17T13:13:21.675891+00:00"
-      },
-      "cc": [
-        "https://ds9.lemmy.ml/c/main"
-      ],
-      "type": "Create",
-      "id": "https://ds9.lemmy.ml/activities/create/25f7d2cb-11d5-4c9c-aa3c-85fbff9f9e0c"
-    },
-    {
-      "actor": "https://ds9.lemmy.ml/u/nutomic",
-      "to": [
-        "https://www.w3.org/ns/activitystreams#Public"
-      ],
-      "object": {
-        "type": "Page",
-        "id": "https://ds9.lemmy.ml/post/1663",
-        "attributedTo": "https://ds9.lemmy.ml/u/nutomic",
-        "to": [
-          "https://ds9.lemmy.ml/c/main",
-          "https://www.w3.org/ns/activitystreams#Public"
-        ],
-        "name": "Webmention test from Lemmy",
-        "mediaType": "text/html",
-        "url": "https://webmention.rocks/test/1",
-        "commentsEnabled": true,
-        "sensitive": false,
-        "stickied": false,
-        "published": "2021-09-17T13:00:15.392844+00:00"
-      },
-      "cc": [
-        "https://ds9.lemmy.ml/c/main"
-      ],
-      "type": "Create",
-      "id": "https://ds9.lemmy.ml/activities/create/cfbd12b8-2e11-42b6-a609-b482decbaf11"
-    },
-    {
-      "actor": "https://ds9.lemmy.ml/u/dess_tester_3",
-      "to": [
-        "https://www.w3.org/ns/activitystreams#Public"
-      ],
-      "object": {
-        "type": "Page",
-        "id": "https://ds9.lemmy.ml/post/1644",
-        "attributedTo": "https://ds9.lemmy.ml/u/dess_tester_3",
-        "to": [
-          "https://ds9.lemmy.ml/c/main",
-          "https://www.w3.org/ns/activitystreams#Public"
-        ],
-        "name": "The best wireless earbuds you can buy right now | Engadget",
-        "mediaType": "text/html",
-        "url": "https://www.engadget.com/best-wireless-earbuds-120058222.html",
-        "image": {
-          "type": "Image",
-          "url": "https://ds9.lemmy.ml/pictrs/image/0WWsYOuwAE.jpg"
-        },
-        "commentsEnabled": true,
-        "sensitive": false,
-        "stickied": false,
-        "published": "2021-08-26T01:22:06.428368+00:00"
-      },
-      "cc": [
-        "https://ds9.lemmy.ml/c/main"
-      ],
-      "type": "Create",
-      "id": "https://ds9.lemmy.ml/activities/create/76c94408-944a-4a2f-a88b-d10f12b472b0"
-    },
-    {
-      "actor": "https://ds9.lemmy.ml/u/dess_ds9",
-      "to": [
-        "https://www.w3.org/ns/activitystreams#Public"
-      ],
-      "object": {
-        "type": "Page",
-        "id": "https://ds9.lemmy.ml/post/1643",
-        "attributedTo": "https://ds9.lemmy.ml/u/dess_ds9",
-        "to": [
-          "https://ds9.lemmy.ml/c/main",
-          "https://www.w3.org/ns/activitystreams#Public"
-        ],
-        "name": "First Look: Cadillac’s luxury EV debut seems like a winner | Engadges",
-        "content": "<p>test</p>\n",
-        "mediaType": "text/html",
-        "source": {
-          "content": "test",
-          "mediaType": "text/markdown"
-        },
-        "url": "https://www.engadget.com/cadillac-lyriq-luxury-ev-first-look-video-171543752.html",
-        "image": {
-          "type": "Image",
-          "url": "https://ds9.lemmy.ml/pictrs/image/gnmtvgXP31.jpg"
-        },
-        "commentsEnabled": true,
-        "sensitive": false,
-        "stickied": false,
-        "published": "2021-08-23T23:43:06.560543+00:00",
-        "updated": "2021-08-23T23:52:51.832606+00:00"
-      },
-      "cc": [
-        "https://ds9.lemmy.ml/c/main"
-      ],
-      "type": "Create",
-      "id": "https://ds9.lemmy.ml/activities/create/b1f95918-f593-4951-91cf-2c3340cd9509"
-    },
-    {
-      "actor": "https://ds9.lemmy.ml/u/dess_ds9_2",
-      "to": [
-        "https://www.w3.org/ns/activitystreams#Public"
-      ],
-      "object": {
-        "type": "Page",
-        "id": "https://ds9.lemmy.ml/post/1642",
-        "attributedTo": "https://ds9.lemmy.ml/u/dess_ds9_2",
-        "to": [
-          "https://ds9.lemmy.ml/c/main",
+          "https://ds9.lemmy.ml/c/testcom",
           "https://www.w3.org/ns/activitystreams#Public"
         ],
-        "name": "A test post from DS9",
+        "cc": [],
+        "name": "outbox test",
         "mediaType": "text/html",
         "commentsEnabled": true,
         "sensitive": false,
         "stickied": false,
-        "published": "2021-08-06T14:10:47.493075+00:00"
+        "published": "2021-11-18T17:19:05.763109+00:00"
       },
       "cc": [
-        "https://ds9.lemmy.ml/c/main"
+        "https://ds9.lemmy.ml/c/testcom/followers"
       ],
-      "type": "Create",
-      "id": "https://ds9.lemmy.ml/activities/create/6359b2e7-badb-4241-b5ee-b093078361bd"
+      "type": "Announce",
+      "id": "https://ds9.lemmy.ml/activities/announce/c6c960ce-c8d8-4231-925e-3ba367468f18"
     }
   ]
 }
\ No newline at end of file
diff --git a/crates/apub/src/activities/community/announce.rs b/crates/apub/src/activities/community/announce.rs
index 4eebcce3..a86317b5 100644
--- a/crates/apub/src/activities/community/announce.rs
+++ b/crates/apub/src/activities/community/announce.rs
@@ -25,7 +25,7 @@ pub(crate) trait GetCommunity {
 }
 
 impl AnnounceActivity {
-  fn new(
+  pub(crate) fn new(
     object: AnnouncableActivities,
     community: &ApubCommunity,
     context: &LemmyContext,
@@ -103,13 +103,20 @@ impl ActivityHandler for AnnounceActivity {
     context: &Data<LemmyContext>,
     request_counter: &mut i32,
   ) -> Result<(), LemmyError> {
-    let object_value = serde_json::to_value(&self.object)?;
-    let object_data: ActivityCommonFields = serde_json::from_value(object_value.to_owned())?;
+    // TODO: this can probably be implemented in a cleaner way
+    match self.object {
+      // Dont insert these into activities table, as they are not activities.
+      AnnouncableActivities::Page(_) | AnnouncableActivities::Note(_) => {}
+      _ => {
+        let object_value = serde_json::to_value(&self.object)?;
+        let object_data: ActivityCommonFields = serde_json::from_value(object_value.to_owned())?;
 
-    if is_activity_already_known(context.pool(), &object_data.id).await? {
-      return Ok(());
+        if is_activity_already_known(context.pool(), &object_data.id).await? {
+          return Ok(());
+        }
+        insert_activity(&object_data.id, object_value, false, true, context.pool()).await?;
+      }
     }
-    insert_activity(&object_data.id, object_value, false, true, context.pool()).await?;
     self.object.receive(context, request_counter).await
   }
 }
diff --git a/crates/apub/src/collections/community_outbox.rs b/crates/apub/src/collections/community_outbox.rs
index 3632f61a..69dfd929 100644
--- a/crates/apub/src/collections/community_outbox.rs
+++ b/crates/apub/src/collections/community_outbox.rs
@@ -1,9 +1,10 @@
 use crate::{
+  activity_lists::AnnouncableActivities,
   collections::CommunityContext,
   generate_outbox_url,
-  objects::{person::ApubPerson, post::ApubPost},
+  objects::post::ApubPost,
   protocol::{
-    activities::{create_or_update::post::CreateOrUpdatePost, CreateOrUpdateType},
+    activities::community::announce::AnnounceActivity,
     collections::group_outbox::GroupOutbox,
   },
 };
@@ -15,10 +16,7 @@ use lemmy_apub_lib::{
   traits::{ActivityHandler, ApubObject},
   verify::verify_domains_match,
 };
-use lemmy_db_schema::{
-  source::{person::Person, post::Post},
-  traits::Crud,
-};
+use lemmy_db_schema::source::post::Post;
 use lemmy_utils::LemmyError;
 use url::Url;
 
@@ -63,13 +61,10 @@ impl ApubObject for ApubCommunityOutbox {
   async fn into_apub(self, data: &Self::DataType) -> Result<Self::ApubType, LemmyError> {
     let mut ordered_items = vec![];
     for post in self.0 {
-      let actor = post.creator_id;
-      let actor: ApubPerson = blocking(data.1.pool(), move |conn| Person::read(conn, actor))
-        .await??
-        .into();
-      let a =
-        CreateOrUpdatePost::new(post, &actor, &data.0, CreateOrUpdateType::Create, &data.1).await?;
-      ordered_items.push(a);
+      let page = post.into_apub(&data.1).await?;
+      let announcable = AnnouncableActivities::Page(page);
+      let announce = AnnounceActivity::new(announcable, &data.0, &data.1)?;
+      ordered_items.push(announce);
     }
 
     Ok(GroupOutbox {
@@ -108,11 +103,12 @@ impl ApubObject for ApubCommunityOutbox {
     // We intentionally ignore errors here. This is because the outbox might contain posts from old
     // Lemmy versions, or from other software which we cant parse. In that case, we simply skip the
     // item and only parse the ones that work.
+    let data = Data::new(data.1.clone());
     for activity in outbox_activities {
-      activity
-        .receive(&Data::new(data.1.clone()), request_counter)
-        .await
-        .ok();
+      let verify = activity.verify(&data, request_counter).await;
+      if verify.is_ok() {
+        activity.receive(&data, request_counter).await.ok();
+      }
     }
 
     // This return value is unused, so just set an empty vec
diff --git a/crates/apub/src/protocol/collections/group_outbox.rs b/crates/apub/src/protocol/collections/group_outbox.rs
index 3b295003..f7c70792 100644
--- a/crates/apub/src/protocol/collections/group_outbox.rs
+++ b/crates/apub/src/protocol/collections/group_outbox.rs
@@ -1,4 +1,4 @@
-use crate::protocol::activities::create_or_update::post::CreateOrUpdatePost;
+use crate::protocol::activities::community::announce::AnnounceActivity;
 use activitystreams::collection::kind::OrderedCollectionType;
 use serde::{Deserialize, Serialize};
 use url::Url;
@@ -9,5 +9,5 @@ pub struct GroupOutbox {
   pub(crate) r#type: OrderedCollectionType,
   pub(crate) id: Url,
   pub(crate) total_items: i32,
-  pub(crate) ordered_items: Vec<CreateOrUpdatePost>,
+  pub(crate) ordered_items: Vec<AnnounceActivity>,
 }
diff --git a/crates/apub/src/protocol/objects/page.rs b/crates/apub/src/protocol/objects/page.rs
index dbf52eee..dd923618 100644
--- a/crates/apub/src/protocol/objects/page.rs
+++ b/crates/apub/src/protocol/objects/page.rs
@@ -8,7 +8,7 @@ use chrono::{DateTime, FixedOffset};
 use lemmy_apub_lib::{
   data::Data,
   object_id::ObjectId,
-  traits::ActivityHandler,
+  traits::{ActivityHandler, ApubObject},
   values::MediaTypeHtml,
 };
 use lemmy_utils::LemmyError;
@@ -79,14 +79,23 @@ impl Page {
   }
 }
 
-// For Pleroma/Mastodon compat. Unimplemented because its only used for sending.
+// Used for community outbox, so that it can be compatible with Pleroma/Mastodon.
 #[async_trait::async_trait(?Send)]
 impl ActivityHandler for Page {
   type DataType = LemmyContext;
-  async fn verify(&self, _: &Data<Self::DataType>, _: &mut i32) -> Result<(), LemmyError> {
-    Err(anyhow!("Announce/Page can only be sent, not received").into())
+  async fn verify(
+    &self,
+    data: &Data<Self::DataType>,
+    request_counter: &mut i32,
+  ) -> Result<(), LemmyError> {
+    ApubPost::verify(self, self.id.inner(), data, request_counter).await
   }
-  async fn receive(self, _: &Data<Self::DataType>, _: &mut i32) -> Result<(), LemmyError> {
-    unimplemented!()
+  async fn receive(
+    self,
+    data: &Data<Self::DataType>,
+    request_counter: &mut i32,
+  ) -> Result<(), LemmyError> {
+    ApubPost::from_apub(self, data, request_counter).await?;
+    Ok(())
   }
 }
-- 
2.44.1