mastodon pins
authorAlexander Strizhakov <alex.strizhakov@gmail.com>
Thu, 25 Feb 2021 11:00:44 +0000 (14:00 +0300)
committerAlexander Strizhakov <alex.strizhakov@gmail.com>
Thu, 25 Mar 2021 10:03:40 +0000 (13:03 +0300)
lib/pleroma/object/containment.ex
lib/pleroma/web/activity_pub/transmogrifier.ex
test/fixtures/statuses/masto-note.json [new file with mode: 0644]
test/pleroma/web/activity_pub/activity_pub_controller_test.exs

index fb0398f92cccc54a6b5dfbfc85a89be32ccb912c..040537acf1a802502fce0d945682050534ef8c53 100644 (file)
@@ -71,6 +71,14 @@ defmodule Pleroma.Object.Containment do
     compare_uris(id_uri, other_uri)
   end
 
+  # Mastodon pin activities don't have an id, so we check the object field, which will be pinned.
+  def contain_origin_from_id(id, %{"object" => object}) when is_binary(object) do
+    id_uri = URI.parse(id)
+    object_uri = URI.parse(object)
+
+    compare_uris(id_uri, object_uri)
+  end
+
   def contain_origin_from_id(_id, _data), do: :error
 
   def contain_child(%{"object" => %{"id" => id, "attributedTo" => _} = object}),
index 270cea6dc13f80ed9d5134458ff9c6e100b28ed2..b662f53796f56efd58b1dfa0e670887fae5a4427 100644 (file)
@@ -557,10 +557,19 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
   end
 
   def handle_incoming(%{"type" => type} = data, _options) when type in ~w(Add Remove) do
-    with :ok <- ObjectValidator.fetch_actor_and_object(data),
-         %Object{} <- Object.normalize(data["object"], fetch: true),
-         {:ok, activity, _meta} <- Pipeline.common_pipeline(data, local: false) do
-      {:ok, activity}
+    with {:ok, user} <- ObjectValidator.fetch_actor(data),
+         %Object{} <- Object.normalize(data["object"], fetch: true) do
+      # Mastodon sends pin/unpin objects without id, to, cc fields
+      data =
+        data
+        |> Map.put_new("id", Utils.generate_activity_id())
+        |> Map.put_new("to", [Pleroma.Constants.as_public()])
+        |> Map.put_new("cc", [user.follower_address])
+
+      case Pipeline.common_pipeline(data, local: false) do
+        {:ok, activity, _meta} -> {:ok, activity}
+        error -> error
+      end
     end
   end
 
diff --git a/test/fixtures/statuses/masto-note.json b/test/fixtures/statuses/masto-note.json
new file mode 100644 (file)
index 0000000..6b96de4
--- /dev/null
@@ -0,0 +1,47 @@
+{
+  "@context": [
+    "https://www.w3.org/ns/activitystreams",
+    {
+      "ostatus": "http://ostatus.org#",
+      "atomUri": "ostatus:atomUri",
+      "inReplyToAtomUri": "ostatus:inReplyToAtomUri",
+      "conversation": "ostatus:conversation",
+      "sensitive": "as:sensitive",
+      "toot": "http://joinmastodon.org/ns#",
+      "votersCount": "toot:votersCount"
+    }
+  ],
+  "id": "https://example.com/users/{{nickname}}/statuses/{{status_id}}",
+  "type": "Note",
+  "summary": null,
+  "inReplyTo": null,
+  "published": "2021-02-24T12:40:49Z",
+  "url": "https://example.com/@{{nickname}}/{{status_id}}",
+  "attributedTo": "https://example.com/users/{{nickname}}",
+  "to": [
+    "https://www.w3.org/ns/activitystreams#Public"
+  ],
+  "cc": [
+    "https://example.com/users/{{nickname}}/followers"
+  ],
+  "sensitive": false,
+  "atomUri": "https://example.com/users/{{nickname}}/statuses/{{status_id}}",
+  "inReplyToAtomUri": null,
+  "conversation": "tag:example.com,2021-02-24:objectId=15:objectType=Conversation",
+  "content": "<p></p>",
+  "contentMap": {
+    "en": "<p></p>"
+  },
+  "attachment": [],
+  "tag": [],
+  "replies": {
+    "id": "https://example.com/users/{{nickname}}/statuses/{{status_id}}/replies",
+    "type": "Collection",
+    "first": {
+      "type": "CollectionPage",
+      "next": "https://example.com/users/{{nickname}}/statuses/{{status_id}}/replies?only_other_accounts=true&page=true",
+      "partOf": "https://example.com/users/{{nickname}}/statuses/{{status_id}}/replies",
+      "items": []
+    }
+  }
+}
index a9cbf90c3898654828a8909f6b6135faa7efb158..d9fa25d9439faa3145d3838a4d44e6c72fc83506 100644 (file)
@@ -716,6 +716,84 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do
       user = refresh_record(user)
       refute user.pinned_objects[data["object"]]
     end
+
+    test "mastodon pin/unpin", %{conn: conn} do
+      status_id = "105786274556060421"
+
+      status =
+        File.read!("test/fixtures/statuses/masto-note.json")
+        |> String.replace("{{nickname}}", "lain")
+        |> String.replace("{{status_id}}", status_id)
+
+      status_url = "https://example.com/users/lain/statuses/#{status_id}"
+
+      user =
+        File.read!("test/fixtures/users_mock/user.json")
+        |> String.replace("{{nickname}}", "lain")
+
+      actor = "https://example.com/users/lain"
+
+      Tesla.Mock.mock(fn
+        %{
+          method: :get,
+          url: ^status_url
+        } ->
+          %Tesla.Env{
+            status: 200,
+            body: status,
+            headers: [{"content-type", "application/activity+json"}]
+          }
+
+        %{
+          method: :get,
+          url: ^actor
+        } ->
+          %Tesla.Env{
+            status: 200,
+            body: user,
+            headers: [{"content-type", "application/activity+json"}]
+          }
+      end)
+
+      data = %{
+        "@context" => "https://www.w3.org/ns/activitystreams",
+        "actor" => actor,
+        "object" => status_url,
+        "target" => "https://example.com/users/lain/collections/featured",
+        "type" => "Add"
+      }
+
+      assert "ok" ==
+               conn
+               |> assign(:valid_signature, true)
+               |> put_req_header("content-type", "application/activity+json")
+               |> post("/inbox", data)
+               |> json_response(200)
+
+      ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
+      assert Activity.get_by_object_ap_id_with_object(data["object"])
+      user = User.get_cached_by_ap_id(data["actor"])
+      assert user.pinned_objects[data["object"]]
+
+      data = %{
+        "actor" => actor,
+        "object" => status_url,
+        "target" => "https://example.com/users/lain/collections/featured",
+        "type" => "Remove"
+      }
+
+      assert "ok" ==
+               conn
+               |> assign(:valid_signature, true)
+               |> put_req_header("content-type", "application/activity+json")
+               |> post("/inbox", data)
+               |> json_response(200)
+
+      ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
+      assert Activity.get_by_object_ap_id_with_object(data["object"])
+      user = refresh_record(user)
+      refute user.pinned_objects[data["object"]]
+    end
   end
 
   describe "/users/:nickname/inbox" do