diff --git a/federation/server.go b/federation/server.go index ba7478de..3e0ed3cf 100644 --- a/federation/server.go +++ b/federation/server.go @@ -180,18 +180,35 @@ func (s *Server) MustMakeRoom(t ct.TestLike, roomVer gomatrixserverlib.RoomVersi if !s.listening { ct.Fatalf(s.t, "MustMakeRoom() called before Listen() - this is not supported because Listen() chooses a high-numbered port and thus changes the server name and thus changes the room ID. Ensure you Listen() first!") } + // Generate a unique room ID, prefixed with an incrementing counter. // This ensures that room IDs are not re-used across tests, even if a Complement server happens // to re-use the same port as a previous one, which // * reduces noise when searching through logs and // * prevents homeservers from getting confused when multiple test cases re-use the same homeserver deployment. + // This value is temporary for domainless room IDs and will be replaced with the create event ID. roomID := fmt.Sprintf("!%d-%s:%s", len(s.rooms), util.RandomString(18), s.serverName) - t.Logf("Creating room %s with version %s", roomID, roomVer) room := NewServerRoom(roomVer, roomID) for _, opt := range opts { + // let the caller replace the room impl before we try to create events opt(room) } + iRoomVer := gomatrixserverlib.MustGetRoomVersion(roomVer) + if iRoomVer.DomainlessRoomIDs() { + if len(events) == 0 || events[0].Type != spec.MRoomCreate { + ct.Fatalf(s.t, "MustMakeRoom: room version %s requires the create event as an initial event but it wasn't found", roomVer) + } + room.RoomID = "" + // build and sign the create event to work out the room ID + createEvent := s.MustCreateEvent(t, room, events[0]) + events = events[1:] + room.RoomID = "!" + createEvent.EventID()[1:] + room.AddEvent(createEvent) + } + + t.Logf("Creating room %s with version %s", room.RoomID, roomVer) + // sign all these events for _, ev := range events { signedEvent := s.MustCreateEvent(t, room, ev) diff --git a/federation/server_room.go b/federation/server_room.go index 5401a234..61c40ed3 100644 --- a/federation/server_room.go +++ b/federation/server_room.go @@ -10,6 +10,7 @@ import ( "github.com/matrix-org/gomatrixserverlib" "github.com/matrix-org/gomatrixserverlib/fclient" "github.com/matrix-org/gomatrixserverlib/spec" + "github.com/matrix-org/util" "github.com/matrix-org/complement/b" "github.com/matrix-org/complement/ct" @@ -302,7 +303,7 @@ func (r *ServerRoom) GetEventInTimeline(eventID string) (gomatrixserverlib.PDU, return nil, false } -func initialPowerLevelsContent(roomCreator string) (c gomatrixserverlib.PowerLevelContent) { +func initialPowerLevelsContent(ver gomatrixserverlib.IRoomVersion, roomCreator string) (c gomatrixserverlib.PowerLevelContent) { c.Defaults() c.Events = map[string]int64{ "m.room.name": 50, @@ -312,14 +313,18 @@ func initialPowerLevelsContent(roomCreator string) (c gomatrixserverlib.PowerLev "m.room.avatar": 50, "m.room.aliases": 0, // anyone can publish aliases by default. Has to be 0 else state_default is used. } - c.Users = map[string]int64{roomCreator: 100} + if ver.PrivilegedCreators() { + c.Users = map[string]int64{} + } else { + c.Users = map[string]int64{roomCreator: 100} + } return c } // InitialRoomEvents returns the initial set of events that get created when making a room. func InitialRoomEvents(roomVer gomatrixserverlib.RoomVersion, creator string) []Event { // need to serialise/deserialise to get map[string]interface{} annoyingly - plContent := initialPowerLevelsContent(creator) + plContent := initialPowerLevelsContent(gomatrixserverlib.MustGetRoomVersion(roomVer), creator) plBytes, _ := json.Marshal(plContent) var plContentMap map[string]interface{} json.Unmarshal(plBytes, &plContentMap) @@ -331,6 +336,11 @@ func InitialRoomEvents(roomVer gomatrixserverlib.RoomVersion, creator string) [] Content: map[string]interface{}{ "creator": creator, "room_version": roomVer, + // We have to add randomness to the create event, else if you create 2x v12+ rooms in the same millisecond + // they will get the same room ID, clobbering internal data structures and causing extremely confusing + // behaviour. By adding this entropy, we ensure that even if rooms are created in the same millisecond, their + // hashes will not be the same. + "complement_entropy": util.RandomString(18), }, }, { @@ -441,12 +451,15 @@ func (i *ServerRoomImplDefault) ProtoEventCreator(room *ServerRoom, ev Event) (* PrevEvents: prevEvents, AuthEvents: ev.AuthEvents, Redacts: ev.Redacts, + Version: gomatrixserverlib.MustGetRoomVersion(room.Version), } if err := proto.SetContent(ev.Content); err != nil { return nil, fmt.Errorf("EventCreator: failed to marshal event content: %s - %+v", err, ev.Content) } - if err := proto.SetUnsigned(ev.Content); err != nil { - return nil, fmt.Errorf("EventCreator: failed to marshal event unsigned: %s - %+v", err, ev.Unsigned) + if len(ev.Unsigned) > 0 { + if err := proto.SetUnsigned(ev.Unsigned); err != nil { + return nil, fmt.Errorf("EventCreator: failed to marshal event unsigned: %s - %+v", err, ev.Unsigned) + } } if proto.AuthEvents == nil { var stateNeeded gomatrixserverlib.StateNeeded @@ -454,6 +467,9 @@ func (i *ServerRoomImplDefault) ProtoEventCreator(room *ServerRoom, ev Event) (* if err != nil { return nil, fmt.Errorf("EventCreator: failed to work out auth_events : %s", err) } + if proto.Version.DomainlessRoomIDs() { + stateNeeded.Create = false + } proto.AuthEvents = room.AuthEvents(stateNeeded) } return &proto, nil diff --git a/tests/v12_test.go b/tests/v12_test.go index 70c86f53..6139bc6f 100644 --- a/tests/v12_test.go +++ b/tests/v12_test.go @@ -29,55 +29,6 @@ var maxCanonicalJSONInt = math.Pow(2, 53) - 1 const roomVersion12 = "12" -var V12ServerRoom = federation.ServerRoomImplCustom{ - ProtoEventCreatorFn: Protov12EventCreator, -} - -// Override how Complement makes proto events so we can conditionally disable/enable the inclusion of the create event -// depending on whether we're running in combined mode or not. -// Complement also doesn't set the room version correctly on the ProtoEvent as this was a new addition to GMSL. -func Protov12EventCreator(def federation.ServerRoomImpl, room *federation.ServerRoom, ev federation.Event) (*gomatrixserverlib.ProtoEvent, error) { - var prevEvents interface{} - if ev.PrevEvents != nil { - // We deliberately want to set the prev events. - prevEvents = ev.PrevEvents - } else { - // No other prev events were supplied so we'll just - // use the forward extremities of the room, which is - // the usual behaviour. - prevEvents = room.ForwardExtremities - } - proto := gomatrixserverlib.ProtoEvent{ - SenderID: ev.Sender, - Depth: int64(room.Depth + 1), // depth starts at 1 - Type: ev.Type, - StateKey: ev.StateKey, - RoomID: room.RoomID, - PrevEvents: prevEvents, - AuthEvents: ev.AuthEvents, - Redacts: ev.Redacts, - Version: gomatrixserverlib.MustGetRoomVersion(room.Version), - } - if err := proto.SetContent(ev.Content); err != nil { - return nil, fmt.Errorf("EventCreator: failed to marshal event content: %s - %+v", err, ev.Content) - } - if err := proto.SetUnsigned(ev.Content); err != nil { - return nil, fmt.Errorf("EventCreator: failed to marshal event unsigned: %s - %+v", err, ev.Unsigned) - } - if proto.AuthEvents == nil { - var stateNeeded gomatrixserverlib.StateNeeded - // this does the right thing for v12 - stateNeeded, err := gomatrixserverlib.StateNeededForProtoEvent(&proto) - if err != nil { - return nil, fmt.Errorf("EventCreator: failed to work out auth_events : %s", err) - } - // we never include the create event if the HS supports MSC4291 - stateNeeded.Create = false - proto.AuthEvents = room.AuthEvents(stateNeeded) - } - return &proto, nil -} - // Test that the creator can kick an admin created both via // trusted_private_chat and by explicit promotion, including beyond PL100. // Also checks the creator isn't in the PL event. @@ -246,7 +197,7 @@ func TestMSC4289PrivilegedRoomCreators(t *testing.T) { "room_version": roomVersion12, "preset": "public_chat", }) - room := srv.MustJoinRoom(t, deployment, "hs1", roomID, bob, federation.WithRoomOpts(federation.WithImpl(&V12ServerRoom))) + room := srv.MustJoinRoom(t, deployment, "hs1", roomID, bob) plEventID := alice.SendEventSynced(t, roomID, b.Event{ Type: spec.MRoomPowerLevels, StateKey: b.Ptr(""), @@ -663,6 +614,36 @@ func TestMSC4291RoomIDAsHashOfCreateEvent(t *testing.T) { assertCreateEventIsRoomID(t, alice, roomID) } +func TestComplementCanCreateValidV12Rooms(t *testing.T) { + deployment := complement.Deploy(t, 1) + defer deployment.Destroy(t) + alice := deployment.Register(t, "hs1", helpers.RegistrationOpts{}) + srv := federation.NewServer(t, deployment, + federation.HandleKeyRequests(), + federation.HandleMakeSendJoinRequests(), + federation.HandleTransactionRequests(nil, nil), + federation.HandleEventRequests(), + ) + srv.UnexpectedRequestsAreErrors = false + cancel := srv.Listen() + defer cancel() + bob := srv.UserID("bob") + srvRoom := srv.MustMakeRoom(t, roomVersion12, federation.InitialRoomEvents(roomVersion12, bob)) + alice.MustJoinRoom(t, srvRoom.RoomID, []spec.ServerName{srv.ServerName()}) + + msg := srv.MustCreateEvent(t, srvRoom, federation.Event{ + Type: "m.room.message", + Sender: bob, + Content: map[string]interface{}{ + "msgtype": "m.text", + "body": "Hello world", + }, + }) + srvRoom.AddEvent(msg) + srv.MustSendTransaction(t, deployment, "hs1", []json.RawMessage{msg.JSON()}, nil) + alice.MustSyncUntil(t, client.SyncReq{}, client.SyncTimelineHasEventID(srvRoom.RoomID, msg.EventID())) +} + func TestMSC4291RoomIDAsHashOfCreateEvent_AuthEventsOmitsCreateEvent(t *testing.T) { deployment := complement.Deploy(t, 1) defer deployment.Destroy(t) @@ -683,7 +664,7 @@ func TestMSC4291RoomIDAsHashOfCreateEvent_AuthEventsOmitsCreateEvent(t *testing. defer cancel() bob := srv.UserID("bob") - room := srv.MustJoinRoom(t, deployment, "hs1", roomID, bob, federation.WithRoomOpts(federation.WithImpl(&V12ServerRoom))) + room := srv.MustJoinRoom(t, deployment, "hs1", roomID, bob) createEvent := room.CurrentState(spec.MRoomCreate, "") if createEvent == nil { @@ -954,7 +935,7 @@ func TestMSC4297StateResolutionV2_1_starts_from_empty_set(t *testing.T) { "preset": "public_chat", }) bob.MustJoinRoom(t, roomID, []spec.ServerName{"hs1"}) - room := srv.MustJoinRoom(t, deployment, "hs1", roomID, charlie, federation.WithRoomOpts(federation.WithImpl(&V12ServerRoom))) + room := srv.MustJoinRoom(t, deployment, "hs1", roomID, charlie) joinRulePublic := room.CurrentState(spec.MRoomJoinRules, "") aliceJoin := room.CurrentState(spec.MRoomMember, alice.UserID) synchronisationEventID := bob.SendEventSynced(t, room.RoomID, b.Event{ @@ -1150,7 +1131,7 @@ func TestMSC4297StateResolutionV2_1_includes_conflicted_subgraph(t *testing.T) { }) alice.MustJoinRoom(t, roomID, []spec.ServerName{"hs1"}) bob.MustJoinRoom(t, roomID, []spec.ServerName{"hs1"}) - room := srv.MustJoinRoom(t, deployment, "hs1", roomID, charlie, federation.WithRoomOpts(federation.WithImpl(&V12ServerRoom))) + room := srv.MustJoinRoom(t, deployment, "hs1", roomID, charlie) firstPowerLevelEvent := room.CurrentState(spec.MRoomPowerLevels, "") alice.SendEventSynced(t, roomID, b.Event{ Type: spec.MRoomPowerLevels,