diff --git a/tests/msc3083_test.go b/tests/msc3083_test.go index 6b9a414d..706b20ec 100644 --- a/tests/msc3083_test.go +++ b/tests/msc3083_test.go @@ -1,4 +1,4 @@ -// +build msc3083 +// +build msc2946,msc3083 // Tests MSC3083, an experimental feature for joining restricted rooms based on // membership in a space. @@ -14,6 +14,7 @@ import ( "github.com/matrix-org/complement/internal/docker" "github.com/matrix-org/complement/internal/match" "github.com/matrix-org/complement/internal/must" + "github.com/tidwall/gjson" ) func failJoinRoom(t *testing.T, c *client.CSAPI, roomIDOrAlias string, serverName string) { @@ -163,3 +164,169 @@ func TestRestrictedRoomsRemoteJoin(t *testing.T) { // Execute the checks. checkRestrictedRoom(t, alice, bob, space, room) } + +// Request the room summary and ensure the expected rooms are in the response. +func requestAndAssertSummary(t *testing.T, user *client.CSAPI, space string, expected_rooms []interface{}) { + t.Helper() + + res := user.MustDo(t, "POST", []string{"_matrix", "client", "unstable", "org.matrix.msc2946", "rooms", space, "spaces"}, map[string]interface{}{}) + must.MatchResponse(t, res, match.HTTPResponse{ + JSON: []match.JSON{ + match.JSONCheckOff("rooms", expected_rooms, func(r gjson.Result) interface{} { + return r.Get("room_id").Str + }, nil), + }, + }) +} + +// Tests that MSC2946 works for a restricted room. +// +// Create a space with a room in it that has join rules restricted to membership +// in that space. +// +// The user should be unable to see the room in the spaces summary unless they +// are a member of the space. +func TestRestrictedRoomsSpacesSummary(t *testing.T) { + deployment := Deploy(t, b.BlueprintOneToOneRoom) + defer deployment.Destroy(t) + + // Create the rooms + alice := deployment.Client(t, "hs1", "@alice:hs1") + space := alice.CreateRoom(t, map[string]interface{}{ + "preset": "public_chat", + "name": "Space", + // World readable to allow peeking without joining. + "initial_state": []map[string]interface{}{ + { + "type": "m.room.history_visibility", + "state_key": "", + "content": map[string]interface{}{ + "history_visibility": "world_readable", + }, + }, + }, + }) + // 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: "org.matrix.msc1772.space.child", + StateKey: &room, + Content: map[string]interface{}{ + "via": []string{"hs1"}, + }, + }) + + t.Logf("Space: %s", space) + t.Logf("Room: %s", room) + + // Create a second user on the same homeserver. + bob := deployment.Client(t, "hs1", "@bob:hs1") + + // Querying the space returns only the space, as the room is restricted. + requestAndAssertSummary(t, bob, space, []interface{}{space}) + + // Join the space, and now the restricted room should appear. + bob.JoinRoom(t, space, []string{"hs1"}) + requestAndAssertSummary(t, bob, space, []interface{}{space, room}) +} + +// Tests that MSC2946 works over federation for a restricted room. +// +// Create a space with a room in it that has join rules restricted to membership +// in that space. The space and room are on different homeservers. While generating +// the summary of space hs1 needs to ask hs2 to generate the summary for room since +// it is not participating in the room. +// +// The user should be unable to see the room in the spaces summary unless they +// are a member of the space. +// +// This tests the interactions over federation where the space and room are on +// different homeservers, and one might not have the proper information needed to +// decide if a user is in a room. +func TestRestrictedRoomsSpacesSummaryFederation(t *testing.T) { + deployment := Deploy(t, b.BlueprintFederationTwoLocalOneRemote) + defer deployment.Destroy(t) + + // Create the rooms + alice := deployment.Client(t, "hs1", "@alice:hs1") + bob := deployment.Client(t, "hs1", "@bob:hs1") + space := alice.CreateRoom(t, map[string]interface{}{ + "preset": "public_chat", + "name": "Space", + "initial_state": []map[string]interface{}{ + { + "type": "m.room.history_visibility", + "state_key": "", + "content": map[string]string{ + "history_visibility": "world_readable", + }, + }, + }, + }) + + // The room is an unstable room version which supports the restricted join_rule + // and is created on hs2. + charlie := deployment.Client(t, "hs2", "@charlie:hs2") + room := charlie.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"}, + }, + }, + }, + }, + }, + }) + + // create the link (this doesn't really make sense since how would alice know + // about the room? but it works for testing) + alice.SendEventSynced(t, space, b.Event{ + Type: spaceChildEventType, + StateKey: &room, + Content: map[string]interface{}{ + "via": []string{"hs2"}, + }, + }) + + // The room appears for neither alice or bob initially. Although alice is in + // the space and should be able to access the room, hs2 doesn't know this! + requestAndAssertSummary(t, alice, space, []interface{}{space}) + requestAndAssertSummary(t, bob, space, []interface{}{space}) + + // charlie joins the space and now hs2 knows that alice is in the space (and + // can join the room). + charlie.JoinRoom(t, space, []string{"hs1"}) + + // The restricted room should appear for alice (who is in the space). + requestAndAssertSummary(t, alice, space, []interface{}{space, room}) + requestAndAssertSummary(t, bob, space, []interface{}{space}) +}