Add canonical alias support
* Add canonical support
* Add test
* Check that the send event is actually an m.room.canonical_alias
Check that we got an event from the database
* Update to get correct required events
* Add flakey test to blacklist
MODIFY
clientapi/jsonerror/jsonerror.go
clientapi/jsonerror/jsonerror.go
@@ -58,6 +58,11 @@ func BadJSON(msg string) *MatrixError {
return &MatrixError{"M_BAD_JSON", msg}
}
+// BadAlias is an error when the client supplies a bad alias.
+func BadAlias(msg string) *MatrixError {
+ return &MatrixError{"M_BAD_ALIAS", msg}
+}
+
// NotJSON is an error when the client supplies something that is not JSON
// to a JSON endpoint.
func NotJSON(msg string) *MatrixError {

MODIFY
clientapi/routing/sendevent.go
clientapi/routing/sendevent.go
@@ -16,6 +16,8 @@ package routing
import (
"context"
+ "encoding/json"
+ "fmt"
"net/http"
"sync"
"time"
@@ -120,6 +122,40 @@ func SendEvent(
}
timeToGenerateEvent := time.Since(startedGeneratingEvent)
+ // validate that the aliases exists
+ if eventType == gomatrixserverlib.MRoomCanonicalAlias && stateKey != nil && *stateKey == "" {
+ aliasReq := api.AliasEvent{}
+ if err = json.Unmarshal(e.Content(), &aliasReq); err != nil {
+ return util.ErrorResponse(fmt.Errorf("unable to parse alias event: %w", err))
+ }
+ if !aliasReq.Valid() {
+ return util.JSONResponse{
+ Code: http.StatusBadRequest,
+ JSON: jsonerror.InvalidParam("Request contains invalid aliases."),
+ }
+ }
+ aliasRes := &api.GetAliasesForRoomIDResponse{}
+ if err = rsAPI.GetAliasesForRoomID(req.Context(), &api.GetAliasesForRoomIDRequest{RoomID: roomID}, aliasRes); err != nil {
+ return jsonerror.InternalServerError()
+ }
+ var found int
+ requestAliases := append(aliasReq.AltAliases, aliasReq.Alias)
+ for _, alias := range aliasRes.Aliases {
+ for _, altAlias := range requestAliases {
+ if altAlias == alias {
+ found++
+ }
+ }
+ }
+ // check that we found at least the same amount of existing aliases as are in the request
+ if aliasReq.Alias != "" && found < len(requestAliases) {
+ return util.JSONResponse{
+ Code: http.StatusBadRequest,
+ JSON: jsonerror.BadAlias("No matching alias found."),
+ }
+ }
+ }
+
var txnAndSessionID *api.TransactionID
if txnID != nil {
txnAndSessionID = &api.TransactionID{

MODIFY
roomserver/api/alias.go
roomserver/api/alias.go
package api
+import "regexp"
+
// SetRoomAliasRequest is a request to SetRoomAlias
type SetRoomAliasRequest struct {
// ID of the user setting the alias
@@ -84,3 +86,20 @@ type RemoveRoomAliasResponse struct {
// Did we remove it?
Removed bool `json:"removed"`
}
+
+type AliasEvent struct {
+ Alias string `json:"alias"`
+ AltAliases []string `json:"alt_aliases"`
+}
+
+var validateAliasRegex = regexp.MustCompile("^#.*:.+$")
+
+func (a AliasEvent) Valid() bool {
+ for _, alias := range a.AltAliases {
+ if !validateAliasRegex.MatchString(alias) {
+ return false
+ }
+ }
+ return a.Alias == "" || validateAliasRegex.MatchString(a.Alias)
+}
+

ADD
roomserver/api/alias_test.go
+package api
+
+import "testing"
+
+func TestAliasEvent_Valid(t *testing.T) {
+ type fields struct {
+ Alias string
+ AltAliases []string
+ }
+ tests := []struct {
+ name string
+ fields fields
+ want bool
+ }{
+ {
+ name: "empty alias",
+ fields: fields{
+ Alias: "",
+ },
+ want: true,
+ },
+ {
+ name: "empty alias, invalid alt aliases",
+ fields: fields{
+ Alias: "",
+ AltAliases: []string{ "%not:valid.local"},
+ },
+ },
+ {
+ name: "valid alias, invalid alt aliases",
+ fields: fields{
+ Alias: "#valid:test.local",
+ AltAliases: []string{ "%not:valid.local"},
+ },
+ },
+ {
+ name: "empty alias, invalid alt aliases",
+ fields: fields{
+ Alias: "",
+ AltAliases: []string{ "%not:valid.local"},
+ },
+ },
+ {
+ name: "invalid alias",
+ fields: fields{
+ Alias: "%not:valid.local",
+ AltAliases: []string{ },
+ },
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ a := AliasEvent{
+ Alias: tt.fields.Alias,
+ AltAliases: tt.fields.AltAliases,
+ }
+ if got := a.Valid(); got != tt.want {
+ t.Errorf("Valid() = %v, want %v", got, tt.want)
+ }
+ })
+ }
+}

MODIFY
roomserver/internal/alias.go
roomserver/internal/alias.go
@@ -16,12 +16,18 @@ package internal
import (
"context"
+ "database/sql"
+ "errors"
"fmt"
+ "time"
+ asAPI "github.com/matrix-org/dendrite/appservice/api"
+ "github.com/matrix-org/dendrite/internal/eventutil"
"github.com/matrix-org/dendrite/roomserver/api"
+ "github.com/matrix-org/dendrite/roomserver/internal/helpers"
"github.com/matrix-org/gomatrixserverlib"
-
- asAPI "github.com/matrix-org/dendrite/appservice/api"
+ "github.com/tidwall/gjson"
+ "github.com/tidwall/sjson"
)
// RoomserverInternalAPIDatabase has the storage APIs needed to implement the alias API.
@@ -183,6 +189,57 @@ func (r *RoomserverInternalAPI) RemoveRoomAlias(
}
}
+ ev, err := r.DB.GetStateEvent(ctx, roomID, gomatrixserverlib.MRoomCanonicalAlias, "")
+ if err != nil && err != sql.ErrNoRows {
+ return err
+ } else if ev != nil {
+ stateAlias := gjson.GetBytes(ev.Content(), "alias").Str
+ // the alias to remove is currently set as the canonical alias, remove it
+ if stateAlias == request.Alias {
+ res, err := sjson.DeleteBytes(ev.Content(), "alias")
+ if err != nil {
+ return err
+ }
+
+ sender := request.UserID
+ if request.UserID != ev.Sender() {
+ sender = ev.Sender()
+ }
+
+ builder := &gomatrixserverlib.EventBuilder{
+ Sender: sender,
+ RoomID: ev.RoomID(),
+ Type: ev.Type(),
+ StateKey: ev.StateKey(),
+ Content: res,
+ }
+
+ eventsNeeded, err := gomatrixserverlib.StateNeededForEventBuilder(builder)
+ if err != nil {
+ return fmt.Errorf("gomatrixserverlib.StateNeededForEventBuilder: %w", err)
+ }
+ if len(eventsNeeded.Tuples()) == 0 {
+ return errors.New("expecting state tuples for event builder, got none")
+ }
+
+ stateRes := &api.QueryLatestEventsAndStateResponse{}
+ if err := helpers.QueryLatestEventsAndState(ctx, r.DB, &api.QueryLatestEventsAndStateRequest{RoomID: roomID, StateToFetch: eventsNeeded.Tuples()}, stateRes); err != nil {
+ return err
+ }
+
+ newEvent, err := eventutil.BuildEvent(ctx, builder, r.Cfg.Matrix, time.Now(), &eventsNeeded, stateRes)
+ if err != nil {
+ return err
+ }
+
+ err = api.SendEvents(ctx, r.RSAPI, api.KindNew, []*gomatrixserverlib.HeaderedEvent{newEvent}, r.ServerName, r.ServerName, nil, false)
+ if err != nil {
+ return err
+ }
+
+ }
+ }
+
// Remove the alias from the database
if err := r.DB.RemoveRoomAlias(ctx, request.Alias); err != nil {
return err

MODIFY
sytest-blacklist
sytest-blacklist
@@ -31,6 +31,7 @@ Remove group role
# Flakey
AS-ghosted users can use rooms themselves
+/context/ with lazy_load_members filter works
# Flakey, need additional investigation
Messages that notify from another user increment notification_count

MODIFY
sytest-whitelist
sytest-whitelist
@@ -648,7 +648,7 @@ Device list doesn't change if remote server is down
/context/ on joined room works
/context/ on non world readable room does not work
/context/ returns correct number of events
-/context/ with lazy_load_members filter works
+
GET /rooms/:room_id/messages lazy loads members correctly
Can query remote device keys using POST after notification
Device deletion propagates over federation
@@ -659,4 +659,8 @@ registration accepts non-ascii passwords
registration with inhibit_login inhibits login
The operation must be consistent through an interactive authentication session
Multiple calls to /sync should not cause 500 errors
-/context/ with lazy_load_members filter works
+
+Canonical alias can be set
+Canonical alias can include alt_aliases
+Can delete canonical alias
+Multiple calls to /sync should not cause 500 errors

Add canonical alias support (#2236)

* Add canonical support

* Add test

* Check that the send event is actually an m.room.canonical_alias
Check that we got an event from the database

* Update to get correct required events

* Add flakey test to blacklist