Create tesla middleware for adding signature to federation requests

Change repo structure
This commit is contained in:
Pim Kunis 2021-08-14 15:20:42 +02:00
parent 214293323c
commit be9860f7d0
23 changed files with 83 additions and 58 deletions

View file

@ -2,7 +2,7 @@ defmodule MatrixServer.QuickCheck do
import Ecto.Query
alias MatrixServer.{Repo, Room, Account, RoomServer}
alias MatrixServerWeb.Request.CreateRoom
alias MatrixServerWeb.Client.Request.CreateRoom
def create_room(name \\ nil, topic \\ nil) do
account = Repo.one!(from a in Account, limit: 1)

View file

@ -5,8 +5,8 @@ defmodule MatrixServer.RoomServer do
import Ecto.Changeset
alias MatrixServer.{Repo, Room, Event, StateResolution}
alias MatrixServerWeb.Request.CreateRoom
alias MatrixServer.StateResolution.Authorization
alias MatrixServerWeb.Client.Request.CreateRoom
@registry MatrixServer.RoomServer.Registry
@supervisor MatrixServer.RoomServer.Supervisor

View file

@ -4,7 +4,7 @@ defmodule MatrixServer.Account do
import Ecto.{Changeset, Query}
alias MatrixServer.{Repo, Account, Device}
alias MatrixServerWeb.Request.{Register, Login}
alias MatrixServerWeb.Client.Request.{Register, Login}
alias Ecto.Multi
@max_mxid_length 255

View file

@ -4,7 +4,7 @@ defmodule MatrixServer.Device do
import Ecto.{Changeset, Query}
alias MatrixServer.{Account, Device, Repo}
alias MatrixServerWeb.Request.Login
alias MatrixServerWeb.Client.Request.Login
@primary_key false
schema "devices" do

View file

@ -5,7 +5,7 @@ defmodule MatrixServer.Room do
import Ecto.Query
alias MatrixServer.{Repo, Room, Event, Alias, RoomServer}
alias MatrixServerWeb.Request.CreateRoom
alias MatrixServerWeb.Client.Request.CreateRoom
@primary_key {:id, :string, []}
schema "rooms" do

View file

@ -4,7 +4,7 @@ defmodule MatrixServer.ServerKeyInfo do
import Ecto.Query
alias MatrixServer.{Repo, ServerKeyInfo, SigningKey}
alias MatrixServerWeb.FederationClient
alias MatrixServerWeb.Federation.HTTPClient
alias MatrixServerWeb.Federation.Request.GetSigningKeys
alias Ecto.Multi
@ -35,7 +35,7 @@ defmodule MatrixServer.ServerKeyInfo do
defp refresh_signing_keys(server_name) do
# TODO: Handle expired keys.
in_a_week = System.os_time(:millisecond) + 1000 * 60 * 60 * 24 * 7
client = FederationClient.client(server_name)
client = HTTPClient.client(server_name)
with {:ok,
%GetSigningKeys{
@ -43,7 +43,7 @@ defmodule MatrixServer.ServerKeyInfo do
verify_keys: verify_keys,
valid_until_ts: valid_until
}} <-
FederationClient.get_signing_keys(client) do
HTTPClient.get_signing_keys(client) do
signing_keys =
Enum.map(verify_keys, fn {key_id, %{"key" => key}} ->
[server_name: server_name, signing_key_id: key_id, signing_key: key]

View file

@ -1,5 +1,5 @@
defmodule MatrixServerWeb.Plug.AuthenticateClient do
import MatrixServerWeb.Plug.Error
defmodule MatrixServerWeb.Client.AuthenticateClientPlug do
import MatrixServerWeb.Error
import Plug.Conn
alias MatrixServer.Account

View file

@ -2,7 +2,7 @@ defmodule MatrixServerWeb.Client.AccountController do
use MatrixServerWeb, :controller
import MatrixServer
import MatrixServerWeb.Plug.Error
import MatrixServerWeb.Error
alias MatrixServer.{Account, Repo}
alias Plug.Conn

View file

@ -1,7 +1,7 @@
defmodule MatrixServerWeb.Client.AliasesController do
use MatrixServerWeb, :controller
import MatrixServerWeb.Plug.Error
import MatrixServerWeb.Error
alias MatrixServer.Alias

View file

@ -1,7 +1,7 @@
defmodule MatrixServerWeb.Client.InfoController do
use MatrixServerWeb, :controller
import MatrixServerWeb.Plug.Error
import MatrixServerWeb.Error
@supported_versions ["r0.6.1"]

View file

@ -1,11 +1,11 @@
defmodule MatrixServerWeb.Client.LoginController do
use MatrixServerWeb, :controller
import MatrixServerWeb.Plug.Error
import MatrixServerWeb.Error
import Ecto.Changeset
alias MatrixServer.{Repo, Account}
alias MatrixServerWeb.Request.Login
alias MatrixServerWeb.Client.Request.Login
alias Ecto.Changeset
@login_type "m.login.password"

View file

@ -1,11 +1,11 @@
defmodule MatrixServerWeb.Client.RegisterController do
use MatrixServerWeb, :controller
import MatrixServerWeb.Plug.Error
import MatrixServerWeb.Error
import Ecto.Changeset
alias MatrixServer.{Repo, Account}
alias MatrixServerWeb.Request.Register
alias MatrixServerWeb.Client.Request.Register
alias Ecto.Changeset
@register_type "m.login.dummy"

View file

@ -1,11 +1,11 @@
defmodule MatrixServerWeb.Client.RoomController do
use MatrixServerWeb, :controller
import MatrixServerWeb.Plug.Error
import MatrixServerWeb.Error
import Ecto.Changeset
alias MatrixServer.Room
alias MatrixServerWeb.Request.{CreateRoom}
alias MatrixServerWeb.Client.Request.CreateRoom
alias Ecto.Changeset
alias Plug.Conn

View file

@ -1,4 +1,4 @@
defmodule MatrixServerWeb.Request.CreateRoom do
defmodule MatrixServerWeb.Client.Request.CreateRoom do
use Ecto.Schema
import Ecto.Changeset

View file

@ -1,4 +1,4 @@
defmodule MatrixServerWeb.Request.Login do
defmodule MatrixServerWeb.Client.Request.Login do
use Ecto.Schema
import Ecto.Changeset

View file

@ -1,4 +1,4 @@
defmodule MatrixServerWeb.Request.Register do
defmodule MatrixServerWeb.Client.Request.Register do
use Ecto.Schema
import Ecto.Changeset

View file

@ -1,4 +1,4 @@
defmodule MatrixServerWeb.Plug.Error do
defmodule MatrixServerWeb.Error do
import Plug.Conn
import Phoenix.Controller, only: [json: 2]

View file

@ -1,5 +1,5 @@
defmodule MatrixServerWeb.AuthenticateServer do
import MatrixServerWeb.Plug.Error
defmodule MatrixServerWeb.Federation.AuthenticateServer do
import MatrixServerWeb.Error
alias MatrixServer.{SigningKey, ServerKeyInfo}
@ -71,7 +71,7 @@ defmodule MatrixServerWeb.AuthenticateServer do
action = action_name(conn)
if action not in unquote(except) do
case MatrixServerWeb.AuthenticateServer.authenticate(conn) do
case MatrixServerWeb.Federation.AuthenticateServer.authenticate(conn) do
{origin, _key, _sig} ->
conn = Plug.Conn.assign(conn, :origin, origin)
apply(__MODULE__, action, [conn, conn.params])

View file

@ -1,7 +1,7 @@
defmodule MatrixServerWeb.Federation.KeyController do
use MatrixServerWeb, :controller
import MatrixServerWeb.Plug.Error
import MatrixServerWeb.Error
alias MatrixServer.KeyServer

View file

@ -1,8 +1,8 @@
defmodule MatrixServerWeb.Federation.QueryController do
use MatrixServerWeb, :controller
use MatrixServerWeb.AuthenticateServer
use MatrixServerWeb.Federation.AuthenticateServer
import MatrixServerWeb.Plug.Error
import MatrixServerWeb.Error
import Ecto.Query
alias MatrixServer.{Repo, Account}

View file

@ -1,20 +1,26 @@
defmodule MatrixServerWeb.FederationClient do
defmodule MatrixServerWeb.Federation.HTTPClient do
use Tesla
alias MatrixServerWeb.Endpoint
alias MatrixServerWeb.Federation.Request.GetSigningKeys
alias MatrixServerWeb.Federation.Middleware.SignRequest
alias MatrixServerWeb.Router.Helpers, as: RouteHelpers
# TODO: Maybe create database-backed homeserver struct to pass to client function.
@middleware [
Tesla.Middleware.JSON
]
# TODO: Fix error propagation.
@adapter {Tesla.Adapter.Finch, name: MatrixServerWeb.HTTPClient}
def client(server_name) do
Tesla.client([{Tesla.Middleware.BaseUrl, "http://" <> server_name} | @middleware], @adapter)
Tesla.client(
[
{Tesla.Middleware.Opts, [server_name: server_name]},
SignRequest,
{Tesla.Middleware.BaseUrl, "http://" <> server_name},
Tesla.Middleware.JSON
],
@adapter
)
end
def get_signing_keys(client) do
@ -45,30 +51,11 @@ defmodule MatrixServerWeb.FederationClient do
end
end
# TODO: Create tesla middleware to add signature and headers.
def query_profile(client, server_name, user_id, field \\ nil) do
origin = MatrixServer.server_name()
def query_profile(client, user_id, field \\ nil) do
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",
uri: URI.decode_www_form(path),
origin: origin,
destination: server_name
}
{:ok, signature, key_id} = MatrixServer.KeyServer.sign_object(object_to_sign)
signatures = %{origin => %{key_id => signature}}
auth_headers = create_signature_authorization_headers(signatures, origin)
Tesla.get(client, path, headers: auth_headers)
end
defp create_signature_authorization_headers(signatures, origin) do
Enum.map(signatures[origin], fn {key, sig} ->
{"Authorization", "X-Matrix origin=#{origin},key=\"#{key}\",sig=\"#{sig}\""}
end)
Tesla.get(client, path)
end
defp tesla_request(method, client, path, request_schema) do

View file

@ -0,0 +1,38 @@
defmodule MatrixServerWeb.Federation.Middleware.SignRequest do
@behaviour Tesla.Middleware
def call(%Tesla.Env{opts: opts} = env, next, _opts) do
sign = Keyword.get(opts, :sign, true)
case sign_request(env, sign) do
%Tesla.Env{} = env -> Tesla.run(env, next)
:error -> {:error, :sign_request}
end
end
defp sign_request(env, false), do: env
defp sign_request(%Tesla.Env{method: method, url: path, opts: opts} = env, true) do
origin = MatrixServer.server_name()
object_to_sign = %{
method: Atom.to_string(method) |> String.upcase(),
origin: origin,
uri: URI.decode_www_form(path),
destination: Keyword.fetch!(opts, :server_name)
}
with {:ok, sig, key_id} <- MatrixServer.KeyServer.sign_object(object_to_sign) do
sigs = %{origin => %{key_id => sig}}
auth_headers = create_signature_authorization_headers(sigs, origin)
Tesla.put_headers(env, auth_headers)
end
end
defp create_signature_authorization_headers(signatures, origin) do
Enum.map(signatures[origin], fn {key, sig} ->
{"Authorization", "X-Matrix origin=#{origin},key=\"#{key}\",sig=\"#{sig}\""}
end)
end
end

View file

@ -1,7 +1,7 @@
defmodule MatrixServerWeb.Router do
use MatrixServerWeb, :router
alias MatrixServerWeb.Plug.AuthenticateClient
alias MatrixServerWeb.Client.AuthenticateClientPlug
# TODO: might be able to handle malformed JSON with custom body reader:
# https://elixirforum.com/t/write-malformed-json-in-the-body-plug/30578/13
@ -12,7 +12,7 @@ defmodule MatrixServerWeb.Router do
pipeline :authenticate_client do
plug :accepts, ["json"]
plug AuthenticateClient
plug AuthenticateClientPlug
end
pipeline :authenticate_server do