From cdf576b951459b34873a916394eb786452b0fb67 Mon Sep 17 00:00:00 2001 From: Oneric Date: Mon, 17 Mar 2025 22:21:19 +0100 Subject: [PATCH 1/2] federation/in: fix activity addressing of Pleroma unlisted MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit While the object itself has the expected adressing for an "unlisted" post, we always use the Create activity’s adressing fields for permission checks. To avoid unintended effects on legacy objects we will continue to use the activity for access perm checks, but fix its addressing fields based on its object data. Ref: https://git.pleroma.social/pleroma/pleroma/-/issues/3323 --- .../web/activity_pub/transmogrifier.ex | 17 ++++++++++- .../transmogrifier/note_handling_test.exs | 28 +++++++++++++++++++ 2 files changed, 44 insertions(+), 1 deletion(-) diff --git a/lib/pleroma/web/activity_pub/transmogrifier.ex b/lib/pleroma/web/activity_pub/transmogrifier.ex index 9ed54fa6e..4469f3d49 100644 --- a/lib/pleroma/web/activity_pub/transmogrifier.ex +++ b/lib/pleroma/web/activity_pub/transmogrifier.ex @@ -118,7 +118,7 @@ defp normalise_addressing_public_list(%{} = map, [field | fields]) do Map.put(map, field, new_fval) else - map + Map.put(map, field, []) end normalise_addressing_public_list(map, fields) @@ -208,6 +208,20 @@ def fix_in_reply_to(%{"inReplyTo" => in_reply_to} = object, options) def fix_in_reply_to(object, _options), do: object + # Pleroma sends unlisted posts without addressing public scope in the enclosing activity + # but we only use the ativity for access perm cheks, see: + # https://git.pleroma.social/pleroma/pleroma/-/issues/3323 + defp fix_create_visibility(%{"type" => "Create", "object" => %{"cc" => occ}} = activity) do + acc = activity["cc"] + if Pleroma.Constants.as_public() in occ and not (Pleroma.Constants.as_public() in acc) do + Map.put(activity, "cc", [Pleroma.Constants.as_public() | acc]) + else + activity + end + end + + defp fix_create_visibility(activity), do: activity + def fix_quote_url(object, options \\ []) def fix_quote_url(%{"quoteUri" => quote_url} = object, options) @@ -513,6 +527,7 @@ defp handle_incoming_normalised( ) when objtype in ~w{Question Answer Audio Video Event Article Note Page} do fetch_options = Keyword.put(options, :depth, (options[:depth] || 0) + 1) + data = fix_create_visibility(data) object = data["object"] diff --git a/test/pleroma/web/activity_pub/transmogrifier/note_handling_test.exs b/test/pleroma/web/activity_pub/transmogrifier/note_handling_test.exs index 234a48990..e9271ce9a 100644 --- a/test/pleroma/web/activity_pub/transmogrifier/note_handling_test.exs +++ b/test/pleroma/web/activity_pub/transmogrifier/note_handling_test.exs @@ -17,6 +17,8 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier.NoteHandlingTest do import Mock import Pleroma.Factory + require Pleroma.Constants + setup_all do Tesla.Mock.mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end) :ok @@ -276,6 +278,32 @@ test "it ensures that address fields become lists" do refute is_nil(data["cc"]) end + test "it fixes Pleroma unlisted" do + # https://git.pleroma.social/pleroma/pleroma/-/issues/3323 + user1 = insert(:user) + user2 = insert(:user) + + data = + File.read!("test/fixtures/mastodon-post-activity.json") + |> Jason.decode!() + |> Map.put("actor", user1.ap_id) + |> Map.put("cc", []) + |> Map.put("to", [user2.ap_id, user1.follower_address]) + + object = + data["object"] + |> Map.put("attributedTo", user1.ap_id) + |> Map.put("cc", [Pleroma.Constants.as_public()]) + |> Map.put("to", [user2.ap_id, user1.follower_address]) + |> Map.put("id", user1.ap_id <> "/activities/12345678") + + data = Map.put(data, "object", object) + + {:ok, %Activity{} = activity} = Transmogrifier.handle_incoming(data) + + assert "unlisted" == Pleroma.Web.ActivityPub.Visibility.get_visibility(activity) + end + test "it strips internal likes" do data = File.read!("test/fixtures/mastodon-post-activity.json") From 0abe01be2e2c9c7b9e9ee46555508f5d8bbc16c2 Mon Sep 17 00:00:00 2001 From: Oneric Date: Mon, 17 Mar 2025 23:02:13 +0100 Subject: [PATCH 2/2] federation/in: always copy object addressing into its Create activity MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Since we later only consider the Create activity for access permission checks, but the semantically more sensible set of fields are the object’s. Changing the check itself to use the object may have unintended consequences on already existing legacy posts as the old code which processed it when it arrived may have never considered effects on the objects addressing fields. --- lib/pleroma/web/activity_pub/transmogrifier.ex | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/lib/pleroma/web/activity_pub/transmogrifier.ex b/lib/pleroma/web/activity_pub/transmogrifier.ex index 4469f3d49..55985d310 100644 --- a/lib/pleroma/web/activity_pub/transmogrifier.ex +++ b/lib/pleroma/web/activity_pub/transmogrifier.ex @@ -211,13 +211,12 @@ def fix_in_reply_to(object, _options), do: object # Pleroma sends unlisted posts without addressing public scope in the enclosing activity # but we only use the ativity for access perm cheks, see: # https://git.pleroma.social/pleroma/pleroma/-/issues/3323 - defp fix_create_visibility(%{"type" => "Create", "object" => %{"cc" => occ}} = activity) do - acc = activity["cc"] - if Pleroma.Constants.as_public() in occ and not (Pleroma.Constants.as_public() in acc) do - Map.put(activity, "cc", [Pleroma.Constants.as_public() | acc]) - else - activity - end + defp fix_create_visibility(%{"type" => "Create", "object" => %{} = object} = activity) do + activity + |> Map.put("to", object["to"]) + |> Map.put("cc", object["cc"]) + |> Map.put("bto", object["bto"]) + |> Map.put("bcc", object["bcc"]) end defp fix_create_visibility(activity), do: activity