Synchronous invites (#1273)

* Refactor invites to be synchronous

* Fix synchronous invites

* Fix client API return type for send invite error

* Linter

* Restore PerformError on rsAPI.PerformInvite

* Update sytest-whitelist

* Don't override PerformError with normal errors

* Fix error passing

* Un-whitelist a couple of tests

* Update sytest-whitelist

* Try to handle multiple invite rejections better

* nolint

* Update gomatrixserverlib

* Fix /v1/invite test

* Remove replace from go.mod
main
Neil Alexander 2020-08-17 11:40:49 +01:00 committed by GitHub
parent 6820b3e024
commit 6cb1a65809
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 334 additions and 443 deletions

View File

@ -386,8 +386,12 @@ func createRoom(
var strippedState []gomatrixserverlib.InviteV2StrippedState var strippedState []gomatrixserverlib.InviteV2StrippedState
for _, event := range candidates { for _, event := range candidates {
switch event.Type() { switch event.Type() {
// TODO: case gomatrixserverlib.MRoomEncryption: case gomatrixserverlib.MRoomName:
// fallthrough fallthrough
case gomatrixserverlib.MRoomCanonicalAlias:
fallthrough
case "m.room.encryption": // TODO: move this to gmsl
fallthrough
case gomatrixserverlib.MRoomMember: case gomatrixserverlib.MRoomMember:
fallthrough fallthrough
case gomatrixserverlib.MRoomJoinRules: case gomatrixserverlib.MRoomJoinRules:
@ -398,15 +402,23 @@ func createRoom(
} }
} }
// Send the invite event to the roomserver. // Send the invite event to the roomserver.
if perr := roomserverAPI.SendInvite( err = roomserverAPI.SendInvite(
req.Context(), rsAPI, req.Context(), rsAPI,
inviteEvent.Headered(roomVersion), inviteEvent.Headered(roomVersion),
strippedState, // invite room state strippedState, // invite room state
cfg.Matrix.ServerName, // send as server cfg.Matrix.ServerName, // send as server
nil, // transaction ID nil, // transaction ID
); perr != nil { )
util.GetLogger(req.Context()).WithError(perr).Error("SendInvite failed") switch e := err.(type) {
return perr.JSONResponse() case *roomserverAPI.PerformError:
return e.JSONResponse()
case nil:
default:
util.GetLogger(req.Context()).WithError(err).Error("roomserverAPI.SendInvite failed")
return util.JSONResponse{
Code: http.StatusInternalServerError,
JSON: jsonerror.InternalServerError(),
}
} }
} }

View File

@ -173,7 +173,7 @@ func SendInvite(
roomID string, cfg *config.ClientAPI, roomID string, cfg *config.ClientAPI,
rsAPI roomserverAPI.RoomserverInternalAPI, asAPI appserviceAPI.AppServiceQueryAPI, rsAPI roomserverAPI.RoomserverInternalAPI, asAPI appserviceAPI.AppServiceQueryAPI,
) util.JSONResponse { ) util.JSONResponse {
body, evTime, roomVer, reqErr := extractRequestData(req, roomID, rsAPI) body, evTime, _, reqErr := extractRequestData(req, roomID, rsAPI)
if reqErr != nil { if reqErr != nil {
return *reqErr return *reqErr
} }
@ -214,20 +214,27 @@ func SendInvite(
return jsonerror.InternalServerError() return jsonerror.InternalServerError()
} }
perr := roomserverAPI.SendInvite( err = roomserverAPI.SendInvite(
req.Context(), rsAPI, req.Context(), rsAPI,
event.Event.Headered(roomVer), *event,
nil, // ask the roomserver to draw up invite room state for us nil, // ask the roomserver to draw up invite room state for us
cfg.Matrix.ServerName, cfg.Matrix.ServerName,
nil, nil,
) )
if perr != nil { switch e := err.(type) {
util.GetLogger(req.Context()).WithError(perr).Error("producer.SendInvite failed") case *roomserverAPI.PerformError:
return perr.JSONResponse() return e.JSONResponse()
} case nil:
return util.JSONResponse{ return util.JSONResponse{
Code: http.StatusOK, Code: http.StatusOK,
JSON: struct{}{}, JSON: struct{}{},
}
default:
util.GetLogger(req.Context()).WithError(err).Error("roomserverAPI.SendInvite failed")
return util.JSONResponse{
Code: http.StatusInternalServerError,
JSON: jsonerror.InternalServerError(),
}
} }
} }

View File

@ -46,7 +46,7 @@ func InviteV2(
} }
} }
return processInvite( return processInvite(
httpReq.Context(), inviteReq.Event(), inviteReq.RoomVersion(), inviteReq.InviteRoomState(), roomID, eventID, cfg, rsAPI, keys, httpReq.Context(), true, inviteReq.Event(), inviteReq.RoomVersion(), inviteReq.InviteRoomState(), roomID, eventID, cfg, rsAPI, keys,
) )
} }
@ -75,12 +75,13 @@ func InviteV1(
util.GetLogger(httpReq.Context()).Warnf("failed to extract stripped state from invite event") util.GetLogger(httpReq.Context()).Warnf("failed to extract stripped state from invite event")
} }
return processInvite( return processInvite(
httpReq.Context(), event, roomVer, strippedState, roomID, eventID, cfg, rsAPI, keys, httpReq.Context(), false, event, roomVer, strippedState, roomID, eventID, cfg, rsAPI, keys,
) )
} }
func processInvite( func processInvite(
ctx context.Context, ctx context.Context,
isInviteV2 bool,
event gomatrixserverlib.Event, event gomatrixserverlib.Event,
roomVer gomatrixserverlib.RoomVersion, roomVer gomatrixserverlib.RoomVersion,
strippedState []gomatrixserverlib.InviteV2StrippedState, strippedState []gomatrixserverlib.InviteV2StrippedState,
@ -143,17 +144,31 @@ func processInvite(
) )
// Add the invite event to the roomserver. // Add the invite event to the roomserver.
if perr := api.SendInvite( err = api.SendInvite(
ctx, rsAPI, signedEvent.Headered(roomVer), strippedState, event.Origin(), nil, ctx, rsAPI, signedEvent.Headered(roomVer), strippedState, api.DoNotSendToOtherServers, nil,
); perr != nil { )
util.GetLogger(ctx).WithError(err).Error("producer.SendInvite failed") switch e := err.(type) {
return perr.JSONResponse() case *api.PerformError:
} return e.JSONResponse()
case nil:
// Return the signed event to the originating server, it should then tell // Return the signed event to the originating server, it should then tell
// the other servers in the room that we have been invited. // the other servers in the room that we have been invited.
return util.JSONResponse{ if isInviteV2 {
Code: http.StatusOK, return util.JSONResponse{
JSON: gomatrixserverlib.RespInviteV2{Event: signedEvent}, Code: http.StatusOK,
JSON: gomatrixserverlib.RespInviteV2{Event: signedEvent},
}
} else {
return util.JSONResponse{
Code: http.StatusOK,
JSON: gomatrixserverlib.RespInvite{Event: signedEvent},
}
}
default:
util.GetLogger(ctx).WithError(err).Error("api.SendInvite failed")
return util.JSONResponse{
Code: http.StatusInternalServerError,
JSON: jsonerror.InternalServerError(),
}
} }
} }

View File

@ -25,6 +25,7 @@ import (
) )
// MakeLeave implements the /make_leave API // MakeLeave implements the /make_leave API
// nolint:gocyclo
func MakeLeave( func MakeLeave(
httpReq *http.Request, httpReq *http.Request,
request *gomatrixserverlib.FederationRequest, request *gomatrixserverlib.FederationRequest,
@ -76,6 +77,23 @@ func MakeLeave(
return jsonerror.InternalServerError() return jsonerror.InternalServerError()
} }
// If the user has already left then just return their last leave
// event. This means that /send_leave will be a no-op, which helps
// to reject invites multiple times - hopefully.
for _, state := range queryRes.StateEvents {
if state.Type() == gomatrixserverlib.MRoomMember && state.StateKeyEquals(userID) {
if mem, merr := state.Membership(); merr == nil && mem == gomatrixserverlib.Leave {
return util.JSONResponse{
Code: http.StatusOK,
JSON: map[string]interface{}{
"room_version": event.RoomVersion,
"event": state,
},
}
}
}
}
// Check that the leave is allowed or not // Check that the leave is allowed or not
stateEvents := make([]*gomatrixserverlib.Event, len(queryRes.StateEvents)) stateEvents := make([]*gomatrixserverlib.Event, len(queryRes.StateEvents))
for i := range queryRes.StateEvents { for i := range queryRes.StateEvents {
@ -99,6 +117,7 @@ func MakeLeave(
} }
// SendLeave implements the /send_leave API // SendLeave implements the /send_leave API
// nolint:gocyclo
func SendLeave( func SendLeave(
httpReq *http.Request, httpReq *http.Request,
request *gomatrixserverlib.FederationRequest, request *gomatrixserverlib.FederationRequest,
@ -149,6 +168,48 @@ func SendLeave(
} }
} }
// Check if the user has already left. If so, no-op!
queryReq := &api.QueryLatestEventsAndStateRequest{
RoomID: roomID,
StateToFetch: []gomatrixserverlib.StateKeyTuple{
{
EventType: gomatrixserverlib.MRoomMember,
StateKey: *event.StateKey(),
},
},
}
queryRes := &api.QueryLatestEventsAndStateResponse{}
err = rsAPI.QueryLatestEventsAndState(httpReq.Context(), queryReq, queryRes)
if err != nil {
util.GetLogger(httpReq.Context()).WithError(err).Error("rsAPI.QueryLatestEventsAndState failed")
return jsonerror.InternalServerError()
}
// The room doesn't exist or we weren't ever joined to it. Might as well
// no-op here.
if !queryRes.RoomExists || len(queryRes.StateEvents) == 0 {
return util.JSONResponse{
Code: http.StatusOK,
JSON: struct{}{},
}
}
// Check if we're recycling a previous leave event.
if event.EventID() == queryRes.StateEvents[0].EventID() {
return util.JSONResponse{
Code: http.StatusOK,
JSON: struct{}{},
}
}
// We are/were joined/invited/banned or something. Check if
// we can no-op here.
if len(queryRes.StateEvents) == 1 {
if mem, merr := queryRes.StateEvents[0].Membership(); merr == nil && mem == gomatrixserverlib.Leave {
return util.JSONResponse{
Code: http.StatusOK,
JSON: struct{}{},
}
}
}
// Check that the event is signed by the server sending the request. // Check that the event is signed by the server sending the request.
redacted := event.Redact() redacted := event.Redact()
verifyRequests := []gomatrixserverlib.VerifyJSONRequest{{ verifyRequests := []gomatrixserverlib.VerifyJSONRequest{{
@ -174,7 +235,8 @@ func SendLeave(
if err != nil { if err != nil {
util.GetLogger(httpReq.Context()).WithError(err).Error("event.Membership failed") util.GetLogger(httpReq.Context()).WithError(err).Error("event.Membership failed")
return jsonerror.InternalServerError() return jsonerror.InternalServerError()
} else if mem != gomatrixserverlib.Leave { }
if mem != gomatrixserverlib.Leave {
return util.JSONResponse{ return util.JSONResponse{
Code: http.StatusBadRequest, Code: http.StatusBadRequest,
JSON: jsonerror.BadJSON("The membership in the event content must be set to leave"), JSON: jsonerror.BadJSON("The membership in the event content must be set to leave"),

View File

@ -90,16 +90,10 @@ func Setup(
JSON: jsonerror.Forbidden("Forbidden by server ACLs"), JSON: jsonerror.Forbidden("Forbidden by server ACLs"),
} }
} }
res := InviteV1( return InviteV1(
httpReq, request, vars["roomID"], vars["eventID"], httpReq, request, vars["roomID"], vars["eventID"],
cfg, rsAPI, keys, cfg, rsAPI, keys,
) )
return util.JSONResponse{
Code: res.Code,
JSON: []interface{}{
res.Code, res.JSON,
},
}
}, },
)).Methods(http.MethodPut, http.MethodOptions) )).Methods(http.MethodPut, http.MethodOptions)

View File

@ -102,7 +102,8 @@ func (t *testRoomserverAPI) PerformInvite(
ctx context.Context, ctx context.Context,
req *api.PerformInviteRequest, req *api.PerformInviteRequest,
res *api.PerformInviteResponse, res *api.PerformInviteResponse,
) { ) error {
return nil
} }
func (t *testRoomserverAPI) PerformJoin( func (t *testRoomserverAPI) PerformJoin(

View File

@ -36,6 +36,12 @@ type FederationSenderInternalAPI interface {
request *PerformLeaveRequest, request *PerformLeaveRequest,
response *PerformLeaveResponse, response *PerformLeaveResponse,
) error ) error
// Handle sending an invite to a remote server.
PerformInvite(
ctx context.Context,
request *PerformInviteRequest,
response *PerformInviteResponse,
) error
// Notifies the federation sender that these servers may be online and to retry sending messages. // Notifies the federation sender that these servers may be online and to retry sending messages.
PerformServersAlive( PerformServersAlive(
ctx context.Context, ctx context.Context,
@ -81,6 +87,16 @@ type PerformLeaveRequest struct {
type PerformLeaveResponse struct { type PerformLeaveResponse struct {
} }
type PerformInviteRequest struct {
RoomVersion gomatrixserverlib.RoomVersion `json:"room_version"`
Event gomatrixserverlib.HeaderedEvent `json:"event"`
InviteRoomState []gomatrixserverlib.InviteV2StrippedState `json:"invite_room_state"`
}
type PerformInviteResponse struct {
Event gomatrixserverlib.HeaderedEvent `json:"event"`
}
type PerformServersAliveRequest struct { type PerformServersAliveRequest struct {
Servers []gomatrixserverlib.ServerName Servers []gomatrixserverlib.ServerName
} }

View File

@ -28,7 +28,6 @@ import (
"github.com/matrix-org/dendrite/roomserver/api" "github.com/matrix-org/dendrite/roomserver/api"
"github.com/matrix-org/gomatrixserverlib" "github.com/matrix-org/gomatrixserverlib"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"github.com/tidwall/gjson"
) )
// OutputRoomEventConsumer consumes events that originated in the room server. // OutputRoomEventConsumer consumes events that originated in the room server.
@ -97,22 +96,6 @@ func (s *OutputRoomEventConsumer) onMessage(msg *sarama.ConsumerMessage) error {
}).Panicf("roomserver output log: write room event failure") }).Panicf("roomserver output log: write room event failure")
return nil return nil
} }
case api.OutputTypeNewInviteEvent:
ev := &output.NewInviteEvent.Event
log.WithFields(log.Fields{
"event_id": ev.EventID(),
"room_id": ev.RoomID(),
"state_key": ev.StateKey(),
}).Info("received invite event from roomserver")
if err := s.processInvite(*output.NewInviteEvent); err != nil {
// panic rather than continue with an inconsistent database
log.WithFields(log.Fields{
"event": string(ev.JSON()),
log.ErrorKey: err,
}).Panicf("roomserver output log: write invite event failure")
return nil
}
default: default:
log.WithField("type", output.Type).Debug( log.WithField("type", output.Type).Debug(
"roomserver output log: ignoring unknown output type", "roomserver output log: ignoring unknown output type",
@ -172,51 +155,6 @@ func (s *OutputRoomEventConsumer) processMessage(ore api.OutputNewRoomEvent) err
) )
} }
// processInvite handles an invite event for sending over federation.
func (s *OutputRoomEventConsumer) processInvite(oie api.OutputNewInviteEvent) error {
// Don't try to reflect and resend invites that didn't originate from us.
if s.cfg.Matrix.ServerName != oie.Event.Origin() {
return nil
}
// Ignore invites that don't have state keys - they are invalid.
if oie.Event.StateKey() == nil {
return fmt.Errorf("event %q doesn't have state key", oie.Event.EventID())
}
// Don't try to handle events that are actually destined for us.
stateKey := *oie.Event.StateKey()
_, destination, err := gomatrixserverlib.SplitID('@', stateKey)
if err != nil {
log.WithFields(log.Fields{
"event_id": oie.Event.EventID(),
"state_key": stateKey,
}).Info("failed to split destination from state key")
return nil
}
if s.cfg.Matrix.ServerName == destination {
return nil
}
// Try to extract the room invite state. The roomserver will have stashed
// this for us in invite_room_state if it didn't already exist.
strippedState := []gomatrixserverlib.InviteV2StrippedState{}
if inviteRoomState := gjson.GetBytes(oie.Event.Unsigned(), "invite_room_state"); inviteRoomState.Exists() {
if err = json.Unmarshal([]byte(inviteRoomState.Raw), &strippedState); err != nil {
log.WithError(err).Warn("failed to extract invite_room_state from event unsigned")
}
}
// Build the invite request with the info we've got.
inviteReq, err := gomatrixserverlib.NewInviteV2Request(&oie.Event, strippedState)
if err != nil {
return fmt.Errorf("gomatrixserverlib.NewInviteV2Request: %w", err)
}
// Send the event.
return s.queues.SendInvite(&inviteReq)
}
// joinedHostsAtEvent works out a list of matrix servers that were joined to // joinedHostsAtEvent works out a list of matrix servers that were joined to
// the room at the event. // the room at the event.
// It is important to use the state at the event for sending messages because: // It is important to use the state at the event for sending messages because:

View File

@ -296,6 +296,43 @@ func (r *FederationSenderInternalAPI) PerformLeave(
) )
} }
// PerformLeaveRequest implements api.FederationSenderInternalAPI
func (r *FederationSenderInternalAPI) PerformInvite(
ctx context.Context,
request *api.PerformInviteRequest,
response *api.PerformInviteResponse,
) (err error) {
if request.Event.StateKey() == nil {
return errors.New("invite must be a state event")
}
_, destination, err := gomatrixserverlib.SplitID('@', *request.Event.StateKey())
if err != nil {
return fmt.Errorf("gomatrixserverlib.SplitID: %w", err)
}
logrus.WithFields(logrus.Fields{
"event_id": request.Event.EventID(),
"user_id": *request.Event.StateKey(),
"room_id": request.Event.RoomID(),
"room_version": request.RoomVersion,
"destination": destination,
}).Info("Sending invite")
inviteReq, err := gomatrixserverlib.NewInviteV2Request(&request.Event, request.InviteRoomState)
if err != nil {
return fmt.Errorf("gomatrixserverlib.NewInviteV2Request: %w", err)
}
inviteRes, err := r.federation.SendInviteV2(ctx, destination, inviteReq)
if err != nil {
return fmt.Errorf("r.federation.SendInviteV2: %w", err)
}
response.Event = inviteRes.Event.Headered(request.RoomVersion)
return nil
}
// PerformServersAlive implements api.FederationSenderInternalAPI // PerformServersAlive implements api.FederationSenderInternalAPI
func (r *FederationSenderInternalAPI) PerformServersAlive( func (r *FederationSenderInternalAPI) PerformServersAlive(
ctx context.Context, ctx context.Context,

View File

@ -18,6 +18,7 @@ const (
FederationSenderPerformDirectoryLookupRequestPath = "/federationsender/performDirectoryLookup" FederationSenderPerformDirectoryLookupRequestPath = "/federationsender/performDirectoryLookup"
FederationSenderPerformJoinRequestPath = "/federationsender/performJoinRequest" FederationSenderPerformJoinRequestPath = "/federationsender/performJoinRequest"
FederationSenderPerformLeaveRequestPath = "/federationsender/performLeaveRequest" FederationSenderPerformLeaveRequestPath = "/federationsender/performLeaveRequest"
FederationSenderPerformInviteRequestPath = "/federationsender/performInviteRequest"
FederationSenderPerformServersAlivePath = "/federationsender/performServersAlive" FederationSenderPerformServersAlivePath = "/federationsender/performServersAlive"
FederationSenderPerformBroadcastEDUPath = "/federationsender/performBroadcastEDU" FederationSenderPerformBroadcastEDUPath = "/federationsender/performBroadcastEDU"
) )
@ -49,6 +50,19 @@ func (h *httpFederationSenderInternalAPI) PerformLeave(
return httputil.PostJSON(ctx, span, h.httpClient, apiURL, request, response) return httputil.PostJSON(ctx, span, h.httpClient, apiURL, request, response)
} }
// Handle sending an invite to a remote server.
func (h *httpFederationSenderInternalAPI) PerformInvite(
ctx context.Context,
request *api.PerformInviteRequest,
response *api.PerformInviteResponse,
) error {
span, ctx := opentracing.StartSpanFromContext(ctx, "PerformInviteRequest")
defer span.Finish()
apiURL := h.federationSenderURL + FederationSenderPerformInviteRequestPath
return httputil.PostJSON(ctx, span, h.httpClient, apiURL, request, response)
}
func (h *httpFederationSenderInternalAPI) PerformServersAlive( func (h *httpFederationSenderInternalAPI) PerformServersAlive(
ctx context.Context, ctx context.Context,
request *api.PerformServersAliveRequest, request *api.PerformServersAliveRequest,

View File

@ -11,6 +11,7 @@ import (
) )
// AddRoutes adds the FederationSenderInternalAPI handlers to the http.ServeMux. // AddRoutes adds the FederationSenderInternalAPI handlers to the http.ServeMux.
// nolint:gocyclo
func AddRoutes(intAPI api.FederationSenderInternalAPI, internalAPIMux *mux.Router) { func AddRoutes(intAPI api.FederationSenderInternalAPI, internalAPIMux *mux.Router) {
internalAPIMux.Handle( internalAPIMux.Handle(
FederationSenderQueryJoinedHostServerNamesInRoomPath, FederationSenderQueryJoinedHostServerNamesInRoomPath,
@ -52,6 +53,20 @@ func AddRoutes(intAPI api.FederationSenderInternalAPI, internalAPIMux *mux.Route
return util.JSONResponse{Code: http.StatusOK, JSON: &response} return util.JSONResponse{Code: http.StatusOK, JSON: &response}
}), }),
) )
internalAPIMux.Handle(
FederationSenderPerformInviteRequestPath,
httputil.MakeInternalAPI("PerformInviteRequest", func(req *http.Request) util.JSONResponse {
var request api.PerformInviteRequest
var response api.PerformInviteResponse
if err := json.NewDecoder(req.Body).Decode(&request); err != nil {
return util.MessageResponse(http.StatusBadRequest, err.Error())
}
if err := intAPI.PerformInvite(req.Context(), &request, &response); err != nil {
return util.ErrorResponse(err)
}
return util.JSONResponse{Code: http.StatusOK, JSON: &response}
}),
)
internalAPIMux.Handle( internalAPIMux.Handle(
FederationSenderPerformDirectoryLookupRequestPath, FederationSenderPerformDirectoryLookupRequestPath,
httputil.MakeInternalAPI("PerformDirectoryLookupRequest", func(req *http.Request) util.JSONResponse { httputil.MakeInternalAPI("PerformDirectoryLookupRequest", func(req *http.Request) util.JSONResponse {

View File

@ -46,20 +46,18 @@ type destinationQueue struct {
db storage.Database db storage.Database
signing *SigningInfo signing *SigningInfo
rsAPI api.RoomserverInternalAPI rsAPI api.RoomserverInternalAPI
client *gomatrixserverlib.FederationClient // federation client client *gomatrixserverlib.FederationClient // federation client
origin gomatrixserverlib.ServerName // origin of requests origin gomatrixserverlib.ServerName // origin of requests
destination gomatrixserverlib.ServerName // destination of requests destination gomatrixserverlib.ServerName // destination of requests
running atomic.Bool // is the queue worker running? running atomic.Bool // is the queue worker running?
backingOff atomic.Bool // true if we're backing off backingOff atomic.Bool // true if we're backing off
statistics *statistics.ServerStatistics // statistics about this remote server statistics *statistics.ServerStatistics // statistics about this remote server
incomingInvites chan *gomatrixserverlib.InviteV2Request // invites to send transactionIDMutex sync.Mutex // protects transactionID
transactionIDMutex sync.Mutex // protects transactionID transactionID gomatrixserverlib.TransactionID // last transaction ID
transactionID gomatrixserverlib.TransactionID // last transaction ID transactionCount atomic.Int32 // how many events in this transaction so far
transactionCount atomic.Int32 // how many events in this transaction so far notifyPDUs chan bool // interrupts idle wait for PDUs
pendingInvites []*gomatrixserverlib.InviteV2Request // owned by backgroundSend notifyEDUs chan bool // interrupts idle wait for EDUs
notifyPDUs chan bool // interrupts idle wait for PDUs interruptBackoff chan bool // interrupts backoff
notifyEDUs chan bool // interrupts idle wait for EDUs
interruptBackoff chan bool // interrupts backoff
} }
// Send event adds the event to the pending queue for the destination. // Send event adds the event to the pending queue for the destination.
@ -138,18 +136,6 @@ func (oq *destinationQueue) sendEDU(receipt *shared.Receipt) {
} }
} }
// sendInvite adds the invite event to the pending queue for the
// destination. If the queue is empty then it starts a background
// goroutine to start sending events to that destination.
func (oq *destinationQueue) sendInvite(ev *gomatrixserverlib.InviteV2Request) {
if oq.statistics.Blacklisted() {
// If the destination is blacklisted then drop the event.
return
}
oq.wakeQueueIfNeeded()
oq.incomingInvites <- ev
}
// wakeQueueIfNeeded will wake up the destination queue if it is // wakeQueueIfNeeded will wake up the destination queue if it is
// not already running. If it is running but it is backing off // not already running. If it is running but it is backing off
// then we will interrupt the backoff, causing any federation // then we will interrupt the backoff, causing any federation
@ -234,23 +220,6 @@ func (oq *destinationQueue) backgroundSend() {
// We were woken up because there are new PDUs waiting in the // We were woken up because there are new PDUs waiting in the
// database. // database.
pendingEDUs = true pendingEDUs = true
case invite := <-oq.incomingInvites:
// There's no strict ordering requirement for invites like
// there is for transactions, so we put the invite onto the
// front of the queue. This means that if an invite that is
// stuck failing already, that it won't block our new invite
// from being sent.
oq.pendingInvites = append(
[]*gomatrixserverlib.InviteV2Request{invite},
oq.pendingInvites...,
)
// If there are any more things waiting in the channel queue
// then read them. This is safe because we guarantee only
// having one goroutine per destination queue, so the channel
// isn't being consumed anywhere else.
for len(oq.incomingInvites) > 0 {
oq.pendingInvites = append(oq.pendingInvites, <-oq.incomingInvites)
}
case <-time.After(queueIdleTimeout): case <-time.After(queueIdleTimeout):
// The worker is idle so stop the goroutine. It'll get // The worker is idle so stop the goroutine. It'll get
// restarted automatically the next time we have an event to // restarted automatically the next time we have an event to
@ -266,7 +235,6 @@ func (oq *destinationQueue) backgroundSend() {
// It's been suggested that we should give up because the backoff // It's been suggested that we should give up because the backoff
// has exceeded a maximum allowable value. Clean up the in-memory // has exceeded a maximum allowable value. Clean up the in-memory
// buffers at this point. The PDU clean-up is already on a defer. // buffers at this point. The PDU clean-up is already on a defer.
oq.cleanPendingInvites()
log.Warnf("Blacklisting %q due to exceeding backoff threshold", oq.destination) log.Warnf("Blacklisting %q due to exceeding backoff threshold", oq.destination)
return return
} }
@ -284,35 +252,9 @@ func (oq *destinationQueue) backgroundSend() {
oq.statistics.Success() oq.statistics.Success()
} }
} }
// Try sending the next invite and see what happens.
if len(oq.pendingInvites) > 0 {
sent, ierr := oq.nextInvites(oq.pendingInvites)
if ierr != nil {
// We failed to send the transaction. Mark it as a failure.
oq.statistics.Failure()
} else if sent > 0 {
// If we successfully sent the invites then clear out
// the pending invites.
oq.statistics.Success()
// Reallocate so that the underlying array can be GC'd, as
// opposed to growing forever.
oq.cleanPendingInvites()
}
}
} }
} }
// cleanPendingInvites cleans out the pending invite buffer,
// removing all references so that the underlying objects can
// be GC'd.
func (oq *destinationQueue) cleanPendingInvites() {
for i := 0; i < len(oq.pendingInvites); i++ {
oq.pendingInvites[i] = nil
}
oq.pendingInvites = []*gomatrixserverlib.InviteV2Request{}
}
// nextTransaction creates a new transaction from the pending event // nextTransaction creates a new transaction from the pending event
// queue and sends it. Returns true if a transaction was sent or // queue and sends it. Returns true if a transaction was sent or
// false otherwise. // false otherwise.
@ -427,66 +369,3 @@ func (oq *destinationQueue) nextTransaction() (bool, error) {
return false, err return false, err
} }
} }
// nextInvite takes pending invite events from the queue and sends
// them. Returns true if a transaction was sent or false otherwise.
func (oq *destinationQueue) nextInvites(
pendingInvites []*gomatrixserverlib.InviteV2Request,
) (int, error) {
done := 0
for _, inviteReq := range pendingInvites {
ev, roomVersion := inviteReq.Event(), inviteReq.RoomVersion()
log.WithFields(log.Fields{
"event_id": ev.EventID(),
"room_version": roomVersion,
"destination": oq.destination,
}).Info("sending invite")
inviteRes, err := oq.client.SendInviteV2(
context.TODO(),
oq.destination,
*inviteReq,
)
switch e := err.(type) {
case nil:
done++
case gomatrix.HTTPError:
log.WithFields(log.Fields{
"event_id": ev.EventID(),
"state_key": ev.StateKey(),
"destination": oq.destination,
"status_code": e.Code,
}).WithError(err).Error("failed to send invite due to HTTP error")
// Check whether we should do something about the error or
// just accept it as unavoidable.
if e.Code >= 400 && e.Code <= 499 {
// We tried but the remote side has sent back a client error.
// It's no use retrying because it will happen again.
done++
continue
}
return done, err
default:
log.WithFields(log.Fields{
"event_id": ev.EventID(),
"state_key": ev.StateKey(),
"destination": oq.destination,
}).WithError(err).Error("failed to send invite")
return done, err
}
invEv := inviteRes.Event.Sign(string(oq.signing.ServerName), oq.signing.KeyID, oq.signing.PrivateKey).Headered(roomVersion)
_, err = api.SendEvents(context.TODO(), oq.rsAPI, []gomatrixserverlib.HeaderedEvent{invEv}, oq.signing.ServerName, nil)
if err != nil {
log.WithFields(log.Fields{
"event_id": ev.EventID(),
"state_key": ev.StateKey(),
"destination": oq.destination,
}).WithError(err).Error("failed to return signed invite to roomserver")
return done, err
}
}
return done, nil
}

View File

@ -108,7 +108,6 @@ func (oqs *OutgoingQueues) getQueue(destination gomatrixserverlib.ServerName) *d
destination: destination, destination: destination,
client: oqs.client, client: oqs.client,
statistics: oqs.statistics.ForServer(destination), statistics: oqs.statistics.ForServer(destination),
incomingInvites: make(chan *gomatrixserverlib.InviteV2Request, 128),
notifyPDUs: make(chan bool, 1), notifyPDUs: make(chan bool, 1),
notifyEDUs: make(chan bool, 1), notifyEDUs: make(chan bool, 1),
interruptBackoff: make(chan bool), interruptBackoff: make(chan bool),
@ -178,51 +177,6 @@ func (oqs *OutgoingQueues) SendEvent(
return nil return nil
} }
// SendEvent sends an event to the destinations
func (oqs *OutgoingQueues) SendInvite(
inviteReq *gomatrixserverlib.InviteV2Request,
) error {
ev := inviteReq.Event()
stateKey := ev.StateKey()
if stateKey == nil {
log.WithFields(log.Fields{
"event_id": ev.EventID(),
}).Info("Invite had no state key, dropping")
return nil
}
_, destination, err := gomatrixserverlib.SplitID('@', *stateKey)
if err != nil {
log.WithFields(log.Fields{
"event_id": ev.EventID(),
"state_key": stateKey,
}).Info("Failed to split destination from state key")
return nil
}
if stateapi.IsServerBannedFromRoom(
context.TODO(),
oqs.stateAPI,
ev.RoomID(),
destination,
) {
log.WithFields(log.Fields{
"room_id": ev.RoomID(),
"destination": destination,
}).Info("Dropping invite to server which is prohibited by ACLs")
return nil
}
log.WithFields(log.Fields{
"event_id": ev.EventID(),
"server_name": destination,
}).Info("Sending invite")
oqs.getQueue(destination).sendInvite(inviteReq)
return nil
}
// SendEDU sends an EDU event to the destinations. // SendEDU sends an EDU event to the destinations.
func (oqs *OutgoingQueues) SendEDU( func (oqs *OutgoingQueues) SendEDU(
e *gomatrixserverlib.EDU, origin gomatrixserverlib.ServerName, e *gomatrixserverlib.EDU, origin gomatrixserverlib.ServerName,

4
go.mod
View File

@ -2,9 +2,7 @@ module github.com/matrix-org/dendrite
require ( require (
github.com/Shopify/sarama v1.26.1 github.com/Shopify/sarama v1.26.1
github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible
github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd // indirect github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd // indirect
github.com/ghodss/yaml v1.0.0
github.com/gologme/log v1.2.0 github.com/gologme/log v1.2.0
github.com/gorilla/mux v1.7.3 github.com/gorilla/mux v1.7.3
github.com/hashicorp/golang-lru v0.5.4 github.com/hashicorp/golang-lru v0.5.4
@ -23,7 +21,7 @@ require (
github.com/matrix-org/go-http-js-libp2p v0.0.0-20200518170932-783164aeeda4 github.com/matrix-org/go-http-js-libp2p v0.0.0-20200518170932-783164aeeda4
github.com/matrix-org/go-sqlite3-js v0.0.0-20200522092705-bc8506ccbcf3 github.com/matrix-org/go-sqlite3-js v0.0.0-20200522092705-bc8506ccbcf3
github.com/matrix-org/gomatrix v0.0.0-20190528120928-7df988a63f26 github.com/matrix-org/gomatrix v0.0.0-20190528120928-7df988a63f26
github.com/matrix-org/gomatrixserverlib v0.0.0-20200807145008-79c173b65786 github.com/matrix-org/gomatrixserverlib v0.0.0-20200817100842-9d02141812f2
github.com/matrix-org/naffka v0.0.0-20200422140631-181f1ee7401f github.com/matrix-org/naffka v0.0.0-20200422140631-181f1ee7401f
github.com/matrix-org/util v0.0.0-20200807132607-55161520e1d4 github.com/matrix-org/util v0.0.0-20200807132607-55161520e1d4
github.com/mattn/go-sqlite3 v2.0.2+incompatible github.com/mattn/go-sqlite3 v2.0.2+incompatible

7
go.sum
View File

@ -51,9 +51,6 @@ github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XL
github.com/cheekybits/genny v1.0.0 h1:uGGa4nei+j20rOSeDeP5Of12XVm7TGUd4dJA9RDitfE= github.com/cheekybits/genny v1.0.0 h1:uGGa4nei+j20rOSeDeP5Of12XVm7TGUd4dJA9RDitfE=
github.com/cheekybits/genny v1.0.0/go.mod h1:+tQajlRqAUrPI7DOSpB0XAqZYtQakVtB7wXkRAgjxjQ= github.com/cheekybits/genny v1.0.0/go.mod h1:+tQajlRqAUrPI7DOSpB0XAqZYtQakVtB7wXkRAgjxjQ=
github.com/cheggaaa/pb/v3 v3.0.4/go.mod h1:7rgWxLrAUcFMkvJuv09+DYi7mMUYi8nO9iOWcvGJPfw= github.com/cheggaaa/pb/v3 v3.0.4/go.mod h1:7rgWxLrAUcFMkvJuv09+DYi7mMUYi8nO9iOWcvGJPfw=
github.com/circonus-labs/circonus-gometrics v1.2.0 h1:Kqa/+nIJhqFJ12B07aeekgC6F95J7yYgEtpD57NQzrE=
github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible h1:C29Ae4G5GtYyYMm1aztcyj/J5ckgJm2zwdDajFbx1NY=
github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd h1:qMd81Ts1T2OTKmB4acZcyKaMtRnY5Y44NuXGX2GFJ1w= github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd h1:qMd81Ts1T2OTKmB4acZcyKaMtRnY5Y44NuXGX2GFJ1w=
github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI= github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI=
@ -425,8 +422,8 @@ github.com/matrix-org/go-sqlite3-js v0.0.0-20200522092705-bc8506ccbcf3 h1:Yb+Wlf
github.com/matrix-org/go-sqlite3-js v0.0.0-20200522092705-bc8506ccbcf3/go.mod h1:e+cg2q7C7yE5QnAXgzo512tgFh1RbQLC0+jozuegKgo= github.com/matrix-org/go-sqlite3-js v0.0.0-20200522092705-bc8506ccbcf3/go.mod h1:e+cg2q7C7yE5QnAXgzo512tgFh1RbQLC0+jozuegKgo=
github.com/matrix-org/gomatrix v0.0.0-20190528120928-7df988a63f26 h1:Hr3zjRsq2bhrnp3Ky1qgx/fzCtCALOoGYylh2tpS9K4= github.com/matrix-org/gomatrix v0.0.0-20190528120928-7df988a63f26 h1:Hr3zjRsq2bhrnp3Ky1qgx/fzCtCALOoGYylh2tpS9K4=
github.com/matrix-org/gomatrix v0.0.0-20190528120928-7df988a63f26/go.mod h1:3fxX6gUjWyI/2Bt7J1OLhpCzOfO/bB3AiX0cJtEKud0= github.com/matrix-org/gomatrix v0.0.0-20190528120928-7df988a63f26/go.mod h1:3fxX6gUjWyI/2Bt7J1OLhpCzOfO/bB3AiX0cJtEKud0=
github.com/matrix-org/gomatrixserverlib v0.0.0-20200807145008-79c173b65786 h1:HQclx5J2CrCBqP88t5Di9IkVDJZn5+h4ZL48viY4FJ4= github.com/matrix-org/gomatrixserverlib v0.0.0-20200817100842-9d02141812f2 h1:9wKwfd5KDcXuqZ7/kAaYe0QM4DGM+2awjjvXQtrDa6k=
github.com/matrix-org/gomatrixserverlib v0.0.0-20200807145008-79c173b65786/go.mod h1:JsAzE1Ll3+gDWS9JSUHPJiiyAksvOOnGWF2nXdg4ZzU= github.com/matrix-org/gomatrixserverlib v0.0.0-20200817100842-9d02141812f2/go.mod h1:JsAzE1Ll3+gDWS9JSUHPJiiyAksvOOnGWF2nXdg4ZzU=
github.com/matrix-org/naffka v0.0.0-20200422140631-181f1ee7401f h1:pRz4VTiRCO4zPlEMc3ESdUOcW4PXHH4Kj+YDz1XyE+Y= github.com/matrix-org/naffka v0.0.0-20200422140631-181f1ee7401f h1:pRz4VTiRCO4zPlEMc3ESdUOcW4PXHH4Kj+YDz1XyE+Y=
github.com/matrix-org/naffka v0.0.0-20200422140631-181f1ee7401f/go.mod h1:y0oDTjZDv5SM9a2rp3bl+CU+bvTRINQsdb7YlDql5Go= github.com/matrix-org/naffka v0.0.0-20200422140631-181f1ee7401f/go.mod h1:y0oDTjZDv5SM9a2rp3bl+CU+bvTRINQsdb7YlDql5Go=
github.com/matrix-org/util v0.0.0-20190711121626-527ce5ddefc7 h1:ntrLa/8xVzeSs8vHFHK25k0C+NV74sYMJnNSg5NoSRo= github.com/matrix-org/util v0.0.0-20190711121626-527ce5ddefc7 h1:ntrLa/8xVzeSs8vHFHK25k0C+NV74sYMJnNSg5NoSRo=

View File

@ -22,7 +22,7 @@ type RoomserverInternalAPI interface {
ctx context.Context, ctx context.Context,
req *PerformInviteRequest, req *PerformInviteRequest,
res *PerformInviteResponse, res *PerformInviteResponse,
) ) error
PerformJoin( PerformJoin(
ctx context.Context, ctx context.Context,

View File

@ -33,9 +33,9 @@ func (t *RoomserverInternalAPITrace) PerformInvite(
ctx context.Context, ctx context.Context,
req *PerformInviteRequest, req *PerformInviteRequest,
res *PerformInviteResponse, res *PerformInviteResponse,
) { ) error {
t.Impl.PerformInvite(ctx, req, res)
util.GetLogger(ctx).Infof("PerformInvite req=%+v res=%+v", js(req), js(res)) util.GetLogger(ctx).Infof("PerformInvite req=%+v res=%+v", js(req), js(res))
return t.Impl.PerformInvite(ctx, req, res)
} }
func (t *RoomserverInternalAPITrace) PerformJoin( func (t *RoomserverInternalAPITrace) PerformJoin(

View File

@ -105,7 +105,6 @@ type PerformInviteRequest struct {
} }
type PerformInviteResponse struct { type PerformInviteResponse struct {
// If non-nil, the invite request failed. Contains more information why it failed.
Error *PerformError Error *PerformError
} }

View File

@ -16,6 +16,7 @@ package api
import ( import (
"context" "context"
"fmt"
"github.com/matrix-org/gomatrixserverlib" "github.com/matrix-org/gomatrixserverlib"
"github.com/matrix-org/util" "github.com/matrix-org/util"
@ -99,21 +100,41 @@ func SendInvite(
rsAPI RoomserverInternalAPI, inviteEvent gomatrixserverlib.HeaderedEvent, rsAPI RoomserverInternalAPI, inviteEvent gomatrixserverlib.HeaderedEvent,
inviteRoomState []gomatrixserverlib.InviteV2StrippedState, inviteRoomState []gomatrixserverlib.InviteV2StrippedState,
sendAsServer gomatrixserverlib.ServerName, txnID *TransactionID, sendAsServer gomatrixserverlib.ServerName, txnID *TransactionID,
) *PerformError { ) error {
request := PerformInviteRequest{ // Start by sending the invite request into the roomserver. This will
// trigger the federation request amongst other things if needed.
request := &PerformInviteRequest{
Event: inviteEvent, Event: inviteEvent,
InviteRoomState: inviteRoomState, InviteRoomState: inviteRoomState,
RoomVersion: inviteEvent.RoomVersion, RoomVersion: inviteEvent.RoomVersion,
SendAsServer: string(sendAsServer), SendAsServer: string(sendAsServer),
TransactionID: txnID, TransactionID: txnID,
} }
var response PerformInviteResponse response := &PerformInviteResponse{}
rsAPI.PerformInvite(ctx, &request, &response) if err := rsAPI.PerformInvite(ctx, request, response); err != nil {
// we need to do this because many places people will use `var err error` as the return return fmt.Errorf("rsAPI.PerformInvite: %w", err)
// arg and a nil interface != nil pointer to a concrete interface (in this case PerformError) }
if response.Error != nil && response.Error.Msg != "" { if response.Error != nil {
return response.Error return response.Error
} }
// Now send the invite event into the roomserver. If the room is known
// locally then this will succeed, notifying existing users in the room
// about the new invite. If the room isn't known locally then this will
// fail - and that's also OK.
inputReq := &InputRoomEventsRequest{
InputRoomEvents: []InputRoomEvent{
{
Kind: KindNew,
Event: inviteEvent,
AuthEventIDs: inviteEvent.AuthEventIDs(),
SendAsServer: string(sendAsServer),
},
},
}
inputRes := &InputRoomEventsResponse{}
_ = rsAPI.InputRoomEvents(ctx, inputReq, inputRes)
return nil return nil
} }

View File

@ -2,9 +2,9 @@ package internal
import ( import (
"context" "context"
"errors"
"fmt" "fmt"
federationSenderAPI "github.com/matrix-org/dendrite/federationsender/api"
"github.com/matrix-org/dendrite/internal/sqlutil" "github.com/matrix-org/dendrite/internal/sqlutil"
"github.com/matrix-org/dendrite/roomserver/api" "github.com/matrix-org/dendrite/roomserver/api"
"github.com/matrix-org/dendrite/roomserver/state" "github.com/matrix-org/dendrite/roomserver/state"
@ -14,73 +14,50 @@ import (
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
) )
// PerformInvite handles inviting to matrix rooms, including over federation by talking to the federationsender. // nolint:gocyclo
func (r *RoomserverInternalAPI) PerformInvite( func (r *RoomserverInternalAPI) PerformInvite(
ctx context.Context, ctx context.Context,
req *api.PerformInviteRequest, req *api.PerformInviteRequest,
res *api.PerformInviteResponse, res *api.PerformInviteResponse,
) {
err := r.performInvite(ctx, req)
if err != nil {
perr, ok := err.(*api.PerformError)
if ok {
res.Error = perr
} else {
res.Error = &api.PerformError{
Msg: err.Error(),
}
}
}
}
func (r *RoomserverInternalAPI) performInvite(ctx context.Context,
req *api.PerformInviteRequest,
) error { ) error {
loopback, err := r.processInviteEvent(ctx, r, req) event := req.Event
if err != nil { if event.StateKey() == nil {
return err return fmt.Errorf("invite must be a state event")
}
// The processInviteEvent function can optionally return a
// loopback room event containing the invite, for local invites.
// If it does, we should process it with the room events below.
if loopback != nil {
var loopbackRes api.InputRoomEventsResponse
err := r.InputRoomEvents(ctx, &api.InputRoomEventsRequest{
InputRoomEvents: []api.InputRoomEvent{*loopback},
}, &loopbackRes)
if err != nil {
return err
}
}
return nil
}
// nolint:gocyclo
func (r *RoomserverInternalAPI) processInviteEvent(
ctx context.Context,
ow *RoomserverInternalAPI,
input *api.PerformInviteRequest,
) (*api.InputRoomEvent, error) {
if input.Event.StateKey() == nil {
return nil, fmt.Errorf("invite must be a state event")
} }
roomID := input.Event.RoomID() roomID := event.RoomID()
targetUserID := *input.Event.StateKey() targetUserID := *event.StateKey()
log.WithFields(log.Fields{ log.WithFields(log.Fields{
"event_id": input.Event.EventID(), "event_id": event.EventID(),
"room_id": roomID, "room_id": roomID,
"room_version": input.RoomVersion, "room_version": req.RoomVersion,
"target_user_id": targetUserID, "target_user_id": targetUserID,
}).Info("processing invite event") }).Info("processing invite event")
_, domain, _ := gomatrixserverlib.SplitID('@', targetUserID) _, domain, _ := gomatrixserverlib.SplitID('@', targetUserID)
isTargetLocalUser := domain == r.Cfg.Matrix.ServerName isTargetLocal := domain == r.Cfg.Matrix.ServerName
isOriginLocal := event.Origin() == r.Cfg.Matrix.ServerName
updater, err := r.DB.MembershipUpdater(ctx, roomID, targetUserID, isTargetLocalUser, input.RoomVersion) inviteState := req.InviteRoomState
if len(inviteState) == 0 {
if is, err := buildInviteStrippedState(ctx, r.DB, req); err == nil {
inviteState = is
}
}
if len(inviteState) == 0 {
if err := event.SetUnsignedField("invite_room_state", struct{}{}); err != nil {
return fmt.Errorf("event.SetUnsignedField: %w", err)
}
} else {
if err := event.SetUnsignedField("invite_room_state", inviteState); err != nil {
return fmt.Errorf("event.SetUnsignedField: %w", err)
}
}
updater, err := r.DB.MembershipUpdater(ctx, roomID, targetUserID, isTargetLocal, req.RoomVersion)
if err != nil { if err != nil {
return nil, err return fmt.Errorf("r.DB.MembershipUpdater: %w", err)
} }
succeeded := false succeeded := false
defer func() { defer func() {
@ -118,109 +95,66 @@ func (r *RoomserverInternalAPI) processInviteEvent(
// For now we will implement option 2. Since in the abesence of a retry // For now we will implement option 2. Since in the abesence of a retry
// mechanism it will be equivalent to option 1, and we don't have a // mechanism it will be equivalent to option 1, and we don't have a
// signalling mechanism to implement option 3. // signalling mechanism to implement option 3.
return nil, &api.PerformError{ res.Error = &api.PerformError{
Code: api.PerformErrorNoOperation, Code: api.PerformErrorNotAllowed,
Msg: "user is already joined to room", Msg: "User is already joined to room",
} }
return nil
} }
// Normally, with a federated invite, the federation sender would do if isOriginLocal {
// the /v2/invite request (in which the remote server signs the invite) // check that the user is allowed to do this. We can only do this check if it is
// and then the signed event gets sent back to the roomserver as an input // a local invite as we have the auth events, else we have to take it on trust.
// event. When the invite is local, we don't interact with the federation _, err = checkAuthEvents(ctx, r.DB, req.Event, req.Event.AuthEventIDs())
// sender therefore we need to generate the loopback invite event for
// the room ourselves.
loopback, err := localInviteLoopback(ow, input)
if err != nil {
return nil, err
}
event := input.Event.Unwrap()
// check that the user is allowed to do this. We can only do this check if it is
// a local invite as we have the auth events, else we have to take it on trust.
if loopback != nil {
_, err = checkAuthEvents(ctx, r.DB, input.Event, input.Event.AuthEventIDs())
if err != nil { if err != nil {
log.WithError(err).WithField("event_id", event.EventID()).WithField("auth_event_ids", event.AuthEventIDs()).Error( log.WithError(err).WithField("event_id", event.EventID()).WithField("auth_event_ids", event.AuthEventIDs()).Error(
"processInviteEvent.checkAuthEvents failed for event", "processInviteEvent.checkAuthEvents failed for event",
) )
if _, ok := err.(*gomatrixserverlib.NotAllowed); ok { if _, ok := err.(*gomatrixserverlib.NotAllowed); ok {
return nil, &api.PerformError{ res.Error = &api.PerformError{
Msg: err.Error(), Msg: err.Error(),
Code: api.PerformErrorNotAllowed, Code: api.PerformErrorNotAllowed,
} }
return nil
} }
return nil, err return fmt.Errorf("checkAuthEvents: %w", err)
}
// If the invite originated from us and the target isn't local then we
// should try and send the invite over federation first. It might be
// that the remote user doesn't exist, in which case we can give up
// processing here.
if req.SendAsServer != api.DoNotSendToOtherServers && !isTargetLocal {
fsReq := &federationSenderAPI.PerformInviteRequest{
RoomVersion: req.RoomVersion,
Event: req.Event,
InviteRoomState: inviteState,
}
fsRes := &federationSenderAPI.PerformInviteResponse{}
if err = r.fsAPI.PerformInvite(ctx, fsReq, fsRes); err != nil {
res.Error = &api.PerformError{
Msg: err.Error(),
Code: api.PerformErrorNoOperation,
}
log.WithError(err).WithField("event_id", event.EventID()).Error("r.fsAPI.PerformInvite failed")
return nil
}
event = fsRes.Event
} }
} }
if len(input.InviteRoomState) > 0 { unwrapped := event.Unwrap()
// If we were supplied with some invite room state already (which is outputUpdates, err := updateToInviteMembership(updater, &unwrapped, nil, req.Event.RoomVersion)
// most likely to be if the event came in over federation) then use
// that.
if err = event.SetUnsignedField("invite_room_state", input.InviteRoomState); err != nil {
return nil, err
}
} else {
// There's no invite room state, so let's have a go at building it
// up from local data (which is most likely to be if the event came
// from the CS API). If we know about the room then we can insert
// the invite room state, if we don't then we just fail quietly.
if irs, ierr := buildInviteStrippedState(ctx, r.DB, input); ierr == nil {
if err = event.SetUnsignedField("invite_room_state", irs); err != nil {
return nil, err
}
} else {
log.WithError(ierr).Error("failed to build invite stripped state")
// still set the field else synapse deployments don't process the invite
if err = event.SetUnsignedField("invite_room_state", struct{}{}); err != nil {
return nil, err
}
}
}
outputUpdates, err := updateToInviteMembership(updater, &event, nil, input.Event.RoomVersion)
if err != nil { if err != nil {
return nil, err return fmt.Errorf("updateToInviteMembership: %w", err)
} }
if err = ow.WriteOutputEvents(roomID, outputUpdates); err != nil { if err = r.WriteOutputEvents(roomID, outputUpdates); err != nil {
return nil, err return fmt.Errorf("r.WriteOutputEvents: %w", err)
} }
succeeded = true succeeded = true
return loopback, nil return nil
}
func localInviteLoopback(
ow *RoomserverInternalAPI,
input *api.PerformInviteRequest,
) (ire *api.InputRoomEvent, err error) {
if input.Event.StateKey() == nil {
return nil, errors.New("no state key on invite event")
}
ourServerName := string(ow.Cfg.Matrix.ServerName)
_, theirServerName, err := gomatrixserverlib.SplitID('@', *input.Event.StateKey())
if err != nil {
return nil, err
}
// Check if the invite originated locally and is destined locally.
if input.Event.Origin() == ow.Cfg.Matrix.ServerName && string(theirServerName) == ourServerName {
rsEvent := input.Event.Sign(
ourServerName,
ow.Cfg.Matrix.KeyID,
ow.Cfg.Matrix.PrivateKey,
).Headered(input.RoomVersion)
ire = &api.InputRoomEvent{
Kind: api.KindNew,
Event: rsEvent,
AuthEventIDs: rsEvent.AuthEventIDs(),
SendAsServer: ourServerName,
TransactionID: nil,
}
}
return ire, nil
} }
func buildInviteStrippedState( func buildInviteStrippedState(
@ -238,7 +172,7 @@ func buildInviteStrippedState(
for _, t := range []string{ for _, t := range []string{
gomatrixserverlib.MRoomName, gomatrixserverlib.MRoomCanonicalAlias, gomatrixserverlib.MRoomName, gomatrixserverlib.MRoomCanonicalAlias,
gomatrixserverlib.MRoomAliases, gomatrixserverlib.MRoomJoinRules, gomatrixserverlib.MRoomAliases, gomatrixserverlib.MRoomJoinRules,
"m.room.avatar", "m.room.avatar", "m.room.encryption",
} { } {
stateWanted = append(stateWanted, gomatrixserverlib.StateKeyTuple{ stateWanted = append(stateWanted, gomatrixserverlib.StateKeyTuple{
EventType: t, EventType: t,

View File

@ -154,17 +154,12 @@ func (h *httpRoomserverInternalAPI) PerformInvite(
ctx context.Context, ctx context.Context,
request *api.PerformInviteRequest, request *api.PerformInviteRequest,
response *api.PerformInviteResponse, response *api.PerformInviteResponse,
) { ) error {
span, ctx := opentracing.StartSpanFromContext(ctx, "PerformInvite") span, ctx := opentracing.StartSpanFromContext(ctx, "PerformInvite")
defer span.Finish() defer span.Finish()
apiURL := h.roomserverURL + RoomserverPerformInvitePath apiURL := h.roomserverURL + RoomserverPerformInvitePath
err := httputil.PostJSON(ctx, span, h.httpClient, apiURL, request, response) return httputil.PostJSON(ctx, span, h.httpClient, apiURL, request, response)
if err != nil {
response.Error = &api.PerformError{
Msg: fmt.Sprintf("failed to communicate with roomserver: %s", err),
}
}
} }
func (h *httpRoomserverInternalAPI) PerformJoin( func (h *httpRoomserverInternalAPI) PerformJoin(

View File

@ -33,7 +33,9 @@ func AddRoutes(r api.RoomserverInternalAPI, internalAPIMux *mux.Router) {
if err := json.NewDecoder(req.Body).Decode(&request); err != nil { if err := json.NewDecoder(req.Body).Decode(&request); err != nil {
return util.MessageResponse(http.StatusBadRequest, err.Error()) return util.MessageResponse(http.StatusBadRequest, err.Error())
} }
r.PerformInvite(req.Context(), &request, &response) if err := r.PerformInvite(req.Context(), &request, &response); err != nil {
return util.ErrorResponse(err)
}
return util.JSONResponse{Code: http.StatusOK, JSON: &response} return util.JSONResponse{Code: http.StatusOK, JSON: &response}
}), }),
) )

View File

@ -418,7 +418,6 @@ Inbound federation rejects attempts to join v2 rooms from servers only supportin
Outbound federation passes make_join failures through to the client Outbound federation passes make_join failures through to the client
Outbound federation correctly handles unsupported room versions Outbound federation correctly handles unsupported room versions
Remote users may not join unfederated rooms Remote users may not join unfederated rooms
Guest users denied access over federation if guest access prohibited
Non-numeric ports in server names are rejected Non-numeric ports in server names are rejected
Invited user can reject invite over federation Invited user can reject invite over federation
Invited user can reject invite over federation for empty room Invited user can reject invite over federation for empty room
@ -454,3 +453,5 @@ Banned servers cannot get missing events
Banned servers cannot get room state ids Banned servers cannot get room state ids
Banned servers cannot backfill Banned servers cannot backfill
Inbound /v1/send_leave rejects leaves from other servers Inbound /v1/send_leave rejects leaves from other servers
Guest users can accept invites to private rooms over federation
AS user (not ghost) can join room without registering