diff --git a/clientapi/jsonerror/jsonerror.go b/clientapi/jsonerror/jsonerror.go index 7f8f264b7..cde3eb825 100644 --- a/clientapi/jsonerror/jsonerror.go +++ b/clientapi/jsonerror/jsonerror.go @@ -169,3 +169,15 @@ func NotTrusted(serverName string) *MatrixError { Err: fmt.Sprintf("Untrusted server '%s'", serverName), } } +// BadAlias is an error which is returned when one or more aliases within a +// m.room.canonical_alias event do not point to the room ID for which the state +// event is to be sent to. +func BadAlias(msg string) *MatrixError { + return &MatrixError{"M_BAD_ALIAS", msg} +} + +// InvalidParam is an error return when an alias from a m.room.canonical_alias +// contains a malformed alias +func InvalidParam(msg string) *MatrixError { + return &MatrixError{"M_INVALID_PARAM", msg} +} diff --git a/clientapi/routing/routing.go b/clientapi/routing/routing.go index d768247a4..ed4cec6f1 100644 --- a/clientapi/routing/routing.go +++ b/clientapi/routing/routing.go @@ -243,7 +243,7 @@ func Setup( if err != nil { return util.ErrorResponse(err) } - return SendEvent(req, device, vars["roomID"], vars["eventType"], nil, nil, cfg, rsAPI, nil) + return SendEvent(req, device, vars["roomID"], vars["eventType"], nil, nil, federation, cfg, rsAPI, nil) }), ).Methods(http.MethodPost, http.MethodOptions) r0mux.Handle("/rooms/{roomID}/send/{eventType}/{txnID}", @@ -254,7 +254,7 @@ func Setup( } txnID := vars["txnID"] return SendEvent(req, device, vars["roomID"], vars["eventType"], &txnID, - nil, cfg, rsAPI, transactionsCache) + nil, federation, cfg, rsAPI, transactionsCache) }), ).Methods(http.MethodPut, http.MethodOptions) r0mux.Handle("/rooms/{roomID}/event/{eventID}", @@ -318,7 +318,7 @@ func Setup( if strings.HasSuffix(eventType, "/") { eventType = eventType[:len(eventType)-1] } - return SendEvent(req, device, vars["roomID"], eventType, nil, &emptyString, cfg, rsAPI, nil) + return SendEvent(req, device, vars["roomID"], eventType, nil, &emptyString, federation, cfg, rsAPI, nil) }), ).Methods(http.MethodPut, http.MethodOptions) @@ -329,7 +329,7 @@ func Setup( return util.ErrorResponse(err) } stateKey := vars["stateKey"] - return SendEvent(req, device, vars["roomID"], vars["eventType"], nil, &stateKey, cfg, rsAPI, nil) + return SendEvent(req, device, vars["roomID"], vars["eventType"], nil, &stateKey, federation, cfg, rsAPI, nil) }), ).Methods(http.MethodPut, http.MethodOptions) diff --git a/clientapi/routing/sendevent.go b/clientapi/routing/sendevent.go index 204d2592a..8e29170c7 100644 --- a/clientapi/routing/sendevent.go +++ b/clientapi/routing/sendevent.go @@ -15,6 +15,7 @@ package routing import ( + "encoding/json" "net/http" "sync" "time" @@ -69,6 +70,7 @@ func SendEvent( req *http.Request, device *userapi.Device, roomID, eventType string, txnID, stateKey *string, + federation *gomatrixserverlib.FederationClient, cfg *config.ClientAPI, rsAPI api.RoomserverInternalAPI, txnCache *transactions.Cache, @@ -97,7 +99,7 @@ func SendEvent( defer mutex.(*sync.Mutex).Unlock() startedGeneratingEvent := time.Now() - e, resErr := generateSendEvent(req, device, roomID, eventType, stateKey, cfg, rsAPI) + e, resErr := generateSendEvent(req, device, roomID, eventType, stateKey, federation, cfg, rsAPI) if resErr != nil { return *resErr } @@ -154,6 +156,7 @@ func generateSendEvent( req *http.Request, device *userapi.Device, roomID, eventType string, stateKey *string, + federation *gomatrixserverlib.FederationClient, cfg *config.ClientAPI, rsAPI api.RoomserverInternalAPI, ) (*gomatrixserverlib.Event, *util.JSONResponse) { @@ -228,5 +231,111 @@ func generateSendEvent( JSON: jsonerror.Forbidden(err.Error()), // TODO: Is this error string comprehensible to the client? } } + + if eventType == gomatrixserverlib.MRoomCanonicalAlias { + var content *eventutil.CanonicalAlias + err = json.Unmarshal(builder.Content, &content) + if err != nil { + return nil, &util.JSONResponse{ + Code: http.StatusBadRequest, + JSON: jsonerror.BadJSON(err.Error()), + } + } + + queryRes := api.GetAliasesForRoomIDResponse { + Aliases: make([]string, 1), + } + + err = rsAPI.GetAliasesForRoomID( + req.Context(), + &api.GetAliasesForRoomIDRequest { + RoomID: roomID, + }, + &queryRes, + ) + if err != nil { + resErr := jsonerror.InternalServerError() + return nil, &resErr + } + + //TODO: maybe do something like synapse where state is retrieved in order to only check new aliases + for _, alias := range content.AltAliases { + _, domain, err := gomatrixserverlib.SplitID('#', alias) + if err != nil { + return nil, &util.JSONResponse{ + Code: http.StatusBadRequest, + JSON: jsonerror.InvalidParam("Room alias must be in the form '#localpart:domain'"), + } + } + found := false + for _, s := range queryRes.Aliases { + if alias == s { + found = true + break + } + } + if !found { + if domain == cfg.Matrix.ServerName { + return nil, &util.JSONResponse{ + Code: http.StatusBadRequest, + JSON: jsonerror.BadAlias("Alt alias not present in the room aliases"), + } + } + fedRes, fedErr := federation.LookupRoomAlias(req.Context(), domain, alias) + if fedErr != nil { + // TODO: Return 502 if the remote server errored. + // TODO: Return 504 if the remote server timed out. + util.GetLogger(req.Context()).WithError(fedErr).Error("federation.LookupRoomAlias failed") + resErr := jsonerror.InternalServerError() + return nil, &resErr + } + if fedRes.RoomID == "" { + return nil, &util.JSONResponse{ + Code: http.StatusBadRequest, + JSON: jsonerror.BadAlias("Alt alias not present in the room aliases"), + } + } + } + } + + if content.Alias != "" { + _, domain, err := gomatrixserverlib.SplitID('#', content.Alias) + if err != nil { + return nil, &util.JSONResponse{ + Code: http.StatusBadRequest, + JSON: jsonerror.InvalidParam("Room alias must be in the form '#localpart:domain'"), + } + } + found := false + for _, s := range queryRes.Aliases { + if content.Alias == s { + found = true + break + } + } + if !found { + if domain == cfg.Matrix.ServerName { + return nil, &util.JSONResponse{ + Code: http.StatusBadRequest, + JSON: jsonerror.BadAlias("Canonical alias not present in the room aliases"), + } + } + fedRes, fedErr := federation.LookupRoomAlias(req.Context(), domain, content.Alias) + if fedErr != nil { + // TODO: Return 502 if the remote server errored. + // TODO: Return 504 if the remote server timed out. + util.GetLogger(req.Context()).WithError(fedErr).Error("federation.LookupRoomAlias failed") + resErr := jsonerror.InternalServerError() + return nil, &resErr + } + if fedRes.RoomID == "" { + return nil, &util.JSONResponse{ + Code: http.StatusBadRequest, + JSON: jsonerror.BadAlias("Canonical alias not present in the room aliases"), + } + } + } + } + } return e.Event, nil } diff --git a/internal/eventutil/eventcontent.go b/internal/eventutil/eventcontent.go index 4ecb5fb56..df17f43ab 100644 --- a/internal/eventutil/eventcontent.go +++ b/internal/eventutil/eventcontent.go @@ -38,7 +38,8 @@ type HistoryVisibilityContent struct { // CanonicalAlias is the event content for https://matrix.org/docs/spec/client_server/r0.6.0#m-room-canonical-alias type CanonicalAlias struct { - Alias string `json:"alias"` + Alias string `json:"alias,omitempty"` + AltAliases []string `json:"alt_aliases,omitempty"` } // InitialPowerLevelsContent returns the initial values for m.room.power_levels on room creation diff --git a/sytest-whitelist b/sytest-whitelist index d90ba4fb9..74cd12b55 100644 --- a/sytest-whitelist +++ b/sytest-whitelist @@ -540,3 +540,5 @@ Key notary server must not overwrite a valid key with a spurious result from the GET /rooms/:room_id/aliases lists aliases Only room members can list aliases of a room Users with sufficient power-level can delete other's aliases +Canonical alias can be set +Canonical alias can include alt_aliases