diff --git a/changelog.d/move-refetch-target.fix b/changelog.d/move-refetch-target.fix new file mode 100644 index 000000000..1c200568e --- /dev/null +++ b/changelog.d/move-refetch-target.fix @@ -0,0 +1 @@ +Accept a Move when the cached remote target does not list the alias yet, by refetching the target once diff --git a/lib/pleroma/web/activity_pub/transmogrifier.ex b/lib/pleroma/web/activity_pub/transmogrifier.ex index 33ab62ca3..0d4b15288 100644 --- a/lib/pleroma/web/activity_pub/transmogrifier.ex +++ b/lib/pleroma/web/activity_pub/transmogrifier.ex @@ -799,7 +799,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do ) do with %User{} = origin_user <- User.get_cached_by_ap_id(origin_actor), {:ok, %User{} = target_user} <- User.get_or_fetch_by_ap_id(target_actor), - true <- origin_actor in target_user.also_known_as do + {:ok, %User{} = target_user} <- refetch_unless_aliased(target_user, origin_actor) do ActivityPub.move(origin_user, target_user, false) else _e -> :error @@ -1204,4 +1204,20 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do def maybe_fix_user_url(data), do: data def maybe_fix_user_object(data), do: maybe_fix_user_url(data) + + # A Move usually follows right after the alias was added, which the cached + # remote target may not have yet. + defp refetch_unless_aliased(%User{} = target, origin_ap_id, refetch? \\ true) do + cond do + origin_ap_id in target.also_known_as -> + {:ok, target} + + refetch? and not target.local -> + with {:ok, %User{} = target} <- User.fetch_by_ap_id(target.ap_id), + do: refetch_unless_aliased(target, origin_ap_id, false) + + true -> + :error + end + end end diff --git a/test/pleroma/web/activity_pub/transmogrifier_test.exs b/test/pleroma/web/activity_pub/transmogrifier_test.exs index bedb466a4..0e4a4c2db 100644 --- a/test/pleroma/web/activity_pub/transmogrifier_test.exs +++ b/test/pleroma/web/activity_pub/transmogrifier_test.exs @@ -147,6 +147,53 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do assert activity.data["type"] == "Move" end + test "it refetches a cached remote Move target that lacks the alias" do + old_user = insert(:user, local: false, ap_id: "https://old.example/users/old") + + new_user = + insert(:user, + local: false, + ap_id: "https://new.example/users/new", + nickname: "new@new.example", + last_refreshed_at: NaiveDateTime.utc_now() + ) + + actor = %{ + "@context" => "https://www.w3.org/ns/activitystreams", + "id" => new_user.ap_id, + "type" => "Person", + "preferredUsername" => "new", + "inbox" => new_user.ap_id <> "/inbox", + "outbox" => new_user.ap_id <> "/outbox", + "followers" => new_user.ap_id <> "/followers", + "following" => new_user.ap_id <> "/following", + "alsoKnownAs" => [old_user.ap_id] + } + + Tesla.Mock.mock(fn + %{method: :get, url: "https://new.example/users/new"} -> + %Tesla.Env{ + status: 200, + headers: [{"content-type", "application/activity+json"}], + body: Jason.encode!(actor) + } + + _ -> + %Tesla.Env{status: 404} + end) + + message = %{ + "@context" => "https://www.w3.org/ns/activitystreams", + "type" => "Move", + "actor" => old_user.ap_id, + "object" => old_user.ap_id, + "target" => new_user.ap_id + } + + assert {:ok, %Activity{data: %{"type" => "Move"}}} = Transmogrifier.handle_incoming(message) + assert old_user.ap_id in User.get_cached_by_ap_id(new_user.ap_id).also_known_as + end + test "it fixes both the Create and object contexts in a reply" do insert(:user, ap_id: "https://mk.absturztau.be/users/8ozbzjs3o8") insert(:user, ap_id: "https://p.helene.moe/users/helene")