From eb361dd4568fca48f699df78e2b6ee0ce994f206 Mon Sep 17 00:00:00 2001 From: Oneric Date: Sat, 21 Mar 2026 00:00:00 +0000 Subject: [PATCH] webfinger/finger: only accept authority of query domain And permit refetching once(!) unless initial query was already designated as the canonical authority. (Only once to not get stuck in loops) Fixes an oversight in c80aec05de435db43b1e257069f222fc600ebdc9. The argument for why subjects from both authorities can be accepted hinged on the assumption that only paths under direct control of the domain operator are involved since both webfinger and host-meta are /.well-known/ paths. However, HTTP redirects or the LRDD schema inside the initial domains host-meta may point at _any_ path on another domain, including e.g. paths containing user uploads, thus enabling third-parties to illegitimately claim handles from urelated domains, _if_ the victim domain can be made to serve attacker-prepared JSON (e.g. via user uploads or (media) proxies). With trust being limited to initial domain and refetches we do not need to make guesses about whether and when being redirected to indicates authorisation of the final domain. It requires more fetch requests in no-FEP-2c59 setups, but makes it more robust. As a side effect current FEP-less Mastodon setups should happen to work. --- FEDERATION.md | 7 +- lib/pleroma/web/web_finger/finger.ex | 96 ++++-- test/pleroma/web/web_finger/finger_test.exs | 320 ++++++++++++++++++-- test/support/http_request_mock.ex | 18 +- test/support/http_request_mock_macros.ex | 24 +- 5 files changed, 388 insertions(+), 77 deletions(-) diff --git a/FEDERATION.md b/FEDERATION.md index 00ecf47dc..8934c0d4b 100644 --- a/FEDERATION.md +++ b/FEDERATION.md @@ -47,9 +47,10 @@ Akkoma strongly encourages ActivityPub implementations to include a FEP-2c59-compliant WebFinger backlink in their actor documents. Without FEP-2c59 and if different domains are used for ActivityPub and the Webfinger subject, -Akkoma relies on the presence of an host-meta LRDD template on the ActivityPub domain -or a HTTP redirect from the ActivityPub domain’s `/.well-known/webfinger` to an equivalent endpoint -on the domain used in the `subject` to discover and validate the domain association. +Akkoma relies on either the presence of an host-meta LRDD template on the ActivityPub domain +or a working WebFinger endpoint on the ActivityPub domain. Additionally all WebFinger endpoints +related to the ActivityPub and canonical WebFinger domain SHOULD also respond to queries about +an alternative acct URI constructed with the WebFinger domain passed as the resource. Without FEP-2c59 Akkoma may not become aware of changes to the preferred WebFinger `subject` domain for already discovered users. diff --git a/lib/pleroma/web/web_finger/finger.ex b/lib/pleroma/web/web_finger/finger.ex index c58ba0bff..406d133aa 100644 --- a/lib/pleroma/web/web_finger/finger.ex +++ b/lib/pleroma/web/web_finger/finger.ex @@ -154,11 +154,9 @@ defmodule Pleroma.Web.WebFinger.Finger do query_uri = make_finger_uri(domain, resource) resp = HTTP.Backoff.get(query_uri, [{"accept", "application/xrd+xml,application/jrd+json"}]) - with {:ok, %{url: resolved_uri, status: status} = resp_data} when status in 200..299 <- resp, + with {:ok, %{status: status} = resp_data} when status in 200..299 <- resp, {_, {:ok, parsed_data}} <- {:parse, parse_finger_response(resp_data)} do - resolved_domain = URI.parse(resolved_uri).host - - {:ok, resolved_domain, parsed_data} + {:ok, parsed_data} else {:ok, %Tesla.Env{} = env} -> {:error, map_fetch_error_reason(env)} {:parse, {:error, _} = error} -> error @@ -180,6 +178,47 @@ defmodule Pleroma.Web.WebFinger.Finger do end end + # Parsed WebFinger response data with the subject acct URI (and thus parsed_subject and normalised_subject) + # being verified to be authorised by the domain in which authority it lies + # (i.e. make sure an "acct:user@domain.example" is acknowlledged by domain.example) + # Does NOT verify the actor pointed at in "ap_id" agrees to the handle! + defp finger_data_with_domainauth(domain, resource, allow_refetch \\ true) do + with {:ok, %{"subject" => finger_subject} = preparsed_data} <- + finger_unverified_data(domain, resource), + handle <- normalise_webfinger_handle(finger_subject), + {nick_user, nick_domain} <- parse_handle(handle), + {_, false} <- {:no_domain, nick_domain == nil}, + # We cannot reliably accepted redirects as auth for the final domain. + # Thus only accepted result if matching the _initial_ domain, else allow a single refetch + # (traversing longer redirect chains may risk getting stuck in loops) + {_, true, _} <- + {:domainauth, nick_domain == domain, {nick_domain, resource_from_mention(handle)}} do + parsed_data = + preparsed_data + |> Map.put("normalised_subject", handle) + |> Map.put("parsed_subject", {nick_user, nick_domain}) + + {:ok, parsed_data} + else + {:domainauth, _, {nick_domain, new_resource}} -> + if allow_refetch do + finger_data_with_domainauth(nick_domain, new_resource) + else + Logger.error( + "Spoofed WebFinger response: #{inspect(domain)} responded with subject from #{inspect(nick_domain)} when no alias was expected!" + ) + + {:error, :finger_domain_spoof} + end + + {:no_domain, _} -> + {:error, :no_domain} + + error -> + error + end + end + @doc """ Discovers and verifies the WebFinger handle of an ActivityPub actor for use as a nickname. If the actor or instance does not use WebFinger or just temporarily unavailable no value @@ -199,15 +238,10 @@ defmodule Pleroma.Web.WebFinger.Finger do with {_, false} <- {:no_domain, domain == nil || ap_domain == nil}, {_, false} <- {:matching_domain, domain == ap_domain}, - # We check for an exact match to the preferred handle which will ALWAYS - # belong to the initial query domain, thus we do not need to consider the final domain here. - # If the query domain delegates to another domain via host-meta or HTTP redirects on - # ./well-known/ paths (which ought to be directly controlled by the operator), - # this clearly indicates consent of the query domain to allow the final domain to manage this data - {_, {:ok, _, %{"ap_id" => fingered_ap_id, "subject" => finger_subject}}} <- - {:query, finger_unverified_data(domain, ap_id)}, + # Since we already query the preferred domain, no refetches ought to be necessary + {_, {:ok, %{"ap_id" => fingered_ap_id, "normalised_subject" => finger_handle}}} <- + {:query, finger_data_with_domainauth(domain, ap_id, false)}, {_, false} <- {:fingered_data_mismatch, ap_id != fingered_ap_id}, - finger_handle <- normalise_webfinger_handle(finger_subject), {_, false} <- {:fingered_data_mismatch, preferred_handle != finger_handle} do {:ok, preferred_handle} else @@ -226,18 +260,15 @@ defmodule Pleroma.Web.WebFinger.Finger do ap_domain = URI.parse(ap_id).host with {_, false} <- {:no_domain, ap_domain == nil}, - {_, {:ok, finger_domain, %{"ap_id" => fingered_ap_id, "subject" => finger_subject}}} <- - {:query, finger_unverified_data(ap_domain, ap_id)}, + {_, + {:ok, + %{ + "ap_id" => fingered_ap_id, + "normalised_subject" => handle, + "parsed_subject" => {nick_user, _} + }}} <- + {:query, finger_data_with_domainauth(ap_domain, ap_id)}, {_, false} <- {:fingered_data_mismatch, fingered_ap_id != ap_id}, - handle <- normalise_webfinger_handle(finger_subject), - {nick_user, nick_domain} <- parse_handle(handle), - # Mastodon in its infinite wisdom encourages setups for custom WebFinger domains, - # such that the actual WebFinger response is _never_ served directly from the domain used in handles. - # Unlike in domain authority checks for AP IDs, here only fixed /.well-known URLs are queried, - # thus a redirect on this endpoint can be considered an approval from the redirecting domain - # (but not the redirected-to domain!) and it should be safe to accept both domain authorities here. - {_, false} <- - {:finger_domain_spoof, nick_domain != finger_domain && nick_domain != ap_domain}, ap_name <- actor_data["preferredUsername"], {_, false} <- {:fingered_data_mismatch, ap_name != nil && ap_name != nick_user} do {:ok, handle} @@ -305,16 +336,17 @@ defmodule Pleroma.Web.WebFinger.Finger do resource = resource_from_mention(mention_handle) with {_, false} <- {:invalid_handle, qname == nil || qdomain == nil}, - {_, {:ok, finger_domain, %{"ap_id" => fingered_ap_id, "subject" => finger_subject}}} <- - {:query, finger_unverified_data(qdomain, resource)}, - handle <- normalise_webfinger_handle(finger_subject), - {nick_user, nick_domain} <- parse_handle(handle), - # see comment in finger_actor for why both domains can and need to be accepted - {_, false} <- - {:finger_domain_spoof, nick_domain != finger_domain && nick_domain != qdomain}, + {_, + {:ok, + %{ + "ap_id" => fingered_ap_id, + "normalised_subject" => handle, + "parsed_subject" => {nick_user, nick_domain} + }}} <- + {:query, finger_data_with_domainauth(qdomain, resource)}, {_, {:ok, data}} <- {:fetch, Fetcher.fetch_and_contain_remote_object_from_id(fingered_ap_id)} do - verify_ap_data_from_finger(data, handle, finger_domain, nick_user) + verify_ap_data_from_finger(data, handle, nick_domain, nick_user) else {:query, error} -> error {:fetch, error} -> error @@ -339,7 +371,7 @@ defmodule Pleroma.Web.WebFinger.Finger do end with {_, domain} when is_binary(domain) <- {:domain, domain}, - {:ok, _, data} <- finger_unverified_data(domain, resource) do + {:ok, data} <- finger_unverified_data(domain, resource) do {:ok, data} else {:domain, _} -> {:error, :no_domain} diff --git a/test/pleroma/web/web_finger/finger_test.exs b/test/pleroma/web/web_finger/finger_test.exs index bdcdab6d6..4b251fb7a 100644 --- a/test/pleroma/web/web_finger/finger_test.exs +++ b/test/pleroma/web/web_finger/finger_test.exs @@ -56,11 +56,17 @@ defmodule Pleroma.Web.WebFinger.FingerTest do end describe "finger_mention/1" do - test "accepts content in authority of final domain" do + test "follows subjects to canonical domain to verify" do # Not FEP-2c59, but otherwise one possible sane setup Tesla.Mock.mock(fn - %{url: "https://fedi.example.com/.well-known/webfinger?resource=" <> rsrc} - when rsrc in ["acct:user@fedi.example.com", "https://fedi.example.com/users/user"] -> + %{url: url} + when url in [ + "https://fedi.example.com/.well-known/webfinger?resource=" <> + "acct:user@fedi.example.com", + "https://fedi.example.com/.well-known/webfinger?resource=" <> + "https://fedi.example.com/users/user", + "https://example.com/.well-known/webfinger?resource=" <> "acct:user@example.com" + ] -> {:ok, %Tesla.Env{ status: 200, @@ -97,10 +103,110 @@ defmodule Pleroma.Web.WebFinger.FingerTest do {:ok, "user@example.com", _} = Finger.finger_mention("user@fedi.example.com") end + test "rejects spoof attempt via redirect to untrusted cross-domain path when handle does not exist on authorative domain" do + Tesla.Mock.mock(fn + %{ + url: + "https://stinkyplace.example/.well-known/webfinger?resource=acct:doppelgaenger@stinkyplace.example" + } -> + {:ok, + %Tesla.Env{ + status: 200, + body: + File.read!("test/fixtures/webfinger/pleroma-webfinger.json") + |> String.replace("{{domain}}", "shinyplace.example") + |> String.replace("{{nickname}}", "doppelgaenger") + |> String.replace("{{subdomain}}", "stinkyplace.example"), + headers: [{"content-type", "application/jrd+json"}], + url: "https://shinyplace.example/user-uploads/123/webfinger.json" + }} + + %{url: url} + when url in [ + "https://stinkyplace.example/.well-known/host-meta", + "https://shinyplace.example/.well-known/host-meta", + "https://shinyplace.example/.well-known/webfinger?resource=acct:doppelgaenger@shinyplace.example" + ] -> + {:ok, %Tesla.Env{status: 404, url: url}} + end) + + assert {:error, :not_found} = + Finger.finger_mention("@doppelgaenger@stinkyplace.example") + end + + test "only uses data from authorative domain when refetching" do + Tesla.Mock.mock(fn + %{ + url: + "https://stinkyplace.example/.well-known/webfinger?resource=acct:doppelgaenger@stinkyplace.example" + } -> + {:ok, + %Tesla.Env{ + status: 200, + body: + File.read!("test/fixtures/webfinger/pleroma-webfinger.json") + |> String.replace("{{domain}}", "shinyplace.example") + |> String.replace("{{nickname}}", "doppelgaenger") + |> String.replace("{{subdomain}}", "stinkyplace.example"), + headers: [{"content-type", "application/jrd+json"}], + url: "https://shinyplace.example/user-uploads/123/webfinger.json" + }} + + %{ + url: + "https://shinyplace.example/.well-known/webfinger?resource=acct:doppelgaenger@shinyplace.example" = + url + } -> + {:ok, + %Tesla.Env{ + status: 200, + body: + File.read!("test/fixtures/webfinger/pleroma-webfinger.json") + |> String.replace("{{domain}}", "shinyplace.example") + |> String.replace("{{nickname}}", "doppelgaenger") + |> String.replace("{{subdomain}}", "shinyplace.example"), + headers: [{"content-type", "application/jrd+json"}], + url: url + }} + + %{url: "https://shinyplace.example/users/doppelgaenger" = url} -> + {:ok, + %Tesla.Env{ + status: 200, + url: url, + headers: [{"content-type", "application/activity+json"}], + body: """ + { + "id": "#{url}", + "type": "Service", + "inbox": "#{url}/inbox", + "outbox": "#{url}/inbox" + } + """ + }} + + %{url: url} + when url in [ + "https://stinkyplace.example/.well-known/host-meta", + "https://shinyplace.example/.well-known/host-meta" + ] -> + {:ok, %Tesla.Env{status: 404, url: url}} + end) + + {:ok, "doppelgaenger@shinyplace.example", + %{"id" => "https://shinyplace.example/users/doppelgaenger"}} = + Finger.finger_mention("@doppelgaenger@stinkyplace.example") + end + test "accepts content in authority of query domain" do # Early 2026 Mastodon style Tesla.Mock.mock(fn - %{url: "https://example.com/.well-known/webfinger?resource=acct:user@example.com"} -> + %{url: url} + when url in [ + "https://example.com/.well-known/webfinger?resource=acct:user@example.com", + "https://fedi.example.com/.well-known/webfinger?resource=acct:user@example.com", + "https://fedi.example.com/.well-known/webfinger?resource=https://fedi.example.com/users/user" + ] -> {:ok, %Tesla.Env{ status: 200, @@ -114,11 +220,15 @@ defmodule Pleroma.Web.WebFinger.FingerTest do |> String.replace("{{subdomain}}", "fedi.example.com") }} - %{url: "https://fedi.example.com/.well-known/host-meta"} -> + %{url: url} + when url in [ + "https://example.com/.well-known/host-meta", + "https://fedi.example.com/.well-known/host-meta" + ] -> {:ok, %Tesla.Env{ status: 404, - url: "https://example.com/.well-known/host-meta" + url: url }} %{url: "https://fedi.example.com/users/user"} -> @@ -137,7 +247,7 @@ defmodule Pleroma.Web.WebFinger.FingerTest do {:ok, "user@example.com", _} = Finger.finger_mention("user@example.com") end - test "errors when being served content from unrelated third-party domain" do + test "errors when being served content from unrelated third-party domain which is not registered" do Tesla.Mock.mock(fn %{url: "https://example.com/.well-known/webfinger?resource=acct:user@example.com"} -> {:ok, @@ -152,15 +262,19 @@ defmodule Pleroma.Web.WebFinger.FingerTest do |> String.replace("{{subdomain}}", "fedi.example.org") }} - %{url: "https://example.com/.well-known/host-meta"} -> + %{url: url} + when url in [ + "https://example.com/.well-known/host-meta", + "https://shinyplace.example/.well-known/webfinger?resource=acct:user@shinyplace.example" + ] -> {:ok, %Tesla.Env{ status: 404, - url: "https://example.com/.well-known/host-meta" + url: url }} end) - {:error, :finger_domain_spoof} = Finger.finger_mention("user@example.com") + {:error, :not_found} = Finger.finger_mention("user@example.com") end test "should use the webfinger property to look up the webfinger data for an actor" do @@ -207,11 +321,13 @@ defmodule Pleroma.Web.WebFinger.FingerTest do {:ok, "user@example.com", _data} = Finger.finger_mention("@user@example.com") end - test "allows HTTP redirects to serve as webfinger domain delegation" do + test "works with HTTP redirects on .well-known webfinger path" do Tesla.Mock.mock(fn - %{ - url: "https://example.com/.well-known/webfinger?resource=acct:user@example.com" - } -> + %{url: url} + when url in [ + "https://example.com/.well-known/webfinger?resource=acct:user@example.com", + "https://somewhere-else.com/.well-known/webfinger?resource=acct:another-user@somewhere-else.com" + ] -> {:ok, %Tesla.Env{ status: 200, @@ -252,11 +368,13 @@ defmodule Pleroma.Web.WebFinger.FingerTest do {:ok, "another-user@somewhere-else.com", _data} = Finger.finger_mention("@user@example.com") end - test "should reject a cross-domain webfinger if the final actor has an incorrect webfinger property" do + test "should reject a cross-domain webfinger if the final actor has an incorrect webfinger property even if domain agrees to handle" do Tesla.Mock.mock(fn - %{ - url: "https://example.com/.well-known/webfinger?resource=acct:user@example.com" - } -> + %{url: url} + when url in [ + "https://example.com/.well-known/webfinger?resource=acct:user@example.com", + "https://somewhere-else.com/.well-known/webfinger?resource=acct:another-user@somewhere-else.com" + ] -> {:ok, %Tesla.Env{ status: 200, @@ -300,9 +418,11 @@ defmodule Pleroma.Web.WebFinger.FingerTest do test "should refetch the initial actor if no backlink exists on the final actor" do Tesla.Mock.mock(fn # first, the initial webfinger we fetch points to somewhere-else.com - %{ - url: "https://example.com/.well-known/webfinger?resource=acct:user@example.com" - } -> + %{url: url} + when url in [ + "https://example.com/.well-known/webfinger?resource=acct:user@example.com", + "https://somewhere-else.com/.well-known/webfinger?resource=acct:another-user@somewhere-else.com" + ] -> {:ok, %Tesla.Env{ status: 200, @@ -545,7 +665,8 @@ defmodule Pleroma.Web.WebFinger.FingerTest do |> String.replace("{{nickname}}", "user") |> String.replace("{{subdomain}}", "social.example.com"), headers: [{"content-type", "application/jrd+json"}], - url: "https://oops-this-was-a-redirect/somewhere" + url: + "https://oops-this-was-a-redirect-to-a-trusted-path/.well-known/my-static-webfinger" }} %{url: "https://example.com/.well-known/host-meta"} -> @@ -566,6 +687,43 @@ defmodule Pleroma.Web.WebFinger.FingerTest do }) end + test "refuses domain mismatches early with webfinger backlink" do + Tesla.Mock.mock(fn + # we should finger the webfinger property + %{ + url: + "https://example.com/.well-known/webfinger?resource=https://social.example.com/users/user" + } -> + {:ok, + %Tesla.Env{ + status: 200, + body: + File.read!("test/fixtures/webfinger/pleroma-webfinger.json") + |> String.replace("{{domain}}", "broken.example.com") + |> String.replace("{{nickname}}", "user") + |> String.replace("{{subdomain}}", "social.example.com"), + headers: [{"content-type", "application/jrd+json"}], + url: "https://broken.example.com/.well-known/webfinger" + }} + + %{url: "https://example.com/.well-known/host-meta"} -> + {:ok, + %Tesla.Env{ + status: 200, + url: "https://example.com/.well-known/host-meta", + body: + File.read!("test/fixtures/webfinger/masto-host-meta.xml") + |> String.replace("{{domain}}", "example.com") + }} + end) + + assert {:error, :finger_domain_spoof} = + Finger.finger_actor(%{ + "id" => "https://social.example.com/users/user", + "webfinger" => "user@example.com" + }) + end + test "can discover nick from WebFinger query alone if actor contains no hints" do Tesla.Mock.mock(fn # we should finger the ID directly @@ -644,6 +802,101 @@ defmodule Pleroma.Web.WebFinger.FingerTest do "id" => "https://example.com/users/user" }) end + + require Logger + + test "enusures final WebFinger response actually links back to inital actor (with FEP-2c59)" do + Tesla.Mock.mock(fn + %{ + url: + "https://shinyplace.example/.well-known/webfinger?resource=https://example.com/users/user" = + url + } -> + {:ok, + %Tesla.Env{ + status: 200, + body: + File.read!("test/fixtures/webfinger/masto-webfinger.json") + |> String.replace("{{domain}}", "shinyplace.example") + |> String.replace("{{nickname}}", "user") + |> String.replace("{{subdomain}}", "example.com") + |> String.replace("{{apid}}", "https://example.com/users/not-this-user"), + headers: [{"content-type", "application/jrd+json"}], + url: url + }} + + %{url: "https://shinyplace.example/.well-known/host-meta" = url} -> + {:ok, + %Tesla.Env{ + status: 200, + url: url, + body: + File.read!("test/fixtures/webfinger/masto-host-meta.xml") + |> String.replace("{{domain}}", "shinyplace.example") + }} + end) + + assert {:error, :fingered_data_mismatch} = + Finger.finger_actor(%{ + "id" => "https://example.com/users/user", + "webfinger" => "user@shinyplace.example" + }) + end + + test "enusures final WebFinger response actually links back to inital acotr (without FEP-2c59)" do + Tesla.Mock.mock(fn + %{ + url: + "https://example.com/.well-known/webfinger?resource=https://example.com/users/user" = + url + } -> + {:ok, + %Tesla.Env{ + status: 200, + body: + File.read!("test/fixtures/webfinger/masto-webfinger.json") + # Indicates different WebFinger domain is preferred + |> String.replace("{{domain}}", "shinyplace.example") + |> String.replace("{{nickname}}", "user") + |> String.replace("{{subdomain}}", "example.com") + # This still matches the initially fingered actor + |> String.replace("{{apid}}", "https://example.com/users/user"), + headers: [{"content-type", "application/jrd+json"}], + url: url + }} + + %{ + url: + "https://shinyplace.example/.well-known/webfinger?resource=acct:user@shinyplace.example" = + url + } -> + {:ok, + %Tesla.Env{ + status: 200, + body: + File.read!("test/fixtures/webfinger/masto-webfinger.json") + |> String.replace("{{domain}}", "shinyplace.example") + |> String.replace("{{nickname}}", "user") + |> String.replace("{{subdomain}}", "shinyplace.example") + # different AP id than initially fingered actor we’re trying to enrich here! + |> String.replace("{{apid}}", "https://shinyplace.com/users/user"), + headers: [{"content-type", "application/jrd+json"}], + url: url + }} + + %{url: url} + when url in [ + "https://example.com/.well-known/host-meta", + "https://shinyplace.example/.well-known/host-meta" + ] -> + {:ok, %Tesla.Env{status: 404, url: url}} + end) + + assert {:error, :fingered_data_mismatch} = + Finger.finger_actor(%{ + "id" => "https://example.com/users/user" + }) + end end describe "finger_raw_data/1" do @@ -803,10 +1056,27 @@ defmodule Pleroma.Web.WebFinger.FingerTest do Tesla.Mock.json(fake_webfinger, url: url) - %{url: "https://bad.com/.well-known/host-meta"} -> - {:ok, %Tesla.Env{status: 404}} + # the AP id from fake WebFInger response + %{url: "https://bad.com/webfingertest" = url} -> + {:ok, + %Tesla.Env{ + status: 200, + url: url, + headers: [{"content-type", "application/activity+json"}], + body: """ + { + "id": "#{url}", + "type": "Service", + "inbox": "#{url}/inbox", + "outbox": "#{url}/inbox" + } + """ + }} + + %{url: url} -> + {:ok, %Tesla.Env{status: 404, url: url}} end) - assert {:error, :finger_domain_spoof} = Finger.finger_mention("meanie@bad.com") + assert {:error, :not_found} = Finger.finger_mention("meanie@bad.com") end end diff --git a/test/support/http_request_mock.ex b/test/support/http_request_mock.ex index c4aa41d44..cdf8b5e94 100644 --- a/test/support/http_request_mock.ex +++ b/test/support/http_request_mock.ex @@ -1715,7 +1715,11 @@ defmodule HttpRequestMock do end Macros.mock_masto_webfinger( - "https://sub.mastodon.example/.well-known/webfinger?resource=acct:a@mastodon.example", + [ + "https://sub.mastodon.example/.well-known/webfinger?resource=https://sub.mastodon.example/users/a", + "https://sub.mastodon.example/.well-known/webfinger?resource=acct:a@mastodon.example", + "https://mastodon.example/.well-known/webfinger?resource=acct:a@mastodon.example" + ], "a", "mastodon.example", "sub.mastodon.example" @@ -1774,12 +1778,12 @@ defmodule HttpRequestMock do }} end - def get( - "https://sub.pleroma.example/.well-known/webfinger?resource=acct:a@pleroma.example" = url, - _, - _, - _ - ) do + def get(url, _, _, _) + when url in [ + "https://sub.pleroma.example/.well-known/webfinger?resource=https://sub.pleroma.example/users/a", + "https://sub.pleroma.example/.well-known/webfinger?resource=acct:a@pleroma.example", + "https://pleroma.example/.well-known/webfinger?resource=acct:a@pleroma.example" + ] do {:ok, %Tesla.Env{ status: 200, diff --git a/test/support/http_request_mock_macros.ex b/test/support/http_request_mock_macros.ex index b5091f5c4..e8946ddad 100644 --- a/test/support/http_request_mock_macros.ex +++ b/test/support/http_request_mock_macros.ex @@ -3,16 +3,20 @@ # SPDX-License-Identifier: AGPL-3.0-only defmodule HttpRequestMockMacros do - defmacro mock_masto_webfinger(url, nick, webfinger_domain, ap_domain \\ nil, ap_id \\ nil) do - quote do - def get(unquote(url) = url, _, _, _) do - webfinger_response_masto( - url, - unquote(nick), - unquote(webfinger_domain), - unquote(ap_domain), - unquote(ap_id) - ) + defmacro mock_masto_webfinger(urls, nick, webfinger_domain, ap_domain \\ nil, ap_id \\ nil) do + urls = if is_binary(urls), do: [urls], else: urls + + for url <- urls do + quote do + def get(unquote(url) = url, _, _, _) do + webfinger_response_masto( + url, + unquote(nick), + unquote(webfinger_domain), + unquote(ap_domain), + unquote(ap_id) + ) + end end end end