diff --git a/dockerfiles/synapse/homeserver.yaml b/dockerfiles/synapse/homeserver.yaml index af4d5e59..b2415afc 100644 --- a/dockerfiles/synapse/homeserver.yaml +++ b/dockerfiles/synapse/homeserver.yaml @@ -105,3 +105,5 @@ AS_REGISTRATION_FILES experimental_features: # Enable knocking support msc2403_enabled: true + # Enable spaces support + spaces_enabled: true diff --git a/internal/client/client.go b/internal/client/client.go index 28ef89d5..d7e42709 100644 --- a/internal/client/client.go +++ b/internal/client/client.go @@ -84,6 +84,23 @@ func (c *CSAPI) JoinRoom(t *testing.T, roomIDOrAlias string, serverNames []strin return GetJSONFieldStr(t, body, "room_id") } +// LeaveRoom joins the room ID, else fails the test. +func (c *CSAPI) LeaveRoom(t *testing.T, roomID string) { + t.Helper() + // leave the room + c.MustDoRaw(t, "POST", []string{"_matrix", "client", "r0", "rooms", roomID, "leave"}, nil, "application/json", nil) +} + +// InviteRoom invites userID to the room ID, else fails the test. +func (c *CSAPI) InviteRoom(t *testing.T, roomID string, userID string) { + t.Helper() + // Invite the user to the room + body := map[string]interface{}{ + "user_id": userID, + } + c.MustDo(t, "POST", []string{"_matrix", "client", "r0", "rooms", roomID, "invite"}, body) +} + // SendEventSynced sends `e` into the room and waits for its event ID to come down /sync. // Returns the event ID of the sent event. func (c *CSAPI) SendEventSynced(t *testing.T, roomID string, e b.Event) string { diff --git a/tests/msc3083_test.go b/tests/msc3083_test.go new file mode 100644 index 00000000..86b51327 --- /dev/null +++ b/tests/msc3083_test.go @@ -0,0 +1,131 @@ +// +build msc3083 + +// Tests MSC3083, an experimental feature for joining restricted rooms based on +// membership in a space. + +package tests + +import ( + "net/url" + "testing" + + "github.com/matrix-org/complement/internal/b" + "github.com/matrix-org/complement/internal/client" +) + +var ( + spaceChildEventType = "org.matrix.msc1772.space.child" + spaceParentEventType = "org.matrix.msc1772.space.parent" +) + +func FailJoinRoom(c *client.CSAPI, t *testing.T, roomIDOrAlias string, serverName string) { + // This is copied from Client.JoinRoom to test a join failure. + query := make(url.Values, 1) + query.Set("server_name", serverName) + c.MustDoWithStatusRaw( + t, + "POST", + []string{"_matrix", "client", "r0", "join", roomIDOrAlias}, + nil, + "application/json", + query, + 403, + ) +} + +// Test joining a room with join rules restricted to membership in a space. +func TestRestrictedRoomsLocalJoin(t *testing.T) { + deployment := Deploy(t, "msc3083", b.BlueprintOneToOneRoom) + defer deployment.Destroy(t) + + // Create the space and put a room in it. + alice := deployment.Client(t, "hs1", "@alice:hs1") + space := alice.CreateRoom(t, map[string]interface{}{ + "preset": "public_chat", + "name": "Space", + }) + // The room is an unstable room version which supports the restricted join_rule. + room := alice.CreateRoom(t, map[string]interface{}{ + "preset": "public_chat", + "name": "Room", + "room_version": "org.matrix.msc3083", + "initial_state": []map[string]interface{}{ + { + "type": "m.room.join_rules", + "state_key": "", + "content": map[string]interface{}{ + "join_rule": "restricted", + "allow": []map[string]interface{}{ + { + "space": &space, + "via": []string{"hs1"}, + }, + }, + }, + }, + }, + }) + alice.SendEventSynced(t, space, b.Event{ + Type: spaceChildEventType, + StateKey: &room, + Content: map[string]interface{}{ + "via": []string{"hs1"}, + }, + }) + + // Create a second user and attempt to join the room, it should fail. + bob := deployment.Client(t, "hs1", "@bob:hs1") + FailJoinRoom(bob, t, room, "hs1") + + // Join the space, attempt to join the room again, which now should succeed. + bob.JoinRoom(t, space, []string{"hs1"}) + bob.JoinRoom(t, room, []string{"hs1"}) + + // Leaving the room works and the user is unable to re-join. + bob.LeaveRoom(t, room) + bob.LeaveRoom(t, space) + FailJoinRoom(bob, t, room, "hs1") + + // Invite the user and joining should work. + alice.InviteRoom(t, room, "@bob:hs1") + bob.JoinRoom(t, room, []string{"hs1"}) + + // Leave the room again, and join the space. + bob.LeaveRoom(t, room) + bob.JoinRoom(t, space, []string{"hs1"}) + + // Update the room to have bad values in the "allow" field, which should stop + // joining from working properly. + emptyStateKey := "" + alice.SendEventSynced( + t, + room, + b.Event{ + Type: "m.room.join_rules", + Sender: alice.UserID, + StateKey: &emptyStateKey, + Content: map[string]interface{}{ + "join_rule": "restricted", + "allow": []string{"invalid"}, + }, + }, + ) + // Fails since invalid values get filtered out of allow. + FailJoinRoom(bob, t, room, "hs1") + + alice.SendEventSynced( + t, + room, + b.Event{ + Type: "m.room.join_rules", + Sender: alice.UserID, + StateKey: &emptyStateKey, + Content: map[string]interface{}{ + "join_rule": "restricted", + "allow": "invalid", + }, + }, + ) + // Fails since a fully invalid allow key rquires an invite. + FailJoinRoom(bob, t, room, "hs1") +}