From 366065c0f635fc14b6b086783ffb844485f73d52 Mon Sep 17 00:00:00 2001 From: Oneric Date: Sat, 11 Jan 2025 21:25:00 +0100 Subject: [PATCH] fetcher: split out core object fetch validation To allow reuse for adapted key validation logic --- lib/pleroma/object/fetcher.ex | 41 +++++++++++++++++++++++++---------- 1 file changed, 29 insertions(+), 12 deletions(-) diff --git a/lib/pleroma/object/fetcher.ex b/lib/pleroma/object/fetcher.ex index 11ed57ed5..0f129f318 100644 --- a/lib/pleroma/object/fetcher.ex +++ b/lib/pleroma/object/fetcher.ex @@ -265,6 +265,28 @@ def fetch_and_contain_remote_object_from_id(%{"id" => id}, is_ap_id), def fetch_and_contain_remote_object_from_id(id, is_ap_id) when is_binary(id) do Logger.debug("Fetching object #{id} via AP [ap_id=#{is_ap_id}]") + fetch_and_contain_remote_ap_doc( + id, + is_ap_id, + fn final_uri, data -> {Containment.contain_id_to_fetch(final_uri, data), data["id"]} end + ) + end + + def fetch_and_contain_remote_object_from_id(_id, _is_ap_id), + do: {:error, :invalid_id} + + # Fetches an AP document and performing variable security checks on it. + # + # Note that the received documents "id" matching the final host domain + # is always enforced before the custom ID check runs. + @spec fetch_and_contain_remote_ap_doc( + String.t(), + boolean(), + (String.t(), Map.t() -> {:ok | :error, String.t() | term()}) + ) :: {:ok, Map.t()} | {:reject, term()} | {:error, term()} + defp fetch_and_contain_remote_ap_doc(id, is_ap_id, verify_id) do + Logger.debug("Dereferencing AP doc #{}") + with {:valid_uri_scheme, true} <- {:valid_uri_scheme, String.starts_with?(id, "http")}, %URI{} = uri <- URI.parse(id), {:mrf_reject_check, {:ok, nil}} <- @@ -277,7 +299,7 @@ def fetch_and_contain_remote_object_from_id(id, is_ap_id) when is_binary(id) do true <- !is_ap_id || final_id == id, {:ok, data} <- safe_json_decode(body), {_, :ok} <- {:containment, Containment.contain_origin(final_id, data)}, - {_, _, :ok} <- {:strict_id, data["id"], Containment.contain_id_to_fetch(final_id, data)} do + {_, {:ok, _}} <- {:strict_id, verify_id.(final_id, data)} do unless Instances.reachable?(final_id) do Instances.set_reachable(final_id) end @@ -289,14 +311,12 @@ def fetch_and_contain_remote_object_from_id(id, is_ap_id) when is_binary(id) do # Similarly keys, either use a fragment ID and are a subobjects or a distinct ID # but for compatibility are still a subobject presenting their owning actors ID at the toplevel. # Refetching _once_ from the listed id, should yield a strict match afterwards. - {:strict_id, ap_id, _} = e -> - case is_ap_id do - false -> - fetch_and_contain_remote_object_from_id(ap_id, true) - - true -> - log_fetch_error(id, e) - {:error, :id_mismatch} + {:strict_id, {_error, ap_id}} = e -> + if !is_ap_id and is_binary(ap_id) do + fetch_and_contain_remote_ap_doc(ap_id, true, verify_id) + else + log_fetch_error(id, e) + {:error, :id_mismatch} end {:mrf_reject_check, _} = e -> @@ -327,9 +347,6 @@ def fetch_and_contain_remote_object_from_id(id, is_ap_id) when is_binary(id) do end end - def fetch_and_contain_remote_object_from_id(_id, _is_ap_id), - do: {:error, :invalid_id} - # HOPEFULLY TEMPORARY # Basically none of our Tesla mocks in tests set the (supposed to # exist for Tesla proper) url parameter for their responses