From ba3e290bf16ad617a767f757f1b73a6faedb0b57 Mon Sep 17 00:00:00 2001 From: Pim Kunis Date: Sat, 21 Aug 2021 21:39:28 +0200 Subject: [PATCH] Implement federation room state snapshot endpoint --- lib/matrix_server/encodable_map.ex | 2 +- lib/matrix_server/room_server.ex | 24 ++++++++++ lib/matrix_server/state_resolution.ex | 12 ++--- lib/matrix_server_web/error.ex | 1 + .../controllers/event_controller.ex | 47 ++++++++++++++++--- .../federation/http_client.ex | 7 +++ .../federation/transaction.ex | 10 ++-- lib/matrix_server_web/router.ex | 1 + 8 files changed, 86 insertions(+), 18 deletions(-) diff --git a/lib/matrix_server/encodable_map.ex b/lib/matrix_server/encodable_map.ex index e48a8e8..d62ea88 100644 --- a/lib/matrix_server/encodable_map.ex +++ b/lib/matrix_server/encodable_map.ex @@ -1,6 +1,6 @@ # https://github.com/michalmuskala/jason/issues/69 defmodule MatrixServer.EncodableMap do - alias MatrixServer.{EncodableMap, Event} + alias MatrixServer.EncodableMap alias MatrixServer.Types.{UserId, RoomId, EventId, GroupId, AliasId} defstruct pairs: [] diff --git a/lib/matrix_server/room_server.ex b/lib/matrix_server/room_server.ex index 58cfa63..11fa69b 100644 --- a/lib/matrix_server/room_server.ex +++ b/lib/matrix_server/room_server.ex @@ -60,6 +60,10 @@ defmodule MatrixServer.RoomServer do GenServer.call(pid, {:server_in_room, domain}) end + def get_state_at_event(pid, event) do + GenServer.call(pid, {:get_state_at_event, event}) + end + ### Implementation @impl true @@ -104,6 +108,26 @@ defmodule MatrixServer.RoomServer do {:reply, result, state} end + def handle_call({:get_state_at_event, %Event{room_id: room_id} = event}, _from, state) do + room_events = + Event + |> where([e], e.room_id == ^room_id) + |> select([e], {e.event_id, e}) + |> Repo.all() + |> Enum.into(%{}) + + state_set = StateResolution.resolve(event, false) + state_events = Map.values(state_set) + + auth_chain = + state_set + |> Map.values() + |> StateResolution.full_auth_chain(room_events) + |> Enum.map(&room_events[&1]) + + {:reply, {state_events, auth_chain}, state} + end + defp create_room_insert_events(room, account, %CreateRoom{ room_version: room_version, preset: preset, diff --git a/lib/matrix_server/state_resolution.ex b/lib/matrix_server/state_resolution.ex index 0eb38fc..8b49cc8 100644 --- a/lib/matrix_server/state_resolution.ex +++ b/lib/matrix_server/state_resolution.ex @@ -55,6 +55,12 @@ defmodule MatrixServer.StateResolution do |> do_resolve(room_events) end + def full_auth_chain(events, room_events) do + events + |> Enum.map(&auth_chain(&1, room_events)) + |> Enum.reduce(MapSet.new(), &MapSet.union/2) + end + defp do_resolve([], _), do: %{} defp do_resolve(state_sets, room_events) do @@ -150,12 +156,6 @@ defmodule MatrixServer.StateResolution do MapSet.difference(auth_chain_union, auth_chain_intersection) end - defp full_auth_chain(events, room_events) do - events - |> Enum.map(&auth_chain(&1, room_events)) - |> Enum.reduce(MapSet.new(), &MapSet.union/2) - end - defp auth_chain(%Event{auth_events: auth_events}, room_events) do auth_events |> Enum.map(&room_events[&1]) diff --git a/lib/matrix_server_web/error.ex b/lib/matrix_server_web/error.ex index 257edba..e5c1001 100644 --- a/lib/matrix_server_web/error.ex +++ b/lib/matrix_server_web/error.ex @@ -13,6 +13,7 @@ defmodule MatrixServerWeb.Error do {400, "M_INVALID_ROOM_STATE", "The request would leave the room in an invalid state."}, unauthorized: {400, "M_UNAUTHORIZED", "The request was unauthorized."}, invalid_param: {400, "M_INVALID_PARAM", "A request parameter was invalid."}, + missing_param: {400, "M_MISSING_PARAM", "A request parameter is missing."}, unknown_token: {401, "M_UNKNOWN_TOKEN", "Invalid access token."}, missing_token: {401, "M_MISSING_TOKEN", "Access token required."}, not_found: {404, "M_NOT_FOUND", "The requested resource was not found."}, diff --git a/lib/matrix_server_web/federation/controllers/event_controller.ex b/lib/matrix_server_web/federation/controllers/event_controller.ex index a447cfa..091a099 100644 --- a/lib/matrix_server_web/federation/controllers/event_controller.ex +++ b/lib/matrix_server_web/federation/controllers/event_controller.ex @@ -25,11 +25,7 @@ defmodule MatrixServerWeb.Federation.EventController do |> put_status(200) |> json(data) else - put_error( - conn, - :unauthorized, - "Origin server is not allowed to see requested event." - ) + put_error(conn, :unauthorized, "Origin server is not participating in room.") end _ -> @@ -41,5 +37,44 @@ defmodule MatrixServerWeb.Federation.EventController do end end - def event(conn, _), do: put_error(conn, :bad_json) + def event(conn, _), do: put_error(conn, :missing_param) + + def state(%Plug.Conn{assigns: %{origin: origin}} = conn, %{ + "event_id" => event_id, + "room_id" => room_id + }) do + query = + Event + |> where([e], e.event_id == ^event_id and e.room_id == ^room_id) + |> preload(:room) + + case Repo.one(query) do + %Event{room: room} = event -> + case RoomServer.get_room_server(room) do + {:ok, pid} -> + if RoomServer.server_in_room(pid, origin) do + {state_events, auth_chain} = RoomServer.get_state_at_event(pid, event) + + data = %{ + auth_chain: auth_chain, + pdus: state_events + } + + conn + |> put_status(200) + |> json(data) + else + put_error(conn, :unauthorized, "Origin server is not participating in room.") + end + + _ -> + put_error(conn, :unknown) + end + + nil -> + put_error(conn, :not_found, "Event or room not found.") + end + end + + def state(conn, _), do: put_error(conn, :missing_param) end diff --git a/lib/matrix_server_web/federation/http_client.ex b/lib/matrix_server_web/federation/http_client.ex index ec4f424..a923952 100644 --- a/lib/matrix_server_web/federation/http_client.ex +++ b/lib/matrix_server_web/federation/http_client.ex @@ -66,6 +66,13 @@ defmodule MatrixServerWeb.Federation.HTTPClient do Tesla.get(client, path) end + def get_state(client, room_id, event_id) do + path = + RouteHelpers.event_path(Endpoint, :state, room_id) |> Tesla.build_url(event_id: event_id) + + Tesla.get(client, path) + end + defp tesla_request(method, client, path, request_schema) do with {:ok, %Tesla.Env{body: body}} <- Tesla.request(client, url: path, method: method), %Ecto.Changeset{valid?: true} = cs <- apply(request_schema, :changeset, [body]) do diff --git a/lib/matrix_server_web/federation/transaction.ex b/lib/matrix_server_web/federation/transaction.ex index 6e2cd6b..a64ab4c 100644 --- a/lib/matrix_server_web/federation/transaction.ex +++ b/lib/matrix_server_web/federation/transaction.ex @@ -6,11 +6,11 @@ defmodule MatrixServerWeb.Federation.Transaction do @type edu :: any() @type t :: %__MODULE__{ - origin: String.t(), - origin_server_ts: integer(), - pdus: [Event.t()], - edus: [edu()] | nil - } + origin: String.t(), + origin_server_ts: integer(), + pdus: [Event.t()], + edus: [edu()] | nil + } defstruct [:origin, :origin_server_ts, :pdus, :edus] diff --git a/lib/matrix_server_web/router.ex b/lib/matrix_server_web/router.ex index 821d301..d3d30e2 100644 --- a/lib/matrix_server_web/router.ex +++ b/lib/matrix_server_web/router.ex @@ -65,6 +65,7 @@ defmodule MatrixServerWeb.Router do scope "/v1" do get "/query/profile", QueryController, :profile get "/event/:event_id", EventController, :event + get "/state/:room_id", EventController, :state end end