From 086683459f8e0a217fcf902474b5117d1d891f41 Mon Sep 17 00:00:00 2001 From: Brendan Abolivier Date: Tue, 12 Sep 2017 17:15:13 +0100 Subject: [PATCH] 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 checks --- .../dendrite/clientapi/threepid/invites.go | 2 + .../dendrite/federationapi/routing/routing.go | 10 ++ .../federationapi/writers/threepid.go | 109 ++++++++++++++++-- vendor/manifest | 2 +- .../gomatrixserverlib/federationclient.go | 30 +++-- 5 files changed, 137 insertions(+), 16 deletions(-) diff --git a/src/github.com/matrix-org/dendrite/clientapi/threepid/invites.go b/src/github.com/matrix-org/dendrite/clientapi/threepid/invites.go index 27cdf343..11850517 100644 --- a/src/github.com/matrix-org/dendrite/clientapi/threepid/invites.go +++ b/src/github.com/matrix-org/dendrite/clientapi/threepid/invites.go @@ -254,6 +254,7 @@ func queryIDServerStoreInvite( // queryIDServerPubKey requests a public key identified with a given ID to the // 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 // or if the key couldn't be decoded from base64. 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 // iterates over the signature for the said domain, retrieves the matching public // key, and verify it. +// We assume that the ID server is trusted at this point. // Returns nil if all the verifications succeeded. // Returns an error if something failed in the process. func checkIDServerSignatures(body *MembershipRequest, res *idServerLookupResponse) error { diff --git a/src/github.com/matrix-org/dendrite/federationapi/routing/routing.go b/src/github.com/matrix-org/dendrite/federationapi/routing/routing.go index c67f8112..4d79fa6c 100644 --- a/src/github.com/matrix-org/dendrite/federationapi/routing/routing.go +++ b/src/github.com/matrix-org/dendrite/federationapi/routing/routing.go @@ -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( "federation_get_event", cfg.Matrix.ServerName, keys, func(httpReq *http.Request, request *gomatrixserverlib.FederationRequest) util.JSONResponse { diff --git a/src/github.com/matrix-org/dendrite/federationapi/writers/threepid.go b/src/github.com/matrix-org/dendrite/federationapi/writers/threepid.go index 9fab040a..cbc30f43 100644 --- a/src/github.com/matrix-org/dendrite/federationapi/writers/threepid.go +++ b/src/github.com/matrix-org/dendrite/federationapi/writers/threepid.go @@ -22,6 +22,7 @@ import ( "time" "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/common" "github.com/matrix-org/dendrite/common/config" @@ -48,6 +49,8 @@ type invites struct { Invites []invite `json:"invites"` } +var errNotInRoom = errors.New("the server isn't currently in the room") + // CreateInvitesFrom3PIDInvites implements POST /_matrix/federation/v1/3pid/onbind func CreateInvitesFrom3PIDInvites( 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 // 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 @@ -108,6 +183,23 @@ func createInviteFrom3PIDInvite( 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) if err != nil { return nil, err @@ -125,7 +217,7 @@ func createInviteFrom3PIDInvite( if !queryRes.RoomExists { // Use federation to auth the event - return nil, sendToRemoteServer(inv, federation, cfg, *builder) + return nil, errNotInRoom } // Auth the event locally @@ -138,7 +230,7 @@ func createInviteFrom3PIDInvite( authEvents.AddEvent(&queryRes.StateEvents[i]) } - if err = fillDisplayName(builder, content, authEvents); err != nil { + if err = fillDisplayName(builder, authEvents); err != nil { return nil, err } @@ -151,11 +243,8 @@ func createInviteFrom3PIDInvite( eventID := fmt.Sprintf("$%s:%s", util.RandomString(16), cfg.Matrix.ServerName) now := time.Now() 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 @@ -201,9 +290,13 @@ func sendToRemoteServer( // found. Returning an error isn't necessary in this case as the event will be // rejected by gomatrixserverlib. func fillDisplayName( - builder *gomatrixserverlib.EventBuilder, content common.MemberContent, - authEvents gomatrixserverlib.AuthEvents, + builder *gomatrixserverlib.EventBuilder, authEvents gomatrixserverlib.AuthEvents, ) 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 thirdPartyInviteEvent, _ := authEvents.ThirdPartyInvite(content.ThirdPartyInvite.Signed.Token) diff --git a/vendor/manifest b/vendor/manifest index 58663b49..3a21cb9d 100644 --- a/vendor/manifest +++ b/vendor/manifest @@ -116,7 +116,7 @@ { "importpath": "github.com/matrix-org/gomatrixserverlib", "repository": "https://github.com/matrix-org/gomatrixserverlib", - "revision": "fe45d482f2280c9f92f09eb6650e7aa3cca051c5", + "revision": "790f02e8f465552dab4317ffe7ca047ccb594cbf", "branch": "master" }, { diff --git a/vendor/src/github.com/matrix-org/gomatrixserverlib/federationclient.go b/vendor/src/github.com/matrix-org/gomatrixserverlib/federationclient.go index 5a627401..49c0fc72 100644 --- a/vendor/src/github.com/matrix-org/gomatrixserverlib/federationclient.go +++ b/vendor/src/github.com/matrix-org/gomatrixserverlib/federationclient.go @@ -84,9 +84,11 @@ func (ac *FederationClient) doRequest(r FederationRequest, resBody interface{}) return json.Unmarshal(contents, resBody) } +var federationPathPrefix = "/_matrix/federation/v1" + // SendTransaction sends a transaction 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) if err = req.SetContent(t); err != nil { return @@ -105,7 +107,7 @@ func (ac *FederationClient) SendTransaction(t Transaction) (res RespSend, err er // server's key and pass it to SendJoin. // 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) { - path := "/_matrix/federation/v1/make_join/" + + path := federationPathPrefix + "/make_join/" + url.PathEscape(roomID) + "/" + url.PathEscape(userID) 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. // See https://matrix.org/docs/spec/server_server/unstable.html#joining-rooms 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.EventID()) 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 // one in a room the local server isn't a member of. 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) req := NewFederationRequest("PUT", s, path) 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 // remote matrix server as full matrix events. func (ac *FederationClient) LookupState(s ServerName, roomID, eventID string) (res RespState, err error) { - path := "/_matrix/federation/v1/state/" + + path := federationPathPrefix + "/state/" + url.PathEscape(roomID) + "/?event_id=" + 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 // remote matrix server as lists of matrix event IDs. 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) + "/?event_id=" + 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 // is returned. 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) req := NewFederationRequest("GET", s, path) err = ac.doRequest(req, &res)