2023-08-24 10:43:51 -05:00
|
|
|
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 {
|
2024-07-27 15:30:17 -05:00
|
|
|
return nil, fmt.Errorf("failed to make event: %s", evErr.Error())
|
2023-08-24 10:43:51 -05:00
|
|
|
}
|
|
|
|
ev := types.HeaderedEvent{PDU: pdu}
|
|
|
|
events[i] = &ev
|
|
|
|
}
|
|
|
|
|
|
|
|
return events, nil
|
|
|
|
}
|