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
This commit is contained in:
parent
86d4eef9f1
commit
9fbaa1194b
|
@ -58,6 +58,11 @@ func BadJSON(msg string) *MatrixError {
|
||||||
return &MatrixError{"M_BAD_JSON", msg}
|
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
|
// NotJSON is an error when the client supplies something that is not JSON
|
||||||
// to a JSON endpoint.
|
// to a JSON endpoint.
|
||||||
func NotJSON(msg string) *MatrixError {
|
func NotJSON(msg string) *MatrixError {
|
||||||
|
|
|
@ -16,6 +16,8 @@ package routing
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
@ -120,6 +122,40 @@ func SendEvent(
|
||||||
}
|
}
|
||||||
timeToGenerateEvent := time.Since(startedGeneratingEvent)
|
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
|
var txnAndSessionID *api.TransactionID
|
||||||
if txnID != nil {
|
if txnID != nil {
|
||||||
txnAndSessionID = &api.TransactionID{
|
txnAndSessionID = &api.TransactionID{
|
||||||
|
|
|
@ -14,6 +14,8 @@
|
||||||
|
|
||||||
package api
|
package api
|
||||||
|
|
||||||
|
import "regexp"
|
||||||
|
|
||||||
// SetRoomAliasRequest is a request to SetRoomAlias
|
// SetRoomAliasRequest is a request to SetRoomAlias
|
||||||
type SetRoomAliasRequest struct {
|
type SetRoomAliasRequest struct {
|
||||||
// ID of the user setting the alias
|
// ID of the user setting the alias
|
||||||
|
@ -84,3 +86,20 @@ type RemoveRoomAliasResponse struct {
|
||||||
// Did we remove it?
|
// Did we remove it?
|
||||||
Removed bool `json:"removed"`
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
62
roomserver/api/alias_test.go
Normal file
62
roomserver/api/alias_test.go
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -16,12 +16,18 @@ package internal
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"database/sql"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"time"
|
||||||
"github.com/matrix-org/dendrite/roomserver/api"
|
|
||||||
"github.com/matrix-org/gomatrixserverlib"
|
|
||||||
|
|
||||||
asAPI "github.com/matrix-org/dendrite/appservice/api"
|
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"
|
||||||
|
"github.com/tidwall/gjson"
|
||||||
|
"github.com/tidwall/sjson"
|
||||||
)
|
)
|
||||||
|
|
||||||
// RoomserverInternalAPIDatabase has the storage APIs needed to implement the alias API.
|
// 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
|
// Remove the alias from the database
|
||||||
if err := r.DB.RemoveRoomAlias(ctx, request.Alias); err != nil {
|
if err := r.DB.RemoveRoomAlias(ctx, request.Alias); err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
|
@ -31,8 +31,9 @@ Remove group role
|
||||||
|
|
||||||
# Flakey
|
# Flakey
|
||||||
AS-ghosted users can use rooms themselves
|
AS-ghosted users can use rooms themselves
|
||||||
|
/context/ with lazy_load_members filter works
|
||||||
|
|
||||||
# Flakey, need additional investigation
|
# Flakey, need additional investigation
|
||||||
Messages that notify from another user increment notification_count
|
Messages that notify from another user increment notification_count
|
||||||
Messages that highlight from another user increment unread highlight count
|
Messages that highlight from another user increment unread highlight count
|
||||||
Notifications can be viewed with GET /notifications
|
Notifications can be viewed with GET /notifications
|
||||||
|
|
|
@ -648,7 +648,7 @@ Device list doesn't change if remote server is down
|
||||||
/context/ on joined room works
|
/context/ on joined room works
|
||||||
/context/ on non world readable room does not work
|
/context/ on non world readable room does not work
|
||||||
/context/ returns correct number of events
|
/context/ returns correct number of events
|
||||||
/context/ with lazy_load_members filter works
|
|
||||||
GET /rooms/:room_id/messages lazy loads members correctly
|
GET /rooms/:room_id/messages lazy loads members correctly
|
||||||
Can query remote device keys using POST after notification
|
Can query remote device keys using POST after notification
|
||||||
Device deletion propagates over federation
|
Device deletion propagates over federation
|
||||||
|
@ -659,4 +659,8 @@ registration accepts non-ascii passwords
|
||||||
registration with inhibit_login inhibits login
|
registration with inhibit_login inhibits login
|
||||||
The operation must be consistent through an interactive authentication session
|
The operation must be consistent through an interactive authentication session
|
||||||
Multiple calls to /sync should not cause 500 errors
|
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
|
||||||
|
|
Loading…
Reference in a new issue