// Copyright 2017 Vector Creations Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package routing import ( "context" "encoding/json" "fmt" "net/http" "github.com/getsentry/sentry-go" "github.com/matrix-org/dendrite/roomserver/api" "github.com/matrix-org/dendrite/roomserver/types" "github.com/matrix-org/dendrite/setup/config" "github.com/matrix-org/gomatrixserverlib" "github.com/matrix-org/gomatrixserverlib/fclient" "github.com/matrix-org/gomatrixserverlib/spec" "github.com/matrix-org/util" ) // InviteV2 implements /_matrix/federation/v2/invite/{roomID}/{eventID} func InviteV2( httpReq *http.Request, request *fclient.FederationRequest, roomID string, eventID string, cfg *config.FederationAPI, rsAPI api.FederationRoomserverAPI, keys gomatrixserverlib.JSONVerifier, ) util.JSONResponse { inviteReq := fclient.InviteV2Request{} err := json.Unmarshal(request.Content(), &inviteReq) switch e := err.(type) { case gomatrixserverlib.UnsupportedRoomVersionError: return util.JSONResponse{ Code: http.StatusBadRequest, JSON: spec.UnsupportedRoomVersion( fmt.Sprintf("Room version %q is not supported by this server.", e.Version), ), } case gomatrixserverlib.BadJSONError: return util.JSONResponse{ Code: http.StatusBadRequest, JSON: spec.BadJSON(err.Error()), } case nil: return processInvite( httpReq.Context(), true, inviteReq.Event(), inviteReq.RoomVersion(), inviteReq.InviteRoomState(), roomID, eventID, cfg, rsAPI, keys, ) default: return util.JSONResponse{ Code: http.StatusBadRequest, JSON: spec.NotJSON("The request body could not be decoded into an invite request. " + err.Error()), } } } // InviteV1 implements /_matrix/federation/v1/invite/{roomID}/{eventID} func InviteV1( httpReq *http.Request, request *fclient.FederationRequest, roomID string, eventID string, cfg *config.FederationAPI, rsAPI api.FederationRoomserverAPI, keys gomatrixserverlib.JSONVerifier, ) util.JSONResponse { roomVer := gomatrixserverlib.RoomVersionV1 body := request.Content() // roomVer is hardcoded to v1 so we know we won't panic on Must event, err := gomatrixserverlib.MustGetRoomVersion(roomVer).NewEventFromTrustedJSON(body, false) switch err.(type) { case gomatrixserverlib.BadJSONError: return util.JSONResponse{ Code: http.StatusBadRequest, JSON: spec.BadJSON(err.Error()), } case nil: default: return util.JSONResponse{ Code: http.StatusBadRequest, JSON: spec.NotJSON("The request body could not be decoded into an invite v1 request. " + err.Error()), } } var strippedState []fclient.InviteV2StrippedState if err := json.Unmarshal(event.Unsigned(), &strippedState); err != nil { // just warn, they may not have added any. util.GetLogger(httpReq.Context()).Warnf("failed to extract stripped state from invite event") } return processInvite( httpReq.Context(), false, event, roomVer, strippedState, roomID, eventID, cfg, rsAPI, keys, ) } func processInvite( ctx context.Context, isInviteV2 bool, event gomatrixserverlib.PDU, roomVer gomatrixserverlib.RoomVersion, strippedState []fclient.InviteV2StrippedState, roomID string, eventID string, cfg *config.FederationAPI, rsAPI api.FederationRoomserverAPI, keys gomatrixserverlib.JSONVerifier, ) util.JSONResponse { // Check that we can accept invites for this room version. verImpl, err := gomatrixserverlib.GetRoomVersion(roomVer) if err != nil { return util.JSONResponse{ Code: http.StatusBadRequest, JSON: spec.UnsupportedRoomVersion( fmt.Sprintf("Room version %q is not supported by this server.", roomVer), ), } } // Check that the room ID is correct. if event.RoomID() != roomID { return util.JSONResponse{ Code: http.StatusBadRequest, JSON: spec.BadJSON("The room ID in the request path must match the room ID in the invite event JSON"), } } // Check that the event ID is correct. if event.EventID() != eventID { return util.JSONResponse{ Code: http.StatusBadRequest, JSON: spec.BadJSON("The event ID in the request path must match the event ID in the invite event JSON"), } } if event.StateKey() == nil { return util.JSONResponse{ Code: http.StatusBadRequest, JSON: spec.BadJSON("The invite event has no state key"), } } _, domain, err := cfg.Matrix.SplitLocalID('@', *event.StateKey()) if err != nil { return util.JSONResponse{ Code: http.StatusBadRequest, JSON: spec.InvalidParam(fmt.Sprintf("The user ID is invalid or domain %q does not belong to this server", domain)), } } // Check that the event is signed by the server sending the request. redacted, err := verImpl.RedactEventJSON(event.JSON()) if err != nil { return util.JSONResponse{ Code: http.StatusBadRequest, JSON: spec.BadJSON("The event JSON could not be redacted"), } } _, serverName, err := gomatrixserverlib.SplitID('@', event.Sender()) if err != nil { return util.JSONResponse{ Code: http.StatusBadRequest, JSON: spec.BadJSON("The event JSON contains an invalid sender"), } } verifyRequests := []gomatrixserverlib.VerifyJSONRequest{{ ServerName: serverName, Message: redacted, AtTS: event.OriginServerTS(), StrictValidityChecking: true, }} verifyResults, err := keys.VerifyJSONs(ctx, verifyRequests) if err != nil { util.GetLogger(ctx).WithError(err).Error("keys.VerifyJSONs failed") return util.JSONResponse{ Code: http.StatusInternalServerError, JSON: spec.InternalServerError{}, } } if verifyResults[0].Error != nil { return util.JSONResponse{ Code: http.StatusForbidden, JSON: spec.Forbidden("The invite must be signed by the server it originated on"), } } // Sign the event so that other servers will know that we have received the invite. signedEvent := event.Sign( string(domain), cfg.Matrix.KeyID, cfg.Matrix.PrivateKey, ) // Add the invite event to the roomserver. inviteEvent := &types.HeaderedEvent{PDU: signedEvent} request := &api.PerformInviteRequest{ Event: inviteEvent, InviteRoomState: strippedState, RoomVersion: inviteEvent.Version(), SendAsServer: string(api.DoNotSendToOtherServers), TransactionID: nil, } if err = rsAPI.PerformInvite(ctx, request); err != nil { util.GetLogger(ctx).WithError(err).Error("PerformInvite failed") return util.JSONResponse{ Code: http.StatusInternalServerError, JSON: spec.InternalServerError{}, } } switch e := err.(type) { case api.ErrInvalidID: return util.JSONResponse{ Code: http.StatusBadRequest, JSON: spec.Unknown(e.Error()), } case api.ErrNotAllowed: return util.JSONResponse{ Code: http.StatusForbidden, JSON: spec.Forbidden(e.Error()), } case nil: default: util.GetLogger(ctx).WithError(err).Error("PerformInvite failed") sentry.CaptureException(err) return util.JSONResponse{ Code: http.StatusInternalServerError, JSON: spec.InternalServerError{}, } } // Return the signed event to the originating server, it should then tell // the other servers in the room that we have been invited. if isInviteV2 { return util.JSONResponse{ Code: http.StatusOK, JSON: fclient.RespInviteV2{Event: signedEvent.JSON()}, } } else { return util.JSONResponse{ Code: http.StatusOK, JSON: fclient.RespInvite{Event: signedEvent.JSON()}, } } }