Add several checks before processing an event for state resolution
This commit is contained in:
parent
857aabd007
commit
e284b3e21a
4 changed files with 166 additions and 25 deletions
|
@ -31,4 +31,20 @@ defmodule MatrixServer do
|
||||||
end
|
end
|
||||||
|
|
||||||
def default_room_version, do: "7"
|
def default_room_version, do: "7"
|
||||||
|
|
||||||
|
def get_domain(id) do
|
||||||
|
case String.split(id, ":") do
|
||||||
|
[_, server_name] -> server_name
|
||||||
|
_ -> nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# https://elixirforum.com/t/22709/9
|
||||||
|
def has_duplicates?(list) do
|
||||||
|
list
|
||||||
|
|> Enum.reduce_while(%MapSet{}, fn x, acc ->
|
||||||
|
if MapSet.member?(acc, x), do: {:halt, false}, else: {:cont, MapSet.put(acc, x)}
|
||||||
|
end)
|
||||||
|
|> is_boolean()
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
defmodule MatrixServer.Event do
|
defmodule MatrixServer.Event do
|
||||||
use Ecto.Schema
|
use Ecto.Schema
|
||||||
|
|
||||||
import Ecto.Changeset
|
import Ecto.Query
|
||||||
|
|
||||||
alias MatrixServer.{Room, Event}
|
alias MatrixServer.{Repo, Room, Event}
|
||||||
|
|
||||||
@primary_key {:event_id, :string, []}
|
@primary_key {:event_id, :string, []}
|
||||||
schema "events" do
|
schema "events" do
|
||||||
|
@ -17,12 +17,6 @@ defmodule MatrixServer.Event do
|
||||||
belongs_to :room, Room, type: :string
|
belongs_to :room, Room, type: :string
|
||||||
end
|
end
|
||||||
|
|
||||||
def changeset(event, params \\ %{}) do
|
|
||||||
event
|
|
||||||
|> cast(params, [:type, :timestamp, :state_key, :sender, :content])
|
|
||||||
|> validate_required([:type, :timestamp, :sender])
|
|
||||||
end
|
|
||||||
|
|
||||||
def new(room_id, sender) do
|
def new(room_id, sender) do
|
||||||
%Event{
|
%Event{
|
||||||
room_id: room_id,
|
room_id: room_id,
|
||||||
|
@ -122,4 +116,110 @@ defmodule MatrixServer.Event do
|
||||||
def is_control_event(_), do: false
|
def is_control_event(_), do: false
|
||||||
|
|
||||||
def is_state_event(%Event{state_key: state_key}), do: state_key != nil
|
def is_state_event(%Event{state_key: state_key}), do: state_key != nil
|
||||||
|
|
||||||
|
# Perform validations that can be done before state resolution.
|
||||||
|
# For example checking the domain of the sender.
|
||||||
|
# We assume that required keys, as well as in the content, is already validated.
|
||||||
|
|
||||||
|
# Rule 1.4 is left to changeset validation.
|
||||||
|
def prevalidate(%Event{
|
||||||
|
type: "m.room.create",
|
||||||
|
prev_events: prev_events,
|
||||||
|
auth_events: auth_events,
|
||||||
|
room_id: room_id,
|
||||||
|
sender: sender
|
||||||
|
}) do
|
||||||
|
# TODO: error check on domains?
|
||||||
|
# TODO: rule 1.3
|
||||||
|
|
||||||
|
# Check rules: 1.1, 1.2
|
||||||
|
prev_events == [] and
|
||||||
|
auth_events == [] and
|
||||||
|
MatrixServer.get_domain(sender) == MatrixServer.get_domain(room_id)
|
||||||
|
end
|
||||||
|
|
||||||
|
def prevalidate(%Event{auth_events: auth_event_ids, prev_events: prev_event_ids} = event) do
|
||||||
|
prev_events =
|
||||||
|
Event
|
||||||
|
|> where([e], e.event_id in ^prev_event_ids)
|
||||||
|
|> Repo.all()
|
||||||
|
|
||||||
|
auth_events =
|
||||||
|
Event
|
||||||
|
|> where([e], e.event_id in ^auth_event_ids)
|
||||||
|
|> Repo.all()
|
||||||
|
|
||||||
|
state_pairs = Enum.map(auth_events, &{&1.type, &1.state_key})
|
||||||
|
|
||||||
|
# Check rules: 2.1, 2.2, 3
|
||||||
|
length(auth_events) == length(auth_event_ids) and
|
||||||
|
length(prev_events) == length(prev_event_ids) and
|
||||||
|
not MatrixServer.has_duplicates?(state_pairs) and
|
||||||
|
valid_auth_events?(event, auth_events) and
|
||||||
|
Enum.find_value(state_pairs, &(&1 == {"m.room.create", ""})) and
|
||||||
|
do_prevalidate(event, auth_events, prev_events)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Rule 4.1 is left to changeset validation.
|
||||||
|
defp do_prevalidate(%Event{type: "m.room.aliases", sender: sender, state_key: state_key}, _, _) do
|
||||||
|
# Check rule: 4.2
|
||||||
|
MatrixServer.get_domain(sender) == MatrixServer.get_domain(state_key)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Rule 5.1 is left to changeset validation.
|
||||||
|
# Rules 5.2.3, 5.2.4, 5.2.5 is left to state resolution.
|
||||||
|
# Check rule: 5.2.1
|
||||||
|
defp do_prevalidate(
|
||||||
|
%Event{type: "m.room.member", content: %{"membership" => "join"}, sender: sender},
|
||||||
|
_,
|
||||||
|
[%Event{type: "m.room.create", state_key: sender}]
|
||||||
|
),
|
||||||
|
do: true
|
||||||
|
|
||||||
|
# Check rule: 5.2.2
|
||||||
|
defp do_prevalidate(
|
||||||
|
%Event{
|
||||||
|
type: "m.room.member",
|
||||||
|
content: %{"membership" => "join"},
|
||||||
|
sender: sender,
|
||||||
|
state_key: state_key
|
||||||
|
},
|
||||||
|
_,
|
||||||
|
_
|
||||||
|
)
|
||||||
|
when sender != state_key,
|
||||||
|
do: false
|
||||||
|
|
||||||
|
# TODO: Rule 5.3.1
|
||||||
|
# All other rules will be checked during state resolution.
|
||||||
|
defp do_prevalidate(_, _, _), do: true
|
||||||
|
|
||||||
|
defp valid_auth_events?(
|
||||||
|
%Event{type: type, sender: sender, state_key: state_key, content: content},
|
||||||
|
auth_events
|
||||||
|
) do
|
||||||
|
Enum.all?(auth_events, fn
|
||||||
|
%Event{type: "m.room.create", state_key: ""} ->
|
||||||
|
true
|
||||||
|
|
||||||
|
%Event{type: "m.room.power_levels", state_key: ""} ->
|
||||||
|
true
|
||||||
|
|
||||||
|
%Event{type: "m.room.member", state_key: ^sender} ->
|
||||||
|
true
|
||||||
|
|
||||||
|
%Event{type: auth_type, state_key: auth_state_key} ->
|
||||||
|
if type == "m.room.member" do
|
||||||
|
%{"membership" => membership} = content
|
||||||
|
|
||||||
|
(auth_type == "m.room.member" and auth_state_key == state_key) or
|
||||||
|
(membership in ["join", "invite"] and auth_type == "m.room.join_rules" and
|
||||||
|
auth_state_key == "") or
|
||||||
|
(membership == "invite" and auth_type == "m.room.third_party_invite" and
|
||||||
|
auth_state_key == "")
|
||||||
|
else
|
||||||
|
false
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -27,7 +27,7 @@ defmodule MatrixServer.Room do
|
||||||
end
|
end
|
||||||
|
|
||||||
def generate_room_id do
|
def generate_room_id do
|
||||||
"!" <> MatrixServer.random_string(18) <> "@" <> MatrixServer.server_name()
|
"!" <> MatrixServer.random_string(18) <> ":" <> MatrixServer.server_name()
|
||||||
end
|
end
|
||||||
|
|
||||||
def update_forward_extremities(%Event{
|
def update_forward_extremities(%Event{
|
||||||
|
|
|
@ -38,8 +38,16 @@ defmodule MatrixServer.RoomServer do
|
||||||
Repo.transaction(fn ->
|
Repo.transaction(fn ->
|
||||||
with {:ok, create_room_event, state_set} <-
|
with {:ok, create_room_event, state_set} <-
|
||||||
room_creation_create_room(account, input, room, state_set),
|
room_creation_create_room(account, input, room, state_set),
|
||||||
{:ok, _join_creator_event, state_set} <-
|
{:ok, join_creator_event, state_set} <-
|
||||||
room_creation_join_creator(account, room, state_set, create_room_event) do
|
room_creation_join_creator(account, room, state_set, create_room_event),
|
||||||
|
{:ok, _power_levels_event, state_set} <-
|
||||||
|
room_creation_power_levels(
|
||||||
|
account,
|
||||||
|
room,
|
||||||
|
state_set,
|
||||||
|
create_room_event,
|
||||||
|
join_creator_event
|
||||||
|
) do
|
||||||
{:ok, %{room_id: room_id, state_set: state_set}}
|
{:ok, %{room_id: room_id, state_set: state_set}}
|
||||||
else
|
else
|
||||||
_ -> {:error, :something}
|
_ -> {:error, :something}
|
||||||
|
@ -61,11 +69,24 @@ defmodule MatrixServer.RoomServer do
|
||||||
%Account{localpart: localpart},
|
%Account{localpart: localpart},
|
||||||
%Room{id: room_id},
|
%Room{id: room_id},
|
||||||
state_set,
|
state_set,
|
||||||
%Event{event_id: create_room_event_id}
|
%Event{event_id: create_room_id}
|
||||||
) do
|
) do
|
||||||
Event.join(room_id, MatrixServer.get_mxid(localpart))
|
Event.join(room_id, MatrixServer.get_mxid(localpart))
|
||||||
|> Map.put(:auth_events, [create_room_event_id])
|
|> Map.put(:auth_events, [create_room_id])
|
||||||
|> Map.put(:prev_events, [create_room_event_id])
|
|> Map.put(:prev_events, [create_room_id])
|
||||||
|
|> verify_and_insert_event(state_set)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp room_creation_power_levels(
|
||||||
|
%Account{localpart: localpart},
|
||||||
|
%Room{id: room_id},
|
||||||
|
state_set,
|
||||||
|
%Event{event_id: create_room_id},
|
||||||
|
%Event{event_id: join_creator_id}
|
||||||
|
) do
|
||||||
|
Event.power_levels(room_id, MatrixServer.get_mxid(localpart))
|
||||||
|
|> Map.put(:auth_events, [create_room_id, join_creator_id])
|
||||||
|
|> Map.put(:prev_events, [join_creator_id])
|
||||||
|> verify_and_insert_event(state_set)
|
|> verify_and_insert_event(state_set)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -77,12 +98,13 @@ defmodule MatrixServer.RoomServer do
|
||||||
# 4. Passes authorization rules based on the event's auth events, otherwise it is rejected.
|
# 4. Passes authorization rules based on the event's auth events, otherwise it is rejected.
|
||||||
# 5. Passes authorization rules based on the state at the event, otherwise it is rejected.
|
# 5. Passes authorization rules based on the state at the event, otherwise it is rejected.
|
||||||
# 6. Passes authorization rules based on the current state of the room, otherwise it is "soft failed".
|
# 6. Passes authorization rules based on the current state of the room, otherwise it is "soft failed".
|
||||||
|
if Event.prevalidate(event) do
|
||||||
if StateResolution.is_authorized_by_auth_events(event) do
|
if StateResolution.is_authorized_by_auth_events(event) do
|
||||||
state_set = StateResolution.resolve(event, false)
|
state_set = StateResolution.resolve(event, false)
|
||||||
|
|
||||||
if StateResolution.is_authorized(event, state_set) do
|
if StateResolution.is_authorized(event, state_set) do
|
||||||
if StateResolution.is_authorized(event, current_state_set) do
|
if StateResolution.is_authorized(event, current_state_set) do
|
||||||
# TODO: Assume the event is a forward extremity, should check this actually.
|
# We assume here that the event is always a forward extremity.
|
||||||
Room.update_forward_extremities(event)
|
Room.update_forward_extremities(event)
|
||||||
{:ok, event} = Repo.insert(event)
|
{:ok, event} = Repo.insert(event)
|
||||||
state_set = StateResolution.resolve_forward_extremities(event)
|
state_set = StateResolution.resolve_forward_extremities(event)
|
||||||
|
@ -96,6 +118,9 @@ defmodule MatrixServer.RoomServer do
|
||||||
else
|
else
|
||||||
{:error, :rejected}
|
{:error, :rejected}
|
||||||
end
|
end
|
||||||
|
else
|
||||||
|
{:error, :invalid}
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def testing do
|
def testing do
|
||||||
|
|
Loading…
Reference in a new issue