aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--clientapi/routing/sendevent.go25
-rw-r--r--clientapi/routing/sendevent_test.go275
-rw-r--r--clientapi/routing/state.go31
-rw-r--r--clientapi/routing/state_test.go253
-rw-r--r--go.mod4
-rw-r--r--go.sum8
-rw-r--r--roomserver/internal/input/input_events.go7
-rw-r--r--syncapi/internal/history_visibility.go28
-rw-r--r--syncapi/internal/history_visibility_test.go214
-rw-r--r--syncapi/internal/keychange_test.go26
-rw-r--r--syncapi/synctypes/clientevent.go30
11 files changed, 865 insertions, 36 deletions
diff --git a/clientapi/routing/sendevent.go b/clientapi/routing/sendevent.go
index a167a5a7..f81e9c1e 100644
--- a/clientapi/routing/sendevent.go
+++ b/clientapi/routing/sendevent.go
@@ -29,6 +29,7 @@ import (
"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/dendrite/syncapi/synctypes"
userapi "github.com/matrix-org/dendrite/userapi/api"
"github.com/matrix-org/gomatrixserverlib"
"github.com/matrix-org/gomatrixserverlib/spec"
@@ -92,6 +93,30 @@ func SendEvent(
}
}
+ // Translate user ID state keys to room keys in pseudo ID rooms
+ if roomVersion == gomatrixserverlib.RoomVersionPseudoIDs && stateKey != nil {
+ parsedRoomID, innerErr := spec.NewRoomID(roomID)
+ if innerErr != nil {
+ return util.JSONResponse{
+ Code: http.StatusBadRequest,
+ JSON: spec.InvalidParam("invalid room ID"),
+ }
+ }
+
+ newStateKey, innerErr := synctypes.FromClientStateKey(*parsedRoomID, *stateKey, func(roomID spec.RoomID, userID spec.UserID) (*spec.SenderID, error) {
+ return rsAPI.QuerySenderIDForUser(req.Context(), roomID, userID)
+ })
+ if innerErr != nil {
+ // TODO: work out better logic for failure cases (e.g. sender ID not found)
+ util.GetLogger(req.Context()).WithError(innerErr).Error("synctypes.FromClientStateKey failed")
+ return util.JSONResponse{
+ Code: http.StatusInternalServerError,
+ JSON: spec.Unknown("internal server error"),
+ }
+ }
+ stateKey = newStateKey
+ }
+
// create a mutex for the specific user in the specific room
// this avoids a situation where events that are received in quick succession are sent to the roomserver in a jumbled order
userID := device.UserID
diff --git a/clientapi/routing/sendevent_test.go b/clientapi/routing/sendevent_test.go
new file mode 100644
index 00000000..9cdd7535
--- /dev/null
+++ b/clientapi/routing/sendevent_test.go
@@ -0,0 +1,275 @@
+package routing
+
+import (
+ "context"
+ "crypto/ed25519"
+ "fmt"
+ "io"
+ "net/http"
+ "strings"
+ "testing"
+
+ rsapi "github.com/matrix-org/dendrite/roomserver/api"
+ "github.com/matrix-org/dendrite/roomserver/types"
+ "github.com/matrix-org/dendrite/setup/config"
+ uapi "github.com/matrix-org/dendrite/userapi/api"
+ "github.com/matrix-org/gomatrixserverlib"
+ "github.com/matrix-org/gomatrixserverlib/fclient"
+ "github.com/matrix-org/gomatrixserverlib/spec"
+ "gotest.tools/v3/assert"
+)
+
+// Mock roomserver API for testing
+//
+// Currently pretty specialised for the pseudo ID test, so will need
+// editing if future (other) sendevent tests are using this.
+type sendEventTestRoomserverAPI struct {
+ rsapi.ClientRoomserverAPI
+ t *testing.T
+ roomIDStr string
+ roomVersion gomatrixserverlib.RoomVersion
+ roomState []*types.HeaderedEvent
+
+ // userID -> room key
+ senderMapping map[string]ed25519.PrivateKey
+
+ savedInputRoomEvents []rsapi.InputRoomEvent
+}
+
+func (s *sendEventTestRoomserverAPI) QueryRoomVersionForRoom(ctx context.Context, roomID string) (gomatrixserverlib.RoomVersion, error) {
+ if roomID == s.roomIDStr {
+ return s.roomVersion, nil
+ } else {
+ s.t.Logf("room version queried for %s", roomID)
+ return "", fmt.Errorf("unknown room")
+ }
+}
+
+func (s *sendEventTestRoomserverAPI) QueryCurrentState(ctx context.Context, req *rsapi.QueryCurrentStateRequest, res *rsapi.QueryCurrentStateResponse) error {
+ res.StateEvents = map[gomatrixserverlib.StateKeyTuple]*types.HeaderedEvent{}
+ for _, stateKeyTuple := range req.StateTuples {
+ for _, stateEv := range s.roomState {
+ if stateEv.Type() == stateKeyTuple.EventType && stateEv.StateKey() != nil && *stateEv.StateKey() == stateKeyTuple.StateKey {
+ res.StateEvents[stateKeyTuple] = stateEv
+ }
+ }
+ }
+ return nil
+}
+
+func (s *sendEventTestRoomserverAPI) QueryLatestEventsAndState(ctx context.Context, req *rsapi.QueryLatestEventsAndStateRequest, res *rsapi.QueryLatestEventsAndStateResponse) error {
+ if req.RoomID == s.roomIDStr {
+ res.RoomExists = true
+ res.RoomVersion = s.roomVersion
+
+ res.StateEvents = make([]*types.HeaderedEvent, len(s.roomState))
+ copy(res.StateEvents, s.roomState)
+
+ res.LatestEvents = []string{}
+ res.Depth = 1
+ return nil
+ } else {
+ s.t.Logf("room event/state queried for %s", req.RoomID)
+ return fmt.Errorf("unknown room")
+ }
+
+}
+
+func (s *sendEventTestRoomserverAPI) QuerySenderIDForUser(
+ ctx context.Context,
+ roomID spec.RoomID,
+ userID spec.UserID,
+) (*spec.SenderID, error) {
+ if roomID.String() == s.roomIDStr {
+ if s.roomVersion == gomatrixserverlib.RoomVersionPseudoIDs {
+ roomKey, ok := s.senderMapping[userID.String()]
+ if ok {
+ sender := spec.SenderIDFromPseudoIDKey(roomKey)
+ return &sender, nil
+ } else {
+ return nil, nil
+ }
+ } else {
+ senderID := spec.SenderIDFromUserID(userID)
+ return &senderID, nil
+ }
+ }
+
+ return nil, fmt.Errorf("room not found")
+}
+
+func (s *sendEventTestRoomserverAPI) QueryUserIDForSender(
+ ctx context.Context,
+ roomID spec.RoomID,
+ senderID spec.SenderID,
+) (*spec.UserID, error) {
+ if roomID.String() == s.roomIDStr {
+ if s.roomVersion == gomatrixserverlib.RoomVersionPseudoIDs {
+ for uID, roomKey := range s.senderMapping {
+ if string(spec.SenderIDFromPseudoIDKey(roomKey)) == string(senderID) {
+ parsedUserID, err := spec.NewUserID(uID, true)
+ if err != nil {
+ s.t.Fatalf("Mock QueryUserIDForSender failed: %s", err)
+ }
+ return parsedUserID, nil
+ }
+ }
+ } else {
+ userID := senderID.ToUserID()
+ if userID == nil {
+ return nil, fmt.Errorf("bad sender ID")
+ }
+ return userID, nil
+ }
+ }
+
+ return nil, fmt.Errorf("room not found")
+}
+
+func (s *sendEventTestRoomserverAPI) SigningIdentityFor(ctx context.Context, roomID spec.RoomID, sender spec.UserID) (fclient.SigningIdentity, error) {
+ if s.roomIDStr == roomID.String() {
+ if s.roomVersion == gomatrixserverlib.RoomVersionPseudoIDs {
+ roomKey, ok := s.senderMapping[sender.String()]
+ if !ok {
+ s.t.Logf("SigningIdentityFor used with unknown user ID: %v", sender.String())
+ return fclient.SigningIdentity{}, fmt.Errorf("could not get signing identity for %v", sender.String())
+ }
+ return fclient.SigningIdentity{PrivateKey: roomKey}, nil
+ } else {
+ return fclient.SigningIdentity{PrivateKey: ed25519.NewKeyFromSeed(make([]byte, 32))}, nil
+ }
+ }
+
+ return fclient.SigningIdentity{}, fmt.Errorf("room not found")
+}
+
+func (s *sendEventTestRoomserverAPI) InputRoomEvents(ctx context.Context, req *rsapi.InputRoomEventsRequest, res *rsapi.InputRoomEventsResponse) {
+ s.savedInputRoomEvents = req.InputRoomEvents
+}
+
+// Test that user ID state keys are translated correctly
+func Test_SendEvent_PseudoIDStateKeys(t *testing.T) {
+ nonpseudoIDRoomVersion := gomatrixserverlib.RoomVersionV10
+ pseudoIDRoomVersion := gomatrixserverlib.RoomVersionPseudoIDs
+
+ senderKeySeed := make([]byte, 32)
+ senderUserID := "@testuser:domain"
+ senderPrivKey := ed25519.NewKeyFromSeed(senderKeySeed)
+ senderPseudoID := string(spec.SenderIDFromPseudoIDKey(senderPrivKey))
+
+ eventType := "com.example.test"
+ roomIDStr := "!id:domain"
+
+ device := &uapi.Device{
+ UserID: senderUserID,
+ }
+
+ t.Run("user ID state key are not translated to room key in non-pseudo ID room", func(t *testing.T) {
+ eventsJSON := []string{
+ fmt.Sprintf(`{"type":"m.room.create","state_key":"","room_id":"%v","sender":"%v","content":{"creator":"%v","room_version":"%v"}}`, roomIDStr, senderUserID, senderUserID, nonpseudoIDRoomVersion),
+ fmt.Sprintf(`{"type":"m.room.member","state_key":"%v","room_id":"%v","sender":"%v","content":{"membership":"join"}}`, senderUserID, roomIDStr, senderUserID),
+ }
+
+ roomState, err := createEvents(eventsJSON, nonpseudoIDRoomVersion)
+ if err != nil {
+ t.Fatalf("failed to prepare state events: %s", err.Error())
+ }
+
+ rsAPI := &sendEventTestRoomserverAPI{
+ t: t,
+ roomIDStr: roomIDStr,
+ roomVersion: nonpseudoIDRoomVersion,
+ roomState: roomState,
+ }
+
+ req, err := http.NewRequest("POST", "https://domain", io.NopCloser(strings.NewReader("{}")))
+ if err != nil {
+ t.Fatalf("failed to make new request: %s", err.Error())
+ }
+
+ cfg := &config.ClientAPI{}
+
+ resp := SendEvent(req, device, roomIDStr, eventType, nil, &senderUserID, cfg, rsAPI, nil)
+
+ if resp.Code != http.StatusOK {
+ t.Fatalf("non-200 HTTP code returned: %v\nfull response: %v", resp.Code, resp)
+ }
+
+ assert.Equal(t, len(rsAPI.savedInputRoomEvents), 1)
+
+ ev := rsAPI.savedInputRoomEvents[0]
+ stateKey := ev.Event.StateKey()
+ if stateKey == nil {
+ t.Fatalf("submitted InputRoomEvent has nil state key, when it should be %v", senderUserID)
+ }
+ if *stateKey != senderUserID {
+ t.Fatalf("expected submitted InputRoomEvent to have user ID state key\nfound: %v\nexpected: %v", *stateKey, senderUserID)
+ }
+ })
+
+ t.Run("user ID state key are translated to room key in pseudo ID room", func(t *testing.T) {
+ eventsJSON := []string{
+ fmt.Sprintf(`{"type":"m.room.create","state_key":"","room_id":"%v","sender":"%v","content":{"creator":"%v","room_version":"%v"}}`, roomIDStr, senderPseudoID, senderPseudoID, pseudoIDRoomVersion),
+ fmt.Sprintf(`{"type":"m.room.member","state_key":"%v","room_id":"%v","sender":"%v","content":{"membership":"join"}}`, senderPseudoID, roomIDStr, senderPseudoID),
+ }
+
+ roomState, err := createEvents(eventsJSON, pseudoIDRoomVersion)
+ if err != nil {
+ t.Fatalf("failed to prepare state events: %s", err.Error())
+ }
+
+ rsAPI := &sendEventTestRoomserverAPI{
+ t: t,
+ roomIDStr: roomIDStr,
+ roomVersion: pseudoIDRoomVersion,
+ senderMapping: map[string]ed25519.PrivateKey{
+ senderUserID: senderPrivKey,
+ },
+ roomState: roomState,
+ }
+
+ req, err := http.NewRequest("POST", "https://domain", io.NopCloser(strings.NewReader("{}")))
+ if err != nil {
+ t.Fatalf("failed to make new request: %s", err.Error())
+ }
+
+ cfg := &config.ClientAPI{}
+
+ resp := SendEvent(req, device, roomIDStr, eventType, nil, &senderUserID, cfg, rsAPI, nil)
+
+ if resp.Code != http.StatusOK {
+ t.Fatalf("non-200 HTTP code returned: %v\nfull response: %v", resp.Code, resp)
+ }
+
+ assert.Equal(t, len(rsAPI.savedInputRoomEvents), 1)
+
+ ev := rsAPI.savedInputRoomEvents[0]
+ stateKey := ev.Event.StateKey()
+ if stateKey == nil {
+ t.Fatalf("submitted InputRoomEvent has nil state key, when it should be %v", senderPseudoID)
+ }
+ if *stateKey != senderPseudoID {
+ t.Fatalf("expected submitted InputRoomEvent to have pseudo ID state key\nfound: %v\nexpected: %v", *stateKey, senderPseudoID)
+ }
+ })
+}
+
+func createEvents(eventsJSON []string, roomVer gomatrixserverlib.RoomVersion) ([]*types.HeaderedEvent, error) {
+ events := make([]*types.HeaderedEvent, len(eventsJSON))
+
+ roomVerImpl, err := gomatrixserverlib.GetRoomVersion(roomVer)
+ if err != nil {
+ return nil, fmt.Errorf("no roomver impl: %s", err.Error())
+ }
+
+ for i, eventJSON := range eventsJSON {
+ pdu, evErr := roomVerImpl.NewEventFromTrustedJSON([]byte(eventJSON), false)
+ if evErr != nil {
+ return nil, fmt.Errorf("failed to make event: %s", err.Error())
+ }
+ ev := types.HeaderedEvent{PDU: pdu}
+ events[i] = &ev
+ }
+
+ return events, nil
+}
diff --git a/clientapi/routing/state.go b/clientapi/routing/state.go
index f53cb301..7648dc47 100644
--- a/clientapi/routing/state.go
+++ b/clientapi/routing/state.go
@@ -217,6 +217,37 @@ func OnIncomingStateTypeRequest(
var worldReadable bool
var wantLatestState bool
+ roomVer, err := rsAPI.QueryRoomVersionForRoom(ctx, roomID)
+ if err != nil {
+ return util.JSONResponse{
+ Code: http.StatusForbidden,
+ JSON: spec.Forbidden(fmt.Sprintf("Unknown room %q or user %q has never joined this room", roomID, device.UserID)),
+ }
+ }
+
+ // Translate user ID state keys to room keys in pseudo ID rooms
+ if roomVer == gomatrixserverlib.RoomVersionPseudoIDs {
+ parsedRoomID, err := spec.NewRoomID(roomID)
+ if err != nil {
+ return util.JSONResponse{
+ Code: http.StatusNotFound,
+ JSON: spec.InvalidParam("invalid room ID"),
+ }
+ }
+ newStateKey, err := synctypes.FromClientStateKey(*parsedRoomID, stateKey, func(roomID spec.RoomID, userID spec.UserID) (*spec.SenderID, error) {
+ return rsAPI.QuerySenderIDForUser(ctx, roomID, userID)
+ })
+ if err != nil {
+ // TODO: work out better logic for failure cases (e.g. sender ID not found)
+ util.GetLogger(ctx).WithError(err).Error("synctypes.FromClientStateKey failed")
+ return util.JSONResponse{
+ Code: http.StatusInternalServerError,
+ JSON: spec.Unknown("internal server error"),
+ }
+ }
+ stateKey = *newStateKey
+ }
+
// Always fetch visibility so that we can work out whether to show
// the latest events or the last event from when the user was joined.
// Then include the requested event type and state key, assuming it
diff --git a/clientapi/routing/state_test.go b/clientapi/routing/state_test.go
new file mode 100644
index 00000000..93b04372
--- /dev/null
+++ b/clientapi/routing/state_test.go
@@ -0,0 +1,253 @@
+package routing
+
+import (
+ "context"
+ "encoding/json"
+ "fmt"
+ "net/http"
+ "testing"
+
+ rsapi "github.com/matrix-org/dendrite/roomserver/api"
+ "github.com/matrix-org/dendrite/roomserver/types"
+ "github.com/matrix-org/dendrite/setup/config"
+ uapi "github.com/matrix-org/dendrite/userapi/api"
+ "github.com/matrix-org/gomatrixserverlib"
+ "github.com/matrix-org/gomatrixserverlib/spec"
+ "github.com/matrix-org/util"
+ "gotest.tools/v3/assert"
+)
+
+var ()
+
+type stateTestRoomserverAPI struct {
+ rsapi.RoomserverInternalAPI
+ t *testing.T
+ roomState map[gomatrixserverlib.StateKeyTuple]*types.HeaderedEvent
+ roomIDStr string
+ roomVersion gomatrixserverlib.RoomVersion
+ userIDStr string
+ // userID -> senderID
+ senderMapping map[string]string
+}
+
+func (s stateTestRoomserverAPI) QueryRoomVersionForRoom(ctx context.Context, roomID string) (gomatrixserverlib.RoomVersion, error) {
+ if roomID == s.roomIDStr {
+ return s.roomVersion, nil
+ } else {
+ s.t.Logf("room version queried for %s", roomID)
+ return "", fmt.Errorf("unknown room")
+ }
+}
+
+func (s stateTestRoomserverAPI) QueryLatestEventsAndState(
+ ctx context.Context,
+ req *rsapi.QueryLatestEventsAndStateRequest,
+ res *rsapi.QueryLatestEventsAndStateResponse,
+) error {
+ res.RoomExists = req.RoomID == s.roomIDStr
+ if !res.RoomExists {
+ return nil
+ }
+
+ res.StateEvents = []*types.HeaderedEvent{}
+ for _, stateKeyTuple := range req.StateToFetch {
+ val, ok := s.roomState[stateKeyTuple]
+ if ok && val != nil {
+ res.StateEvents = append(res.StateEvents, val)
+ }
+ }
+
+ return nil
+}
+
+func (s stateTestRoomserverAPI) QueryMembershipForUser(
+ ctx context.Context,
+ req *rsapi.QueryMembershipForUserRequest,
+ res *rsapi.QueryMembershipForUserResponse,
+) error {
+ if req.UserID.String() == s.userIDStr {
+ res.HasBeenInRoom = true
+ res.IsInRoom = true
+ res.RoomExists = true
+ res.Membership = spec.Join
+ }
+
+ return nil
+}
+
+func (s stateTestRoomserverAPI) QuerySenderIDForUser(
+ ctx context.Context,
+ roomID spec.RoomID,
+ userID spec.UserID,
+) (*spec.SenderID, error) {
+ sID, ok := s.senderMapping[userID.String()]
+ if ok {
+ sender := spec.SenderID(sID)
+ return &sender, nil
+ } else {
+ return nil, nil
+ }
+}
+
+func (s stateTestRoomserverAPI) QueryUserIDForSender(
+ ctx context.Context,
+ roomID spec.RoomID,
+ senderID spec.SenderID,
+) (*spec.UserID, error) {
+ for uID, sID := range s.senderMapping {
+ if sID == string(senderID) {
+ parsedUserID, err := spec.NewUserID(uID, true)
+ if err != nil {
+ s.t.Fatalf("Mock QueryUserIDForSender failed: %s", err)
+ }
+ return parsedUserID, nil
+ }
+ }
+ return nil, nil
+}
+
+func (s stateTestRoomserverAPI) QueryStateAfterEvents(
+ ctx context.Context,
+ req *rsapi.QueryStateAfterEventsRequest,
+ res *rsapi.QueryStateAfterEventsResponse,
+) error {
+ return nil
+}
+
+func Test_OnIncomingStateTypeRequest(t *testing.T) {
+ var tempRoomServerCfg config.RoomServer
+ tempRoomServerCfg.Defaults(config.DefaultOpts{})
+ defaultRoomVersion := tempRoomServerCfg.DefaultRoomVersion
+ pseudoIDRoomVersion := gomatrixserverlib.RoomVersionPseudoIDs
+ nonPseudoIDRoomVersion := gomatrixserverlib.RoomVersionV10
+
+ userIDStr := "@testuser:domain"
+ eventType := "com.example.test"
+ stateKey := "testStateKey"
+ roomIDStr := "!id:domain"
+
+ device := &uapi.Device{
+ UserID: userIDStr,
+ }
+
+ t.Run("request simple state key", func(t *testing.T) {
+ ctx := context.Background()
+
+ rsAPI := stateTestRoomserverAPI{
+ roomVersion: defaultRoomVersion,
+ roomIDStr: roomIDStr,
+ roomState: map[gomatrixserverlib.StateKeyTuple]*types.HeaderedEvent{
+ {
+ EventType: eventType,
+ StateKey: stateKey,
+ }: mustCreateStatePDU(t, defaultRoomVersion, roomIDStr, eventType, stateKey, map[string]interface{}{
+ "foo": "bar",
+ }),
+ },
+ userIDStr: userIDStr,
+ }
+
+ jsonResp := OnIncomingStateTypeRequest(ctx, device, rsAPI, roomIDStr, eventType, stateKey, false)
+
+ assert.DeepEqual(t, jsonResp, util.JSONResponse{
+ Code: http.StatusOK,
+ JSON: spec.RawJSON(`{"foo":"bar"}`),
+ })
+ })
+
+ t.Run("user ID key translated to room key in pseudo ID rooms", func(t *testing.T) {
+ ctx := context.Background()
+
+ stateSenderUserID := "@sender:domain"
+ stateSenderRoomKey := "testsenderkey"
+
+ rsAPI := stateTestRoomserverAPI{
+ roomVersion: pseudoIDRoomVersion,
+ roomIDStr: roomIDStr,
+ roomState: map[gomatrixserverlib.StateKeyTuple]*types.HeaderedEvent{
+ {
+ EventType: eventType,
+ StateKey: stateSenderRoomKey,
+ }: mustCreateStatePDU(t, pseudoIDRoomVersion, roomIDStr, eventType, stateSenderRoomKey, map[string]interface{}{
+ "foo": "bar",
+ }),
+ {
+ EventType: eventType,
+ StateKey: stateSenderUserID,
+ }: mustCreateStatePDU(t, pseudoIDRoomVersion, roomIDStr, eventType, stateSenderUserID, map[string]interface{}{
+ "not": "thisone",
+ }),
+ },
+ userIDStr: userIDStr,
+ senderMapping: map[string]string{
+ stateSenderUserID: stateSenderRoomKey,
+ },
+ }
+
+ jsonResp := OnIncomingStateTypeRequest(ctx, device, rsAPI, roomIDStr, eventType, stateSenderUserID, false)
+
+ assert.DeepEqual(t, jsonResp, util.JSONResponse{
+ Code: http.StatusOK,
+ JSON: spec.RawJSON(`{"foo":"bar"}`),
+ })
+ })
+
+ t.Run("user ID key not translated to room key in non-pseudo ID rooms", func(t *testing.T) {
+ ctx := context.Background()
+
+ stateSenderUserID := "@sender:domain"
+ stateSenderRoomKey := "testsenderkey"
+
+ rsAPI := stateTestRoomserverAPI{
+ roomVersion: nonPseudoIDRoomVersion,
+ roomIDStr: roomIDStr,
+ roomState: map[gomatrixserverlib.StateKeyTuple]*types.HeaderedEvent{
+ {
+ EventType: eventType,
+ StateKey: stateSenderRoomKey,
+ }: mustCreateStatePDU(t, nonPseudoIDRoomVersion, roomIDStr, eventType, stateSenderRoomKey, map[string]interface{}{
+ "not": "thisone",
+ }),
+ {
+ EventType: eventType,
+ StateKey: stateSenderUserID,
+ }: mustCreateStatePDU(t, nonPseudoIDRoomVersion, roomIDStr, eventType, stateSenderUserID, map[string]interface{}{
+ "foo": "bar",
+ }),
+ },
+ userIDStr: userIDStr,
+ senderMapping: map[string]string{
+ stateSenderUserID: stateSenderUserID,
+ },
+ }
+
+ jsonResp := OnIncomingStateTypeRequest(ctx, device, rsAPI, roomIDStr, eventType, stateSenderUserID, false)
+
+ assert.DeepEqual(t, jsonResp, util.JSONResponse{
+ Code: http.StatusOK,
+ JSON: spec.RawJSON(`{"foo":"bar"}`),
+ })
+ })
+}
+
+func mustCreateStatePDU(t *testing.T, roomVer gomatrixserverlib.RoomVersion, roomID string, stateType string, stateKey string, stateContent map[string]interface{}) *types.HeaderedEvent {
+ t.Helper()
+ roomVerImpl := gomatrixserverlib.MustGetRoomVersion(roomVer)
+
+ evBytes, err := json.Marshal(map[string]interface{}{
+ "room_id": roomID,
+ "type": stateType,
+ "state_key": stateKey,
+ "content": stateContent,
+ })
+ if err != nil {
+ t.Fatalf("failed to create event: %v", err)
+ }
+
+ ev, err := roomVerImpl.NewEventFromTrustedJSON(evBytes, false)
+ if err != nil {
+ t.Fatalf("failed to create event: %v", err)
+ }
+
+ return &types.HeaderedEvent{PDU: ev}
+}
diff --git a/go.mod b/go.mod
index 915e813a..4be0ede4 100644
--- a/go.mod
+++ b/go.mod
@@ -22,7 +22,7 @@ require (
github.com/matrix-org/dugong v0.0.0-20210921133753-66e6b1c67e2e
github.com/matrix-org/go-sqlite3-js v0.0.0-20220419092513-28aa791a1c91
github.com/matrix-org/gomatrix v0.0.0-20220926102614-ceba4d9f7530
- github.com/matrix-org/gomatrixserverlib v0.0.0-20230807152937-c48e302e15ac
+ github.com/matrix-org/gomatrixserverlib v0.0.0-20230823153616-484e7693bb8d
github.com/matrix-org/pinecone v0.11.1-0.20230810010612-ea4c33717fd7
github.com/matrix-org/util v0.0.0-20221111132719-399730281e66
github.com/mattn/go-sqlite3 v1.14.17
@@ -36,7 +36,7 @@ require (
github.com/prometheus/client_golang v1.16.0
github.com/sirupsen/logrus v1.9.3
github.com/stretchr/testify v1.8.2
- github.com/tidwall/gjson v1.15.0
+ github.com/tidwall/gjson v1.16.0
github.com/tidwall/sjson v1.2.5
github.com/uber/jaeger-client-go v2.30.0+incompatible
github.com/uber/jaeger-lib v2.4.1+incompatible
diff --git a/go.sum b/go.sum
index 6c7e48db..3fdfe01a 100644
--- a/go.sum
+++ b/go.sum
@@ -208,8 +208,8 @@ github.com/matrix-org/go-sqlite3-js v0.0.0-20220419092513-28aa791a1c91 h1:s7fexw
github.com/matrix-org/go-sqlite3-js v0.0.0-20220419092513-28aa791a1c91/go.mod h1:e+cg2q7C7yE5QnAXgzo512tgFh1RbQLC0+jozuegKgo=
github.com/matrix-org/gomatrix v0.0.0-20220926102614-ceba4d9f7530 h1:kHKxCOLcHH8r4Fzarl4+Y3K5hjothkVW5z7T1dUM11U=
github.com/matrix-org/gomatrix v0.0.0-20220926102614-ceba4d9f7530/go.mod h1:/gBX06Kw0exX1HrwmoBibFA98yBk/jxKpGVeyQbff+s=
-github.com/matrix-org/gomatrixserverlib v0.0.0-20230807152937-c48e302e15ac h1:s4EZRNT6/TtGAzcO6yzL+UTv96vEeeaH6y2RrIOfsWw=
-github.com/matrix-org/gomatrixserverlib v0.0.0-20230807152937-c48e302e15ac/go.mod h1:H9V9N3Uqn1bBJqYJNGK1noqtgJTaCEhtTdcH/mp50uU=
+github.com/matrix-org/gomatrixserverlib v0.0.0-20230823153616-484e7693bb8d h1:yFoT2nyjD4TFrgYMJGgrotFbTLjaYKfZbRmnsj7lvZE=
+github.com/matrix-org/gomatrixserverlib v0.0.0-20230823153616-484e7693bb8d/go.mod h1:H9V9N3Uqn1bBJqYJNGK1noqtgJTaCEhtTdcH/mp50uU=
github.com/matrix-org/pinecone v0.11.1-0.20230810010612-ea4c33717fd7 h1:6t8kJr8i1/1I5nNttw6nn1ryQJgzVlBmSGgPiiaTdw4=
github.com/matrix-org/pinecone v0.11.1-0.20230810010612-ea4c33717fd7/go.mod h1:ReWMS/LoVnOiRAdq9sNUC2NZnd1mZkMNB52QhpTRWjg=
github.com/matrix-org/util v0.0.0-20221111132719-399730281e66 h1:6z4KxomXSIGWqhHcfzExgkH3Z3UkIXry4ibJS4Aqz2Y=
@@ -318,8 +318,8 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO
github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8=
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
-github.com/tidwall/gjson v1.15.0 h1:5n/pM+v3r5ujuNl4YLZLsQ+UE5jlkLVm7jMzT5Mpolw=
-github.com/tidwall/gjson v1.15.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
+github.com/tidwall/gjson v1.16.0 h1:SyXa+dsSPpUlcwEDuKuEBJEz5vzTvOea+9rjyYodQFg=
+github.com/tidwall/gjson v1.16.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
diff --git a/roomserver/internal/input/input_events.go b/roomserver/internal/input/input_events.go
index 88049ddf..bf321662 100644
--- a/roomserver/internal/input/input_events.go
+++ b/roomserver/internal/input/input_events.go
@@ -933,12 +933,7 @@ func (r *Inputer) kickGuests(ctx context.Context, event gomatrixserverlib.PDU, r
return err
}
- userID, err := spec.NewUserID(stateKey, true)
- if err != nil {
- return err
- }
-
- signingIdentity, err := r.SigningIdentity(ctx, *validRoomID, *userID)
+ signingIdentity, err := r.SigningIdentity(ctx, *validRoomID, *memberUserID)
if err != nil {
return err
}
diff --git a/syncapi/internal/history_visibility.go b/syncapi/internal/history_visibility.go
index 91a2d63c..7aae9fd3 100644
--- a/syncapi/internal/history_visibility.go
+++ b/syncapi/internal/history_visibility.go
@@ -163,17 +163,23 @@ func ApplyHistoryVisibilityFilter(
// by setting the effective evVis to the least restrictive
// of the old vs new.
// https://spec.matrix.org/v1.3/client-server-api/#server-behaviour-5
- if hisVis, err := ev.HistoryVisibility(); err == nil {
- prevHisVis := gjson.GetBytes(ev.Unsigned(), "prev_content.history_visibility").String()
- oldPrio, ok := historyVisibilityPriority[gomatrixserverlib.HistoryVisibility(prevHisVis)]
- // if we can't get the previous history visibility, default to shared.
- if !ok {
- oldPrio = historyVisibilityPriority[gomatrixserverlib.HistoryVisibilityShared]
- }
- // no OK check, since this should have been validated when setting the value
- newPrio := historyVisibilityPriority[hisVis]
- if oldPrio < newPrio {
- evVis.visibility = gomatrixserverlib.HistoryVisibility(prevHisVis)
+ if ev.Type() == spec.MRoomHistoryVisibility {
+ hisVis, err := ev.HistoryVisibility()
+
+ if err == nil && hisVis != "" {
+ prevHisVis := gjson.GetBytes(ev.Unsigned(), "prev_content.history_visibility").String()
+ oldPrio, ok := historyVisibilityPriority[gomatrixserverlib.HistoryVisibility(prevHisVis)]
+ // if we can't get the previous history visibility, default to shared.
+ if !ok {
+ oldPrio = historyVisibilityPriority[gomatrixserverlib.HistoryVisibilityShared]
+ }
+ // no OK check, since this should have been validated when setting the value
+ newPrio := historyVisibilityPriority[hisVis]
+ if oldPrio < newPrio {
+ evVis.visibility = gomatrixserverlib.HistoryVisibility(prevHisVis)
+ } else {
+ evVis.visibility = hisVis
+ }
}
}
// do the actual check
diff --git a/syncapi/internal/history_visibility_test.go b/syncapi/internal/history_visibility_test.go
new file mode 100644
index 00000000..984f90ed
--- /dev/null
+++ b/syncapi/internal/history_visibility_test.go
@@ -0,0 +1,214 @@
+package internal
+
+import (
+ "context"
+ "fmt"
+ "math"
+ "testing"
+
+ rsapi "github.com/matrix-org/dendrite/roomserver/api"
+ "github.com/matrix-org/dendrite/roomserver/types"
+ "github.com/matrix-org/dendrite/syncapi/storage"
+ "github.com/matrix-org/gomatrixserverlib"
+ "github.com/matrix-org/gomatrixserverlib/spec"
+ "gotest.tools/v3/assert"
+)
+
+type mockHisVisRoomserverAPI struct {
+ rsapi.RoomserverInternalAPI
+ events []*types.HeaderedEvent
+ roomID string
+}
+
+func (s *mockHisVisRoomserverAPI) QueryMembershipAtEvent(ctx context.Context, roomID spec.RoomID, eventIDs []string, senderID spec.SenderID) (map[string]*types.HeaderedEvent, error) {
+ if roomID.String() == s.roomID {
+ membershipMap := map[string]*types.HeaderedEvent{}
+
+ for _, queriedEventID := range eventIDs {
+ for _, event := range s.events {
+ if event.EventID() == queriedEventID {
+ membershipMap[queriedEventID] = event
+ }
+ }
+ }
+
+ return membershipMap, nil
+ } else {
+ return nil, fmt.Errorf("room not found: \"%v\"", roomID)
+ }
+}
+
+func (s *mockHisVisRoomserverAPI) QuerySenderIDForUser(ctx context.Context, roomID spec.RoomID, userID spec.UserID) (*spec.SenderID, error) {
+ senderID := spec.SenderIDFromUserID(userID)
+ return &senderID, nil
+}
+
+func (s *mockHisVisRoomserverAPI) QueryUserIDForSender(ctx context.Context, roomID spec.RoomID, senderID spec.SenderID) (*spec.UserID, error) {
+ userID := senderID.ToUserID()
+ if userID == nil {
+ return nil, fmt.Errorf("sender ID not user ID")
+ }
+ return userID, nil
+}
+
+type mockDB struct {
+ storage.DatabaseTransaction
+ // user ID -> membership (i.e. 'join', 'leave', etc.)
+ currentMembership map[string]string
+ roomID string
+}
+
+func (s *mockDB) SelectMembershipForUser(ctx context.Context, roomID string, userID string, pos int64) (string, int, error) {
+ if roomID == s.roomID {
+ membership, ok := s.currentMembership[userID]
+ if !ok {
+ return spec.Leave, math.MaxInt64, nil
+ }
+ return membership, math.MaxInt64, nil
+ }
+
+ return "", 0, fmt.Errorf("room not found: \"%v\"", roomID)
+}
+
+// Tests logic around history visibility boundaries
+//
+// Specifically that if a room's history visibility before or after a particular history visibility event
+// allows them to see events (a boundary), then the history visibility event itself should be shown
+// ( spec: https://spec.matrix.org/v1.8/client-server-api/#server-behaviour-5 )
+//
+// This also aims to emulate "Only see history_visibility changes on bounadries" in sytest/tests/30rooms/30history-visibility.pl
+func Test_ApplyHistoryVisbility_Boundaries(t *testing.T) {
+ ctx := context.Background()
+
+ roomID := "!roomid:domain"
+
+ creatorUserID := spec.NewUserIDOrPanic("@creator:domain", false)
+ otherUserID := spec.NewUserIDOrPanic("@other:domain", false)
+ roomVersion := gomatrixserverlib.RoomVersionV10
+ roomVerImpl := gomatrixserverlib.MustGetRoomVersion(roomVersion)
+
+ eventsJSON := []struct {
+ id string
+ json string
+ }{
+ {id: "$create-event", json: fmt.Sprintf(`{
+ "type": "m.room.create", "state_key": "",
+ "room_id": "%v", "sender": "%v",
+ "content": {"creator": "%v", "room_version": "%v"}
+ }`, roomID, creatorUserID.String(), creatorUserID.String(), roomVersion)},
+ {id: "$creator-joined", json: fmt.Sprintf(`{
+ "type": "m.room.member", "state_key": "%v",
+ "room_id": "%v", "sender": "%v",
+ "content": {"membership": "join"}
+ }`, creatorUserID.String(), roomID, creatorUserID.String())},
+ {id: "$hisvis-1", json: fmt.Sprintf(`{
+ "type": "m.room.history_visibility", "state_key": "",
+ "room_id": "%v", "sender": "%v",
+ "content": {"history_visibility": "shared"}
+ }`, roomID, creatorUserID.String())},
+ {id: "$msg-1", json: fmt.Sprintf(`{
+ "type": "m.room.message",
+ "room_id": "%v", "sender": "%v",
+ "content": {"body": "1"}
+ }`, roomID, creatorUserID.String())},
+ {id: "$hisvis-2", json: fmt.Sprintf(`{
+ "type": "m.room.history_visibility", "state_key": "",
+ "room_id": "%v", "sender": "%v",
+ "content": {"history_visibility": "joined"},
+ "unsigned": {"prev_content": {"history_visibility": "shared"}}
+ }`, roomID, creatorUserID.String())},
+ {id: "$msg-2", json: fmt.Sprintf(`{
+ "type": "m.room.message",
+ "room_id": "%v", "sender": "%v",
+ "content": {"body": "1"}
+ }`, roomID, creatorUserID.String())},
+ {id: "$hisvis-3", json: fmt.Sprintf(`{
+ "type": "m.room.history_visibility", "state_key": "",
+ "room_id": "%v", "sender": "%v",
+ "content": {"history_visibility": "invited"},
+ "unsigned": {"prev_content": {"history_visibility": "joined"}}
+ }`, roomID, creatorUserID.String())},
+ {id: "$msg-3", json: fmt.Sprintf(`{
+ "type": "m.room.message",
+ "room_id": "%v", "sender": "%v",
+ "content": {"body": "2"}
+ }`, roomID, creatorUserID.String())},
+ {id: "$hisvis-4", json: fmt.Sprintf(`{
+ "type": "m.room.history_visibility", "state_key": "",
+ "room_id": "%v", "sender": "%v",
+ "content": {"history_visibility": "shared"},
+ "unsigned": {"prev_content": {"history_visibility": "invited"}}
+ }`, roomID, creatorUserID.String())},
+ {id: "$msg-4", json: fmt.Sprintf(`{
+ "type": "m.room.message",
+ "room_id": "%v", "sender": "%v",
+ "content": {"body": "3"}
+ }`, roomID, creatorUserID.String())},
+ {id: "$other-joined", json: fmt.Sprintf(`{
+ "type": "m.room.member", "state_key": "%v",
+ "room_id": "%v", "sender": "%v",
+ "content": {"membership": "join"}
+ }`, otherUserID.String(), roomID, otherUserID.String())},
+ }
+
+ events := make([]*types.HeaderedEvent, len(eventsJSON))
+
+ hisVis := gomatrixserverlib.HistoryVisibilityShared
+
+ for i, eventJSON := range eventsJSON {
+ pdu, err := roomVerImpl.NewEventFromTrustedJSONWithEventID(eventJSON.id, []byte(eventJSON.json), false)
+ if err != nil {
+ t.Fatalf("failed to prepare event %s for test: %s", eventJSON.id, err.Error())
+ }
+ events[i] = &types.HeaderedEvent{PDU: pdu}
+
+ // 'Visibility' should be the visibility of the room just before this event was sent
+ // (according to processRoomEvent in roomserver/internal/input/input_events.go)
+ events[i].Visibility = hisVis
+ if pdu.Type() == spec.MRoomHistoryVisibility {
+ newHisVis, err := pdu.HistoryVisibility()
+ if err != nil {
+ t.Fatalf("failed to prepare history visibility event: %s", err.Error())
+ }
+ hisVis = newHisVis
+ }
+ }
+
+ rsAPI := &mockHisVisRoomserverAPI{
+ events: events,
+ roomID: roomID,
+ }
+ syncDB := &mockDB{
+ roomID: roomID,
+ currentMembership: map[string]string{
+ creatorUserID.String(): spec.Join,
+ otherUserID.String(): spec.Join,
+ },
+ }
+
+ filteredEvents, err := ApplyHistoryVisibilityFilter(ctx, syncDB, rsAPI, events, nil, otherUserID, "hisVisTest")
+ if err != nil {
+ t.Fatalf("ApplyHistoryVisibility returned non-nil error: %s", err.Error())
+ }
+
+ filteredEventIDs := make([]string, len(filteredEvents))
+ for i, event := range filteredEvents {
+ filteredEventIDs[i] = event.EventID()
+ }
+
+ assert.DeepEqual(t,
+ []string{
+ "$create-event", // Always see m.room.create
+ "$creator-joined", // Always see membership
+ "$hisvis-1", // Sets room to shared (technically the room is already shared since shared is default)
+ "$msg-1", // Room currently 'shared'
+ "$hisvis-2", // Room changed from 'shared' to 'joined', so boundary event and should be shared
+ // Other events hidden, as other is not joined yet
+ // hisvis-3 is also hidden, as it changes from joined to invited, neither of which is visible to other
+ "$hisvis-4", // Changes from 'invited' to 'shared', so is a boundary event and visible
+ "$msg-4", // Room is 'shared', so visible
+ "$other-joined", // other's membership
+ },
+ filteredEventIDs,
+ )
+}
diff --git a/syncapi/internal/keychange_test.go b/syncapi/internal/keychange_test.go
index 81b82bf6..56954cfa 100644
--- a/syncapi/internal/keychange_test.go
+++ b/syncapi/internal/keychange_test.go
@@ -59,22 +59,22 @@ func (k *mockKeyAPI) QueryDeviceMessages(ctx context.Context, req *userapi.Query
func (k *mockKeyAPI) QuerySignatures(ctx context.Context, req *userapi.QuerySignaturesRequest, res *userapi.QuerySignaturesResponse) {
}
-type mockRoomserverAPI struct {
+type keyChangeMockRoomserverAPI struct {
api.RoomserverInternalAPI
roomIDToJoinedMembers map[string][]string
}
-func (s *mockRoomserverAPI) QueryUserIDForSender(ctx context.Context, roomID spec.RoomID, senderID spec.SenderID) (*spec.UserID, error) {
+func (s *keyChangeMockRoomserverAPI) QueryUserIDForSender(ctx context.Context, roomID spec.RoomID, senderID spec.SenderID) (*spec.UserID, error) {
return spec.NewUserID(string(senderID), true)
}
// QueryRoomsForUser retrieves a list of room IDs matching the given query.
-func (s *mockRoomserverAPI) QueryRoomsForUser(ctx context.Context, userID spec.UserID, desiredMembership string) ([]spec.RoomID, error) {
+func (s *keyChangeMockRoomserverAPI) QueryRoomsForUser(ctx context.Context, userID spec.UserID, desiredMembership string) ([]spec.RoomID, error) {
return nil, nil
}
// QueryBulkStateContent does a bulk query for state event content in the given rooms.
-func (s *mockRoomserverAPI) QueryBulkStateContent(ctx context.Context, req *api.QueryBulkStateContentRequest, res *api.QueryBulkStateContentResponse) error {
+func (s *keyChangeMockRoomserverAPI) QueryBulkStateContent(ctx context.Context, req *api.QueryBulkStateContentRequest, res *api.QueryBulkStateContentResponse) error {
res.Rooms = make(map[string]map[gomatrixserverlib.StateKeyTuple]string)
if req.AllowWildcards && len(req.StateTuples) == 1 && req.StateTuples[0].EventType == spec.MRoomMember && req.StateTuples[0].StateKey == "*" {
for _, roomID := range req.RoomIDs {
@@ -91,7 +91,7 @@ func (s *mockRoomserverAPI) QueryBulkStateContent(ctx context.Context, req *api.
}
// QuerySharedUsers returns a list of users who share at least 1 room in common with the given user.
-func (s *mockRoomserverAPI) QuerySharedUsers(ctx context.Context, req *api.QuerySharedUsersRequest, res *api.QuerySharedUsersResponse) error {
+func (s *keyChangeMockRoomserverAPI) QuerySharedUsers(ctx context.Context, req *api.QuerySharedUsersRequest, res *api.QuerySharedUsersResponse) error {
roomsToQuery := req.IncludeRoomIDs
for roomID, members := range s.roomIDToJoinedMembers {
exclude := false
@@ -123,7 +123,7 @@ func (s *mockRoomserverAPI) QuerySharedUsers(ctx context.Context, req *api.Query
// This is actually a database function, but seeing as we track the state inside the
// *mockRoomserverAPI, we'll just comply with the interface here instead.
-func (s *mockRoomserverAPI) SharedUsers(ctx context.Context, userID string, otherUserIDs []string) ([]string, error) {
+func (s *keyChangeMockRoomserverAPI) SharedUsers(ctx context.Context, userID string, otherUserIDs []string) ([]string, error) {
commonUsers := []string{}
for _, members := range s.roomIDToJoinedMembers {
for _, member := range members {
@@ -211,7 +211,7 @@ func TestKeyChangeCatchupOnJoinShareNewUser(t *testing.T) {
syncResponse := types.NewResponse()
syncResponse = joinResponseWithRooms(syncResponse, syncingUser, []string{newlyJoinedRoom})
- rsAPI := &mockRoomserverAPI{
+ rsAPI := &keyChangeMockRoomserverAPI{
roomIDToJoinedMembers: map[string][]string{
newlyJoinedRoom: {syncingUser, newShareUser},
"!another:room": {syncingUser},
@@ -234,7 +234,7 @@ func TestKeyChangeCatchupOnLeaveShareLeftUser(t *testing.T) {
syncResponse := types.NewResponse()
syncResponse = leaveResponseWithRooms(syncResponse, syncingUser, []string{newlyLeftRoom})
- rsAPI := &mockRoomserverAPI{
+ rsAPI := &keyChangeMockRoomserverAPI{
roomIDToJoinedMembers: map[string][]string{
newlyLeftRoom: {removeUser},
"!another:room": {syncingUser},
@@ -257,7 +257,7 @@ func TestKeyChangeCatchupOnJoinShareNoNewUsers(t *testing.T) {
syncResponse := types.NewResponse()
syncResponse = joinResponseWithRooms(syncResponse, syncingUser, []string{newlyJoinedRoom})
- rsAPI := &mockRoomserverAPI{
+ rsAPI := &keyChangeMockRoomserverAPI{
roomIDToJoinedMembers: map[string][]string{
newlyJoinedRoom: {syncingUser, existingUser},
"!another:room": {syncingUser, existingUser},
@@ -279,7 +279,7 @@ func TestKeyChangeCatchupOnLeaveShareNoUsers(t *testing.T) {
syncResponse := types.NewResponse()
syncResponse = leaveResponseWithRooms(syncResponse, syncingUser, []string{newlyLeftRoom})
- rsAPI := &mockRoomserverAPI{
+ rsAPI := &keyChangeMockRoomserverAPI{
roomIDToJoinedMembers: map[string][]string{
newlyLeftRoom: {existingUser},
"!another:room": {syncingUser, existingUser},
@@ -343,7 +343,7 @@ func TestKeyChangeCatchupNoNewJoinsButMessages(t *testing.T) {
jr.Timeline = &types.Timeline{Events: roomTimelineEvents}
syncResponse.Rooms.Join[roomID] = jr
- rsAPI := &mockRoomserverAPI{
+ rsAPI := &keyChangeMockRoomserverAPI{
roomIDToJoinedMembers: map[string][]string{
roomID: {syncingUser, existingUser},
},
@@ -369,7 +369,7 @@ func TestKeyChangeCatchupChangeAndLeft(t *testing.T) {
syncResponse = joinResponseWithRooms(syncResponse, syncingUser, []string{newlyJoinedRoom})
syncResponse = leaveResponseWithRooms(syncResponse, syncingUser, []string{newlyLeftRoom})
- rsAPI := &mockRoomserverAPI{
+ rsAPI := &keyChangeMockRoomserverAPI{
roomIDToJoinedMembers: map[string][]string{
newlyJoinedRoom: {syncingUser, newShareUser, newShareUser2},
newlyLeftRoom: {newlyLeftUser, newlyLeftUser2},
@@ -459,7 +459,7 @@ func TestKeyChangeCatchupChangeAndLeftSameRoom(t *testing.T) {
lr.Timeline = &types.Timeline{Events: roomEvents}
syncResponse.Rooms.Leave[roomID] = lr
- rsAPI := &mockRoomserverAPI{
+ rsAPI := &keyChangeMockRoomserverAPI{
roomIDToJoinedMembers: map[string][]string{
roomID: {newShareUser, newShareUser2},
"!another:room": {syncingUser},
diff --git a/syncapi/synctypes/clientevent.go b/syncapi/synctypes/clientevent.go
index 6f03d9ff..a78aea1c 100644
--- a/syncapi/synctypes/clientevent.go
+++ b/syncapi/synctypes/clientevent.go
@@ -16,6 +16,8 @@
package synctypes
import (
+ "fmt"
+
"github.com/matrix-org/gomatrixserverlib"
"github.com/matrix-org/gomatrixserverlib/spec"
)
@@ -118,3 +120,31 @@ func ToClientEventDefault(userIDQuery spec.UserIDForSender, event gomatrixserver
}
return ToClientEvent(event, FormatAll, sender, sk)
}
+
+// If provided state key is a user ID (state keys beginning with @ are reserved for this purpose)
+// fetch it's associated sender ID and use that instead. Otherwise returns the same state key back.
+//
+// # This function either returns the state key that should be used, or an error
+//
+// TODO: handle failure cases better (e.g. no sender ID)
+func FromClientStateKey(roomID spec.RoomID, stateKey string, senderIDQuery spec.SenderIDForUser) (*string, error) {
+ if len(stateKey) >= 1 && stateKey[0] == '@' {
+ parsedStateKey, err := spec.NewUserID(stateKey, true)
+ if err != nil {
+ // If invalid user ID, then there is no associated state event.
+ return nil, fmt.Errorf("Provided state key begins with @ but is not a valid user ID: %s", err.Error())
+ }
+ senderID, err := senderIDQuery(roomID, *parsedStateKey)
+ if err != nil {
+ return nil, fmt.Errorf("Failed to query sender ID: %s", err.Error())
+ }
+ if senderID == nil {
+ // If no sender ID, then there is no associated state event.
+ return nil, fmt.Errorf("No associated sender ID found.")
+ }
+ newStateKey := string(*senderID)
+ return &newStateKey, nil
+ } else {
+ return &stateKey, nil
+ }
+}