Implement federation route PUT /exchange_third_party_invite (#224)
* Add comment * gb vendor update github.com/matrix-org/gomatrixserverlib * Add handler for the exchange_third_party_invite endpoint * Doc * Use SendEvents to send the invite to the roomserver * Add missing error check * Add checksmain
parent
82c82a3412
commit
086683459f
|
@ -254,6 +254,7 @@ func queryIDServerStoreInvite(
|
||||||
|
|
||||||
// queryIDServerPubKey requests a public key identified with a given ID to the
|
// queryIDServerPubKey requests a public key identified with a given ID to the
|
||||||
// a given identity server and returns the matching base64-decoded public key.
|
// a given identity server and returns the matching base64-decoded public key.
|
||||||
|
// We assume that the ID server is trusted at this point.
|
||||||
// Returns an error if the request couldn't be sent, if its body couldn't be parsed
|
// Returns an error if the request couldn't be sent, if its body couldn't be parsed
|
||||||
// or if the key couldn't be decoded from base64.
|
// or if the key couldn't be decoded from base64.
|
||||||
func queryIDServerPubKey(idServerName string, keyID string) ([]byte, error) {
|
func queryIDServerPubKey(idServerName string, keyID string) ([]byte, error) {
|
||||||
|
@ -280,6 +281,7 @@ func queryIDServerPubKey(idServerName string, keyID string) ([]byte, error) {
|
||||||
// If no signature can be found for the ID server's domain, returns an error, else
|
// If no signature can be found for the ID server's domain, returns an error, else
|
||||||
// iterates over the signature for the said domain, retrieves the matching public
|
// iterates over the signature for the said domain, retrieves the matching public
|
||||||
// key, and verify it.
|
// key, and verify it.
|
||||||
|
// We assume that the ID server is trusted at this point.
|
||||||
// Returns nil if all the verifications succeeded.
|
// Returns nil if all the verifications succeeded.
|
||||||
// Returns an error if something failed in the process.
|
// Returns an error if something failed in the process.
|
||||||
func checkIDServerSignatures(body *MembershipRequest, res *idServerLookupResponse) error {
|
func checkIDServerSignatures(body *MembershipRequest, res *idServerLookupResponse) error {
|
||||||
|
|
|
@ -85,6 +85,16 @@ func Setup(
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
|
|
||||||
|
v1fedmux.Handle("/exchange_third_party_invite/{roomID}", common.MakeFedAPI(
|
||||||
|
"exchange_third_party_invite", cfg.Matrix.ServerName, keys,
|
||||||
|
func(httpReq *http.Request, request *gomatrixserverlib.FederationRequest) util.JSONResponse {
|
||||||
|
vars := mux.Vars(httpReq)
|
||||||
|
return writers.ExchangeThirdPartyInvite(
|
||||||
|
httpReq, request, vars["roomID"], query, cfg, federation, producer,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
))
|
||||||
|
|
||||||
v1fedmux.Handle("/event/{eventID}", common.MakeFedAPI(
|
v1fedmux.Handle("/event/{eventID}", common.MakeFedAPI(
|
||||||
"federation_get_event", cfg.Matrix.ServerName, keys,
|
"federation_get_event", cfg.Matrix.ServerName, keys,
|
||||||
func(httpReq *http.Request, request *gomatrixserverlib.FederationRequest) util.JSONResponse {
|
func(httpReq *http.Request, request *gomatrixserverlib.FederationRequest) util.JSONResponse {
|
||||||
|
|
|
@ -22,6 +22,7 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/matrix-org/dendrite/clientapi/httputil"
|
"github.com/matrix-org/dendrite/clientapi/httputil"
|
||||||
|
"github.com/matrix-org/dendrite/clientapi/jsonerror"
|
||||||
"github.com/matrix-org/dendrite/clientapi/producers"
|
"github.com/matrix-org/dendrite/clientapi/producers"
|
||||||
"github.com/matrix-org/dendrite/common"
|
"github.com/matrix-org/dendrite/common"
|
||||||
"github.com/matrix-org/dendrite/common/config"
|
"github.com/matrix-org/dendrite/common/config"
|
||||||
|
@ -48,6 +49,8 @@ type invites struct {
|
||||||
Invites []invite `json:"invites"`
|
Invites []invite `json:"invites"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var errNotInRoom = errors.New("the server isn't currently in the room")
|
||||||
|
|
||||||
// CreateInvitesFrom3PIDInvites implements POST /_matrix/federation/v1/3pid/onbind
|
// CreateInvitesFrom3PIDInvites implements POST /_matrix/federation/v1/3pid/onbind
|
||||||
func CreateInvitesFrom3PIDInvites(
|
func CreateInvitesFrom3PIDInvites(
|
||||||
req *http.Request, queryAPI api.RoomserverQueryAPI, cfg config.Dendrite,
|
req *http.Request, queryAPI api.RoomserverQueryAPI, cfg config.Dendrite,
|
||||||
|
@ -80,6 +83,78 @@ func CreateInvitesFrom3PIDInvites(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ExchangeThirdPartyInvite implements PUT /_matrix/federation/v1/exchange_third_party_invite/{roomID}
|
||||||
|
func ExchangeThirdPartyInvite(
|
||||||
|
httpReq *http.Request,
|
||||||
|
request *gomatrixserverlib.FederationRequest,
|
||||||
|
roomID string,
|
||||||
|
queryAPI api.RoomserverQueryAPI,
|
||||||
|
cfg config.Dendrite,
|
||||||
|
federation *gomatrixserverlib.FederationClient,
|
||||||
|
producer *producers.RoomserverProducer,
|
||||||
|
) util.JSONResponse {
|
||||||
|
var builder gomatrixserverlib.EventBuilder
|
||||||
|
if err := json.Unmarshal(request.Content(), &builder); err != nil {
|
||||||
|
return util.JSONResponse{
|
||||||
|
Code: 400,
|
||||||
|
JSON: jsonerror.NotJSON("The request body could not be decoded into valid JSON. " + err.Error()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check that the room ID is correct.
|
||||||
|
if builder.RoomID != roomID {
|
||||||
|
return util.JSONResponse{
|
||||||
|
Code: 400,
|
||||||
|
JSON: jsonerror.BadJSON("The room ID in the request path must match the room ID in the invite event JSON"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check that the state key is correct.
|
||||||
|
_, targetDomain, err := gomatrixserverlib.SplitID('@', *builder.StateKey)
|
||||||
|
if err != nil {
|
||||||
|
return util.JSONResponse{
|
||||||
|
Code: 400,
|
||||||
|
JSON: jsonerror.BadJSON("The event's state key isn't a Matrix user ID"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check that the target user is from the requesting homeserver.
|
||||||
|
if targetDomain != request.Origin() {
|
||||||
|
return util.JSONResponse{
|
||||||
|
Code: 400,
|
||||||
|
JSON: jsonerror.BadJSON("The event's state key doesn't have the same domain as the request's origin"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Auth and build the event from what the remote server sent us
|
||||||
|
event, err := buildMembershipEvent(&builder, queryAPI, cfg)
|
||||||
|
if err == errNotInRoom {
|
||||||
|
return util.JSONResponse{
|
||||||
|
Code: 404,
|
||||||
|
JSON: jsonerror.NotFound("Unknown room " + roomID),
|
||||||
|
}
|
||||||
|
} else if err != nil {
|
||||||
|
return httputil.LogThenError(httpReq, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ask the requesting server to sign the newly created event so we know it
|
||||||
|
// acknowledged it
|
||||||
|
signedEvent, err := federation.SendInvite(request.Origin(), *event)
|
||||||
|
if err != nil {
|
||||||
|
return httputil.LogThenError(httpReq, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send the event to the roomserver
|
||||||
|
if err = producer.SendEvents([]gomatrixserverlib.Event{signedEvent.Event}, cfg.Matrix.ServerName); err != nil {
|
||||||
|
return httputil.LogThenError(httpReq, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return util.JSONResponse{
|
||||||
|
Code: 200,
|
||||||
|
JSON: struct{}{},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// createInviteFrom3PIDInvite processes an invite provided by the identity server
|
// createInviteFrom3PIDInvite processes an invite provided by the identity server
|
||||||
// and creates a m.room.member event (with "invite" membership) from it.
|
// and creates a m.room.member event (with "invite" membership) from it.
|
||||||
// Returns an error if there was a problem building the event or fetching the
|
// Returns an error if there was a problem building the event or fetching the
|
||||||
|
@ -108,6 +183,23 @@ func createInviteFrom3PIDInvite(
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
event, err := buildMembershipEvent(builder, queryAPI, cfg)
|
||||||
|
if err == errNotInRoom {
|
||||||
|
return nil, sendToRemoteServer(inv, federation, cfg, *builder)
|
||||||
|
}
|
||||||
|
|
||||||
|
return event, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// buildMembershipEvent uses a builder for a m.room.member invite event derived
|
||||||
|
// from a third-party invite to auth and build the said event. Returns the said
|
||||||
|
// event.
|
||||||
|
// Returns errNotInRoom if the server is not in the room the invite is for.
|
||||||
|
// Returns an error if something failed during the process.
|
||||||
|
func buildMembershipEvent(
|
||||||
|
builder *gomatrixserverlib.EventBuilder, queryAPI api.RoomserverQueryAPI,
|
||||||
|
cfg config.Dendrite,
|
||||||
|
) (*gomatrixserverlib.Event, error) {
|
||||||
eventsNeeded, err := gomatrixserverlib.StateNeededForEventBuilder(builder)
|
eventsNeeded, err := gomatrixserverlib.StateNeededForEventBuilder(builder)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -125,7 +217,7 @@ func createInviteFrom3PIDInvite(
|
||||||
|
|
||||||
if !queryRes.RoomExists {
|
if !queryRes.RoomExists {
|
||||||
// Use federation to auth the event
|
// Use federation to auth the event
|
||||||
return nil, sendToRemoteServer(inv, federation, cfg, *builder)
|
return nil, errNotInRoom
|
||||||
}
|
}
|
||||||
|
|
||||||
// Auth the event locally
|
// Auth the event locally
|
||||||
|
@ -138,7 +230,7 @@ func createInviteFrom3PIDInvite(
|
||||||
authEvents.AddEvent(&queryRes.StateEvents[i])
|
authEvents.AddEvent(&queryRes.StateEvents[i])
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = fillDisplayName(builder, content, authEvents); err != nil {
|
if err = fillDisplayName(builder, authEvents); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -151,11 +243,8 @@ func createInviteFrom3PIDInvite(
|
||||||
eventID := fmt.Sprintf("$%s:%s", util.RandomString(16), cfg.Matrix.ServerName)
|
eventID := fmt.Sprintf("$%s:%s", util.RandomString(16), cfg.Matrix.ServerName)
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
event, err := builder.Build(eventID, now, cfg.Matrix.ServerName, cfg.Matrix.KeyID, cfg.Matrix.PrivateKey)
|
event, err := builder.Build(eventID, now, cfg.Matrix.ServerName, cfg.Matrix.KeyID, cfg.Matrix.PrivateKey)
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return &event, nil
|
return &event, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// sendToRemoteServer uses federation to send an invite provided by an identity
|
// sendToRemoteServer uses federation to send an invite provided by an identity
|
||||||
|
@ -201,9 +290,13 @@ func sendToRemoteServer(
|
||||||
// found. Returning an error isn't necessary in this case as the event will be
|
// found. Returning an error isn't necessary in this case as the event will be
|
||||||
// rejected by gomatrixserverlib.
|
// rejected by gomatrixserverlib.
|
||||||
func fillDisplayName(
|
func fillDisplayName(
|
||||||
builder *gomatrixserverlib.EventBuilder, content common.MemberContent,
|
builder *gomatrixserverlib.EventBuilder, authEvents gomatrixserverlib.AuthEvents,
|
||||||
authEvents gomatrixserverlib.AuthEvents,
|
|
||||||
) error {
|
) error {
|
||||||
|
var content common.MemberContent
|
||||||
|
if err := json.Unmarshal(builder.Content, &content); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
// Look for the m.room.third_party_invite event
|
// Look for the m.room.third_party_invite event
|
||||||
thirdPartyInviteEvent, _ := authEvents.ThirdPartyInvite(content.ThirdPartyInvite.Signed.Token)
|
thirdPartyInviteEvent, _ := authEvents.ThirdPartyInvite(content.ThirdPartyInvite.Signed.Token)
|
||||||
|
|
||||||
|
|
|
@ -116,7 +116,7 @@
|
||||||
{
|
{
|
||||||
"importpath": "github.com/matrix-org/gomatrixserverlib",
|
"importpath": "github.com/matrix-org/gomatrixserverlib",
|
||||||
"repository": "https://github.com/matrix-org/gomatrixserverlib",
|
"repository": "https://github.com/matrix-org/gomatrixserverlib",
|
||||||
"revision": "fe45d482f2280c9f92f09eb6650e7aa3cca051c5",
|
"revision": "790f02e8f465552dab4317ffe7ca047ccb594cbf",
|
||||||
"branch": "master"
|
"branch": "master"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
@ -84,9 +84,11 @@ func (ac *FederationClient) doRequest(r FederationRequest, resBody interface{})
|
||||||
return json.Unmarshal(contents, resBody)
|
return json.Unmarshal(contents, resBody)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var federationPathPrefix = "/_matrix/federation/v1"
|
||||||
|
|
||||||
// SendTransaction sends a transaction
|
// SendTransaction sends a transaction
|
||||||
func (ac *FederationClient) SendTransaction(t Transaction) (res RespSend, err error) {
|
func (ac *FederationClient) SendTransaction(t Transaction) (res RespSend, err error) {
|
||||||
path := "/_matrix/federation/v1/send/" + string(t.TransactionID) + "/"
|
path := federationPathPrefix + "/send/" + string(t.TransactionID) + "/"
|
||||||
req := NewFederationRequest("PUT", t.Destination, path)
|
req := NewFederationRequest("PUT", t.Destination, path)
|
||||||
if err = req.SetContent(t); err != nil {
|
if err = req.SetContent(t); err != nil {
|
||||||
return
|
return
|
||||||
|
@ -105,7 +107,7 @@ func (ac *FederationClient) SendTransaction(t Transaction) (res RespSend, err er
|
||||||
// server's key and pass it to SendJoin.
|
// server's key and pass it to SendJoin.
|
||||||
// See https://matrix.org/docs/spec/server_server/unstable.html#joining-rooms
|
// See https://matrix.org/docs/spec/server_server/unstable.html#joining-rooms
|
||||||
func (ac *FederationClient) MakeJoin(s ServerName, roomID, userID string) (res RespMakeJoin, err error) {
|
func (ac *FederationClient) MakeJoin(s ServerName, roomID, userID string) (res RespMakeJoin, err error) {
|
||||||
path := "/_matrix/federation/v1/make_join/" +
|
path := federationPathPrefix + "/make_join/" +
|
||||||
url.PathEscape(roomID) + "/" +
|
url.PathEscape(roomID) + "/" +
|
||||||
url.PathEscape(userID)
|
url.PathEscape(userID)
|
||||||
req := NewFederationRequest("GET", s, path)
|
req := NewFederationRequest("GET", s, path)
|
||||||
|
@ -118,7 +120,21 @@ func (ac *FederationClient) MakeJoin(s ServerName, roomID, userID string) (res R
|
||||||
// This is used to join a room the local server isn't a member of.
|
// 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
|
// See https://matrix.org/docs/spec/server_server/unstable.html#joining-rooms
|
||||||
func (ac *FederationClient) SendJoin(s ServerName, event Event) (res RespSendJoin, err error) {
|
func (ac *FederationClient) SendJoin(s ServerName, event Event) (res RespSendJoin, err error) {
|
||||||
path := "/_matrix/federation/v1/send_join/" +
|
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(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(s ServerName, event Event) (res RespInvite, err error) {
|
||||||
|
path := federationPathPrefix + "/invite/" +
|
||||||
url.PathEscape(event.RoomID()) + "/" +
|
url.PathEscape(event.RoomID()) + "/" +
|
||||||
url.PathEscape(event.EventID())
|
url.PathEscape(event.EventID())
|
||||||
req := NewFederationRequest("PUT", s, path)
|
req := NewFederationRequest("PUT", s, path)
|
||||||
|
@ -135,7 +151,7 @@ func (ac *FederationClient) SendJoin(s ServerName, event Event) (res RespSendJoi
|
||||||
// This is used to exchange a m.room.third_party_invite event for a m.room.member
|
// 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.
|
// one in a room the local server isn't a member of.
|
||||||
func (ac *FederationClient) ExchangeThirdPartyInvite(s ServerName, builder EventBuilder) (err error) {
|
func (ac *FederationClient) ExchangeThirdPartyInvite(s ServerName, builder EventBuilder) (err error) {
|
||||||
path := "/_matrix/federation/v1/exchange_third_party_invite/" +
|
path := federationPathPrefix + "/exchange_third_party_invite/" +
|
||||||
url.PathEscape(builder.RoomID)
|
url.PathEscape(builder.RoomID)
|
||||||
req := NewFederationRequest("PUT", s, path)
|
req := NewFederationRequest("PUT", s, path)
|
||||||
if err = req.SetContent(builder); err != nil {
|
if err = req.SetContent(builder); err != nil {
|
||||||
|
@ -148,7 +164,7 @@ func (ac *FederationClient) ExchangeThirdPartyInvite(s ServerName, builder Event
|
||||||
// LookupState retrieves the room state for a room at an event from a
|
// LookupState retrieves the room state for a room at an event from a
|
||||||
// remote matrix server as full matrix events.
|
// remote matrix server as full matrix events.
|
||||||
func (ac *FederationClient) LookupState(s ServerName, roomID, eventID string) (res RespState, err error) {
|
func (ac *FederationClient) LookupState(s ServerName, roomID, eventID string) (res RespState, err error) {
|
||||||
path := "/_matrix/federation/v1/state/" +
|
path := federationPathPrefix + "/state/" +
|
||||||
url.PathEscape(roomID) +
|
url.PathEscape(roomID) +
|
||||||
"/?event_id=" +
|
"/?event_id=" +
|
||||||
url.QueryEscape(eventID)
|
url.QueryEscape(eventID)
|
||||||
|
@ -160,7 +176,7 @@ func (ac *FederationClient) LookupState(s ServerName, roomID, eventID string) (r
|
||||||
// LookupStateIDs retrieves the room state for a room at an event from a
|
// LookupStateIDs retrieves the room state for a room at an event from a
|
||||||
// remote matrix server as lists of matrix event IDs.
|
// remote matrix server as lists of matrix event IDs.
|
||||||
func (ac *FederationClient) LookupStateIDs(s ServerName, roomID, eventID string) (res RespStateIDs, err error) {
|
func (ac *FederationClient) LookupStateIDs(s ServerName, roomID, eventID string) (res RespStateIDs, err error) {
|
||||||
path := "/_matrix/federation/v1/state_ids/" +
|
path := federationPathPrefix + "/state_ids/" +
|
||||||
url.PathEscape(roomID) +
|
url.PathEscape(roomID) +
|
||||||
"/?event_id=" +
|
"/?event_id=" +
|
||||||
url.QueryEscape(eventID)
|
url.QueryEscape(eventID)
|
||||||
|
@ -175,7 +191,7 @@ func (ac *FederationClient) LookupStateIDs(s ServerName, roomID, eventID string)
|
||||||
// If the room alias doesn't exist on the remote server then a 404 gomatrix.HTTPError
|
// If the room alias doesn't exist on the remote server then a 404 gomatrix.HTTPError
|
||||||
// is returned.
|
// is returned.
|
||||||
func (ac *FederationClient) LookupRoomAlias(s ServerName, roomAlias string) (res RespDirectory, err error) {
|
func (ac *FederationClient) LookupRoomAlias(s ServerName, roomAlias string) (res RespDirectory, err error) {
|
||||||
path := "/_matrix/federation/v1/query/directory?room_alias=" +
|
path := federationPathPrefix + "/query/directory?room_alias=" +
|
||||||
url.QueryEscape(roomAlias)
|
url.QueryEscape(roomAlias)
|
||||||
req := NewFederationRequest("GET", s, path)
|
req := NewFederationRequest("GET", s, path)
|
||||||
err = ac.doRequest(req, &res)
|
err = ac.doRequest(req, &res)
|
||||||
|
|
Loading…
Reference in New Issue