Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
158 changes: 151 additions & 7 deletions tests/msc3083_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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,
})
}

Expand Down Expand Up @@ -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"})
Expand All @@ -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)
Expand All @@ -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,
Expand All @@ -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.
Expand Down Expand Up @@ -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"})
Expand Down Expand Up @@ -309,6 +309,150 @@ 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 hs1 (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)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why don't you use setupRestrictedRoom in the previous test?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The joiners need to be on the same server while the creator needs to be on a separate server. Unfortunately BlueprintFederationTwoLocalOneRemote has alice and bob on one server and charlie on another.


// 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"})
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't we wait for these to come down alice sync stream?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It doesn't matter if alice sees them.


// 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, so the join fails.
failJoinRoom(t, charlie, room, "hs2", 502)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fairly sure there won't be any guarantee that hs2 would've seen the join from hs3 because the join was between hs3 and hs1. Therefore, doing this will be racey.


// 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("join_authorised_via_users_server").Str, alice.UserID, "Join authorised via incorrect server")

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
}
return ev.Get("content").Get("membership").Str == "leave"
},
)

// 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("join_authorised_via_users_server").Str, alice.UserID, "Join authorised via incorrect server")

return true
},
)
}

// 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()
Expand Down