[pseudoIDs] More pseudo ID fixes - Part 2 (#3181)
Fixes include: - Translating state keys that contain user IDs to their respective room keys for both querying and sending state events - **NOTE**: there may be design discussion needed on what should happen when sender keys cannot be found for users - A simple fix for kicking guests from rooms properly - Logic for boundary history visibilities was slightly off (I'm surprised this only manifested in pseudo ID room versions) Signed-off-by: `Sam Wedgwood <sam@wedgwood.dev>`
This commit is contained in:
parent
a721294e2b
commit
9b5be6b9c5
|
@ -29,6 +29,7 @@ import (
|
||||||
"github.com/matrix-org/dendrite/roomserver/api"
|
"github.com/matrix-org/dendrite/roomserver/api"
|
||||||
"github.com/matrix-org/dendrite/roomserver/types"
|
"github.com/matrix-org/dendrite/roomserver/types"
|
||||||
"github.com/matrix-org/dendrite/setup/config"
|
"github.com/matrix-org/dendrite/setup/config"
|
||||||
|
"github.com/matrix-org/dendrite/syncapi/synctypes"
|
||||||
userapi "github.com/matrix-org/dendrite/userapi/api"
|
userapi "github.com/matrix-org/dendrite/userapi/api"
|
||||||
"github.com/matrix-org/gomatrixserverlib"
|
"github.com/matrix-org/gomatrixserverlib"
|
||||||
"github.com/matrix-org/gomatrixserverlib/spec"
|
"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
|
// 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
|
// this avoids a situation where events that are received in quick succession are sent to the roomserver in a jumbled order
|
||||||
userID := device.UserID
|
userID := device.UserID
|
||||||
|
|
275
clientapi/routing/sendevent_test.go
Normal file
275
clientapi/routing/sendevent_test.go
Normal file
|
@ -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
|
||||||
|
}
|
|
@ -217,6 +217,37 @@ func OnIncomingStateTypeRequest(
|
||||||
var worldReadable bool
|
var worldReadable bool
|
||||||
var wantLatestState 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
|
// 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.
|
// the latest events or the last event from when the user was joined.
|
||||||
// Then include the requested event type and state key, assuming it
|
// Then include the requested event type and state key, assuming it
|
||||||
|
|
253
clientapi/routing/state_test.go
Normal file
253
clientapi/routing/state_test.go
Normal file
|
@ -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}
|
||||||
|
}
|
4
go.mod
4
go.mod
|
@ -22,7 +22,7 @@ require (
|
||||||
github.com/matrix-org/dugong v0.0.0-20210921133753-66e6b1c67e2e
|
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/go-sqlite3-js v0.0.0-20220419092513-28aa791a1c91
|
||||||
github.com/matrix-org/gomatrix v0.0.0-20220926102614-ceba4d9f7530
|
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/pinecone v0.11.1-0.20230810010612-ea4c33717fd7
|
||||||
github.com/matrix-org/util v0.0.0-20221111132719-399730281e66
|
github.com/matrix-org/util v0.0.0-20221111132719-399730281e66
|
||||||
github.com/mattn/go-sqlite3 v1.14.17
|
github.com/mattn/go-sqlite3 v1.14.17
|
||||||
|
@ -36,7 +36,7 @@ require (
|
||||||
github.com/prometheus/client_golang v1.16.0
|
github.com/prometheus/client_golang v1.16.0
|
||||||
github.com/sirupsen/logrus v1.9.3
|
github.com/sirupsen/logrus v1.9.3
|
||||||
github.com/stretchr/testify v1.8.2
|
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/tidwall/sjson v1.2.5
|
||||||
github.com/uber/jaeger-client-go v2.30.0+incompatible
|
github.com/uber/jaeger-client-go v2.30.0+incompatible
|
||||||
github.com/uber/jaeger-lib v2.4.1+incompatible
|
github.com/uber/jaeger-lib v2.4.1+incompatible
|
||||||
|
|
8
go.sum
8
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/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 h1:kHKxCOLcHH8r4Fzarl4+Y3K5hjothkVW5z7T1dUM11U=
|
||||||
github.com/matrix-org/gomatrix v0.0.0-20220926102614-ceba4d9f7530/go.mod h1:/gBX06Kw0exX1HrwmoBibFA98yBk/jxKpGVeyQbff+s=
|
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-20230823153616-484e7693bb8d h1:yFoT2nyjD4TFrgYMJGgrotFbTLjaYKfZbRmnsj7lvZE=
|
||||||
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/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 h1:6t8kJr8i1/1I5nNttw6nn1ryQJgzVlBmSGgPiiaTdw4=
|
||||||
github.com/matrix-org/pinecone v0.11.1-0.20230810010612-ea4c33717fd7/go.mod h1:ReWMS/LoVnOiRAdq9sNUC2NZnd1mZkMNB52QhpTRWjg=
|
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=
|
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 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8=
|
||||||
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
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.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
||||||
github.com/tidwall/gjson v1.15.0 h1:5n/pM+v3r5ujuNl4YLZLsQ+UE5jlkLVm7jMzT5Mpolw=
|
github.com/tidwall/gjson v1.16.0 h1:SyXa+dsSPpUlcwEDuKuEBJEz5vzTvOea+9rjyYodQFg=
|
||||||
github.com/tidwall/gjson v1.15.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
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 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
|
||||||
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
|
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
|
||||||
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
|
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
|
||||||
|
|
|
@ -933,12 +933,7 @@ func (r *Inputer) kickGuests(ctx context.Context, event gomatrixserverlib.PDU, r
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
userID, err := spec.NewUserID(stateKey, true)
|
signingIdentity, err := r.SigningIdentity(ctx, *validRoomID, *memberUserID)
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
signingIdentity, err := r.SigningIdentity(ctx, *validRoomID, *userID)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -163,17 +163,23 @@ func ApplyHistoryVisibilityFilter(
|
||||||
// by setting the effective evVis to the least restrictive
|
// by setting the effective evVis to the least restrictive
|
||||||
// of the old vs new.
|
// of the old vs new.
|
||||||
// https://spec.matrix.org/v1.3/client-server-api/#server-behaviour-5
|
// https://spec.matrix.org/v1.3/client-server-api/#server-behaviour-5
|
||||||
if hisVis, err := ev.HistoryVisibility(); err == nil {
|
if ev.Type() == spec.MRoomHistoryVisibility {
|
||||||
prevHisVis := gjson.GetBytes(ev.Unsigned(), "prev_content.history_visibility").String()
|
hisVis, err := ev.HistoryVisibility()
|
||||||
oldPrio, ok := historyVisibilityPriority[gomatrixserverlib.HistoryVisibility(prevHisVis)]
|
|
||||||
// if we can't get the previous history visibility, default to shared.
|
if err == nil && hisVis != "" {
|
||||||
if !ok {
|
prevHisVis := gjson.GetBytes(ev.Unsigned(), "prev_content.history_visibility").String()
|
||||||
oldPrio = historyVisibilityPriority[gomatrixserverlib.HistoryVisibilityShared]
|
oldPrio, ok := historyVisibilityPriority[gomatrixserverlib.HistoryVisibility(prevHisVis)]
|
||||||
}
|
// if we can't get the previous history visibility, default to shared.
|
||||||
// no OK check, since this should have been validated when setting the value
|
if !ok {
|
||||||
newPrio := historyVisibilityPriority[hisVis]
|
oldPrio = historyVisibilityPriority[gomatrixserverlib.HistoryVisibilityShared]
|
||||||
if oldPrio < newPrio {
|
}
|
||||||
evVis.visibility = gomatrixserverlib.HistoryVisibility(prevHisVis)
|
// 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
|
// do the actual check
|
||||||
|
|
214
syncapi/internal/history_visibility_test.go
Normal file
214
syncapi/internal/history_visibility_test.go
Normal file
|
@ -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,
|
||||||
|
)
|
||||||
|
}
|
|
@ -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) {
|
func (k *mockKeyAPI) QuerySignatures(ctx context.Context, req *userapi.QuerySignaturesRequest, res *userapi.QuerySignaturesResponse) {
|
||||||
}
|
}
|
||||||
|
|
||||||
type mockRoomserverAPI struct {
|
type keyChangeMockRoomserverAPI struct {
|
||||||
api.RoomserverInternalAPI
|
api.RoomserverInternalAPI
|
||||||
roomIDToJoinedMembers map[string][]string
|
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)
|
return spec.NewUserID(string(senderID), true)
|
||||||
}
|
}
|
||||||
|
|
||||||
// QueryRoomsForUser retrieves a list of room IDs matching the given query.
|
// 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
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// QueryBulkStateContent does a bulk query for state event content in the given rooms.
|
// 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)
|
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 == "*" {
|
if req.AllowWildcards && len(req.StateTuples) == 1 && req.StateTuples[0].EventType == spec.MRoomMember && req.StateTuples[0].StateKey == "*" {
|
||||||
for _, roomID := range req.RoomIDs {
|
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.
|
// 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
|
roomsToQuery := req.IncludeRoomIDs
|
||||||
for roomID, members := range s.roomIDToJoinedMembers {
|
for roomID, members := range s.roomIDToJoinedMembers {
|
||||||
exclude := false
|
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
|
// 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.
|
// *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{}
|
commonUsers := []string{}
|
||||||
for _, members := range s.roomIDToJoinedMembers {
|
for _, members := range s.roomIDToJoinedMembers {
|
||||||
for _, member := range members {
|
for _, member := range members {
|
||||||
|
@ -211,7 +211,7 @@ func TestKeyChangeCatchupOnJoinShareNewUser(t *testing.T) {
|
||||||
syncResponse := types.NewResponse()
|
syncResponse := types.NewResponse()
|
||||||
syncResponse = joinResponseWithRooms(syncResponse, syncingUser, []string{newlyJoinedRoom})
|
syncResponse = joinResponseWithRooms(syncResponse, syncingUser, []string{newlyJoinedRoom})
|
||||||
|
|
||||||
rsAPI := &mockRoomserverAPI{
|
rsAPI := &keyChangeMockRoomserverAPI{
|
||||||
roomIDToJoinedMembers: map[string][]string{
|
roomIDToJoinedMembers: map[string][]string{
|
||||||
newlyJoinedRoom: {syncingUser, newShareUser},
|
newlyJoinedRoom: {syncingUser, newShareUser},
|
||||||
"!another:room": {syncingUser},
|
"!another:room": {syncingUser},
|
||||||
|
@ -234,7 +234,7 @@ func TestKeyChangeCatchupOnLeaveShareLeftUser(t *testing.T) {
|
||||||
syncResponse := types.NewResponse()
|
syncResponse := types.NewResponse()
|
||||||
syncResponse = leaveResponseWithRooms(syncResponse, syncingUser, []string{newlyLeftRoom})
|
syncResponse = leaveResponseWithRooms(syncResponse, syncingUser, []string{newlyLeftRoom})
|
||||||
|
|
||||||
rsAPI := &mockRoomserverAPI{
|
rsAPI := &keyChangeMockRoomserverAPI{
|
||||||
roomIDToJoinedMembers: map[string][]string{
|
roomIDToJoinedMembers: map[string][]string{
|
||||||
newlyLeftRoom: {removeUser},
|
newlyLeftRoom: {removeUser},
|
||||||
"!another:room": {syncingUser},
|
"!another:room": {syncingUser},
|
||||||
|
@ -257,7 +257,7 @@ func TestKeyChangeCatchupOnJoinShareNoNewUsers(t *testing.T) {
|
||||||
syncResponse := types.NewResponse()
|
syncResponse := types.NewResponse()
|
||||||
syncResponse = joinResponseWithRooms(syncResponse, syncingUser, []string{newlyJoinedRoom})
|
syncResponse = joinResponseWithRooms(syncResponse, syncingUser, []string{newlyJoinedRoom})
|
||||||
|
|
||||||
rsAPI := &mockRoomserverAPI{
|
rsAPI := &keyChangeMockRoomserverAPI{
|
||||||
roomIDToJoinedMembers: map[string][]string{
|
roomIDToJoinedMembers: map[string][]string{
|
||||||
newlyJoinedRoom: {syncingUser, existingUser},
|
newlyJoinedRoom: {syncingUser, existingUser},
|
||||||
"!another:room": {syncingUser, existingUser},
|
"!another:room": {syncingUser, existingUser},
|
||||||
|
@ -279,7 +279,7 @@ func TestKeyChangeCatchupOnLeaveShareNoUsers(t *testing.T) {
|
||||||
syncResponse := types.NewResponse()
|
syncResponse := types.NewResponse()
|
||||||
syncResponse = leaveResponseWithRooms(syncResponse, syncingUser, []string{newlyLeftRoom})
|
syncResponse = leaveResponseWithRooms(syncResponse, syncingUser, []string{newlyLeftRoom})
|
||||||
|
|
||||||
rsAPI := &mockRoomserverAPI{
|
rsAPI := &keyChangeMockRoomserverAPI{
|
||||||
roomIDToJoinedMembers: map[string][]string{
|
roomIDToJoinedMembers: map[string][]string{
|
||||||
newlyLeftRoom: {existingUser},
|
newlyLeftRoom: {existingUser},
|
||||||
"!another:room": {syncingUser, existingUser},
|
"!another:room": {syncingUser, existingUser},
|
||||||
|
@ -343,7 +343,7 @@ func TestKeyChangeCatchupNoNewJoinsButMessages(t *testing.T) {
|
||||||
jr.Timeline = &types.Timeline{Events: roomTimelineEvents}
|
jr.Timeline = &types.Timeline{Events: roomTimelineEvents}
|
||||||
syncResponse.Rooms.Join[roomID] = jr
|
syncResponse.Rooms.Join[roomID] = jr
|
||||||
|
|
||||||
rsAPI := &mockRoomserverAPI{
|
rsAPI := &keyChangeMockRoomserverAPI{
|
||||||
roomIDToJoinedMembers: map[string][]string{
|
roomIDToJoinedMembers: map[string][]string{
|
||||||
roomID: {syncingUser, existingUser},
|
roomID: {syncingUser, existingUser},
|
||||||
},
|
},
|
||||||
|
@ -369,7 +369,7 @@ func TestKeyChangeCatchupChangeAndLeft(t *testing.T) {
|
||||||
syncResponse = joinResponseWithRooms(syncResponse, syncingUser, []string{newlyJoinedRoom})
|
syncResponse = joinResponseWithRooms(syncResponse, syncingUser, []string{newlyJoinedRoom})
|
||||||
syncResponse = leaveResponseWithRooms(syncResponse, syncingUser, []string{newlyLeftRoom})
|
syncResponse = leaveResponseWithRooms(syncResponse, syncingUser, []string{newlyLeftRoom})
|
||||||
|
|
||||||
rsAPI := &mockRoomserverAPI{
|
rsAPI := &keyChangeMockRoomserverAPI{
|
||||||
roomIDToJoinedMembers: map[string][]string{
|
roomIDToJoinedMembers: map[string][]string{
|
||||||
newlyJoinedRoom: {syncingUser, newShareUser, newShareUser2},
|
newlyJoinedRoom: {syncingUser, newShareUser, newShareUser2},
|
||||||
newlyLeftRoom: {newlyLeftUser, newlyLeftUser2},
|
newlyLeftRoom: {newlyLeftUser, newlyLeftUser2},
|
||||||
|
@ -459,7 +459,7 @@ func TestKeyChangeCatchupChangeAndLeftSameRoom(t *testing.T) {
|
||||||
lr.Timeline = &types.Timeline{Events: roomEvents}
|
lr.Timeline = &types.Timeline{Events: roomEvents}
|
||||||
syncResponse.Rooms.Leave[roomID] = lr
|
syncResponse.Rooms.Leave[roomID] = lr
|
||||||
|
|
||||||
rsAPI := &mockRoomserverAPI{
|
rsAPI := &keyChangeMockRoomserverAPI{
|
||||||
roomIDToJoinedMembers: map[string][]string{
|
roomIDToJoinedMembers: map[string][]string{
|
||||||
roomID: {newShareUser, newShareUser2},
|
roomID: {newShareUser, newShareUser2},
|
||||||
"!another:room": {syncingUser},
|
"!another:room": {syncingUser},
|
||||||
|
|
|
@ -16,6 +16,8 @@
|
||||||
package synctypes
|
package synctypes
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
"github.com/matrix-org/gomatrixserverlib"
|
"github.com/matrix-org/gomatrixserverlib"
|
||||||
"github.com/matrix-org/gomatrixserverlib/spec"
|
"github.com/matrix-org/gomatrixserverlib/spec"
|
||||||
)
|
)
|
||||||
|
@ -118,3 +120,31 @@ func ToClientEventDefault(userIDQuery spec.UserIDForSender, event gomatrixserver
|
||||||
}
|
}
|
||||||
return ToClientEvent(event, FormatAll, sender, sk)
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue