diff options
author | Haelwenn (lanodan) Monnier <contact@hacktivis.me> | 2023-09-07 01:33:35 +0200 |
---|---|---|
committer | Haelwenn (lanodan) Monnier <contact@hacktivis.me> | 2023-09-10 09:55:12 +0200 |
commit | beddda85475bd475249b7eed2442f823175fedf8 (patch) | |
tree | eb79b0c6ee2a6bd65b9f7ef6a6d1b5f03f9842f2 | |
parent | 44f272b9fae270330a17ddf2c8cce410e6c35f21 (diff) |
WebFinger: Serve /.well-known/host-meta.json requestscleanup-webfinger-xml
-rw-r--r-- | changelog.d/host-meta-json.add | 1 | ||||
-rw-r--r-- | docs/configuration/how_to_serve_another_domain_for_webfinger.md | 5 | ||||
-rw-r--r-- | lib/pleroma/web/router.ex | 1 | ||||
-rw-r--r-- | lib/pleroma/web/web_finger.ex | 80 | ||||
-rw-r--r-- | lib/pleroma/web/web_finger/web_finger_controller.ex | 8 | ||||
-rw-r--r-- | test/pleroma/web/web_finger/web_finger_controller_test.exs | 22 |
6 files changed, 107 insertions, 10 deletions
diff --git a/changelog.d/host-meta-json.add b/changelog.d/host-meta-json.add new file mode 100644 index 000000000..f145530db --- /dev/null +++ b/changelog.d/host-meta-json.add @@ -0,0 +1 @@ +- Add support for serving `/.well-known/host-meta.json` requests. If you use a different hostname between WebFinger and Pleroma, like say `user@domain.tld` pointing to `social.domain.tld`, you'll need to update your HTTP server configuration. diff --git a/docs/configuration/how_to_serve_another_domain_for_webfinger.md b/docs/configuration/how_to_serve_another_domain_for_webfinger.md index 5ae3e7943..b41c84850 100644 --- a/docs/configuration/how_to_serve_another_domain_for_webfinger.md +++ b/docs/configuration/how_to_serve_another_domain_for_webfinger.md @@ -21,7 +21,7 @@ Both account identifiers are unique and required for Pleroma. An important risk As said earlier, each Pleroma user has an `acct`: URI, which is used for discovery and authentication. When you add @user@example.org, a webfinger query is performed. This is done in two steps: -1. Querying `https://example.org/.well-known/host-meta` (where the domain of the URL matches the domain part of the `acct`: URI) to get information on how to perform the query. +1. Querying `https://example.org/.well-known/host-meta` or `https://example.org/.well-known/host-meta.json` (where the domain of the URL matches the domain part of the `acct`: URI) to get information on how to perform the query. This file will indeed contain a URL template of the form `https://example.org/.well-known/webfinger?resource={uri}` that will be used in the second step. 2. Fill the returned template with the `acct`: URI to be queried and perform the query: `https://example.org/.well-known/webfinger?resource=acct:user@example.org` @@ -57,6 +57,9 @@ With nginx, it would be as simple as adding: location = /.well-known/host-meta { return 301 https://pleroma.example.org$request_uri; } +location = /.well-known/host-meta.json { + return 301 https://pleroma.example.org$request_uri; +} ``` in example.org's server block. diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex index 6b9e158a3..16c9b75bb 100644 --- a/lib/pleroma/web/router.ex +++ b/lib/pleroma/web/router.ex @@ -920,6 +920,7 @@ defmodule Pleroma.Web.Router do pipe_through(:well_known) get("/host-meta", WebFinger.WebFingerController, :host_meta) + get("/host-meta.json", WebFinger.WebFingerController, :host_meta_json) get("/webfinger", WebFinger.WebFingerController, :webfinger) get("/nodeinfo", Nodeinfo.NodeinfoController, :schemas) end diff --git a/lib/pleroma/web/web_finger.ex b/lib/pleroma/web/web_finger.ex index f95dc2458..979cc09fd 100644 --- a/lib/pleroma/web/web_finger.ex +++ b/lib/pleroma/web/web_finger.ex @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2023 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.WebFinger do @@ -9,9 +9,13 @@ defmodule Pleroma.Web.WebFinger do alias Pleroma.Web.Federator.Publisher alias Pleroma.Web.XML alias Pleroma.XmlBuilder + require Jason require Logger + @cachex Pleroma.Config.get([:cachex, :provider], Cachex) + + # Technically not WebFinger but RFC6415 "Web Host Metadata" def host_meta do base_url = Endpoint.url() @@ -30,6 +34,21 @@ defmodule Pleroma.Web.WebFinger do |> XmlBuilder.to_doc() end + def host_meta_json do + base_url = Endpoint.url() + + %{ + "links" => [ + %{ + "rel" => "lrdd", + "type" => "application/jrd+json", + "template" => "#{base_url}/.well-known/webfinger?resource={uri}" + } + ] + } + end + + # WebFinger RFC7033 def webfinger(resource, fmt) when fmt in ["XML", "JSON"] do host = Pleroma.Web.Endpoint.host() @@ -68,7 +87,7 @@ defmodule Pleroma.Web.WebFinger do [user.ap_id | user.also_known_as] end - def represent_user(user, "JSON") do + defp represent_user(user, "JSON") do %{ "subject" => "acct:#{user.nickname}@#{domain()}", "aliases" => gather_aliases(user), @@ -76,7 +95,7 @@ defmodule Pleroma.Web.WebFinger do } end - def represent_user(user, "XML") do + defp represent_user(user, "XML") do aliases = user |> gather_aliases() @@ -146,7 +165,7 @@ defmodule Pleroma.Web.WebFinger do end end - def get_template_from_xml(body) do + defp get_template_from_xml(body) do xpath = "//Link[@rel='lrdd']/@template" with {:ok, doc} <- XML.parse_document(body), @@ -155,16 +174,61 @@ defmodule Pleroma.Web.WebFinger do end end - def find_lrdd_template(domain) do + defp cached_find_lrdd_template(domain) do + @cachex.fetch!(:webfinger_cache, "lrdd:#{domain}", fn _ -> + with {:ok, _} = template <- find_lrdd_template(domain) do + {:commit, template} + else + e -> {:ignore, e} + end + end) + end + + defp find_lrdd_template(domain) do + with {:ok, _} = template <- find_lrdd_template_json(domain) do + template + else + :error -> + with {:ok, _} = template <- find_lrdd_template_xml(domain) do + template + end + end + end + + defp find_lrdd_template_json(domain) do + # WebFinger is restricted to HTTPS - https://tools.ietf.org/html/rfc7033#section-9.1 + meta_url = "https://#{domain}/.well-known/host-meta.json" + + with + {_, {:ok, %{status: status, body: body} = resp}} when status in 200..299 <- {:http, HTTP.get(meta_json_url, [{"accept", "application/json"}])}, + {_, content_type} when is_binary(content_type) <- {:content_type, Tesla.get_header(resp, "content-type")}, + true <- content_type =~ ~r[^application/(jrd\+)?json(; .*)?], + %{"template" => template} <- Enum.find(body, fn link -> link["rel"] == "lrdd" and Map.has_key?(link, "template" end) do + {:ok, template} + else + {:http, e} -> + Logger.warn("WebFinger: #{meta_url} HTTP error: #{inspect(e)}") + :error + {:content_type, _} -> + Logger.warn("WebFinger: #{meta_url} had no Content-Type header") + :error + false -> + Logger.warn("WebFinger: #{meta_url} gave #{content_type} instead of application/json") + :error + nil -> + Logger.warn("WebFinger: #{meta_url} had no LRDD template") + :error + end + end + + defp find_lrdd_template_xml(domain) do # WebFinger is restricted to HTTPS - https://tools.ietf.org/html/rfc7033#section-9.1 meta_url = "https://#{domain}/.well-known/host-meta" with {:ok, %{status: status, body: body}} when status in 200..299 <- HTTP.get(meta_url) do get_template_from_xml(body) else - error -> - Logger.warn("Can't find LRDD template in #{inspect(meta_url)}: #{inspect(error)}") - {:error, :lrdd_not_found} + error -> {:error, error} end end diff --git a/lib/pleroma/web/web_finger/web_finger_controller.ex b/lib/pleroma/web/web_finger/web_finger_controller.ex index 093a8efd0..0733fe9db 100644 --- a/lib/pleroma/web/web_finger/web_finger_controller.ex +++ b/lib/pleroma/web/web_finger/web_finger_controller.ex @@ -18,6 +18,14 @@ defmodule Pleroma.Web.WebFinger.WebFingerController do |> send_resp(200, xml) end + def host_meta_json(conn, _params) do + jrd = WebFinger.host_meta_json() + + conn + |> put_resp_content_type("application/jrd+json") + |> json(jrd) + end + def webfinger(%{assigns: %{format: format}} = conn, %{"resource" => resource}) when format in ["xml", "xrd+xml"] do with {:ok, response} <- WebFinger.webfinger(resource, "XML") do 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 3a3e06b17..2096b2627 100644 --- a/test/pleroma/web/web_finger/web_finger_controller_test.exs +++ b/test/pleroma/web/web_finger/web_finger_controller_test.exs @@ -22,9 +22,29 @@ defmodule Pleroma.Web.WebFinger.WebFingerControllerTest do |> get("/.well-known/host-meta") assert response.status == 200 + assert get_resp_header(response, "content-type") == ["application/xrd+xml; charset=utf-8"] assert response.resp_body == - ~s(<?xml version="1.0" encoding="UTF-8"?><XRD xmlns="http://docs.oasis-open.org/ns/xri/xrd-1.0"><Link rel="lrdd" template="#{Pleroma.Web.Endpoint.url()}/.well-known/webfinger?resource={uri}" type="application/xrd+xml" /></XRD>) + ~s[<?xml version="1.0" encoding="UTF-8"?><XRD xmlns="http://docs.oasis-open.org/ns/xri/xrd-1.0"><Link rel="lrdd" template="#{Pleroma.Web.Endpoint.url()}/.well-known/webfinger?resource={uri}" type="application/xrd+xml" /></XRD>] + end + + test "GET host-meta.json" do + response = + build_conn() + |> get("/.well-known/host-meta.json") + + # RFC6415 (Web Host Metadata) says it MUST be application/json + assert get_resp_header(resp, "content-type") == ["application/json; charset=utf-8"] + + assert response == json_response(resp, 200) + + assert response["links"] == [ + %{ + "rel" => "lrdd", + "type" => "application/jrd+json", + "template" => "#{Pleroma.Web.Endpoint.url()}/.well-known/webfinger?resource={uri}" + } + ] end test "Webfinger JRD" do |