From cd8e6a4235fe96359e03f7251e5dc874ce930577 Mon Sep 17 00:00:00 2001 From: Oneric Date: Wed, 18 Dec 2024 01:34:33 +0100 Subject: [PATCH] transmogrifier: gracefully ignore duplicated object deletes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The object lookup is later repeated in the validator, but due to caching shouldn't incur any noticeable performance impact. It’s actually preferable to check here, since it avoids the otherwise occuring user lookup and overhead from starting and aborting a transaction --- lib/pleroma/object.ex | 5 +++++ lib/pleroma/web/activity_pub/transmogrifier.ex | 18 ++++++++++++++---- 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/lib/pleroma/object.ex b/lib/pleroma/object.ex index 5d84bb286..40c1534b9 100644 --- a/lib/pleroma/object.ex +++ b/lib/pleroma/object.ex @@ -215,6 +215,11 @@ def get_cached_by_ap_id(ap_id) do end end + # Intentionally accepts non-Object arguments! + @spec is_tombstone_object?(term()) :: boolean() + def is_tombstone_object?(%Object{data: %{"type" => "Tombstone"}}), do: true + def is_tombstone_object?(_), do: false + def make_tombstone(%Object{data: %{"id" => id, "type" => type}}, deleted \\ DateTime.utc_now()) do %ObjectTombstone{ id: id, diff --git a/lib/pleroma/web/activity_pub/transmogrifier.ex b/lib/pleroma/web/activity_pub/transmogrifier.ex index 973f950fb..01f39e985 100644 --- a/lib/pleroma/web/activity_pub/transmogrifier.ex +++ b/lib/pleroma/web/activity_pub/transmogrifier.ex @@ -556,19 +556,29 @@ defp handle_incoming_normalised( %{"type" => "Delete"} = data, _options ) do - with {:ok, activity, _} <- - Pipeline.common_pipeline(data, local: false) do + oid_result = ObjectValidators.ObjectID.cast(data["object"]) + + with {_, {:ok, object_id}} <- {:object_id, oid_result}, + object <- Object.get_cached_by_ap_id(object_id), + {_, false} <- {:tombstone, Object.is_tombstone_object?(object) && !data["actor"]}, + {:ok, activity, _} <- Pipeline.common_pipeline(data, local: false) do {:ok, activity} else + {:object_id, _} -> + {:error, {:validate, "Invalid object id: #{data["object"]}"}} + + {:tombstone, true} -> + {:error, :ignore} + {:error, {:validate, {:error, %Ecto.Changeset{errors: errors}}}} = e -> if errors[:object] == {"can't find object", []} do # Check if we have a create activity for this # (e.g. from a db prune without --prune-activities) - # We'd still like to process side effects so insert a tombstone and retry + # We'd still like to process side effects so insert a fake tombstone and retry + # (real tombstones from Object.delete do not have an actor field) with {:ok, object_id} <- ObjectValidators.ObjectID.cast(data["object"]), %Activity{data: %{"actor" => actor}} <- Activity.create_by_object_ap_id(object_id) |> Repo.one(), - # We have one, insert a tombstone and retry {:ok, tombstone_data, _} <- Builder.tombstone(actor, object_id), {:ok, _tombstone} <- Object.create(tombstone_data) do handle_incoming(data)