Merge branch 'remote-fetcher-fixes' into 'develop'

Improve Remote Object Fetcher error handling, Oban

See merge request pleroma/pleroma!4230
This commit is contained in:
feld 2024-08-19 16:48:25 +00:00
commit 12d682c62f
5 changed files with 63 additions and 63 deletions

View file

@ -0,0 +1 @@
Remote Fetcher Worker recognizes more permanent failure errors

View file

@ -73,48 +73,20 @@ defmodule Pleroma.Object.Fetcher do
{:object, data, Object.normalize(activity, fetch: false)} do {:object, data, Object.normalize(activity, fetch: false)} do
{:ok, object} {:ok, object}
else else
{:allowed_depth, false} = e ->
log_fetch_error(id, e)
{:error, :allowed_depth}
{:containment, reason} = e ->
log_fetch_error(id, e)
{:error, reason}
{:transmogrifier, {:error, {:reject, reason}}} = e ->
log_fetch_error(id, e)
{:reject, reason}
{:transmogrifier, {:reject, reason}} = e ->
log_fetch_error(id, e)
{:reject, reason}
{:transmogrifier, reason} = e ->
log_fetch_error(id, e)
{:error, reason}
{:object, data, nil} ->
reinject_object(%Object{}, data)
{:normalize, object = %Object{}} -> {:normalize, object = %Object{}} ->
{:ok, object} {:ok, object}
{:fetch_object, %Object{} = object} -> {:fetch_object, %Object{} = object} ->
{:ok, object} {:ok, object}
{:fetch, {:error, reason}} = e -> {:object, data, nil} ->
log_fetch_error(id, e) reinject_object(%Object{}, data)
{:error, reason}
e -> e ->
log_fetch_error(id, e)
{:error, e}
end
end
defp log_fetch_error(id, error) do
Logger.metadata(object: id) Logger.metadata(object: id)
Logger.error("Object rejected while fetching #{id} #{inspect(error)}") Logger.error("Object rejected while fetching #{id} #{inspect(e)}")
e
end
end end
defp prepare_activity_params(data) do defp prepare_activity_params(data) do

View file

@ -13,17 +13,26 @@ defmodule Pleroma.Workers.RemoteFetcherWorker do
{:ok, _object} -> {:ok, _object} ->
:ok :ok
{:reject, reason} -> {:allowed_depth, false} ->
{:cancel, :allowed_depth}
{:containment, reason} ->
{:cancel, reason} {:cancel, reason}
{:error, :forbidden} -> {:transmogrifier, reason} ->
{:cancel, :forbidden} {:cancel, reason}
{:error, :not_found} -> {:fetch, {:error, :forbidden = reason}} ->
{:cancel, :not_found} {:cancel, reason}
{:error, :allowed_depth} -> {:fetch, {:error, :not_found = reason}} ->
{:cancel, :allowed_depth} {:cancel, reason}
{:fetch, {:error, {:content_type, _}} = reason} ->
{:cancel, reason}
{:fetch, {:error, reason}} ->
{:error, reason}
{:error, _} = e -> {:error, _} = e ->
e e

View file

@ -100,7 +100,7 @@ defmodule Pleroma.Object.FetcherTest do
test "it returns thread depth exceeded error if thread depth is exceeded" do test "it returns thread depth exceeded error if thread depth is exceeded" do
clear_config([:instance, :federation_incoming_replies_max_depth], 0) clear_config([:instance, :federation_incoming_replies_max_depth], 0)
assert {:error, :allowed_depth} = Fetcher.fetch_object_from_id(@ap_id, depth: 1) assert {:allowed_depth, false} = Fetcher.fetch_object_from_id(@ap_id, depth: 1)
end end
test "it fetches object if max thread depth is restricted to 0 and depth is not specified" do test "it fetches object if max thread depth is restricted to 0 and depth is not specified" do
@ -118,15 +118,18 @@ defmodule Pleroma.Object.FetcherTest do
describe "actor origin containment" do describe "actor origin containment" do
test "it rejects objects with a bogus origin" do test "it rejects objects with a bogus origin" do
{:error, _} = Fetcher.fetch_object_from_id("https://info.pleroma.site/activity.json") {:containment, :error} =
Fetcher.fetch_object_from_id("https://info.pleroma.site/activity.json")
end end
test "it rejects objects when attributedTo is wrong (variant 1)" do test "it rejects objects when attributedTo is wrong (variant 1)" do
{:error, _} = Fetcher.fetch_object_from_id("https://info.pleroma.site/activity2.json") {:containment, :error} =
Fetcher.fetch_object_from_id("https://info.pleroma.site/activity2.json")
end end
test "it rejects objects when attributedTo is wrong (variant 2)" do test "it rejects objects when attributedTo is wrong (variant 2)" do
{:error, _} = Fetcher.fetch_object_from_id("https://info.pleroma.site/activity3.json") {:containment, :error} =
Fetcher.fetch_object_from_id("https://info.pleroma.site/activity3.json")
end end
end end
@ -150,14 +153,14 @@ defmodule Pleroma.Object.FetcherTest do
clear_config([:mrf_keyword, :reject], ["yeah"]) clear_config([:mrf_keyword, :reject], ["yeah"])
clear_config([:mrf, :policies], [Pleroma.Web.ActivityPub.MRF.KeywordPolicy]) clear_config([:mrf, :policies], [Pleroma.Web.ActivityPub.MRF.KeywordPolicy])
assert {:reject, "[KeywordPolicy] Matches with rejected keyword"} == assert {:transmogrifier, {:reject, "[KeywordPolicy] Matches with rejected keyword"}} ==
Fetcher.fetch_object_from_id( Fetcher.fetch_object_from_id(
"http://mastodon.example.org/@admin/99541947525187367" "http://mastodon.example.org/@admin/99541947525187367"
) )
end end
test "it does not fetch a spoofed object uploaded on an instance as an attachment" do test "it does not fetch a spoofed object uploaded on an instance as an attachment" do
assert {:error, _} = assert {:fetch, {:error, {:content_type, "application/json"}}} =
Fetcher.fetch_object_from_id( Fetcher.fetch_object_from_id(
"https://patch.cx/media/03ca3c8b4ac3ddd08bf0f84be7885f2f88de0f709112131a22d83650819e36c2.json" "https://patch.cx/media/03ca3c8b4ac3ddd08bf0f84be7885f2f88de0f709112131a22d83650819e36c2.json"
) )

View file

@ -12,6 +12,7 @@ defmodule Pleroma.Workers.RemoteFetcherWorkerTest do
@deleted_object_two "https://deleted-410.example.com/" @deleted_object_two "https://deleted-410.example.com/"
@unauthorized_object "https://unauthorized.example.com/" @unauthorized_object "https://unauthorized.example.com/"
@depth_object "https://depth.example.com/" @depth_object "https://depth.example.com/"
@content_type_object "https://bad_content_type.example.com/"
describe "RemoteFetcherWorker" do describe "RemoteFetcherWorker" do
setup do setup do
@ -35,34 +36,48 @@ defmodule Pleroma.Workers.RemoteFetcherWorkerTest do
%Tesla.Env{ %Tesla.Env{
status: 200 status: 200
} }
%{method: :get, url: @content_type_object} ->
%Tesla.Env{
status: 200,
headers: [{"content-type", "application/json"}],
body: File.read!("test/fixtures/spoofed-object.json")
}
end) end)
end end
test "does not requeue a deleted object" do test "does not retry jobs for a deleted object" do
assert {:cancel, _} = [
RemoteFetcherWorker.perform(%Oban.Job{ %{"op" => "fetch_remote", "id" => @deleted_object_one},
args: %{"op" => "fetch_remote", "id" => @deleted_object_one} %{"op" => "fetch_remote", "id" => @deleted_object_two}
}) ]
|> Enum.each(fn job -> assert {:cancel, _} = perform_job(RemoteFetcherWorker, job) end)
end
test "does not retry jobs for an unauthorized object" do
assert {:cancel, _} = assert {:cancel, _} =
RemoteFetcherWorker.perform(%Oban.Job{ perform_job(RemoteFetcherWorker, %{
args: %{"op" => "fetch_remote", "id" => @deleted_object_two} "op" => "fetch_remote",
"id" => @unauthorized_object
}) })
end end
test "does not requeue an unauthorized object" do test "does not retry jobs for an an object that exceeded depth" do
assert {:cancel, _} =
RemoteFetcherWorker.perform(%Oban.Job{
args: %{"op" => "fetch_remote", "id" => @unauthorized_object}
})
end
test "does not requeue an object that exceeded depth" do
clear_config([:instance, :federation_incoming_replies_max_depth], 0) clear_config([:instance, :federation_incoming_replies_max_depth], 0)
assert {:cancel, _} = assert {:cancel, _} =
RemoteFetcherWorker.perform(%Oban.Job{ perform_job(RemoteFetcherWorker, %{
args: %{"op" => "fetch_remote", "id" => @depth_object, "depth" => 1} "op" => "fetch_remote",
"id" => @depth_object,
"depth" => 1
})
end
test "does not retry jobs for when object returns wrong content type" do
assert {:cancel, _} =
perform_job(RemoteFetcherWorker, %{
"op" => "fetch_remote",
"id" => @content_type_object
}) })
end end
end end