mirror of
https://github.com/matrix-org/dendrite.git
synced 2025-12-16 11:23:11 -06:00
204 lines
6.6 KiB
Go
204 lines
6.6 KiB
Go
package gomatrixserverlib
|
|
|
|
import (
|
|
"context"
|
|
"net/url"
|
|
"strconv"
|
|
|
|
"golang.org/x/crypto/ed25519"
|
|
)
|
|
|
|
// A FederationClient is a matrix federation client that adds
|
|
// "Authorization: X-Matrix" headers to requests that need ed25519 signatures
|
|
type FederationClient struct {
|
|
Client
|
|
serverName ServerName
|
|
serverKeyID KeyID
|
|
serverPrivateKey ed25519.PrivateKey
|
|
}
|
|
|
|
// NewFederationClient makes a new FederationClient
|
|
func NewFederationClient(
|
|
serverName ServerName, keyID KeyID, privateKey ed25519.PrivateKey,
|
|
) *FederationClient {
|
|
return &FederationClient{
|
|
Client: *NewClient(),
|
|
serverName: serverName,
|
|
serverKeyID: keyID,
|
|
serverPrivateKey: privateKey,
|
|
}
|
|
}
|
|
|
|
func (ac *FederationClient) doRequest(ctx context.Context, r FederationRequest, resBody interface{}) error {
|
|
if err := r.Sign(ac.serverName, ac.serverKeyID, ac.serverPrivateKey); err != nil {
|
|
return err
|
|
}
|
|
|
|
req, err := r.HTTPRequest()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return ac.Client.DoRequestAndParseResponse(ctx, req, resBody)
|
|
}
|
|
|
|
var federationPathPrefix = "/_matrix/federation/v1"
|
|
|
|
// SendTransaction sends a transaction
|
|
func (ac *FederationClient) SendTransaction(
|
|
ctx context.Context, t Transaction,
|
|
) (res RespSend, err error) {
|
|
path := federationPathPrefix + "/send/" + string(t.TransactionID) + "/"
|
|
req := NewFederationRequest("PUT", t.Destination, path)
|
|
if err = req.SetContent(t); err != nil {
|
|
return
|
|
}
|
|
err = ac.doRequest(ctx, req, &res)
|
|
return
|
|
}
|
|
|
|
// MakeJoin makes a join m.room.member event for a room on a remote matrix server.
|
|
// This is used to join a room the local server isn't a member of.
|
|
// We need to query a remote server because if we aren't in the room we don't
|
|
// know what to use for the "prev_events" in the join event.
|
|
// The remote server should return us a m.room.member event for our local user
|
|
// with the "prev_events" filled out.
|
|
// If this successfully returns an acceptable event we will sign it with our
|
|
// server's key and pass it to SendJoin.
|
|
// See https://matrix.org/docs/spec/server_server/unstable.html#joining-rooms
|
|
func (ac *FederationClient) MakeJoin(
|
|
ctx context.Context, s ServerName, roomID, userID string,
|
|
) (res RespMakeJoin, err error) {
|
|
path := federationPathPrefix + "/make_join/" +
|
|
url.PathEscape(roomID) + "/" +
|
|
url.PathEscape(userID)
|
|
req := NewFederationRequest("GET", s, path)
|
|
err = ac.doRequest(ctx, req, &res)
|
|
return
|
|
}
|
|
|
|
// SendJoin sends a join m.room.member event obtained using MakeJoin via a
|
|
// remote matrix server.
|
|
// This is used to join a room the local server isn't a member of.
|
|
// See https://matrix.org/docs/spec/server_server/unstable.html#joining-rooms
|
|
func (ac *FederationClient) SendJoin(
|
|
ctx context.Context, s ServerName, event Event,
|
|
) (res RespSendJoin, err error) {
|
|
path := federationPathPrefix + "/send_join/" +
|
|
url.PathEscape(event.RoomID()) + "/" +
|
|
url.PathEscape(event.EventID())
|
|
req := NewFederationRequest("PUT", s, path)
|
|
if err = req.SetContent(event); err != nil {
|
|
return
|
|
}
|
|
err = ac.doRequest(ctx, req, &res)
|
|
return
|
|
}
|
|
|
|
// SendInvite sends an invite m.room.member event to an invited server to be
|
|
// signed by it. This is used to invite a user that is not on the local server.
|
|
func (ac *FederationClient) SendInvite(
|
|
ctx context.Context, s ServerName, event Event,
|
|
) (res RespInvite, err error) {
|
|
path := federationPathPrefix + "/invite/" +
|
|
url.PathEscape(event.RoomID()) + "/" +
|
|
url.PathEscape(event.EventID())
|
|
req := NewFederationRequest("PUT", s, path)
|
|
if err = req.SetContent(event); err != nil {
|
|
return
|
|
}
|
|
err = ac.doRequest(ctx, req, &res)
|
|
return
|
|
}
|
|
|
|
// ExchangeThirdPartyInvite sends the builder of a m.room.member event of
|
|
// "invite" membership derived from a response from invites sent by an identity
|
|
// server.
|
|
// This is used to exchange a m.room.third_party_invite event for a m.room.member
|
|
// one in a room the local server isn't a member of.
|
|
func (ac *FederationClient) ExchangeThirdPartyInvite(
|
|
ctx context.Context, s ServerName, builder EventBuilder,
|
|
) (err error) {
|
|
path := federationPathPrefix + "/exchange_third_party_invite/" +
|
|
url.PathEscape(builder.RoomID)
|
|
req := NewFederationRequest("PUT", s, path)
|
|
if err = req.SetContent(builder); err != nil {
|
|
return
|
|
}
|
|
err = ac.doRequest(ctx, req, nil)
|
|
return
|
|
}
|
|
|
|
// LookupState retrieves the room state for a room at an event from a
|
|
// remote matrix server as full matrix events.
|
|
func (ac *FederationClient) LookupState(
|
|
ctx context.Context, s ServerName, roomID, eventID string,
|
|
) (res RespState, err error) {
|
|
path := federationPathPrefix + "/state/" +
|
|
url.PathEscape(roomID) +
|
|
"/?event_id=" +
|
|
url.QueryEscape(eventID)
|
|
req := NewFederationRequest("GET", s, path)
|
|
err = ac.doRequest(ctx, req, &res)
|
|
return
|
|
}
|
|
|
|
// LookupStateIDs retrieves the room state for a room at an event from a
|
|
// remote matrix server as lists of matrix event IDs.
|
|
func (ac *FederationClient) LookupStateIDs(
|
|
ctx context.Context, s ServerName, roomID, eventID string,
|
|
) (res RespStateIDs, err error) {
|
|
path := federationPathPrefix + "/state_ids/" +
|
|
url.PathEscape(roomID) +
|
|
"/?event_id=" +
|
|
url.QueryEscape(eventID)
|
|
req := NewFederationRequest("GET", s, path)
|
|
err = ac.doRequest(ctx, req, &res)
|
|
return
|
|
}
|
|
|
|
// LookupRoomAlias looks up a room alias hosted on the remote server.
|
|
// The domain part of the roomAlias must match the name of the server it is
|
|
// being looked up on.
|
|
// If the room alias doesn't exist on the remote server then a 404 gomatrix.HTTPError
|
|
// is returned.
|
|
func (ac *FederationClient) LookupRoomAlias(
|
|
ctx context.Context, s ServerName, roomAlias string,
|
|
) (res RespDirectory, err error) {
|
|
path := federationPathPrefix + "/query/directory?room_alias=" +
|
|
url.QueryEscape(roomAlias)
|
|
req := NewFederationRequest("GET", s, path)
|
|
err = ac.doRequest(ctx, req, &res)
|
|
return
|
|
}
|
|
|
|
// Backfill asks a homeserver for events early enough for them to not be in the
|
|
// local database.
|
|
// See https://matrix.org/docs/spec/server_server/unstable.html#get-matrix-federation-v1-backfill-roomid
|
|
func (ac *FederationClient) Backfill(
|
|
ctx context.Context, s ServerName, roomID string, limit int, eventIDs []string,
|
|
) (res Transaction, err error) {
|
|
// Encode the room ID so it won't interfer with the path.
|
|
roomID = url.PathEscape(roomID)
|
|
|
|
// Parse the limit into a string so that we can include it in the URL's query.
|
|
limitStr := strconv.Itoa(limit)
|
|
|
|
// Define the URL's query.
|
|
query := url.Values{}
|
|
query["v"] = eventIDs
|
|
query.Set("limit", limitStr)
|
|
|
|
// Use the url.URL structure to easily generate the request's URI (path?query).
|
|
u := url.URL{
|
|
Path: "/_matrix/federation/v1/backfill/" + roomID + "/",
|
|
RawQuery: query.Encode(),
|
|
}
|
|
path := u.RequestURI()
|
|
|
|
// Send the request.
|
|
req := NewFederationRequest("GET", s, path)
|
|
err = ac.doRequest(ctx, req, &res)
|
|
return
|
|
}
|