From 6f8c224d501d014f642f0d63659756f07e6bd8c6 Mon Sep 17 00:00:00 2001 From: Pim Kunis Date: Mon, 23 Aug 2021 12:59:12 +0200 Subject: [PATCH] Implement client joined rooms endpoint Track which rooms a local account has joined Add some documentation to modules --- lib/matrix_server/room_server.ex | 7 +++- lib/matrix_server/schema/account.ex | 7 +++- lib/matrix_server/schema/joined_room.ex | 16 +++++++++ lib/matrix_server/state_resolution.ex | 33 +++++++++++++++---- .../state_resolution/authorization.ex | 7 ++++ .../client/controllers/room_controller.ex | 19 +++++++++-- lib/matrix_server_web/router.ex | 1 + ...210823083642_create_joined_rooms_table.exs | 16 +++++++++ 8 files changed, 95 insertions(+), 11 deletions(-) create mode 100644 lib/matrix_server/schema/joined_room.ex create mode 100644 priv/repo/migrations/20210823083642_create_joined_rooms_table.exs diff --git a/lib/matrix_server/room_server.ex b/lib/matrix_server/room_server.ex index be58038..9779847 100644 --- a/lib/matrix_server/room_server.ex +++ b/lib/matrix_server/room_server.ex @@ -11,7 +11,7 @@ defmodule MatrixServer.RoomServer do import Ecto.Query import Ecto.Changeset - alias MatrixServer.{Repo, Room, Event, StateResolution} + alias MatrixServer.{Repo, Room, Event, StateResolution, JoinedRoom, Account} alias MatrixServer.StateResolution.Authorization alias MatrixServerWeb.Client.Request.CreateRoom @@ -199,6 +199,7 @@ defmodule MatrixServer.RoomServer do join_creator <- Event.join(room, account, [create_room]), {:ok, state_set, join_creator, room} <- verify_and_insert_event(join_creator, state_set, room), + {:ok, _} <- insert_joined_room_assoc(account, room), pls <- Event.power_levels(room, account, [create_room, join_creator]), {:ok, state_set, pls, room} <- verify_and_insert_event(pls, state_set, room) do auth_events = [create_room, join_creator, pls] @@ -236,6 +237,10 @@ defmodule MatrixServer.RoomServer do end end + defp insert_joined_room_assoc(%Account{localpart: localpart}, %Room{id: room_id}) do + Repo.insert(%JoinedRoom{localpart: localpart, room_id: room_id}) + end + # TODO: trusted_private_chat: # All invitees are given the same power level as the room creator. defp room_creation_preset(account, nil, %Room{visibility: visibility} = room, auth_events) do diff --git a/lib/matrix_server/schema/account.ex b/lib/matrix_server/schema/account.ex index dc69b6e..9fc4ce6 100644 --- a/lib/matrix_server/schema/account.ex +++ b/lib/matrix_server/schema/account.ex @@ -3,7 +3,7 @@ defmodule MatrixServer.Account do import Ecto.{Changeset, Query} - alias MatrixServer.{Repo, Account, Device} + alias MatrixServer.{Repo, Account, Device, Room, JoinedRoom} alias MatrixServerWeb.Client.Request.{Register, Login} alias Ecto.Multi @@ -17,6 +17,11 @@ defmodule MatrixServer.Account do schema "accounts" do field :password_hash, :string, redact: true has_many :devices, Device, foreign_key: :localpart + + many_to_many :joined_rooms, Room, + join_through: JoinedRoom, + join_keys: [localpart: :localpart, room_id: :id] + timestamps(updated_at: false) end diff --git a/lib/matrix_server/schema/joined_room.ex b/lib/matrix_server/schema/joined_room.ex new file mode 100644 index 0000000..044b5a8 --- /dev/null +++ b/lib/matrix_server/schema/joined_room.ex @@ -0,0 +1,16 @@ +defmodule MatrixServer.JoinedRoom do + use Ecto.Schema + + alias MatrixServer.{Account, Room} + + @primary_key false + schema "joined_rooms" do + belongs_to :account, Account, + foreign_key: :localpart, + references: :localpart, + type: :string, + primary_key: true + + belongs_to :room, Room, primary_key: true, type: :string + end +end diff --git a/lib/matrix_server/state_resolution.ex b/lib/matrix_server/state_resolution.ex index 8b49cc8..74893b8 100644 --- a/lib/matrix_server/state_resolution.ex +++ b/lib/matrix_server/state_resolution.ex @@ -1,4 +1,23 @@ defmodule MatrixServer.StateResolution do + @moduledoc """ + Functions for resolving the state of a Matrix room. + + Currently, only state resolution from room version 2 is supported, + see [the Matrix docs](https://spec.matrix.org/unstable/rooms/v2/). + + The current implementation of the state resolution algorithm performs + rather badly. + Each time state is resolved, all events in the room are fetched from + the database and loaded into memory. + This is mostly so I didn't have to worry about fetching events from the + database when developing this initial implementation. + Then, the state is calculated using the new event's previous events and auth + events. + To prevent loading all events into memory, and calculating the whole state each + time, we should make snapshots of the state of a room at regular intervals. + It looks like Dendrite does this too. + """ + import Ecto.Query alias MatrixServer.{Repo, Event, Room} @@ -61,6 +80,13 @@ defmodule MatrixServer.StateResolution do |> Enum.reduce(MapSet.new(), &MapSet.union/2) end + def update_state_set( + %Event{type: event_type, state_key: state_key} = event, + state_set + ) do + Map.put(state_set, {event_type, state_key}, event) + end + defp do_resolve([], _), do: %{} defp do_resolve(state_sets, room_events) do @@ -265,13 +291,6 @@ defmodule MatrixServer.StateResolution do end) end - def update_state_set( - %Event{type: event_type, state_key: state_key} = event, - state_set - ) do - Map.put(state_set, {event_type, state_key}, event) - end - defp authorized?(%Event{auth_events: auth_event_ids} = event, state_set, room_events) do state_set = auth_event_ids diff --git a/lib/matrix_server/state_resolution/authorization.ex b/lib/matrix_server/state_resolution/authorization.ex index 5563e40..e61d4cb 100644 --- a/lib/matrix_server/state_resolution/authorization.ex +++ b/lib/matrix_server/state_resolution/authorization.ex @@ -1,4 +1,11 @@ defmodule MatrixServer.StateResolution.Authorization do + @moduledoc """ + Implementation of Matrix event authorization rules for stat resolution. + + Note that some authorization rules are already checked in + `MatrixServer.Event.prevalidate/1` so they are skipped here. + """ + import MatrixServer.StateResolution import Ecto.Query diff --git a/lib/matrix_server_web/client/controllers/room_controller.ex b/lib/matrix_server_web/client/controllers/room_controller.ex index cd3c814..e6a49b7 100644 --- a/lib/matrix_server_web/client/controllers/room_controller.ex +++ b/lib/matrix_server_web/client/controllers/room_controller.ex @@ -2,9 +2,9 @@ defmodule MatrixServerWeb.Client.RoomController do use MatrixServerWeb, :controller import MatrixServerWeb.Error - import Ecto.Changeset + import Ecto.{Changeset, Query} - alias MatrixServer.Room + alias MatrixServer.{Repo, Room} alias MatrixServerWeb.Client.Request.CreateRoom alias Ecto.Changeset alias Plug.Conn @@ -31,4 +31,19 @@ defmodule MatrixServerWeb.Client.RoomController do put_error(conn, :bad_json) end end + + def joined_rooms(%Conn{assigns: %{account: account}} = conn, _params) do + joined_room_ids = account + |> Ecto.assoc(:joined_rooms) + |> select([jr], jr.id) + |> Repo.all() + + data = %{ + joined_rooms: joined_room_ids + } + + conn + |> put_status(200) + |> json(data) + end end diff --git a/lib/matrix_server_web/router.ex b/lib/matrix_server_web/router.ex index 5bd6f3f..a105cc8 100644 --- a/lib/matrix_server_web/router.ex +++ b/lib/matrix_server_web/router.ex @@ -51,6 +51,7 @@ defmodule MatrixServerWeb.Router do post "/logout", AccountController, :logout post "/logout/all", AccountController, :logout_all post "/createRoom", RoomController, :create + get "/joined_rooms", RoomController, :joined_rooms scope "/directory/room" do put "/:alias", AliasesController, :create diff --git a/priv/repo/migrations/20210823083642_create_joined_rooms_table.exs b/priv/repo/migrations/20210823083642_create_joined_rooms_table.exs new file mode 100644 index 0000000..a26b0f6 --- /dev/null +++ b/priv/repo/migrations/20210823083642_create_joined_rooms_table.exs @@ -0,0 +1,16 @@ +defmodule MatrixServer.Repo.Migrations.CreateJoinedRoomsTable do + use Ecto.Migration + + def change do + create table(:joined_rooms, primary_key: false) do + add :localpart, + references(:accounts, column: :localpart, type: :string), + primary_key: true, + null: false + + add :room_id, references(:rooms, type: :string), + primary_key: true, + null: false + end + end +end