From a3dc950d1139a5cf717d8f9c612721eff1da6977 Mon Sep 17 00:00:00 2001 From: Patrick Cloke Date: Wed, 21 Jul 2021 12:21:58 -0400 Subject: [PATCH 1/5] Add tests for failover. --- tests/msc3083_test.go | 91 +++++++++++++++++++++++++++++++++++++++---- 1 file changed, 84 insertions(+), 7 deletions(-) diff --git a/tests/msc3083_test.go b/tests/msc3083_test.go index 9da71271..29c93ded 100644 --- a/tests/msc3083_test.go +++ b/tests/msc3083_test.go @@ -17,7 +17,7 @@ import ( "github.com/tidwall/gjson" ) -func failJoinRoom(t *testing.T, c *client.CSAPI, roomIDOrAlias string, serverName string) { +func failJoinRoom(t *testing.T, c *client.CSAPI, roomIDOrAlias string, serverName string, expectedErrorCode int) { t.Helper() // This is copied from Client.JoinRoom to test a join failure. @@ -30,7 +30,7 @@ func failJoinRoom(t *testing.T, c *client.CSAPI, roomIDOrAlias string, serverNam client.WithQueries(query), ) must.MatchResponse(t, res, match.HTTPResponse{ - StatusCode: 403, + StatusCode: expectedErrorCode, }) } @@ -84,7 +84,7 @@ func setupRestrictedRoom(t *testing.T, deployment *docker.Deployment) (*client.C func checkRestrictedRoom(t *testing.T, alice *client.CSAPI, bob *client.CSAPI, space string, room string) { t.Helper() - failJoinRoom(t, bob, room, "hs1") + failJoinRoom(t, bob, room, "hs1", 403) // Join the space, attempt to join the room again, which now should succeed. bob.JoinRoom(t, space, []string{"hs1"}) @@ -108,7 +108,7 @@ func checkRestrictedRoom(t *testing.T, alice *client.CSAPI, bob *client.CSAPI, s return ev.Get("content").Get("membership").Str == "leave" }) - failJoinRoom(t, bob, room, "hs1") + failJoinRoom(t, bob, room, "hs1", 403) // Invite the user and joining should work. alice.InviteRoom(t, room, bob.UserID) @@ -135,7 +135,7 @@ func checkRestrictedRoom(t *testing.T, alice *client.CSAPI, bob *client.CSAPI, s }, ) // Fails since invalid values get filtered out of allow. - failJoinRoom(t, bob, room, "hs1") + failJoinRoom(t, bob, room, "hs1", 403) alice.SendEventSynced( t, @@ -151,7 +151,7 @@ func checkRestrictedRoom(t *testing.T, alice *client.CSAPI, bob *client.CSAPI, s }, ) // Fails since a fully invalid allow key requires an invite. - failJoinRoom(t, bob, room, "hs1") + failJoinRoom(t, bob, room, "hs1", 403) } // Test joining a room with join rules restricted to membership in a space. @@ -250,7 +250,7 @@ func TestRestrictedRoomsRemoteJoinLocalUser(t *testing.T) { }) // Bob cannot join the room. - failJoinRoom(t, bob, room, "hs1") + failJoinRoom(t, bob, room, "hs1", 403) // Join the space via hs2. bob.JoinRoom(t, space, []string{"hs2"}) @@ -309,6 +309,83 @@ func TestRestrictedRoomsRemoteJoinLocalUser(t *testing.T) { bob.JoinRoom(t, room, []string{"hs1"}) } +// A server will request a failover if asked to /make_join and it does not have +// the appropriate authorisation to complete the request. +// +// Setup 3 homeservers: +// * hs1 creates the space/room. +// * hs2 joins the room +// * hs3 attempts to join via hs2 (should fail) and hs3 (should work) +func TestRestrictedRoomsRemoteJoinFailOver(t *testing.T) { + deployment := Deploy(t, b.Blueprint{ + Name: "federation_three_homeservers", + Homeservers: []b.Homeserver{ + { + Name: "hs1", + Users: []b.User{ + { + Localpart: "alice", + DisplayName: "Alice", + }, + }, + }, + { + Name: "hs2", + Users: []b.User{ + { + Localpart: "bob", + DisplayName: "Bob", + }, + }, + }, + { + Name: "hs3", + Users: []b.User{ + { + Localpart: "charlie", + DisplayName: "Charlie", + }, + }, + }, + }, + }) + defer deployment.Destroy(t) + + // Setup the user, space, and restricted room. + alice, space, room := setupRestrictedRoom(t, deployment) + + // Raise the power level so that only alice can invite. + state_key := "" + alice.SendEventSynced(t, room, b.Event{ + Type: "m.room.power_levels", + StateKey: &state_key, + Content: map[string]interface{}{ + "invite": 100, + "users": map[string]interface{}{ + alice.UserID: 100, + }, + }, + }) + + // Create a second user on a different homeserver. + bob := deployment.Client(t, "hs2", "@bob:hs2") + + // Bob joins the room and space. + bob.JoinRoom(t, space, []string{"hs1"}) + bob.JoinRoom(t, room, []string{"hs1"}) + + // Charlie should join the space (which gives access to the room), but joining + // via just hs1 will fail. + charlie := deployment.Client(t, "hs3", "@charlie:hs3") + charlie.JoinRoom(t, space, []string{"hs1"}) + + // hs2 doesn't have anyone to invite from. + failJoinRoom(t, charlie, room, "hs2", 502) + + // Failing over to hs1 works. + charlie.JoinRoom(t, room, []string{"hs2", "hs1"}) +} + // 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() From a818c33016b697685e6b17f73733f262921a7657 Mon Sep 17 00:00:00 2001 From: Patrick Cloke Date: Wed, 21 Jul 2021 15:12:16 -0400 Subject: [PATCH 2/5] Test failing over when the resident server is not in an allowed room. --- tests/msc3083_test.go | 77 ++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 73 insertions(+), 4 deletions(-) diff --git a/tests/msc3083_test.go b/tests/msc3083_test.go index 29c93ded..3f795c38 100644 --- a/tests/msc3083_test.go +++ b/tests/msc3083_test.go @@ -374,16 +374,85 @@ func TestRestrictedRoomsRemoteJoinFailOver(t *testing.T) { bob.JoinRoom(t, space, []string{"hs1"}) bob.JoinRoom(t, room, []string{"hs1"}) - // Charlie should join the space (which gives access to the room), but joining - // via just hs1 will fail. + // Charlie should join the space (which gives access to the room). charlie := deployment.Client(t, "hs3", "@charlie:hs3") charlie.JoinRoom(t, space, []string{"hs1"}) - // hs2 doesn't have anyone to invite from. + // hs2 doesn't have anyone to invite from, so the join fails. failJoinRoom(t, charlie, room, "hs2", 502) - // Failing over to hs1 works. + // Including hs1 (and failing over to it) allows the join to succeed. charlie.JoinRoom(t, room, []string{"hs2", "hs1"}) + + // Double check that the join was authorised via hs1. + bob.SyncUntilTimelineHas( + t, + room, + func(ev gjson.Result) bool { + if ev.Get("type").Str != "m.room.member" || ev.Get("state_key").Str != charlie.UserID { + return false + } + must.EqualStr(t, ev.Get("content").Get("membership").Str, "join", "Charlie failed to join the room") + must.EqualStr(t, ev.Get("content").Get("membership").Str, "join_authorised_via_users_server", alice.UserID) + + return true + }, + ) + + // Bump the power-level of bob. + alice.SendEventSynced(t, room, b.Event{ + Type: "m.room.power_levels", + StateKey: &state_key, + Content: map[string]interface{}{ + "invite": 100, + "users": map[string]interface{}{ + alice.UserID: 100, + bob.UserID: 100, + }, + }, + }) + + // Charlie leaves the room (so they can rejoin). + charlie.LeaveRoom(t, room) + + // Ensure the events have synced to hs2. + bob.SyncUntilTimelineHas( + t, + room, + func(ev gjson.Result) bool { + if ev.Get("type").Str != "m.room.member" || ev.Get("state_key").Str != charlie.UserID { + return false + } + must.EqualStr(t, ev.Get("content").Get("membership").Str, "leave", "Charlie failed to leave the room") + + return true + }, + ) + + // Bob leaves the space so that hs2 doesn't know if Charlie is in the space or not. + bob.LeaveRoom(t, space) + + // hs2 cannot complete the join since they do not know if Charlie meets the + // requirements (since it is no longer in the space). + failJoinRoom(t, charlie, room, "hs2", 502) + + // Including hs1 (and failing over to it) allows the join to succeed. + charlie.JoinRoom(t, room, []string{"hs2", "hs1"}) + + // Double check that the join was authorised via hs1. + bob.SyncUntilTimelineHas( + t, + room, + func(ev gjson.Result) bool { + if ev.Get("type").Str != "m.room.member" || ev.Get("state_key").Str != charlie.UserID { + return false + } + must.EqualStr(t, ev.Get("content").Get("membership").Str, "join", "Charlie failed to join the room") + must.EqualStr(t, ev.Get("content").Get("membership").Str, "join_authorised_via_users_server", alice.UserID) + + return true + }, + ) } // Request the room summary and ensure the expected rooms are in the response. From 4dba4672fd1e53550270030c26da8bc6f7785ee9 Mon Sep 17 00:00:00 2001 From: Patrick Cloke Date: Thu, 22 Jul 2021 11:55:35 -0400 Subject: [PATCH 3/5] Properly check that the join is authorized. --- tests/msc3083_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/msc3083_test.go b/tests/msc3083_test.go index 3f795c38..6d659bbf 100644 --- a/tests/msc3083_test.go +++ b/tests/msc3083_test.go @@ -393,7 +393,7 @@ func TestRestrictedRoomsRemoteJoinFailOver(t *testing.T) { return false } must.EqualStr(t, ev.Get("content").Get("membership").Str, "join", "Charlie failed to join the room") - must.EqualStr(t, ev.Get("content").Get("membership").Str, "join_authorised_via_users_server", alice.UserID) + must.EqualStr(t, ev.Get("content").Get("join_authorised_via_users_server").Str, alice.UserID, "Join authorised via incorrect server") return true }, @@ -448,7 +448,7 @@ func TestRestrictedRoomsRemoteJoinFailOver(t *testing.T) { return false } must.EqualStr(t, ev.Get("content").Get("membership").Str, "join", "Charlie failed to join the room") - must.EqualStr(t, ev.Get("content").Get("membership").Str, "join_authorised_via_users_server", alice.UserID) + must.EqualStr(t, ev.Get("content").Get("join_authorised_via_users_server").Str, alice.UserID, "Join authorised via incorrect server") return true }, From 11ddf9cadfbd1b931f9846b2f03a49abc5d674a5 Mon Sep 17 00:00:00 2001 From: Patrick Cloke Date: Thu, 22 Jul 2021 11:55:44 -0400 Subject: [PATCH 4/5] Keep syncing until the leave is seen. --- tests/msc3083_test.go | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/tests/msc3083_test.go b/tests/msc3083_test.go index 6d659bbf..077b89ac 100644 --- a/tests/msc3083_test.go +++ b/tests/msc3083_test.go @@ -423,9 +423,7 @@ func TestRestrictedRoomsRemoteJoinFailOver(t *testing.T) { if ev.Get("type").Str != "m.room.member" || ev.Get("state_key").Str != charlie.UserID { return false } - must.EqualStr(t, ev.Get("content").Get("membership").Str, "leave", "Charlie failed to leave the room") - - return true + return ev.Get("content").Get("membership").Str == "leave" }, ) From 24ecdb8e573d45f8d6a117a451a951f50ab6965d Mon Sep 17 00:00:00 2001 From: Patrick Cloke Date: Fri, 30 Jul 2021 14:16:43 -0400 Subject: [PATCH 5/5] Fix typo. --- tests/msc3083_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/msc3083_test.go b/tests/msc3083_test.go index 077b89ac..58a79ade 100644 --- a/tests/msc3083_test.go +++ b/tests/msc3083_test.go @@ -315,7 +315,7 @@ func TestRestrictedRoomsRemoteJoinLocalUser(t *testing.T) { // Setup 3 homeservers: // * hs1 creates the space/room. // * hs2 joins the room -// * hs3 attempts to join via hs2 (should fail) and hs3 (should work) +// * hs3 attempts to join via hs2 (should fail) and hs1 (should work) func TestRestrictedRoomsRemoteJoinFailOver(t *testing.T) { deployment := Deploy(t, b.Blueprint{ Name: "federation_three_homeservers",