From f10977a99ac73ce5a702a12ed31e773a4b0f6961 Mon Sep 17 00:00:00 2001 From: Thomas Citharel Date: Thu, 14 Dec 2023 16:31:58 +0100 Subject: [PATCH] feat(activitypub): implement FEP-2677 to identify the application actor used for federation Instead of always assuming it will be @relay@host.tld Closes #1367 Signed-off-by: Thomas Citharel --- lib/federation/activity_pub/relay.ex | 10 +++--- lib/federation/node_info.ex | 33 +++++++++++++++++++ lib/web/controllers/node_info_controller.ex | 10 ++++++ .../controllers/nodeinfo_controller_test.exs | 8 +++++ 4 files changed, 56 insertions(+), 5 deletions(-) create mode 100644 lib/federation/node_info.ex diff --git a/lib/federation/activity_pub/relay.ex b/lib/federation/activity_pub/relay.ex index b722eca62..3bc9e9212 100644 --- a/lib/federation/activity_pub/relay.ex +++ b/lib/federation/activity_pub/relay.ex @@ -13,7 +13,7 @@ defmodule Mobilizon.Federation.ActivityPub.Relay do alias Mobilizon.Federation.ActivityPub.{Actions, Activity, Transmogrifier} alias Mobilizon.Federation.ActivityPub.Actor, as: ActivityPubActor - alias Mobilizon.Federation.WebFinger + alias Mobilizon.Federation.{NodeInfo, WebFinger} alias Mobilizon.GraphQL.API.Follows alias Mobilizon.Service.Workers.Background import Mobilizon.Federation.ActivityPub.Utils, only: [create_full_domain_string: 1] @@ -192,10 +192,10 @@ defmodule Mobilizon.Federation.ActivityPub.Relay do check_actor(address) !is_nil(host) -> - uri - |> create_full_domain_string() - |> then(&Kernel.<>("relay@", &1)) - |> check_actor() + case NodeInfo.application_actor(host) do + nil -> check_actor("relay@#{host}") + actor_url when is_binary(actor_url) -> {:ok, actor_url} + end true -> {:error, :bad_url} diff --git a/lib/federation/node_info.ex b/lib/federation/node_info.ex new file mode 100644 index 000000000..688abdac8 --- /dev/null +++ b/lib/federation/node_info.ex @@ -0,0 +1,33 @@ +defmodule Mobilizon.Federation.NodeInfo do + @moduledoc """ + Performs NodeInfo requests + """ + + alias Mobilizon.Service.HTTP.WebfingerClient + require Logger + + @application_uri "https://www.w3.org/ns/activitystreams#Application" + @env Application.compile_env(:mobilizon, :env) + + @spec application_actor(String.t()) :: String.t() | nil + def application_actor(host) do + prefix = if @env !== :dev, do: "https", else: "http" + + case WebfingerClient.get("#{prefix}://#{host}/.well-known/nodeinfo") do + {:ok, %{body: body, status: code}} when code in 200..299 -> + extract_application_actor(body) + + err -> + Logger.debug("Failed to fetch NodeInfo data #{inspect(err)}") + nil + end + end + + defp extract_application_actor(body) do + body + |> Enum.find(%{rel: @application_uri, href: nil}, fn %{rel: rel, href: href} -> + rel == @application_uri and is_binary(href) + end) + |> Map.get(:href) + end +end diff --git a/lib/web/controllers/node_info_controller.ex b/lib/web/controllers/node_info_controller.ex index f9a09b7f8..71e7b79aa 100644 --- a/lib/web/controllers/node_info_controller.ex +++ b/lib/web/controllers/node_info_controller.ex @@ -7,13 +7,17 @@ defmodule Mobilizon.Web.NodeInfoController do use Mobilizon.Web, :controller alias Mobilizon.Config + alias Mobilizon.Federation.ActivityPub.Relay alias Mobilizon.Service.Statistics @node_info_supported_versions ["2.0", "2.1"] @node_info_schema_uri "http://nodeinfo.diaspora.software/ns/schema/" + @application_uri "https://www.w3.org/ns/activitystreams#Application" @spec schemas(Plug.Conn.t(), any) :: Plug.Conn.t() def schemas(conn, _params) do + relay = Relay.get_actor() + links = @node_info_supported_versions |> Enum.map(fn version -> @@ -22,6 +26,12 @@ defmodule Mobilizon.Web.NodeInfoController do href: url(~p"/.well-known/nodeinfo/#{version}") } end) + |> Kernel.++([ + %{ + rel: @application_uri, + href: relay.url + } + ]) json(conn, %{ links: links diff --git a/test/web/controllers/nodeinfo_controller_test.exs b/test/web/controllers/nodeinfo_controller_test.exs index 08eca37a8..88bec3b2f 100644 --- a/test/web/controllers/nodeinfo_controller_test.exs +++ b/test/web/controllers/nodeinfo_controller_test.exs @@ -2,12 +2,16 @@ defmodule Mobilizon.Web.NodeInfoControllerTest do use Mobilizon.Web.ConnCase alias Mobilizon.Config + alias Mobilizon.Federation.ActivityPub.Relay use Mobilizon.Web, :verified_routes test "Get node info schemas", %{conn: conn} do conn = get(conn, url(~p"/.well-known/nodeinfo")) + relay = Relay.get_actor() + relay_url = relay.url + assert json_response(conn, 200) == %{ "links" => [ %{ @@ -17,6 +21,10 @@ defmodule Mobilizon.Web.NodeInfoControllerTest do %{ "href" => url(~p"/.well-known/nodeinfo/2.1"), "rel" => "http://nodeinfo.diaspora.software/ns/schema/2.1" + }, + %{ + "href" => relay_url, + "rel" => "https://www.w3.org/ns/activitystreams#Application" } ] }