diff --git a/lib/matrix_server/room_server.ex b/lib/matrix_server/room_server.ex index a3a3f81..9c33749 100644 --- a/lib/matrix_server/room_server.ex +++ b/lib/matrix_server/room_server.ex @@ -74,16 +74,10 @@ defmodule MatrixServer.RoomServer do # TODO: power_level_content_override, initial_state, invite, invite_3pid room = Repo.one!(from r in Room, where: r.id == ^room_id) - case create_room_events(room, account, input) do - events when is_list(events) -> - case Repo.transaction(create_room_insert_events(room, events)) do - {:ok, state_set} -> {:reply, {:ok, room_id}, %{state | state_set: state_set}} - {:error, reason} -> {:reply, {:error, reason}, state} - _ -> {:reply, {:error, :unknown}, state} - end - - :error -> - {:reply, {:error, :event_creation}, state} + case Repo.transaction(create_room_insert_events(room, account, input)) do + {:ok, state_set} -> {:reply, {:ok, room_id}, %{state | state_set: state_set}} + {:error, reason} -> {:reply, {:error, reason}, state} + _ -> {:reply, {:error, :unknown}, state} end end @@ -100,51 +94,54 @@ defmodule MatrixServer.RoomServer do {:reply, result, state} end - defp create_room_events(room, account, %CreateRoom{ + defp create_room_insert_events(room, account, %CreateRoom{ room_version: room_version, + preset: preset, name: name, - topic: topic, - preset: preset + topic: topic }) do - with {:ok, create_room} <- Event.create_room(room, account, room_version), - {:ok, join_creator} <- Event.join(room, account, [create_room]), - {:ok, pls} <- Event.power_levels(room, account, [create_room, join_creator]), - auth_events <- [create_room, join_creator, pls], - {:ok, preset_events} <- room_creation_preset(account, preset, room, auth_events), - {:ok, name_event} <- - if(name, do: Event.name(room, account, name, auth_events), else: {:ok, nil}), - {:ok, topic_event} <- - if(topic, do: Event.topic(room, account, topic, auth_events), else: {:ok, nil}) do - events = [create_room, join_creator, pls] ++ preset_events ++ [name_event, topic_event] - - Enum.reject(events, &Kernel.is_nil/1) - else - _ -> :error - end - end - - defp create_room_insert_events(room, events) do fn -> - result = - Enum.reduce_while(events, {%{}, room}, fn event, {state_set, room} -> - case verify_and_insert_event(event, state_set, room) do - {:ok, state_set, room} -> {:cont, {state_set, room}} - {:error, reason} -> {:halt, {:error, reason}} - end - end) + state_set = %{} - case result do - {:error, reason} -> - Repo.rollback(reason) + with create_room <- Event.create_room(room, account, room_version), + {:ok, state_set, create_room, room} <- + verify_and_insert_event(create_room, state_set, room), + join_creator <- Event.join(room, account, [create_room]), + {:ok, state_set, join_creator, room} <- + verify_and_insert_event(join_creator, state_set, 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] + preset_events = room_creation_preset(account, preset, room, auth_events) + name_event = if name, do: Event.name(room, account, name, auth_events) + topic_event = if topic, do: Event.topic(room, account, topic, auth_events) - {state_set, room} -> - serialized_state_set = - Enum.map(state_set, fn {{type, state_key}, event} -> - [type, state_key, event.event_id] - end) + remaining_events = + Enum.reject(preset_events ++ [name_event, topic_event], &Kernel.is_nil/1) - Repo.update!(change(room, state: serialized_state_set)) - state_set + result = + Enum.reduce_while(remaining_events, {state_set, room}, fn event, {state_set, room} -> + case verify_and_insert_event(event, state_set, room) do + {:ok, state_set, _event, room} -> {:cont, {state_set, room}} + {:error, reason} -> {:halt, {:error, reason}} + end + end) + + case result do + {:error, reason} -> + Repo.rollback(reason) + + {state_set, room} -> + serialized_state_set = + Enum.map(state_set, fn {{type, state_key}, event} -> + [type, state_key, event.event_id] + end) + + Repo.update!(change(room, state: serialized_state_set)) + state_set + end + else + _ -> Repo.rollback(:event_creation) end end end @@ -169,11 +166,11 @@ defmodule MatrixServer.RoomServer do "public_chat" -> {"public", "shared", "forbidden"} end - with {:ok, join_rules} <- Event.join_rules(room, account, join_rule, auth_events), - {:ok, his_vis} <- Event.history_visibility(room, account, his_vis, auth_events), - {:ok, guest_access} <- Event.guest_access(room, account, guest_access, auth_events) do - {:ok, [join_rules, his_vis, guest_access]} - end + [ + Event.join_rules(room, account, join_rule, auth_events), + Event.history_visibility(room, account, his_vis, auth_events), + Event.guest_access(room, account, guest_access, auth_events) + ] end defp verify_and_insert_event( @@ -181,6 +178,7 @@ defmodule MatrixServer.RoomServer do current_state_set, %Room{forward_extremities: forward_extremities} = room ) do + # TODO: Correct error values. # Check the following things: # 1. TODO: Is a valid event, otherwise it is dropped. # 2. TODO: Passes signature checks, otherwise it is dropped. @@ -190,7 +188,8 @@ defmodule MatrixServer.RoomServer do # 6. Passes authorization rules based on the current state of the room, otherwise it is "soft failed". event = %Event{event | prev_events: forward_extremities} - with true <- Event.prevalidate(event), + with {:ok, event} <- Event.post_process(event), + true <- Event.prevalidate(event), true <- Authorization.authorized_by_auth_events?(event), state_set <- StateResolution.resolve(event, false), true <- Authorization.authorized?(event, state_set), @@ -198,7 +197,7 @@ defmodule MatrixServer.RoomServer do room = Room.update_forward_extremities(event, room) event = Repo.insert!(event) state_set = StateResolution.resolve_forward_extremities(event) - {:ok, state_set, room} + {:ok, state_set, event, room} else _ -> {:error, :authorization} end diff --git a/lib/matrix_server/schema/event.ex b/lib/matrix_server/schema/event.ex index 20e76da..c56a6a6 100644 --- a/lib/matrix_server/schema/event.ex +++ b/lib/matrix_server/schema/event.ex @@ -14,11 +14,10 @@ defmodule MatrixServer.Event do field :content, :map field :prev_events, {:array, :string} field :auth_events, {:array, :string} - # TODO: make these database fields eventually? - field :signing_keys, :map, virtual: true, default: %{} - field :unsigned, :map, virtual: true, default: %{} - field :signatures, :map, virtual: true, default: %{} - field :hashes, :map, virtual: true, default: %{} + field :unsigned, :map + field :signatures, {:map, {:map, :string}} + field :hashes, {:map, :string} + belongs_to :room, Room, type: :string end @@ -35,12 +34,11 @@ defmodule MatrixServer.Event do def create_room( room, %Account{localpart: localpart} = creator, - room_version, - generate_id \\ true + room_version ) do mxid = MatrixServer.get_mxid(localpart) - event = %Event{ + %Event{ new(room, creator) | type: "m.room.create", state_key: "", @@ -49,14 +47,12 @@ defmodule MatrixServer.Event do "room_version" => room_version || MatrixServer.default_room_version() } } - - if generate_id, do: set_event_id(event), else: event end - def join(room, %Account{localpart: localpart} = sender, auth_events, generate_id \\ true) do + def join(room, %Account{localpart: localpart} = sender, auth_events) do mxid = MatrixServer.get_mxid(localpart) - event = %Event{ + %Event{ new(room, sender) | type: "m.room.member", state_key: mxid, @@ -65,19 +61,16 @@ defmodule MatrixServer.Event do }, auth_events: Enum.map(auth_events, & &1.event_id) } - - if generate_id, do: set_event_id(event), else: event end def power_levels( room, %Account{localpart: localpart} = sender, - auth_events, - generate_id \\ true + auth_events ) do mxid = MatrixServer.get_mxid(localpart) - event = %Event{ + %Event{ new(room, sender) | type: "m.room.power_levels", state_key: "", @@ -99,12 +92,10 @@ defmodule MatrixServer.Event do }, auth_events: Enum.map(auth_events, & &1.event_id) } - - if generate_id, do: set_event_id(event), else: event end - def name(room, sender, name, auth_events, generate_id \\ true) do - event = %Event{ + def name(room, sender, name, auth_events) do + %Event{ new(room, sender) | type: "m.room.name", state_key: "", @@ -113,12 +104,10 @@ defmodule MatrixServer.Event do }, auth_events: Enum.map(auth_events, & &1.event_id) } - - if generate_id, do: set_event_id(event), else: event end - def topic(room, sender, topic, auth_events, generate_id \\ true) do - event = %Event{ + def topic(room, sender, topic, auth_events) do + %Event{ new(room, sender) | type: "m.room.topic", state_key: "", @@ -127,12 +116,10 @@ defmodule MatrixServer.Event do }, auth_events: Enum.map(auth_events, & &1.event_id) } - - if generate_id, do: set_event_id(event), else: event end - def join_rules(room, sender, join_rule, auth_events, generate_id \\ true) do - event = %Event{ + def join_rules(room, sender, join_rule, auth_events) do + %Event{ new(room, sender) | type: "m.room.join_rules", state_key: "", @@ -141,12 +128,10 @@ defmodule MatrixServer.Event do }, auth_events: Enum.map(auth_events, & &1.event_id) } - - if generate_id, do: set_event_id(event), else: event end - def history_visibility(room, sender, history_visibility, auth_events, generate_id \\ true) do - event = %Event{ + def history_visibility(room, sender, history_visibility, auth_events) do + %Event{ new(room, sender) | type: "m.room.history_visibility", state_key: "", @@ -155,12 +140,10 @@ defmodule MatrixServer.Event do }, auth_events: Enum.map(auth_events, & &1.event_id) } - - if generate_id, do: set_event_id(event), else: event end - def guest_access(room, sender, guest_access, auth_events, generate_id \\ true) do - event = %Event{ + def guest_access(room, sender, guest_access, auth_events) do + %Event{ new(room, sender) | type: "m.room.guest_access", state_key: "", @@ -169,8 +152,6 @@ defmodule MatrixServer.Event do }, auth_events: Enum.map(auth_events, & &1.event_id) } - - if generate_id, do: set_event_id(event), else: event end def is_control_event(%Event{type: "m.room.power_levels", state_key: ""}), do: true @@ -294,28 +275,15 @@ defmodule MatrixServer.Event do end) end - def sign(event) do - content_hash = - event - |> calculate_content_hash() - |> MatrixServer.encode_unpadded_base64() - - event - |> Map.put(:hashes, %{"sha256" => content_hash}) - |> redact() - |> KeyServer.sign_object() - end - defp calculate_content_hash(event) do m = event |> MatrixServer.to_serializable_map() |> Map.drop([:unsigned, :signature, :hashes]) |> EncodableMap.from_map() - |> Jason.encode() with {:ok, json} <- Jason.encode(m) do - :crypto.hash(:sha256, json) + {:ok, :crypto.hash(:sha256, json)} end end @@ -367,6 +335,22 @@ defmodule MatrixServer.Event do defp redact_content(_, _), do: %{} + # Adds content hash, adds signature and calculates event id. + def post_process(event) do + with {:ok, content_hash} <- calculate_content_hash(event) do + encoded_hash = MatrixServer.encode_unpadded_base64(content_hash) + event = %Event{event | hashes: %{"sha256" => encoded_hash}} + + with {:ok, sig, key_id} <- KeyServer.sign_object(redact(event)) do + event = %Event{event | signatures: %{MatrixServer.server_name() => %{key_id => sig}}} + + with {:ok, event} <- set_event_id(event) do + {:ok, event} + end + end + end + end + def set_event_id(event) do with {:ok, event_id} <- generate_event_id(event) do {:ok, %Event{event | event_id: event_id}} diff --git a/priv/repo/migrations/20210818133912_add_fields_to_events.exs b/priv/repo/migrations/20210818133912_add_fields_to_events.exs new file mode 100644 index 0000000..2c53bcf --- /dev/null +++ b/priv/repo/migrations/20210818133912_add_fields_to_events.exs @@ -0,0 +1,11 @@ +defmodule MatrixServer.Repo.Migrations.AddFieldsToEvents do + use Ecto.Migration + + def change do + alter table(:events) do + add :unsigned, :map, default: %{}, null: true + add :hashes, :map, null: false + add :signatures, :map, null: false + end + end +end