Add more type checking
This commit is contained in:
parent
4b60a145ca
commit
1881b7f3d6
10 changed files with 155 additions and 59 deletions
10
lib/matrix_server/check.ex
Normal file
10
lib/matrix_server/check.ex
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
defmodule MatrixServer.Check do
|
||||||
|
import Ecto.Query
|
||||||
|
alias MatrixServer.{Repo, Account, Room}
|
||||||
|
alias MatrixServerWeb.Client.Request.CreateRoom
|
||||||
|
|
||||||
|
def create_room do
|
||||||
|
account = Repo.one!(from a in Account, limit: 1)
|
||||||
|
Room.create(account, %CreateRoom{})
|
||||||
|
end
|
||||||
|
end
|
|
@ -1,6 +1,7 @@
|
||||||
# https://github.com/michalmuskala/jason/issues/69
|
# https://github.com/michalmuskala/jason/issues/69
|
||||||
defmodule MatrixServer.EncodableMap do
|
defmodule MatrixServer.EncodableMap do
|
||||||
alias MatrixServer.EncodableMap
|
alias MatrixServer.EncodableMap
|
||||||
|
alias MatrixServer.Types.{UserId, RoomId, EventId, GroupId, AliasId}
|
||||||
|
|
||||||
defstruct pairs: []
|
defstruct pairs: []
|
||||||
|
|
||||||
|
@ -17,6 +18,11 @@ defmodule MatrixServer.EncodableMap do
|
||||||
{k, v} when is_struct(v, DateTime) ->
|
{k, v} when is_struct(v, DateTime) ->
|
||||||
{k, DateTime.to_unix(v, :millisecond)}
|
{k, DateTime.to_unix(v, :millisecond)}
|
||||||
|
|
||||||
|
{k, v}
|
||||||
|
when is_struct(v, UserId) or is_struct(v, RoomId) or is_struct(v, EventId) or
|
||||||
|
is_struct(v, GroupId) or is_struct(v, AliasId) ->
|
||||||
|
{k, to_string(v)}
|
||||||
|
|
||||||
{k, v} when is_map(v) ->
|
{k, v} when is_map(v) ->
|
||||||
{k, from_map(v)}
|
{k, from_map(v)}
|
||||||
|
|
||||||
|
|
|
@ -4,13 +4,28 @@ defmodule MatrixServer.Event do
|
||||||
import Ecto.Query
|
import Ecto.Query
|
||||||
|
|
||||||
alias MatrixServer.{Repo, Room, Event, Account, EncodableMap, KeyServer}
|
alias MatrixServer.{Repo, Room, Event, Account, EncodableMap, KeyServer}
|
||||||
|
alias MatrixServer.Types.UserId
|
||||||
|
|
||||||
|
# TODO: Could refactor to also always set prev_events, but not necessary.
|
||||||
|
@type t :: %__MODULE__{
|
||||||
|
type: String.t(),
|
||||||
|
origin_server_ts: DateTime.t(),
|
||||||
|
state_key: String.t(),
|
||||||
|
sender: UserId.t(),
|
||||||
|
content: map(),
|
||||||
|
prev_events: [String.t()] | nil,
|
||||||
|
auth_events: [String.t()],
|
||||||
|
unsigned: map() | nil,
|
||||||
|
signatures: map() | nil,
|
||||||
|
hashes: map() | nil
|
||||||
|
}
|
||||||
|
|
||||||
@primary_key {:event_id, :string, []}
|
@primary_key {:event_id, :string, []}
|
||||||
schema "events" do
|
schema "events" do
|
||||||
field :type, :string
|
field :type, :string
|
||||||
field :origin_server_ts, :utc_datetime_usec
|
field :origin_server_ts, :utc_datetime_usec
|
||||||
field :state_key, :string
|
field :state_key, :string
|
||||||
field :sender, :string
|
field :sender, UserId
|
||||||
field :content, :map
|
field :content, :map
|
||||||
field :prev_events, {:array, :string}
|
field :prev_events, {:array, :string}
|
||||||
field :auth_events, {:array, :string}
|
field :auth_events, {:array, :string}
|
||||||
|
@ -21,16 +36,18 @@ defmodule MatrixServer.Event do
|
||||||
belongs_to :room, Room, type: :string
|
belongs_to :room, Room, type: :string
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@spec new(Room.t(), Account.t()) :: %Event{}
|
||||||
def new(%Room{id: room_id}, %Account{localpart: localpart}) do
|
def new(%Room{id: room_id}, %Account{localpart: localpart}) do
|
||||||
%Event{
|
%Event{
|
||||||
room_id: room_id,
|
room_id: room_id,
|
||||||
sender: MatrixServer.get_mxid(localpart),
|
sender: %UserId{localpart: localpart, domain: MatrixServer.server_name()},
|
||||||
origin_server_ts: DateTime.utc_now(),
|
origin_server_ts: DateTime.utc_now(),
|
||||||
prev_events: [],
|
prev_events: [],
|
||||||
auth_events: []
|
auth_events: []
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@spec create_room(Room.t(), Account.t(), String.t()) :: t()
|
||||||
def create_room(
|
def create_room(
|
||||||
room,
|
room,
|
||||||
%Account{localpart: localpart} = creator,
|
%Account{localpart: localpart} = creator,
|
||||||
|
@ -49,6 +66,7 @@ defmodule MatrixServer.Event do
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@spec join(Room.t(), Account.t(), [t()]) :: t()
|
||||||
def join(room, %Account{localpart: localpart} = sender, auth_events) do
|
def join(room, %Account{localpart: localpart} = sender, auth_events) do
|
||||||
mxid = MatrixServer.get_mxid(localpart)
|
mxid = MatrixServer.get_mxid(localpart)
|
||||||
|
|
||||||
|
@ -63,6 +81,7 @@ defmodule MatrixServer.Event do
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@spec power_levels(Room.t(), Account.t(), [t()]) :: t()
|
||||||
def power_levels(
|
def power_levels(
|
||||||
room,
|
room,
|
||||||
%Account{localpart: localpart} = sender,
|
%Account{localpart: localpart} = sender,
|
||||||
|
@ -94,6 +113,7 @@ defmodule MatrixServer.Event do
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@spec name(Room.t(), Account.t(), String.t(), [t()]) :: %Event{}
|
||||||
def name(room, sender, name, auth_events) do
|
def name(room, sender, name, auth_events) do
|
||||||
%Event{
|
%Event{
|
||||||
new(room, sender)
|
new(room, sender)
|
||||||
|
@ -106,6 +126,7 @@ defmodule MatrixServer.Event do
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@spec topic(Room.t(), Account.t(), String.t(), [t()]) :: t()
|
||||||
def topic(room, sender, topic, auth_events) do
|
def topic(room, sender, topic, auth_events) do
|
||||||
%Event{
|
%Event{
|
||||||
new(room, sender)
|
new(room, sender)
|
||||||
|
@ -118,6 +139,7 @@ defmodule MatrixServer.Event do
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@spec join_rules(Room.t(), Account.t(), String.t(), [t()]) :: t()
|
||||||
def join_rules(room, sender, join_rule, auth_events) do
|
def join_rules(room, sender, join_rule, auth_events) do
|
||||||
%Event{
|
%Event{
|
||||||
new(room, sender)
|
new(room, sender)
|
||||||
|
@ -130,6 +152,7 @@ defmodule MatrixServer.Event do
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@spec history_visibility(Room.t(), Account.t(), String.t(), [t()]) :: t()
|
||||||
def history_visibility(room, sender, history_visibility, auth_events) do
|
def history_visibility(room, sender, history_visibility, auth_events) do
|
||||||
%Event{
|
%Event{
|
||||||
new(room, sender)
|
new(room, sender)
|
||||||
|
@ -142,6 +165,7 @@ defmodule MatrixServer.Event do
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@spec guest_access(Room.t(), Account.t(), String.t(), [t()]) :: t()
|
||||||
def guest_access(room, sender, guest_access, auth_events) do
|
def guest_access(room, sender, guest_access, auth_events) do
|
||||||
%Event{
|
%Event{
|
||||||
new(room, sender)
|
new(room, sender)
|
||||||
|
@ -154,6 +178,7 @@ defmodule MatrixServer.Event do
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@spec is_control_event(t()) :: boolean()
|
||||||
def is_control_event(%Event{type: "m.room.power_levels", state_key: ""}), do: true
|
def is_control_event(%Event{type: "m.room.power_levels", state_key: ""}), do: true
|
||||||
def is_control_event(%Event{type: "m.room.join_rules", state_key: ""}), do: true
|
def is_control_event(%Event{type: "m.room.join_rules", state_key: ""}), do: true
|
||||||
|
|
||||||
|
@ -162,12 +187,13 @@ defmodule MatrixServer.Event do
|
||||||
state_key: state_key,
|
state_key: state_key,
|
||||||
sender: sender,
|
sender: sender,
|
||||||
content: %{membership: membership}
|
content: %{membership: membership}
|
||||||
})
|
}) do
|
||||||
when sender != state_key and membership in ["leave", "ban"],
|
to_string(sender) != state_key and membership in ["leave", "ban"]
|
||||||
do: true
|
end
|
||||||
|
|
||||||
def is_control_event(_), do: false
|
def is_control_event(_), do: false
|
||||||
|
|
||||||
|
@spec is_state_event(t()) :: boolean()
|
||||||
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.
|
# Perform validations that can be done before state resolution.
|
||||||
|
@ -175,12 +201,13 @@ defmodule MatrixServer.Event do
|
||||||
# We assume that required keys, as well as in the content, is already validated.
|
# We assume that required keys, as well as in the content, is already validated.
|
||||||
|
|
||||||
# Rule 1.4 is left to changeset validation.
|
# Rule 1.4 is left to changeset validation.
|
||||||
|
@spec prevalidate(t()) :: boolean()
|
||||||
def prevalidate(%Event{
|
def prevalidate(%Event{
|
||||||
type: "m.room.create",
|
type: "m.room.create",
|
||||||
prev_events: prev_events,
|
prev_events: prev_events,
|
||||||
auth_events: auth_events,
|
auth_events: auth_events,
|
||||||
room_id: room_id,
|
room_id: room_id,
|
||||||
sender: sender
|
sender: %UserId{domain: domain}
|
||||||
}) do
|
}) do
|
||||||
# TODO: error check on domains?
|
# TODO: error check on domains?
|
||||||
# TODO: rule 1.3
|
# TODO: rule 1.3
|
||||||
|
@ -188,7 +215,7 @@ defmodule MatrixServer.Event do
|
||||||
# Check rules: 1.1, 1.2
|
# Check rules: 1.1, 1.2
|
||||||
prev_events == [] and
|
prev_events == [] and
|
||||||
auth_events == [] and
|
auth_events == [] and
|
||||||
MatrixServer.get_domain(sender) == MatrixServer.get_domain(room_id)
|
domain == MatrixServer.get_domain(room_id)
|
||||||
end
|
end
|
||||||
|
|
||||||
def prevalidate(%Event{auth_events: auth_event_ids, prev_events: prev_event_ids} = event) do
|
def prevalidate(%Event{auth_events: auth_event_ids, prev_events: prev_event_ids} = event) do
|
||||||
|
@ -214,18 +241,27 @@ defmodule MatrixServer.Event do
|
||||||
end
|
end
|
||||||
|
|
||||||
# Rule 4.1 is left to changeset validation.
|
# Rule 4.1 is left to changeset validation.
|
||||||
defp do_prevalidate(%Event{type: "m.room.aliases", sender: sender, state_key: state_key}, _, _) do
|
@spec do_prevalidate(t(), [t()], [t()]) :: boolean()
|
||||||
|
defp do_prevalidate(
|
||||||
|
%Event{type: "m.room.aliases", sender: %UserId{domain: domain}, state_key: state_key},
|
||||||
|
_,
|
||||||
|
_
|
||||||
|
) do
|
||||||
# Check rule: 4.2
|
# Check rule: 4.2
|
||||||
MatrixServer.get_domain(sender) == MatrixServer.get_domain(state_key)
|
domain == MatrixServer.get_domain(state_key)
|
||||||
end
|
end
|
||||||
|
|
||||||
# Rule 5.1 is left to changeset validation.
|
# Rule 5.1 is left to changeset validation.
|
||||||
# Rules 5.2.3, 5.2.4, 5.2.5 is left to state resolution.
|
# Rules 5.2.3, 5.2.4, 5.2.5 is left to state resolution.
|
||||||
# Check rule: 5.2.1
|
# Check rule: 5.2.1
|
||||||
defp do_prevalidate(
|
defp do_prevalidate(
|
||||||
%Event{type: "m.room.member", content: %{"membership" => "join"}, sender: sender},
|
%Event{
|
||||||
|
type: "m.room.member",
|
||||||
|
content: %{"membership" => "join"},
|
||||||
|
sender: %UserId{localpart: localpart, domain: domain}
|
||||||
|
},
|
||||||
_,
|
_,
|
||||||
[%Event{type: "m.room.create", state_key: sender}]
|
[%Event{type: "m.room.create", state_key: %UserId{localpart: localpart, domain: domain}}]
|
||||||
),
|
),
|
||||||
do: true
|
do: true
|
||||||
|
|
||||||
|
@ -239,17 +275,20 @@ defmodule MatrixServer.Event do
|
||||||
},
|
},
|
||||||
_,
|
_,
|
||||||
_
|
_
|
||||||
)
|
) do
|
||||||
when sender != state_key,
|
to_string(sender) == state_key
|
||||||
do: false
|
end
|
||||||
|
|
||||||
# All other rules will be checked during state resolution.
|
# All other rules will be checked during state resolution.
|
||||||
defp do_prevalidate(_, _, _), do: true
|
defp do_prevalidate(_, _, _), do: true
|
||||||
|
|
||||||
|
@spec valid_auth_events?(t(), [t()]) :: boolean()
|
||||||
defp valid_auth_events?(
|
defp valid_auth_events?(
|
||||||
%Event{type: type, sender: sender, state_key: state_key, content: content},
|
%Event{type: type, sender: sender, state_key: state_key, content: content},
|
||||||
auth_events
|
auth_events
|
||||||
) do
|
) do
|
||||||
|
sender = to_string(sender)
|
||||||
|
|
||||||
Enum.all?(auth_events, fn
|
Enum.all?(auth_events, fn
|
||||||
%Event{type: "m.room.create", state_key: ""} ->
|
%Event{type: "m.room.create", state_key: ""} ->
|
||||||
true
|
true
|
||||||
|
@ -275,6 +314,7 @@ defmodule MatrixServer.Event do
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@spec calculate_content_hash(t()) :: {:ok, binary()} | {:error, Jason.EncodeError.t()}
|
||||||
defp calculate_content_hash(event) do
|
defp calculate_content_hash(event) do
|
||||||
m =
|
m =
|
||||||
event
|
event
|
||||||
|
@ -287,6 +327,7 @@ defmodule MatrixServer.Event do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@spec redact(t()) :: map()
|
||||||
defp redact(%Event{type: type, content: content} = event) do
|
defp redact(%Event{type: type, content: content} = event) do
|
||||||
redacted_event =
|
redacted_event =
|
||||||
event
|
event
|
||||||
|
@ -312,6 +353,7 @@ defmodule MatrixServer.Event do
|
||||||
%{redacted_event | content: redact_content(type, content)}
|
%{redacted_event | content: redact_content(type, content)}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@spec redact_content(String.t(), map()) :: map()
|
||||||
defp redact_content("m.room.member", content), do: Map.take(content, ["membership"])
|
defp redact_content("m.room.member", content), do: Map.take(content, ["membership"])
|
||||||
defp redact_content("m.room.create", content), do: Map.take(content, ["creator"])
|
defp redact_content("m.room.create", content), do: Map.take(content, ["creator"])
|
||||||
defp redact_content("m.room.join_rules", content), do: Map.take(content, ["join_rule"])
|
defp redact_content("m.room.join_rules", content), do: Map.take(content, ["join_rule"])
|
||||||
|
@ -336,6 +378,7 @@ defmodule MatrixServer.Event do
|
||||||
defp redact_content(_, _), do: %{}
|
defp redact_content(_, _), do: %{}
|
||||||
|
|
||||||
# Adds content hash, adds signature and calculates event id.
|
# Adds content hash, adds signature and calculates event id.
|
||||||
|
@spec post_process(t()) :: {:ok, t()} | :error
|
||||||
def post_process(event) do
|
def post_process(event) do
|
||||||
with {:ok, content_hash} <- calculate_content_hash(event) do
|
with {:ok, content_hash} <- calculate_content_hash(event) do
|
||||||
encoded_hash = MatrixServer.encode_unpadded_base64(content_hash)
|
encoded_hash = MatrixServer.encode_unpadded_base64(content_hash)
|
||||||
|
@ -346,23 +389,30 @@ defmodule MatrixServer.Event do
|
||||||
|
|
||||||
with {:ok, event} <- set_event_id(event) do
|
with {:ok, event} <- set_event_id(event) do
|
||||||
{:ok, event}
|
{:ok, event}
|
||||||
|
else
|
||||||
|
_ -> :error
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
else
|
||||||
|
_ -> :error
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@spec set_event_id(t()) :: {:ok, t()} | {:error, Jason.EncodeError.t()}
|
||||||
def set_event_id(event) do
|
def set_event_id(event) do
|
||||||
with {:ok, event_id} <- generate_event_id(event) do
|
with {:ok, event_id} <- generate_event_id(event) do
|
||||||
{:ok, %Event{event | event_id: event_id}}
|
{:ok, %Event{event | event_id: event_id}}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@spec generate_event_id(t()) :: {:ok, String.t()} | {:error, Jason.EncodeError.t()}
|
||||||
defp generate_event_id(event) do
|
defp generate_event_id(event) do
|
||||||
with {:ok, hash} <- calculate_reference_hash(event) do
|
with {:ok, hash} <- calculate_reference_hash(event) do
|
||||||
{:ok, "$" <> MatrixServer.encode_url_safe_base64(hash)}
|
{:ok, "$" <> MatrixServer.encode_url_safe_base64(hash)}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@spec calculate_reference_hash(t()) :: {:ok, binary()} | {:error, Jason.EncodeError.t()}
|
||||||
defp calculate_reference_hash(event) do
|
defp calculate_reference_hash(event) do
|
||||||
redacted_event =
|
redacted_event =
|
||||||
event
|
event
|
||||||
|
|
|
@ -4,6 +4,8 @@ defmodule MatrixServer.StateResolution do
|
||||||
alias MatrixServer.{Repo, Event, Room}
|
alias MatrixServer.{Repo, Event, Room}
|
||||||
alias MatrixServer.StateResolution.Authorization
|
alias MatrixServer.StateResolution.Authorization
|
||||||
|
|
||||||
|
@type state_set :: map()
|
||||||
|
|
||||||
def resolve(event), do: resolve(event, true)
|
def resolve(event), do: resolve(event, true)
|
||||||
|
|
||||||
def resolve(%Event{room_id: room_id} = event, apply_state) do
|
def resolve(%Event{room_id: room_id} = event, apply_state) do
|
||||||
|
@ -191,7 +193,7 @@ defmodule MatrixServer.StateResolution do
|
||||||
|
|
||||||
# TODO: refactor
|
# TODO: refactor
|
||||||
case room_events[pl_event_id] do
|
case room_events[pl_event_id] do
|
||||||
%Event{content: %{"users" => pl_users}} -> Map.get(pl_users, sender, 0)
|
%Event{content: %{"users" => pl_users}} -> Map.get(pl_users, to_string(sender), 0)
|
||||||
nil -> 0
|
nil -> 0
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -3,7 +3,12 @@ defmodule MatrixServer.StateResolution.Authorization do
|
||||||
import Ecto.Query
|
import Ecto.Query
|
||||||
|
|
||||||
alias MatrixServer.{Repo, Event}
|
alias MatrixServer.{Repo, Event}
|
||||||
|
alias MatrixServer.Types.UserId
|
||||||
|
alias MatrixServer.StateResolution, as: StateRes
|
||||||
|
|
||||||
|
@typep action :: :invite | :ban | :redact | :kick | {:event, Event.t()}
|
||||||
|
|
||||||
|
@spec authorized?(Event.t(), StateRes.state_set()) :: boolean()
|
||||||
def authorized?(%Event{type: "m.room.create", prev_events: prev_events}, %{}),
|
def authorized?(%Event{type: "m.room.create", prev_events: prev_events}, %{}),
|
||||||
do: prev_events == []
|
do: prev_events == []
|
||||||
|
|
||||||
|
@ -18,7 +23,7 @@ defmodule MatrixServer.StateResolution.Authorization do
|
||||||
state_set
|
state_set
|
||||||
) do
|
) do
|
||||||
join_rule = get_join_rule(state_set)
|
join_rule = get_join_rule(state_set)
|
||||||
membership = get_membership(sender, state_set)
|
membership = get_membership(to_string(sender), state_set)
|
||||||
|
|
||||||
# Check rules: 5.2.3, 5.2.4, 5.2.5
|
# Check rules: 5.2.3, 5.2.4, 5.2.5
|
||||||
cond do
|
cond do
|
||||||
|
@ -48,7 +53,7 @@ defmodule MatrixServer.StateResolution.Authorization do
|
||||||
},
|
},
|
||||||
state_set
|
state_set
|
||||||
) do
|
) do
|
||||||
sender_membership = get_membership(sender, state_set)
|
sender_membership = get_membership(to_string(sender), state_set)
|
||||||
target_membership = get_membership(state_key, state_set)
|
target_membership = get_membership(state_key, state_set)
|
||||||
power_levels = get_power_levels(state_set)
|
power_levels = get_power_levels(state_set)
|
||||||
|
|
||||||
|
@ -56,7 +61,7 @@ defmodule MatrixServer.StateResolution.Authorization do
|
||||||
cond do
|
cond do
|
||||||
sender_membership != "join" -> false
|
sender_membership != "join" -> false
|
||||||
target_membership in ["join", "ban"] -> false
|
target_membership in ["join", "ban"] -> false
|
||||||
has_power_level(sender, power_levels, :invite) -> true
|
has_power_level?(to_string(sender), power_levels, :invite) -> true
|
||||||
true -> false
|
true -> false
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -71,7 +76,7 @@ defmodule MatrixServer.StateResolution.Authorization do
|
||||||
state_set
|
state_set
|
||||||
) do
|
) do
|
||||||
# Check rule: 5.4.1
|
# Check rule: 5.4.1
|
||||||
get_membership(sender, state_set) in ["invite", "join"]
|
get_membership(to_string(sender), state_set) in ["invite", "join"]
|
||||||
end
|
end
|
||||||
|
|
||||||
def authorized?(
|
def authorized?(
|
||||||
|
@ -83,18 +88,25 @@ defmodule MatrixServer.StateResolution.Authorization do
|
||||||
},
|
},
|
||||||
state_set
|
state_set
|
||||||
) do
|
) do
|
||||||
sender_membership = get_membership(sender, state_set)
|
sender_membership = get_membership(to_string(sender), state_set)
|
||||||
target_membership = get_membership(state_key, state_set)
|
target_membership = get_membership(state_key, state_set)
|
||||||
power_levels = get_power_levels(state_set)
|
power_levels = get_power_levels(state_set)
|
||||||
sender_pl = get_user_power_level(sender, power_levels)
|
sender_pl = get_user_power_level(to_string(sender), power_levels)
|
||||||
target_pl = get_user_power_level(state_key, power_levels)
|
target_pl = get_user_power_level(state_key, power_levels)
|
||||||
|
|
||||||
# Check rules: 5.4.2, 5.4.3, 5.4.4
|
# Check rules: 5.4.2, 5.4.3, 5.4.4
|
||||||
cond do
|
cond do
|
||||||
sender_membership != "join" -> false
|
sender_membership != "join" ->
|
||||||
target_membership == "ban" and not has_power_level(sender, power_levels, :ban) -> false
|
false
|
||||||
has_power_level(sender, power_levels, :kick) and target_pl < sender_pl -> true
|
|
||||||
true -> false
|
target_membership == "ban" and not has_power_level?(to_string(sender), power_levels, :ban) ->
|
||||||
|
false
|
||||||
|
|
||||||
|
has_power_level?(to_string(sender), power_levels, :kick) and target_pl < sender_pl ->
|
||||||
|
true
|
||||||
|
|
||||||
|
true ->
|
||||||
|
false
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -107,15 +119,15 @@ defmodule MatrixServer.StateResolution.Authorization do
|
||||||
},
|
},
|
||||||
state_set
|
state_set
|
||||||
) do
|
) do
|
||||||
sender_membership = get_membership(sender, state_set)
|
sender_membership = get_membership(to_string(sender), state_set)
|
||||||
power_levels = get_power_levels(state_set)
|
power_levels = get_power_levels(state_set)
|
||||||
sender_pl = get_user_power_level(sender, power_levels)
|
sender_pl = get_user_power_level(to_string(sender), power_levels)
|
||||||
target_pl = get_user_power_level(state_key, power_levels)
|
target_pl = get_user_power_level(state_key, power_levels)
|
||||||
|
|
||||||
# Check rules: 5.5.1, 5.5.2
|
# Check rules: 5.5.1, 5.5.2
|
||||||
cond do
|
cond do
|
||||||
sender_membership != "join" -> false
|
sender_membership != "join" -> false
|
||||||
has_power_level(sender, power_levels, :ban) and target_pl < sender_pl -> true
|
has_power_level?(to_string(sender), power_levels, :ban) and target_pl < sender_pl -> true
|
||||||
true -> false
|
true -> false
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -125,14 +137,15 @@ defmodule MatrixServer.StateResolution.Authorization do
|
||||||
|
|
||||||
def authorized?(%Event{sender: sender} = event, state_set) do
|
def authorized?(%Event{sender: sender} = event, state_set) do
|
||||||
# Check rule: 6
|
# Check rule: 6
|
||||||
get_membership(sender, state_set) == "join" and _authorized?(event, state_set)
|
get_membership(to_string(sender), state_set) == "join" and _authorized?(event, state_set)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@spec _authorized?(Event.t(), StateRes.state_set()) :: boolean()
|
||||||
defp _authorized?(%Event{type: "m.room.third_party_invite", sender: sender}, state_set) do
|
defp _authorized?(%Event{type: "m.room.third_party_invite", sender: sender}, state_set) do
|
||||||
power_levels = get_power_levels(state_set)
|
power_levels = get_power_levels(state_set)
|
||||||
# Check rule: 7.1
|
# Check rule: 7.1
|
||||||
|
|
||||||
has_power_level(sender, power_levels, :invite)
|
has_power_level?(to_string(sender), power_levels, :invite)
|
||||||
end
|
end
|
||||||
|
|
||||||
defp _authorized?(%Event{state_key: state_key, sender: sender} = event, state_set) do
|
defp _authorized?(%Event{state_key: state_key, sender: sender} = event, state_set) do
|
||||||
|
@ -140,19 +153,20 @@ defmodule MatrixServer.StateResolution.Authorization do
|
||||||
|
|
||||||
# Check rules: 8, 9
|
# Check rules: 8, 9
|
||||||
cond do
|
cond do
|
||||||
not has_power_level(sender, power_levels, {:event, event}) -> false
|
not has_power_level?(to_string(sender), power_levels, {:event, event}) -> false
|
||||||
String.starts_with?(state_key, "@") and state_key != sender -> false
|
String.starts_with?(state_key, "@") and state_key != sender -> false
|
||||||
true -> __authorized?(event, state_set)
|
true -> __authorized?(event, state_set)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@spec __authorized?(Event.t(), StateRes.state_set()) :: boolean()
|
||||||
defp __authorized?(
|
defp __authorized?(
|
||||||
%Event{type: "m.room.power_levels", sender: sender, content: content},
|
%Event{type: "m.room.power_levels", sender: sender, content: content},
|
||||||
state_set
|
state_set
|
||||||
) do
|
) do
|
||||||
current_pls = get_power_levels(state_set)
|
current_pls = get_power_levels(state_set)
|
||||||
new_pls = content
|
new_pls = content
|
||||||
sender_pl = get_user_power_level(sender, new_pls)
|
sender_pl = get_user_power_level(to_string(sender), new_pls)
|
||||||
|
|
||||||
# Check rules: 10.2, 10.3, 10.4, 10.5
|
# Check rules: 10.2, 10.3, 10.4, 10.5
|
||||||
cond do
|
cond do
|
||||||
|
@ -166,40 +180,43 @@ defmodule MatrixServer.StateResolution.Authorization do
|
||||||
|
|
||||||
defp __authorized?(_, _), do: true
|
defp __authorized?(_, _), do: true
|
||||||
|
|
||||||
|
@spec get_power_levels(StateRes.state_set()) :: map() | nil
|
||||||
defp get_power_levels(state_set) do
|
defp get_power_levels(state_set) do
|
||||||
case state_set[{"m.room.power_levels", ""}] do
|
with %Event{content: content} <- state_set[{"m.room.power_levels", ""}] do
|
||||||
%Event{content: content} -> content
|
content
|
||||||
nil -> nil
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@spec get_join_rule(StateRes.state_set()) :: String.t() | nil
|
||||||
defp get_join_rule(state_set) do
|
defp get_join_rule(state_set) do
|
||||||
case state_set[{"m.room.join_rules", ""}] do
|
with %Event{content: %{"join_rule" => join_rule}} <- state_set[{"m.room.join_rules", ""}] do
|
||||||
%Event{content: %{"join_rule" => join_rule}} -> join_rule
|
join_rule
|
||||||
nil -> nil
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@spec get_membership(String.t(), StateRes.state_set()) :: String.t() | nil
|
||||||
defp get_membership(user, state_set) do
|
defp get_membership(user, state_set) do
|
||||||
case state_set[{"m.room.member", user}] do
|
with %Event{content: %{"membership" => membership}} <- state_set[{"m.room.member", user}] do
|
||||||
%Event{content: %{"membership" => membership}} -> membership
|
membership
|
||||||
nil -> nil
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
defp has_power_level(user, power_levels, action) do
|
@spec has_power_level?(String.t(), map() | nil, action()) :: boolean()
|
||||||
|
defp has_power_level?(user, power_levels, action) do
|
||||||
user_pl = get_user_power_level(user, power_levels)
|
user_pl = get_user_power_level(user, power_levels)
|
||||||
action_pl = get_action_power_level(action, power_levels)
|
action_pl = get_action_power_level(action, power_levels)
|
||||||
|
|
||||||
user_pl >= action_pl
|
user_pl >= action_pl
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@spec get_user_power_level(String.t(), map() | nil) :: non_neg_integer()
|
||||||
defp get_user_power_level(user, %{"users" => users}) when is_map_key(users, user),
|
defp get_user_power_level(user, %{"users" => users}) when is_map_key(users, user),
|
||||||
do: users[user]
|
do: users[user]
|
||||||
|
|
||||||
defp get_user_power_level(_, %{"users_default" => pl}), do: pl
|
defp get_user_power_level(_, %{"users_default" => pl}), do: pl
|
||||||
defp get_user_power_level(_, _), do: 0
|
defp get_user_power_level(_, _), do: 0
|
||||||
|
|
||||||
|
@spec get_action_power_level(action(), map() | nil) :: non_neg_integer()
|
||||||
defp get_action_power_level(:invite, %{"invite" => pl}), do: pl
|
defp get_action_power_level(:invite, %{"invite" => pl}), do: pl
|
||||||
defp get_action_power_level(:invite, _), do: 50
|
defp get_action_power_level(:invite, _), do: 50
|
||||||
defp get_action_power_level(:ban, %{"ban" => pl}), do: pl
|
defp get_action_power_level(:ban, %{"ban" => pl}), do: pl
|
||||||
|
@ -218,7 +235,7 @@ defmodule MatrixServer.StateResolution.Authorization do
|
||||||
case power_levels do
|
case power_levels do
|
||||||
%{"state_default" => pl} -> pl
|
%{"state_default" => pl} -> pl
|
||||||
%{} -> 50
|
%{} -> 50
|
||||||
_ -> 0
|
nil -> 0
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
case power_levels do
|
case power_levels do
|
||||||
|
@ -228,6 +245,9 @@ defmodule MatrixServer.StateResolution.Authorization do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# TODO: Power_levels may not have all these keys defined.
|
||||||
|
@spec authorize_power_levels(UserId.t(), non_neg_integer(), map() | nil, map() | nil) ::
|
||||||
|
boolean()
|
||||||
defp authorize_power_levels(
|
defp authorize_power_levels(
|
||||||
user,
|
user,
|
||||||
user_pl,
|
user_pl,
|
||||||
|
@ -239,9 +259,12 @@ defmodule MatrixServer.StateResolution.Authorization do
|
||||||
valid_power_level_key_changes(Map.take(current_pls, keys), Map.take(new_pls, keys), user_pl) and
|
valid_power_level_key_changes(Map.take(current_pls, keys), Map.take(new_pls, keys), user_pl) and
|
||||||
valid_power_level_key_changes(current_events, new_events, user_pl) and
|
valid_power_level_key_changes(current_events, new_events, user_pl) and
|
||||||
valid_power_level_key_changes(current_users, new_users, user_pl) and
|
valid_power_level_key_changes(current_users, new_users, user_pl) and
|
||||||
valid_power_level_users_changes(current_users, new_users, user, user_pl)
|
valid_power_level_users_changes(current_users, new_users, to_string(user), user_pl)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp authorize_power_levels(_, _, _, _), do: false
|
||||||
|
|
||||||
|
@spec valid_power_level_key_changes(map(), map(), non_neg_integer()) :: boolean()
|
||||||
defp valid_power_level_key_changes(l1, l2, user_pl) do
|
defp valid_power_level_key_changes(l1, l2, user_pl) do
|
||||||
set1 = MapSet.new(l1)
|
set1 = MapSet.new(l1)
|
||||||
set2 = MapSet.new(l2)
|
set2 = MapSet.new(l2)
|
||||||
|
|
|
@ -18,7 +18,7 @@ defmodule MatrixServer.Types.AliasId do
|
||||||
[localpart, domain] <- String.split(rest, ":", parts: 2) do
|
[localpart, domain] <- String.split(rest, ":", parts: 2) do
|
||||||
if String.length(localpart) + String.length(domain) + 2 <= 255 and
|
if String.length(localpart) + String.length(domain) + 2 <= 255 and
|
||||||
MatrixServer.valid_domain?(domain) do
|
MatrixServer.valid_domain?(domain) do
|
||||||
%AliasId{localpart: localpart, domain: domain}
|
{:ok, %AliasId{localpart: localpart, domain: domain}}
|
||||||
else
|
else
|
||||||
:error
|
:error
|
||||||
end
|
end
|
||||||
|
@ -33,11 +33,11 @@ defmodule MatrixServer.Types.AliasId do
|
||||||
"@" <> rest = s
|
"@" <> rest = s
|
||||||
[localpart, domain] = String.split(rest, ":", parts: 2)
|
[localpart, domain] = String.split(rest, ":", parts: 2)
|
||||||
|
|
||||||
%AliasId{localpart: localpart, domain: domain}
|
{:ok, %AliasId{localpart: localpart, domain: domain}}
|
||||||
end
|
end
|
||||||
|
|
||||||
def load(_), do: :error
|
def load(_), do: :error
|
||||||
|
|
||||||
def dump(%AliasId{} = alias_id), do: to_string(alias_id)
|
def dump(%AliasId{} = alias_id), do: {:ok, to_string(alias_id)}
|
||||||
def dump(_), do: :error
|
def dump(_), do: :error
|
||||||
end
|
end
|
||||||
|
|
|
@ -18,7 +18,7 @@ defmodule MatrixServer.Types.EventId do
|
||||||
def cast(s) when is_binary(s) do
|
def cast(s) when is_binary(s) do
|
||||||
with "$" <> id <- s do
|
with "$" <> id <- s do
|
||||||
if Regex.match?(@id_regex, id) do
|
if Regex.match?(@id_regex, id) do
|
||||||
%EventId{id: id}
|
{:ok, %EventId{id: id}}
|
||||||
else
|
else
|
||||||
:error
|
:error
|
||||||
end
|
end
|
||||||
|
@ -32,11 +32,11 @@ defmodule MatrixServer.Types.EventId do
|
||||||
def load(s) when is_binary(s) do
|
def load(s) when is_binary(s) do
|
||||||
"$" <> rest = s
|
"$" <> rest = s
|
||||||
|
|
||||||
%EventId{id: rest}
|
{:ok, %EventId{id: rest}}
|
||||||
end
|
end
|
||||||
|
|
||||||
def load(_), do: :error
|
def load(_), do: :error
|
||||||
|
|
||||||
def dump(%EventId{} = event_id), do: to_string(event_id)
|
def dump(%EventId{} = event_id), do: {:ok, to_string(event_id)}
|
||||||
def dump(_), do: :error
|
def dump(_), do: :error
|
||||||
end
|
end
|
||||||
|
|
|
@ -21,7 +21,7 @@ defmodule MatrixServer.Types.GroupId do
|
||||||
if String.length(localpart) + String.length(domain) + 2 <= 255 and
|
if String.length(localpart) + String.length(domain) + 2 <= 255 and
|
||||||
Regex.match?(@localpart_regex, localpart) and
|
Regex.match?(@localpart_regex, localpart) and
|
||||||
MatrixServer.valid_domain?(domain) do
|
MatrixServer.valid_domain?(domain) do
|
||||||
%GroupId{localpart: localpart, domain: domain}
|
{:ok, %GroupId{localpart: localpart, domain: domain}}
|
||||||
else
|
else
|
||||||
:error
|
:error
|
||||||
end
|
end
|
||||||
|
@ -36,11 +36,11 @@ defmodule MatrixServer.Types.GroupId do
|
||||||
"@" <> rest = s
|
"@" <> rest = s
|
||||||
[localpart, domain] = String.split(rest, ":", parts: 2)
|
[localpart, domain] = String.split(rest, ":", parts: 2)
|
||||||
|
|
||||||
%GroupId{localpart: localpart, domain: domain}
|
{:ok, %GroupId{localpart: localpart, domain: domain}}
|
||||||
end
|
end
|
||||||
|
|
||||||
def load(_), do: :error
|
def load(_), do: :error
|
||||||
|
|
||||||
def dump(%GroupId{} = group_id), do: to_string(group_id)
|
def dump(%GroupId{} = group_id), do: {:ok, to_string(group_id)}
|
||||||
def dump(_), do: :error
|
def dump(_), do: :error
|
||||||
end
|
end
|
||||||
|
|
|
@ -17,7 +17,7 @@ defmodule MatrixServer.Types.RoomId do
|
||||||
with "!" <> rest <- s,
|
with "!" <> rest <- s,
|
||||||
[localpart, domain] <- String.split(rest, ":", parts: 2) do
|
[localpart, domain] <- String.split(rest, ":", parts: 2) do
|
||||||
if MatrixServer.valid_domain?(domain) do
|
if MatrixServer.valid_domain?(domain) do
|
||||||
%RoomId{localpart: localpart, domain: domain}
|
{:ok, %RoomId{localpart: localpart, domain: domain}}
|
||||||
else
|
else
|
||||||
:error
|
:error
|
||||||
end
|
end
|
||||||
|
@ -32,11 +32,11 @@ defmodule MatrixServer.Types.RoomId do
|
||||||
"!" <> rest = s
|
"!" <> rest = s
|
||||||
[localpart, domain] = String.split(rest, ":", parts: 2)
|
[localpart, domain] = String.split(rest, ":", parts: 2)
|
||||||
|
|
||||||
%RoomId{localpart: localpart, domain: domain}
|
{:ok, %RoomId{localpart: localpart, domain: domain}}
|
||||||
end
|
end
|
||||||
|
|
||||||
def load(_), do: :error
|
def load(_), do: :error
|
||||||
|
|
||||||
def dump(%RoomId{} = room_id), do: to_string(room_id)
|
def dump(%RoomId{} = room_id), do: {:ok, to_string(room_id)}
|
||||||
def dump(_), do: :error
|
def dump(_), do: :error
|
||||||
end
|
end
|
||||||
|
|
|
@ -3,6 +3,11 @@ defmodule MatrixServer.Types.UserId do
|
||||||
|
|
||||||
alias MatrixServer.Types.UserId
|
alias MatrixServer.Types.UserId
|
||||||
|
|
||||||
|
@type t :: %__MODULE__{
|
||||||
|
localpart: String.t(),
|
||||||
|
domain: String.t()
|
||||||
|
}
|
||||||
|
|
||||||
defstruct [:localpart, :domain]
|
defstruct [:localpart, :domain]
|
||||||
|
|
||||||
@localpart_regex ~r/^[a-z0-9._=\-\/]+$/
|
@localpart_regex ~r/^[a-z0-9._=\-\/]+$/
|
||||||
|
@ -20,7 +25,7 @@ defmodule MatrixServer.Types.UserId do
|
||||||
[localpart, domain] <- String.split(rest, ":", parts: 2) do
|
[localpart, domain] <- String.split(rest, ":", parts: 2) do
|
||||||
if String.length(localpart) + String.length(domain) + 2 <= 255 and
|
if String.length(localpart) + String.length(domain) + 2 <= 255 and
|
||||||
Regex.match?(@localpart_regex, localpart) and MatrixServer.valid_domain?(domain) do
|
Regex.match?(@localpart_regex, localpart) and MatrixServer.valid_domain?(domain) do
|
||||||
%UserId{localpart: localpart, domain: domain}
|
{:ok, %UserId{localpart: localpart, domain: domain}}
|
||||||
else
|
else
|
||||||
:error
|
:error
|
||||||
end
|
end
|
||||||
|
@ -35,11 +40,11 @@ defmodule MatrixServer.Types.UserId do
|
||||||
"@" <> rest = s
|
"@" <> rest = s
|
||||||
[localpart, domain] = String.split(rest, ":", parts: 2)
|
[localpart, domain] = String.split(rest, ":", parts: 2)
|
||||||
|
|
||||||
%UserId{localpart: localpart, domain: domain}
|
{:ok, %UserId{localpart: localpart, domain: domain}}
|
||||||
end
|
end
|
||||||
|
|
||||||
def load(_), do: :error
|
def load(_), do: :error
|
||||||
|
|
||||||
def dump(%UserId{} = user_id), do: to_string(user_id)
|
def dump(%UserId{} = user_id), do: {:ok, to_string(user_id)}
|
||||||
def dump(_), do: :error
|
def dump(_), do: :error
|
||||||
end
|
end
|
||||||
|
|
Loading…
Reference in a new issue