2021-08-06 20:03:34 +00:00
|
|
|
defmodule MatrixServerWeb.FederationClient do
|
|
|
|
use Tesla
|
|
|
|
|
|
|
|
alias MatrixServerWeb.Endpoint
|
2021-08-12 22:45:07 +00:00
|
|
|
alias MatrixServerWeb.Federation.Request.GetSigningKeys
|
2021-08-06 20:03:34 +00:00
|
|
|
alias MatrixServerWeb.Router.Helpers, as: RouteHelpers
|
|
|
|
|
|
|
|
# TODO: Maybe create database-backed homeserver struct to pass to client function.
|
|
|
|
|
|
|
|
@middleware [
|
|
|
|
Tesla.Middleware.JSON
|
|
|
|
]
|
|
|
|
|
|
|
|
@adapter {Tesla.Adapter.Finch, name: MatrixServerWeb.HTTPClient}
|
|
|
|
|
|
|
|
def client(server_name) do
|
2021-08-08 17:20:10 +00:00
|
|
|
Tesla.client([{Tesla.Middleware.BaseUrl, "http://" <> server_name} | @middleware], @adapter)
|
2021-08-06 20:03:34 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
def get_signing_keys(client) do
|
2021-08-13 15:36:34 +00:00
|
|
|
path = RouteHelpers.key_path(Endpoint, :get_signing_keys)
|
|
|
|
|
2021-08-13 11:45:10 +00:00
|
|
|
with {:ok,
|
|
|
|
%GetSigningKeys{server_name: server_name, verify_keys: verify_keys, signatures: sigs} =
|
2021-08-13 15:36:34 +00:00
|
|
|
response} <- tesla_request(:get, client, path, GetSigningKeys),
|
|
|
|
{:ok, encoded_body} <- MatrixServer.serialize_and_encode(response),
|
|
|
|
server_sigs when not is_nil(server_sigs) <- sigs[server_name] do
|
2021-08-13 11:45:10 +00:00
|
|
|
# For each verify key, check if there is a matching signature.
|
|
|
|
# If not, invalidate the whole response.
|
2021-08-13 15:36:34 +00:00
|
|
|
Enum.all?(verify_keys, fn {key_id, %{"key" => key}} ->
|
|
|
|
with true <- Map.has_key?(server_sigs, key_id),
|
|
|
|
{:ok, decoded_key} <- MatrixServer.decode_base64(key),
|
|
|
|
{:ok, decoded_sig} <- MatrixServer.decode_base64(server_sigs[key_id]) do
|
|
|
|
MatrixServer.sign_verify(decoded_sig, encoded_body, decoded_key)
|
2021-08-13 11:45:10 +00:00
|
|
|
else
|
2021-08-13 15:36:34 +00:00
|
|
|
_ -> false
|
2021-08-13 11:45:10 +00:00
|
|
|
end
|
2021-08-13 15:36:34 +00:00
|
|
|
end)
|
|
|
|
|> then(fn
|
|
|
|
true -> {:ok, response}
|
|
|
|
false -> :error
|
|
|
|
end)
|
|
|
|
else
|
|
|
|
_ -> :error
|
2021-08-12 22:45:07 +00:00
|
|
|
end
|
2021-08-06 20:03:34 +00:00
|
|
|
end
|
2021-08-08 17:20:10 +00:00
|
|
|
|
2021-08-10 16:02:53 +00:00
|
|
|
# TODO: Create tesla middleware to add signature and headers.
|
|
|
|
def query_profile(client, server_name, user_id, field \\ nil) do
|
|
|
|
origin = MatrixServer.server_name()
|
|
|
|
path = RouteHelpers.query_path(Endpoint, :profile) |> Tesla.build_url(user_id: user_id)
|
|
|
|
path = if field, do: Tesla.build_url(path, field: field), else: path
|
|
|
|
|
|
|
|
object_to_sign = %{
|
|
|
|
method: "GET",
|
2021-08-08 17:20:10 +00:00
|
|
|
uri: path,
|
|
|
|
origin: origin,
|
2021-08-10 16:02:53 +00:00
|
|
|
destination: server_name
|
2021-08-08 17:20:10 +00:00
|
|
|
}
|
|
|
|
|
2021-08-12 22:45:07 +00:00
|
|
|
{:ok, signature, key_id} = MatrixServer.KeyServer.sign_object(object_to_sign)
|
2021-08-10 16:02:53 +00:00
|
|
|
signatures = %{origin => %{key_id => signature}}
|
|
|
|
auth_headers = create_signature_authorization_headers(signatures, origin)
|
2021-08-08 17:20:10 +00:00
|
|
|
|
2021-08-10 16:02:53 +00:00
|
|
|
Tesla.get(client, path, headers: auth_headers)
|
2021-08-08 17:20:10 +00:00
|
|
|
end
|
|
|
|
|
2021-08-10 16:02:53 +00:00
|
|
|
defp create_signature_authorization_headers(signatures, origin) do
|
2021-08-08 17:20:10 +00:00
|
|
|
Enum.map(signatures[origin], fn {key, sig} ->
|
|
|
|
{"Authorization", "X-Matrix origin=#{origin},key=\"#{key}\",sig=\"#{sig}\""}
|
|
|
|
end)
|
|
|
|
end
|
2021-08-13 11:45:10 +00:00
|
|
|
|
|
|
|
defp tesla_request(method, client, path, request_schema) do
|
|
|
|
with {:ok, %Tesla.Env{body: body}} <- Tesla.request(client, url: path, method: method),
|
|
|
|
%Ecto.Changeset{valid?: true} = cs <- apply(request_schema, :changeset, [body]) do
|
|
|
|
{:ok, Ecto.Changeset.apply_changes(cs)}
|
|
|
|
else
|
|
|
|
_ -> :error
|
|
|
|
end
|
|
|
|
end
|
2021-08-06 20:03:34 +00:00
|
|
|
end
|