Announcements: Handle through common pipeline.
authorlain <lain@soykaf.club>
Wed, 20 May 2020 13:44:37 +0000 (15:44 +0200)
committerlain <lain@soykaf.club>
Wed, 20 May 2020 13:44:37 +0000 (15:44 +0200)
lib/pleroma/web/activity_pub/activity_pub.ex
lib/pleroma/web/activity_pub/builder.ex
lib/pleroma/web/activity_pub/object_validator.ex
lib/pleroma/web/activity_pub/object_validators/announce_validator.ex
lib/pleroma/web/activity_pub/side_effects.ex
lib/pleroma/web/activity_pub/transmogrifier.ex
lib/pleroma/web/common_api/common_api.ex
test/fixtures/mastodon-note-object.json
test/web/activity_pub/side_effects_test.exs
test/web/activity_pub/transmogrifier/announce_handling_test.exs
test/web/common_api/common_api_test.exs

index d752f4f04a800abe57893038ccc209a681d52ca6..2cea55285f36104a4fce779979d9ad7f419f3c6e 100644 (file)
@@ -356,36 +356,6 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
     end
   end
 
-  @spec announce(User.t(), Object.t(), String.t() | nil, boolean(), boolean()) ::
-          {:ok, Activity.t(), Object.t()} | {:error, any()}
-  def announce(
-        %User{ap_id: _} = user,
-        %Object{data: %{"id" => _}} = object,
-        activity_id \\ nil,
-        local \\ true,
-        public \\ true
-      ) do
-    with {:ok, result} <-
-           Repo.transaction(fn -> do_announce(user, object, activity_id, local, public) end) do
-      result
-    end
-  end
-
-  defp do_announce(user, object, activity_id, local, public) do
-    with true <- is_announceable?(object, user, public),
-         object <- Object.get_by_id(object.id),
-         announce_data <- make_announce_data(user, object, activity_id, public),
-         {:ok, activity} <- insert(announce_data, local),
-         {:ok, object} <- add_announce_to_object(activity, object),
-         _ <- notify_and_stream(activity),
-         :ok <- maybe_federate(activity) do
-      {:ok, activity, object}
-    else
-      false -> {:error, false}
-      {:error, error} -> Repo.rollback(error)
-    end
-  end
-
   @spec follow(User.t(), User.t(), String.t() | nil, boolean()) ::
           {:ok, Activity.t()} | {:error, any()}
   def follow(follower, followed, activity_id \\ nil, local \\ true) do
index 63f89c2b4fed0baf79728b0aa03a2c79f89c5a35..7ece764f5ac17286cb59749aa3d67aa1e0cc0c31 100644 (file)
@@ -10,6 +10,8 @@ defmodule Pleroma.Web.ActivityPub.Builder do
   alias Pleroma.Web.ActivityPub.Utils
   alias Pleroma.Web.ActivityPub.Visibility
 
+  require Pleroma.Constants
+
   @spec emoji_react(User.t(), Object.t(), String.t()) :: {:ok, map(), keyword()}
   def emoji_react(actor, object, emoji) do
     with {:ok, data, meta} <- object_action(actor, object) do
@@ -83,9 +85,17 @@ defmodule Pleroma.Web.ActivityPub.Builder do
     end
   end
 
-  def announce(actor, object) do
+  def announce(actor, object, options \\ []) do
+    public? = Keyword.get(options, :public, false)
     to = [actor.follower_address, object.data["actor"]]
 
+    to =
+      if public? do
+        [Pleroma.Constants.as_public() | to]
+      else
+        to
+      end
+
     {:ok,
      %{
        "id" => Utils.generate_activity_id(),
@@ -93,7 +103,8 @@ defmodule Pleroma.Web.ActivityPub.Builder do
        "object" => object.data["id"],
        "to" => to,
        "context" => object.data["context"],
-       "type" => "Announce"
+       "type" => "Announce",
+       "published" => Utils.make_date()
      }, []}
   end
 
index 600e58123e60c5e311969b83237346bc157807f0..2599067a87089141db941ff3cdb94a94e117ece8 100644 (file)
@@ -88,7 +88,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidator do
 
   def fetch_actor_and_object(object) do
     fetch_actor(object)
-    Object.normalize(object["object"])
+    Object.normalize(object["object"], true)
     :ok
   end
 end
index 158ae199d4d6ff6bd884901103680e9e1e2ea7b9..082fdea4d986d5340737c1084e79a140033d191b 100644 (file)
@@ -18,9 +18,10 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.AnnounceValidator do
     field(:type, :string)
     field(:object, Types.ObjectID)
     field(:actor, Types.ObjectID)
-    field(:context, :string)
+    field(:context, :string, autogenerate: {Utils, :generate_context_id, []})
     field(:to, Types.Recipients, default: [])
     field(:cc, Types.Recipients, default: [])
+    field(:published, Types.DateTime)
   end
 
   def cast_and_validate(data) do
@@ -47,7 +48,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.AnnounceValidator do
   def validate_data(data_cng) do
     data_cng
     |> validate_inclusion(:type, ["Announce"])
-    |> validate_required([:id, :type, :object, :actor, :context, :to, :cc])
+    |> validate_required([:id, :type, :object, :actor, :to, :cc])
     |> validate_actor_presence()
     |> validate_object_presence()
     |> validate_existing_announce()
index bfc2ab845d7aac444c7ce8b8a563e768a6f3cfd9..bc0d31c456770a180e7db9ed0cd5109a400f92eb 100644 (file)
@@ -27,6 +27,18 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do
     {:ok, object, meta}
   end
 
+  # Tasks this handles:
+  # - Add announce to object
+  # - Set up notification
+  def handle(%{data: %{"type" => "Announce"}} = object, meta) do
+    announced_object = Object.get_by_ap_id(object.data["object"])
+    Utils.add_announce_to_object(object, announced_object)
+
+    Notification.create_notifications(object)
+
+    {:ok, object, meta}
+  end
+
   def handle(%{data: %{"type" => "Undo", "object" => undone_object}} = object, meta) do
     with undone_object <- Activity.get_by_ap_id(undone_object),
          :ok <- handle_undoing(undone_object) do
index 6104af4f97ca937e269837ce91d71f893be4fdb4..d594c64f486e56542629a7d5424119632a0db920 100644 (file)
@@ -662,7 +662,8 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
     |> handle_incoming(options)
   end
 
-  def handle_incoming(%{"type" => type} = data, _options) when type in ["Like", "EmojiReact"] do
+  def handle_incoming(%{"type" => type} = data, _options)
+      when type in ["Like", "EmojiReact", "Announce"] do
     with :ok <- ObjectValidator.fetch_actor_and_object(data),
          {:ok, activity, _meta} <-
            Pipeline.common_pipeline(data, local: false) do
@@ -672,22 +673,6 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
     end
   end
 
-  def handle_incoming(
-        %{"type" => "Announce", "object" => object_id, "actor" => _actor, "id" => id} = data,
-        _options
-      ) do
-    with actor <- Containment.get_actor(data),
-         {_, {:ok, %User{} = actor}} <- {:fetch_user, User.get_or_fetch_by_ap_id(actor)},
-         {_, {:ok, object}} <- {:get_embedded, get_embedded_obj_helper(object_id, actor)},
-         public <- Visibility.is_public?(data),
-         {_, {:ok, activity, _object}} <-
-           {:announce, ActivityPub.announce(actor, object, id, false, public)} do
-      {:ok, activity}
-    else
-      e -> {:error, e}
-    end
-  end
-
   def handle_incoming(
         %{"type" => "Update", "object" => %{"type" => object_type} = object, "actor" => actor_id} =
           data,
index 447dbe4e68791a0be6d04a457ec6aa457c5ed1d7..dbb3d7ade5d7bed0f3a855c56d1cc103a54085ec 100644 (file)
@@ -127,18 +127,19 @@ defmodule Pleroma.Web.CommonAPI do
   end
 
   def repeat(id, user, params \\ %{}) do
-    with %Activity{data: %{"type" => "Create"}} = activity <- Activity.get_by_id(id) do
-      object = Object.normalize(activity)
-      announce_activity = Utils.get_existing_announce(user.ap_id, object)
-      public = public_announce?(object, params)
-
-      if announce_activity do
-        {:ok, announce_activity, object}
-      else
-        ActivityPub.announce(user, object, nil, true, public)
-      end
+    with %Activity{data: %{"type" => "Create"}} = activity <- Activity.get_by_id(id),
+         object = %Object{} <- Object.normalize(activity, false),
+         {_, nil} <- {:existing_announce, Utils.get_existing_announce(user.ap_id, object)},
+         public = public_announce?(object, params),
+         {:ok, announce, _} <- Builder.announce(user, object, public: public),
+         {:ok, activity, _} <- Pipeline.common_pipeline(announce, local: true) do
+      {:ok, activity}
     else
-      _ -> {:error, :not_found}
+      {:existing_announce, %Activity{} = announce} ->
+        {:ok, announce}
+
+      _ ->
+        {:error, :not_found}
     end
   end
 
index 75bed9625020a433d517c6b80c8673e4eb55fc58..d28c7fbe97dc7555dabe75a0a077219416e098ce 100644 (file)
@@ -1,9 +1,45 @@
-{"@context":["https://www.w3.org/ns/activitystreams","https://w3id.org/security/v1",{"manuallyApprovesFollowers":"as:manuallyApprovesFollowers","sensitive":"as:sensitive","movedTo":"as:movedTo","Hashtag":"as:Hashtag","ostatus":"http://ostatus.org#","atomUri":"ostatus:atomUri","inReplyToAtomUri":"ostatus:inReplyToAtomUri","conversation":"ostatus:conversation","toot":"http://joinmastodon.org/ns#","Emoji":"toot:Emoji"}],"id":"http://mastodon.example.org/users/admin/statuses/99541947525187367","type":"Note","summary":null,"content":"\u003cp\u003eyeah.\u003c/p\u003e","inReplyTo":null,"published":"2018-02-17T17:46:20Z","url":"http://mastodon.example.org/@admin/99541947525187367","attributedTo":"http://mastodon.example.org/users/admin","to":["https://www.w3.org/ns/activitystreams#Public"],"cc":["http://mastodon.example.org/users/admin/followers"],"sensitive":false,"atomUri":"http://mastodon.example.org/users/admin/statuses/99541947525187367","inReplyToAtomUri":null,"conversation":"tag:mastodon.example.org,2018-02-17:objectId=59:objectType=Conversation","tag":[],
-    "attachment": [
+{
+   "@context" : [
+      "https://www.w3.org/ns/activitystreams",
+      "https://w3id.org/security/v1",
       {
-        "url": "http://mastodon.example.org/system/media_attachments/files/000/000/002/original/334ce029e7bfb920.jpg",
-        "type": "Document",
-        "name": null,
-        "mediaType": "image/jpeg"
+         "Emoji" : "toot:Emoji",
+         "Hashtag" : "as:Hashtag",
+         "atomUri" : "ostatus:atomUri",
+         "conversation" : "ostatus:conversation",
+         "inReplyToAtomUri" : "ostatus:inReplyToAtomUri",
+         "manuallyApprovesFollowers" : "as:manuallyApprovesFollowers",
+         "movedTo" : "as:movedTo",
+         "ostatus" : "http://ostatus.org#",
+         "sensitive" : "as:sensitive",
+         "toot" : "http://joinmastodon.org/ns#"
       }
-    ]}
+   ],
+   "atomUri" : "http://mastodon.example.org/users/admin/statuses/99541947525187367",
+   "attachment" : [
+      {
+         "mediaType" : "image/jpeg",
+         "name" : null,
+         "type" : "Document",
+         "url" : "http://mastodon.example.org/system/media_attachments/files/000/000/002/original/334ce029e7bfb920.jpg"
+      }
+   ],
+   "attributedTo" : "http://mastodon.example.org/users/admin",
+   "cc" : [
+      "http://mastodon.example.org/users/admin/followers"
+   ],
+   "content" : "<p>yeah.</p>",
+   "conversation" : "tag:mastodon.example.org,2018-02-17:objectId=59:objectType=Conversation",
+   "id" : "http://mastodon.example.org/users/admin/statuses/99541947525187367",
+   "inReplyTo" : null,
+   "inReplyToAtomUri" : null,
+   "published" : "2018-02-17T17:46:20Z",
+   "sensitive" : false,
+   "summary" : null,
+   "tag" : [],
+   "to" : [
+      "https://www.w3.org/ns/activitystreams#Public"
+   ],
+   "type" : "Note",
+   "url" : "http://mastodon.example.org/@admin/99541947525187367"
+}
index a46254a05b358328ab5852a8d62dfa4962233940..5dede3957f73361c74e31e8179e4ebfd613cfc14 100644 (file)
@@ -289,4 +289,29 @@ defmodule Pleroma.Web.ActivityPub.SideEffectsTest do
       assert Repo.get_by(Notification, user_id: poster.id, activity_id: like.id)
     end
   end
+
+  describe "announce objects" do
+    setup do
+      poster = insert(:user)
+      user = insert(:user)
+      {:ok, post} = CommonAPI.post(poster, %{status: "hey"})
+
+      {:ok, announce_data, _meta} = Builder.announce(user, post.object)
+      {:ok, announce, _meta} = ActivityPub.persist(announce_data, local: true)
+
+      %{announce: announce, user: user, poster: poster}
+    end
+
+    test "add the announce to the original object", %{announce: announce, user: user} do
+      {:ok, announce, _} = SideEffects.handle(announce)
+      object = Object.get_by_ap_id(announce.data["object"])
+      assert object.data["announcement_count"] == 1
+      assert user.ap_id in object.data["announcements"]
+    end
+
+    test "creates a notification", %{announce: announce, poster: poster} do
+      {:ok, announce, _} = SideEffects.handle(announce)
+      assert Repo.get_by(Notification, user_id: poster.id, activity_id: announce.id)
+    end
+  end
 end
index 8a4af65463fe2785756961727462cabb03bbae3e..50bcb307f33689af0fb81e307cd442f3be2d97c9 100644 (file)
@@ -13,7 +13,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier.AnnounceHandlingTest do
   import Pleroma.Factory
 
   test "it works for incoming honk announces" do
-    _user = insert(:user, ap_id: "https://honktest/u/test", local: false)
+    user = insert(:user, ap_id: "https://honktest/u/test", local: false)
     other_user = insert(:user)
     {:ok, post} = CommonAPI.post(other_user, %{status: "bonkeronk"})
 
@@ -28,6 +28,11 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier.AnnounceHandlingTest do
     }
 
     {:ok, %Activity{local: false}} = Transmogrifier.handle_incoming(announce)
+
+    object = Object.get_by_ap_id(post.data["object"])
+
+    assert length(object.data["announcements"]) == 1
+    assert user.ap_id in object.data["announcements"]
   end
 
   test "it works for incoming announces with actor being inlined (kroeg)" do
@@ -48,8 +53,15 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier.AnnounceHandlingTest do
   end
 
   test "it works for incoming announces, fetching the announced object" do
-    Tesla.Mock.mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end)
-    data = File.read!("test/fixtures/mastodon-announce.json") |> Poison.decode!()
+    data =
+      File.read!("test/fixtures/mastodon-announce.json")
+      |> Poison.decode!()
+      |> Map.put("object", "http://mastodon.example.org/users/admin/statuses/99541947525187367")
+
+    Tesla.Mock.mock(fn
+      %{method: :get} ->
+        %Tesla.Env{status: 200, body: File.read!("test/fixtures/mastodon-note-object.json")}
+    end)
 
     _user = insert(:user, local: false, ap_id: data["actor"])
 
@@ -92,6 +104,8 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier.AnnounceHandlingTest do
     assert Activity.get_create_by_object_ap_id(data["object"]).id == activity.id
   end
 
+  # Ignore inlined activities for now
+  @tag skip: true
   test "it works for incoming announces with an inlined activity" do
     data =
       File.read!("test/fixtures/mastodon-announce-private.json")
index 52e95397cf005107aa613c93069d97b7a686b38b..e68a6a7d21a0ffc7d2a5efeef6463ce0b05981b0 100644 (file)
@@ -416,7 +416,8 @@ defmodule Pleroma.Web.CommonAPITest do
 
       {:ok, activity} = CommonAPI.post(other_user, %{status: "cofe"})
 
-      {:ok, %Activity{}, _} = CommonAPI.repeat(activity.id, user)
+      {:ok, %Activity{} = announce_activity} = CommonAPI.repeat(activity.id, user)
+      assert Visibility.is_public?(announce_activity)
     end
 
     test "can't repeat a repeat" do
@@ -424,9 +425,9 @@ defmodule Pleroma.Web.CommonAPITest do
       other_user = insert(:user)
       {:ok, activity} = CommonAPI.post(other_user, %{status: "cofe"})
 
-      {:ok, %Activity{} = announce, _} = CommonAPI.repeat(activity.id, other_user)
+      {:ok, %Activity{} = announce} = CommonAPI.repeat(activity.id, other_user)
 
-      refute match?({:ok, %Activity{}, _}, CommonAPI.repeat(announce.id, user))
+      refute match?({:ok, %Activity{}}, CommonAPI.repeat(announce.id, user))
     end
 
     test "repeating a status privately" do
@@ -435,7 +436,7 @@ defmodule Pleroma.Web.CommonAPITest do
 
       {:ok, activity} = CommonAPI.post(other_user, %{status: "cofe"})
 
-      {:ok, %Activity{} = announce_activity, _} =
+      {:ok, %Activity{} = announce_activity} =
         CommonAPI.repeat(activity.id, user, %{visibility: "private"})
 
       assert Visibility.is_private?(announce_activity)
@@ -458,8 +459,8 @@ defmodule Pleroma.Web.CommonAPITest do
       other_user = insert(:user)
 
       {:ok, activity} = CommonAPI.post(other_user, %{status: "cofe"})
-      {:ok, %Activity{} = announce, object} = CommonAPI.repeat(activity.id, user)
-      {:ok, ^announce, ^object} = CommonAPI.repeat(activity.id, user)
+      {:ok, %Activity{} = announce} = CommonAPI.repeat(activity.id, user)
+      {:ok, ^announce} = CommonAPI.repeat(activity.id, user)
     end
 
     test "favoriting a status twice returns ok, but without the like activity" do