Add common struct to represent matrix identifiers
Use identifier struct in profile query validation
This commit is contained in:
parent
46231b9b09
commit
5d75244bc0
2 changed files with 179 additions and 6 deletions
173
lib/matrix_server/identifier.ex
Normal file
173
lib/matrix_server/identifier.ex
Normal file
|
@ -0,0 +1,173 @@
|
|||
defmodule MatrixServer.Identifier do
|
||||
use Ecto.Type
|
||||
|
||||
alias MatrixServer.Identifier
|
||||
|
||||
defstruct [:type, :localpart, :domain]
|
||||
|
||||
def type(), do: :string
|
||||
|
||||
def cast(id) when is_binary(id) do
|
||||
with %Identifier{} = identifier <- parse(id) do
|
||||
{:ok, identifier}
|
||||
end
|
||||
end
|
||||
|
||||
def cast(_), do: :error
|
||||
|
||||
defp parse(id) do
|
||||
{sigil, id} = String.split_at(id, 1)
|
||||
|
||||
case get_type(sigil) do
|
||||
{:ok, :event} ->
|
||||
parse_event(id)
|
||||
|
||||
{:ok, type} when type in [:user, :room, :group, :alias] ->
|
||||
case String.split(id, ":", parts: 2) do
|
||||
[localpart, domain] ->
|
||||
case type do
|
||||
:user -> parse_user(localpart, domain)
|
||||
:room -> parse_room(localpart, domain)
|
||||
:group -> parse_group(localpart, domain)
|
||||
:alias -> parse_alias(localpart, domain)
|
||||
end
|
||||
|
||||
_ ->
|
||||
:error
|
||||
end
|
||||
|
||||
:error ->
|
||||
:error
|
||||
end
|
||||
end
|
||||
|
||||
defp get_type("@"), do: {:ok, :user}
|
||||
defp get_type("!"), do: {:ok, :room}
|
||||
defp get_type("$"), do: {:ok, :event}
|
||||
defp get_type("+"), do: {:ok, :group}
|
||||
defp get_type("#"), do: {:ok, :alias}
|
||||
defp get_type(_), do: :error
|
||||
|
||||
@user_regex ~r/^[a-z0-9._=\-\/]+$/
|
||||
defp parse_user(localpart, domain) do
|
||||
if String.length(localpart) + String.length(domain) + 2 <= 255 and
|
||||
Regex.match?(@user_regex, localpart) and
|
||||
valid_domain?(domain) do
|
||||
%Identifier{type: :user, localpart: localpart, domain: domain}
|
||||
else
|
||||
:error
|
||||
end
|
||||
end
|
||||
|
||||
defp parse_room(localpart, domain) do
|
||||
if valid_domain?(domain) do
|
||||
%Identifier{type: :room, localpart: localpart, domain: domain}
|
||||
else
|
||||
:error
|
||||
end
|
||||
end
|
||||
|
||||
@event_regex ~r/^[[:alnum:]-_]+$/
|
||||
defp parse_event(localpart) do
|
||||
if Regex.match?(@event_regex, localpart) do
|
||||
%Identifier{type: :event, localpart: localpart}
|
||||
else
|
||||
:error
|
||||
end
|
||||
end
|
||||
|
||||
@group_regex ~r/^[[:lower:][:digit:]._=\-\/]+$/
|
||||
defp parse_group(localpart, domain) do
|
||||
if String.length(localpart) + String.length(domain) + 2 <= 255 and
|
||||
Regex.match?(@group_regex, localpart) and
|
||||
valid_domain?(domain) do
|
||||
%Identifier{type: :group, localpart: localpart, domain: domain}
|
||||
else
|
||||
:error
|
||||
end
|
||||
end
|
||||
|
||||
defp parse_alias(localpart, domain) do
|
||||
if String.length(localpart) + String.length(domain) + 2 <= 255 and valid_domain?(domain) do
|
||||
%Identifier{type: :alias, localpart: localpart, domain: domain}
|
||||
else
|
||||
:error
|
||||
end
|
||||
end
|
||||
|
||||
def load(id) when is_binary(id) do
|
||||
{:ok, parse_unvalidated(id)}
|
||||
end
|
||||
|
||||
def load(_), do: :error
|
||||
|
||||
defp parse_unvalidated(id) do
|
||||
{sigil, id} = String.split_at(id, 1)
|
||||
|
||||
case get_type(sigil) do
|
||||
{:ok, :event} ->
|
||||
%Identifier{type: :event, localpart: id}
|
||||
|
||||
{:ok, type} ->
|
||||
[localpart, domain] = String.split(id, ":", parts: 2)
|
||||
|
||||
identifier = %Identifier{localpart: localpart, domain: domain}
|
||||
|
||||
case type do
|
||||
:user -> %Identifier{identifier | type: :user}
|
||||
:room -> %Identifier{identifier | type: :room}
|
||||
:group -> %Identifier{identifier | type: :group}
|
||||
:alias -> %Identifier{identifier | type: :alias}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def dump(%Identifier{type: :event, localpart: localpart}) do
|
||||
{:ok, "$" <> localpart}
|
||||
end
|
||||
|
||||
def dump(%Identifier{type: type, localpart: localpart, domain: domain}) do
|
||||
{:ok, type_to_sigil(type) <> localpart <> ":" <> domain}
|
||||
end
|
||||
|
||||
def dump(_), do: :error
|
||||
|
||||
defp type_to_sigil(:user), do: "@"
|
||||
defp type_to_sigil(:room), do: "!"
|
||||
defp type_to_sigil(:event), do: "$"
|
||||
defp type_to_sigil(:group), do: "+"
|
||||
defp type_to_sigil(:alias), do: "#"
|
||||
|
||||
@ipv6_regex ~r/^\[(?<ip>[^\]]+)\](?<port>:\d{1,5})?$/
|
||||
@ipv4_regex ~r/^(?<hostname>[^:]+)(?<port>:\d{1,5})?$/
|
||||
@dns_regex ~r/^[[:alnum:]-.]{1,255}$/
|
||||
defp valid_domain?(domain) do
|
||||
if String.starts_with?(domain, "[") do
|
||||
# Parse as ipv6.
|
||||
with %{"ip" => ip} <-
|
||||
Regex.named_captures(@ipv6_regex, domain),
|
||||
{:ok, _} <- :inet.parse_address(String.to_charlist(ip)) do
|
||||
true
|
||||
else
|
||||
_ -> false
|
||||
end
|
||||
else
|
||||
# Parse as ipv4 or dns name.
|
||||
case Regex.named_captures(@ipv4_regex, domain) do
|
||||
nil ->
|
||||
false
|
||||
|
||||
%{"hostname" => hostname} ->
|
||||
# Try to parse as ipv4.
|
||||
case String.to_charlist(hostname) |> :inet.parse_address() do
|
||||
{:ok, _} ->
|
||||
true
|
||||
|
||||
{:error, _} ->
|
||||
# Try to parse as dns name.
|
||||
Regex.match?(@dns_regex, hostname)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -5,7 +5,7 @@ defmodule MatrixServerWeb.Federation.QueryController do
|
|||
import MatrixServerWeb.Error
|
||||
import Ecto.Query
|
||||
|
||||
alias MatrixServer.{Repo, Account}
|
||||
alias MatrixServer.{Repo, Account, Identifier}
|
||||
|
||||
defmodule ProfileRequest do
|
||||
use Ecto.Schema
|
||||
|
@ -14,7 +14,7 @@ defmodule MatrixServerWeb.Federation.QueryController do
|
|||
|
||||
@primary_key false
|
||||
embedded_schema do
|
||||
field :user_id, :string
|
||||
field :user_id, Identifier
|
||||
field :field, :string
|
||||
end
|
||||
|
||||
|
@ -31,10 +31,10 @@ defmodule MatrixServerWeb.Federation.QueryController do
|
|||
end
|
||||
|
||||
def profile(conn, params) do
|
||||
with {:ok, %ProfileRequest{user_id: user_id}} <- ProfileRequest.validate(params) do
|
||||
if MatrixServer.get_domain(user_id) == MatrixServer.server_name() do
|
||||
localpart = MatrixServer.get_localpart(user_id)
|
||||
|
||||
with {:ok,
|
||||
%ProfileRequest{user_id: %Identifier{type: :user, localpart: localpart, domain: domain}}} <-
|
||||
ProfileRequest.validate(params) do
|
||||
if domain == MatrixServer.server_name() do
|
||||
case Repo.one(from a in Account, where: a.localpart == ^localpart) do
|
||||
%Account{} ->
|
||||
# TODO: Return displayname and avatar_url when we implement them.
|
||||
|
|
Loading…
Reference in a new issue