From 1f2f7e044d1be1e56789ce01ce4e54dd86a74f36 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?marcin=20miko=C5=82ajczak?= Date: Wed, 22 May 2024 15:52:10 +0200 Subject: [PATCH 1/5] Revert "Webfinger: Allow managing account for subdomain" This reverts commit 84bb854056e406d5235dd442c28127891a8a8a86. --- lib/pleroma/web/web_finger.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pleroma/web/web_finger.ex b/lib/pleroma/web/web_finger.ex index e149d9247..a84a4351b 100644 --- a/lib/pleroma/web/web_finger.ex +++ b/lib/pleroma/web/web_finger.ex @@ -230,7 +230,7 @@ def finger(account) do defp validate_webfinger(url, %{"subject" => "acct:" <> acct} = data) do with %URI{host: request_host} <- URI.parse(url), [_name, acct_host] <- String.split(acct, "@"), - {_, true} <- {:hosts_match_or_subdomain, String.ends_with?(request_host, acct_host)} do + {_, true} <- {:hosts_match, acct_host == request_host} do {:ok, data} else _ -> {:error, {:webfinger_invalid, url, data}} From d0b18e338bfed05c6b2c4a8f5c63d865d9eb669c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?marcin=20miko=C5=82ajczak?= Date: Thu, 24 Aug 2023 00:37:39 +0200 Subject: [PATCH 2/5] Fix validate_webfinger when running a different domain for Webfinger MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: marcin mikołajczak --- lib/pleroma/application.ex | 3 ++- lib/pleroma/web/web_finger.ex | 30 ++++++++++++++++++++++-------- test/pleroma/user_test.exs | 4 ++-- 3 files changed, 26 insertions(+), 11 deletions(-) diff --git a/lib/pleroma/application.ex b/lib/pleroma/application.ex index 75154f94c..649bb11c8 100644 --- a/lib/pleroma/application.ex +++ b/lib/pleroma/application.ex @@ -162,7 +162,8 @@ defp cachex_children do expiration: chat_message_id_idempotency_key_expiration(), limit: 500_000 ), - build_cachex("rel_me", limit: 2500) + build_cachex("rel_me", limit: 2500), + build_cachex("host_meta", default_ttl: :timer.minutes(120), limit: 5000) ] end diff --git a/lib/pleroma/web/web_finger.ex b/lib/pleroma/web/web_finger.ex index a84a4351b..e653b3338 100644 --- a/lib/pleroma/web/web_finger.ex +++ b/lib/pleroma/web/web_finger.ex @@ -155,7 +155,16 @@ def get_template_from_xml(body) do end end + @cachex Pleroma.Config.get([:cachex, :provider], Cachex) def find_lrdd_template(domain) do + @cachex.fetch!(:host_meta_cache, domain, fn _ -> + {:commit, fetch_lrdd_template(domain)} + end) + rescue + e -> {:error, "Cachex error: #{inspect(e)}"} + end + + defp fetch_lrdd_template(domain) do # WebFinger is restricted to HTTPS - https://tools.ietf.org/html/rfc7033#section-9.1 meta_url = "https://#{domain}/.well-known/host-meta" @@ -168,7 +177,7 @@ def find_lrdd_template(domain) do end end - defp get_address_from_domain(domain, encoded_account) when is_binary(domain) do + defp get_address_from_domain(domain, "acct:" <> _ = encoded_account) when is_binary(domain) do case find_lrdd_template(domain) do {:ok, template} -> String.replace(template, "{uri}", encoded_account) @@ -178,6 +187,11 @@ defp get_address_from_domain(domain, encoded_account) when is_binary(domain) do end end + defp get_address_from_domain(domain, account) when is_binary(domain) do + encoded_account = URI.encode("acct:#{account}") + get_address_from_domain(domain, encoded_account) + end + defp get_address_from_domain(_, _), do: {:error, :webfinger_no_domain} @spec finger(String.t()) :: {:ok, map()} | {:error, any()} @@ -192,9 +206,7 @@ def finger(account) do URI.parse(account).host end - encoded_account = URI.encode("acct:#{account}") - - with address when is_binary(address) <- get_address_from_domain(domain, encoded_account), + with address when is_binary(address) <- get_address_from_domain(domain, account), {:ok, %{status: status, body: body, headers: headers}} when status in 200..299 <- HTTP.get( address, @@ -227,13 +239,15 @@ def finger(account) do end end - defp validate_webfinger(url, %{"subject" => "acct:" <> acct} = data) do - with %URI{host: request_host} <- URI.parse(url), - [_name, acct_host] <- String.split(acct, "@"), + defp validate_webfinger(request_url, %{"subject" => "acct:" <> acct = subject} = data) do + with [_name, acct_host] <- String.split(acct, "@"), + {_, url} <- {:address, get_address_from_domain(acct_host, subject)}, + %URI{host: request_host} <- URI.parse(request_url), + %URI{host: acct_host} <- URI.parse(url), {_, true} <- {:hosts_match, acct_host == request_host} do {:ok, data} else - _ -> {:error, {:webfinger_invalid, url, data}} + _ -> {:error, {:webfinger_invalid, request_url, data}} end end diff --git a/test/pleroma/user_test.exs b/test/pleroma/user_test.exs index 48391d871..7f1a8d893 100644 --- a/test/pleroma/user_test.exs +++ b/test/pleroma/user_test.exs @@ -877,7 +877,7 @@ test "gets an existing user by nickname starting with http" do setup do: clear_config([Pleroma.Web.WebFinger, :update_nickname_on_user_fetch], true) test "for mastodon" do - Tesla.Mock.mock(fn + Tesla.Mock.mock_global(fn %{url: "https://example.com/.well-known/host-meta"} -> %Tesla.Env{ status: 302, @@ -935,7 +935,7 @@ test "for mastodon" do end test "for pleroma" do - Tesla.Mock.mock(fn + Tesla.Mock.mock_global(fn %{url: "https://example.com/.well-known/host-meta"} -> %Tesla.Env{ status: 302, From 70cabbf6dc2f8440484f1e56d3aa2d27f65ee88f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?marcin=20miko=C5=82ajczak?= Date: Thu, 24 Aug 2023 01:09:00 +0200 Subject: [PATCH 3/5] Fix tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: marcin mikołajczak --- test/pleroma/user_test.exs | 102 +--------------- .../web_finger/web_finger_controller_test.exs | 5 + test/support/http_request_mock.ex | 114 ++++++++++++++++++ 3 files changed, 125 insertions(+), 96 deletions(-) diff --git a/test/pleroma/user_test.exs b/test/pleroma/user_test.exs index 7f1a8d893..5b7a65658 100644 --- a/test/pleroma/user_test.exs +++ b/test/pleroma/user_test.exs @@ -877,109 +877,19 @@ test "gets an existing user by nickname starting with http" do setup do: clear_config([Pleroma.Web.WebFinger, :update_nickname_on_user_fetch], true) test "for mastodon" do - Tesla.Mock.mock_global(fn - %{url: "https://example.com/.well-known/host-meta"} -> - %Tesla.Env{ - status: 302, - headers: [{"location", "https://sub.example.com/.well-known/host-meta"}] - } - - %{url: "https://sub.example.com/.well-known/host-meta"} -> - %Tesla.Env{ - status: 200, - body: - "test/fixtures/webfinger/masto-host-meta.xml" - |> File.read!() - |> String.replace("{{domain}}", "sub.example.com") - } - - %{url: "https://sub.example.com/.well-known/webfinger?resource=acct:a@example.com"} -> - %Tesla.Env{ - status: 200, - body: - "test/fixtures/webfinger/masto-webfinger.json" - |> File.read!() - |> String.replace("{{nickname}}", "a") - |> String.replace("{{domain}}", "example.com") - |> String.replace("{{subdomain}}", "sub.example.com"), - headers: [{"content-type", "application/jrd+json"}] - } - - %{url: "https://sub.example.com/users/a"} -> - %Tesla.Env{ - status: 200, - body: - "test/fixtures/webfinger/masto-user.json" - |> File.read!() - |> String.replace("{{nickname}}", "a") - |> String.replace("{{domain}}", "sub.example.com"), - headers: [{"content-type", "application/activity+json"}] - } - - %{url: "https://sub.example.com/users/a/collections/featured"} -> - %Tesla.Env{ - status: 200, - body: - File.read!("test/fixtures/users_mock/masto_featured.json") - |> String.replace("{{domain}}", "sub.example.com") - |> String.replace("{{nickname}}", "a"), - headers: [{"content-type", "application/activity+json"}] - } - end) - - ap_id = "a@example.com" + ap_id = "a@mastodon.example" {:ok, fetched_user} = User.get_or_fetch(ap_id) - assert fetched_user.ap_id == "https://sub.example.com/users/a" - assert fetched_user.nickname == "a@example.com" + assert fetched_user.ap_id == "https://sub.mastodon.example/users/a" + assert fetched_user.nickname == "a@mastodon.example" end test "for pleroma" do - Tesla.Mock.mock_global(fn - %{url: "https://example.com/.well-known/host-meta"} -> - %Tesla.Env{ - status: 302, - headers: [{"location", "https://sub.example.com/.well-known/host-meta"}] - } - - %{url: "https://sub.example.com/.well-known/host-meta"} -> - %Tesla.Env{ - status: 200, - body: - "test/fixtures/webfinger/pleroma-host-meta.xml" - |> File.read!() - |> String.replace("{{domain}}", "sub.example.com") - } - - %{url: "https://sub.example.com/.well-known/webfinger?resource=acct:a@example.com"} -> - %Tesla.Env{ - status: 200, - body: - "test/fixtures/webfinger/pleroma-webfinger.json" - |> File.read!() - |> String.replace("{{nickname}}", "a") - |> String.replace("{{domain}}", "example.com") - |> String.replace("{{subdomain}}", "sub.example.com"), - headers: [{"content-type", "application/jrd+json"}] - } - - %{url: "https://sub.example.com/users/a"} -> - %Tesla.Env{ - status: 200, - body: - "test/fixtures/webfinger/pleroma-user.json" - |> File.read!() - |> String.replace("{{nickname}}", "a") - |> String.replace("{{domain}}", "sub.example.com"), - headers: [{"content-type", "application/activity+json"}] - } - end) - - ap_id = "a@example.com" + ap_id = "a@pleroma.example" {:ok, fetched_user} = User.get_or_fetch(ap_id) - assert fetched_user.ap_id == "https://sub.example.com/users/a" - assert fetched_user.nickname == "a@example.com" + assert fetched_user.ap_id == "https://sub.pleroma.example/users/a" + assert fetched_user.nickname == "a@pleroma.example" end end diff --git a/test/pleroma/web/web_finger/web_finger_controller_test.exs b/test/pleroma/web/web_finger/web_finger_controller_test.exs index 80e072163..f501c6e44 100644 --- a/test/pleroma/web/web_finger/web_finger_controller_test.exs +++ b/test/pleroma/web/web_finger/web_finger_controller_test.exs @@ -56,6 +56,11 @@ test "Webfinger JRD" do end test "reach user on tld, while pleroma is running on subdomain" do + Pleroma.Web.Endpoint.config_change( + [{Pleroma.Web.Endpoint, url: [host: "sub.example.com"]}], + [] + ) + clear_config([Pleroma.Web.Endpoint, :url, :host], "sub.example.com") clear_config([Pleroma.Web.WebFinger, :domain], "example.com") diff --git a/test/support/http_request_mock.ex b/test/support/http_request_mock.ex index f656c9412..20e410424 100644 --- a/test/support/http_request_mock.ex +++ b/test/support/http_request_mock.ex @@ -1521,6 +1521,120 @@ def get("https://friends.grishka.me/users/1", _, _, _) do }} end + def get("https://mastodon.example/.well-known/host-meta", _, _, _) do + {:ok, + %Tesla.Env{ + status: 302, + headers: [{"location", "https://sub.mastodon.example/.well-known/host-meta"}] + }} + end + + def get("https://sub.mastodon.example/.well-known/host-meta", _, _, _) do + {:ok, + %Tesla.Env{ + status: 200, + body: + "test/fixtures/webfinger/masto-host-meta.xml" + |> File.read!() + |> String.replace("{{domain}}", "sub.mastodon.example") + }} + end + + def get( + "https://sub.mastodon.example/.well-known/webfinger?resource=acct:a@mastodon.example", + _, + _, + _ + ) do + {:ok, + %Tesla.Env{ + status: 200, + body: + "test/fixtures/webfinger/masto-webfinger.json" + |> File.read!() + |> String.replace("{{nickname}}", "a") + |> String.replace("{{domain}}", "mastodon.example") + |> String.replace("{{subdomain}}", "sub.mastodon.example"), + headers: [{"content-type", "application/jrd+json"}] + }} + end + + def get("https://sub.mastodon.example/users/a", _, _, _) do + {:ok, + %Tesla.Env{ + status: 200, + body: + "test/fixtures/webfinger/masto-user.json" + |> File.read!() + |> String.replace("{{nickname}}", "a") + |> String.replace("{{domain}}", "sub.mastodon.example"), + headers: [{"content-type", "application/activity+json"}] + }} + end + + def get("https://sub.mastodon.example/users/a/collections/featured", _, _, _) do + {:ok, + %Tesla.Env{ + status: 200, + body: + File.read!("test/fixtures/users_mock/masto_featured.json") + |> String.replace("{{domain}}", "sub.mastodon.example") + |> String.replace("{{nickname}}", "a"), + headers: [{"content-type", "application/activity+json"}] + }} + end + + def get("https://pleroma.example/.well-known/host-meta", _, _, _) do + {:ok, + %Tesla.Env{ + status: 302, + headers: [{"location", "https://sub.pleroma.example/.well-known/host-meta"}] + }} + end + + def get("https://sub.pleroma.example/.well-known/host-meta", _, _, _) do + {:ok, + %Tesla.Env{ + status: 200, + body: + "test/fixtures/webfinger/pleroma-host-meta.xml" + |> File.read!() + |> String.replace("{{domain}}", "sub.pleroma.example") + }} + end + + def get( + "https://sub.pleroma.example/.well-known/webfinger?resource=acct:a@pleroma.example", + _, + _, + _ + ) do + {:ok, + %Tesla.Env{ + status: 200, + body: + "test/fixtures/webfinger/pleroma-webfinger.json" + |> File.read!() + |> String.replace("{{nickname}}", "a") + |> String.replace("{{domain}}", "pleroma.example") + |> String.replace("{{subdomain}}", "sub.pleroma.example"), + headers: [{"content-type", "application/jrd+json"}] + }} + end + + def get("https://sub.pleroma.example/users/a", _, _, _) do + {:ok, + %Tesla.Env{ + status: 200, + body: + "test/fixtures/webfinger/pleroma-user.json" + |> File.read!() + |> String.replace("{{nickname}}", "a") + |> String.replace("{{domain}}", "sub.pleroma.example"), + headers: [{"content-type", "application/activity+json"}] + }} + end + def get(url, query, body, headers) do {:error, "Mock response not implemented for GET #{inspect(url)}, #{query}, #{inspect(body)}, #{inspect(headers)}"} From d536d58080d68598ca282263159f9d565a048642 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?marcin=20miko=C5=82ajczak?= Date: Wed, 22 May 2024 15:53:32 +0200 Subject: [PATCH 4/5] changelog MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: marcin mikołajczak --- changelog.d/webfinger-validation.fix | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/webfinger-validation.fix diff --git a/changelog.d/webfinger-validation.fix b/changelog.d/webfinger-validation.fix new file mode 100644 index 000000000..e64312666 --- /dev/null +++ b/changelog.d/webfinger-validation.fix @@ -0,0 +1 @@ +Fix validate_webfinger when running a different domain for Webfinger \ No newline at end of file From 5f1f574f01ea18170a228a8cb273e143d2f05ab4 Mon Sep 17 00:00:00 2001 From: Lain Soykaf Date: Wed, 22 May 2024 18:45:34 +0400 Subject: [PATCH 5/5] WebFingerControllerTest: Restore host after test. --- test/pleroma/web/web_finger/web_finger_controller_test.exs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/test/pleroma/web/web_finger/web_finger_controller_test.exs b/test/pleroma/web/web_finger/web_finger_controller_test.exs index f501c6e44..80e072163 100644 --- a/test/pleroma/web/web_finger/web_finger_controller_test.exs +++ b/test/pleroma/web/web_finger/web_finger_controller_test.exs @@ -56,11 +56,6 @@ test "Webfinger JRD" do end test "reach user on tld, while pleroma is running on subdomain" do - Pleroma.Web.Endpoint.config_change( - [{Pleroma.Web.Endpoint, url: [host: "sub.example.com"]}], - [] - ) - clear_config([Pleroma.Web.Endpoint, :url, :host], "sub.example.com") clear_config([Pleroma.Web.WebFinger, :domain], "example.com")