diff --git a/federationapi/routing/leave.go b/federationapi/routing/leave.go index 314b4c729..71b7b1817 100644 --- a/federationapi/routing/leave.go +++ b/federationapi/routing/leave.go @@ -76,6 +76,23 @@ func MakeLeave( 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 stateEvents := make([]*gomatrixserverlib.Event, len(queryRes.StateEvents)) for i := range queryRes.StateEvents { @@ -99,6 +116,7 @@ func MakeLeave( } // SendLeave implements the /send_leave API +// nolint:gocyclo func SendLeave( httpReq *http.Request, request *gomatrixserverlib.FederationRequest, @@ -149,6 +167,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. redacted := event.Redact() verifyRequests := []gomatrixserverlib.VerifyJSONRequest{{ @@ -174,7 +234,8 @@ func SendLeave( if err != nil { util.GetLogger(httpReq.Context()).WithError(err).Error("event.Membership failed") return jsonerror.InternalServerError() - } else if mem != gomatrixserverlib.Leave { + } + if mem != gomatrixserverlib.Leave { return util.JSONResponse{ Code: http.StatusBadRequest, JSON: jsonerror.BadJSON("The membership in the event content must be set to leave"),