From 5d11bbb558dccd1fc6bbd2c0f9dbaf097af221b5 Mon Sep 17 00:00:00 2001 From: timedout Date: Mon, 22 Dec 2025 20:17:51 +0000 Subject: [PATCH 1/6] Add `TestRestrictedRoomsLocalJoinNoCreatorsUsesPowerLevels` Signed-off-by: timedout --- tests/restricted_rooms_test.go | 74 ++++++++++++++++++++++++++++++++++ 1 file changed, 74 insertions(+) diff --git a/tests/restricted_rooms_test.go b/tests/restricted_rooms_test.go index 8bb8406d..0f69052c 100644 --- a/tests/restricted_rooms_test.go +++ b/tests/restricted_rooms_test.go @@ -446,3 +446,77 @@ func doTestRestrictedRoomsRemoteJoinFailOver(t *testing.T, roomVersion string, j return true })) } + +// A server will attempt to perform a local join to a room with creators (v12), +// using a creator as the authorising user, but falling back to power levels if +// an appropriate creator cannot be found. +// +// ref: https://github.com/element-hq/synapse/issues/19120 +func TestRestrictedRoomsLocalJoinNoCreatorsUsesPowerLevels(t *testing.T) { + doTestRestrictedRoomsLocalJoinNoCreatorsUsesPowerLevels(t, "12", "restricted") +} + +func doTestRestrictedRoomsLocalJoinNoCreatorsUsesPowerLevels(t *testing.T, roomVersion string, joinRule string) { + deployment := complement.Deploy(t, 2) + defer deployment.Destroy(t) + // Create the room + alice, allowed_room, room := setupRestrictedRoom(t, deployment, roomVersion, joinRule) + // Create two users on the other homeserver. + bob := deployment.Register(t, "hs2", helpers.RegistrationOpts{}) + charlie := deployment.Register(t, "hs2", helpers.RegistrationOpts{}) + + // Bob joins the allowed room. + bob.JoinRoom(t, allowed_room, []spec.ServerName{ + deployment.GetFullyQualifiedHomeserverName(t, "hs1"), + }) + // Bob joins the restricted room. This join should go remotely + // and consequently be authorised by Alice as she is the only + // member. + bob.JoinRoom(t, room, []spec.ServerName{ + deployment.GetFullyQualifiedHomeserverName(t, "hs1"), + }) + // Ensure the join was authorised by alice + bob.MustSyncUntil(t, client.SyncReq{}, client.SyncJoinedTo(bob.UserID, room, func(ev gjson.Result) bool { + must.MatchGJSON(t, ev, + match.JSONKeyEqual("content.join_authorised_via_users_server", alice.UserID), + ) + return true + })) + + // Alice restricts the invite power level to moderators and promotes Bob to + // moderator. + state_key := "" + alice.SendEventSynced(t, room, b.Event{ + Type: "m.room.power_levels", + StateKey: &state_key, + Content: map[string]interface{}{ + "invite": 50, + "users": map[string]interface{}{ + bob.UserID: 50, + }, + }, + }) + + // Charlie joins the allowed room. + charlie.JoinRoom(t, allowed_room, []spec.ServerName{ + deployment.GetFullyQualifiedHomeserverName(t, "hs1"), + }) + // Charlie attempts to join the restricted room. + // hs2 should attempt to find a creator to authorise the room join, + // but can't as the only creator is remote. It should then fall back + // to checking the power levels for a user that can authorise the join. + // The end result should be that charlie is allowed to join. + charlie.JoinRoom(t, room, []spec.ServerName{ + deployment.GetFullyQualifiedHomeserverName(t, "hs2"), + }) + + // Ensure the join was authorised by bob. The join should not be + // authorised by alice as hs2 should not have attempted a remote + // join given bob is a local user that can authorise the join. + bob.MustSyncUntil(t, client.SyncReq{}, client.SyncJoinedTo(charlie.UserID, room, func(ev gjson.Result) bool { + must.MatchGJSON(t, ev, + match.JSONKeyEqual("content.join_authorised_via_users_server", bob.UserID), + ) + return true + })) +} From f47ed5e9115867f2a44fd0b1d255244e359d82d6 Mon Sep 17 00:00:00 2001 From: timedout Date: Thu, 8 Jan 2026 15:35:29 +0000 Subject: [PATCH 2/6] Update TestRestrictedRoomsLocalJoinNoCreatorsUsesPowerLevels Co-authored-by: Eric Eastwood --- tests/restricted_rooms_test.go | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/tests/restricted_rooms_test.go b/tests/restricted_rooms_test.go index 0f69052c..ef3d665a 100644 --- a/tests/restricted_rooms_test.go +++ b/tests/restricted_rooms_test.go @@ -447,11 +447,18 @@ func doTestRestrictedRoomsRemoteJoinFailOver(t *testing.T, roomVersion string, j })) } -// A server will attempt to perform a local join to a room with creators (v12), -// using a creator as the authorising user, but falling back to power levels if -// an appropriate creator cannot be found. +// A homeserver should be able to do a local join (when someone else from the same +// homeserver is already joined to the room) to a room using any local user who has +// invite power levels. // -// ref: https://github.com/element-hq/synapse/issues/19120 +// In the test case, there are no local room creators on hs2, so hs2 will need to use +// one of the local people listed in the power levels who can invite. While hs2, could +// do another remote join to get charlie in the room, we assert it was a local join by +// checking the `join_authorised_via_users_server` (homeservers should prefer a local +// join). +// +// This is a regression test for Synapse as it previously only looked for local room +// creators in v12 rooms, https://github.com/element-hq/synapse/issues/19120 func TestRestrictedRoomsLocalJoinNoCreatorsUsesPowerLevels(t *testing.T) { doTestRestrictedRoomsLocalJoinNoCreatorsUsesPowerLevels(t, "12", "restricted") } From d36fc46d878e39ed727d8bccff8f658df9631f79 Mon Sep 17 00:00:00 2001 From: timedout Date: Thu, 8 Jan 2026 15:36:02 +0000 Subject: [PATCH 3/6] Perform new test against v12 and v11 Signed-off-by: timedout --- tests/restricted_rooms_test.go | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/restricted_rooms_test.go b/tests/restricted_rooms_test.go index ef3d665a..9d7b5bc0 100644 --- a/tests/restricted_rooms_test.go +++ b/tests/restricted_rooms_test.go @@ -461,6 +461,7 @@ func doTestRestrictedRoomsRemoteJoinFailOver(t *testing.T, roomVersion string, j // creators in v12 rooms, https://github.com/element-hq/synapse/issues/19120 func TestRestrictedRoomsLocalJoinNoCreatorsUsesPowerLevels(t *testing.T) { doTestRestrictedRoomsLocalJoinNoCreatorsUsesPowerLevels(t, "12", "restricted") + doTestRestrictedRoomsLocalJoinNoCreatorsUsesPowerLevels(t, "11", "restricted") } func doTestRestrictedRoomsLocalJoinNoCreatorsUsesPowerLevels(t *testing.T, roomVersion string, joinRule string) { From 7b140fa5b57eaf8430cee8aa6ce8fa6ac7d0b89f Mon Sep 17 00:00:00 2001 From: timedout Date: Thu, 8 Jan 2026 18:36:52 +0000 Subject: [PATCH 4/6] Split v12 and v11 local restricted join tests Signed-off-by: timedout --- tests/restricted_rooms_test.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/restricted_rooms_test.go b/tests/restricted_rooms_test.go index 9d7b5bc0..22d54abb 100644 --- a/tests/restricted_rooms_test.go +++ b/tests/restricted_rooms_test.go @@ -459,8 +459,11 @@ func doTestRestrictedRoomsRemoteJoinFailOver(t *testing.T, roomVersion string, j // // This is a regression test for Synapse as it previously only looked for local room // creators in v12 rooms, https://github.com/element-hq/synapse/issues/19120 -func TestRestrictedRoomsLocalJoinNoCreatorsUsesPowerLevels(t *testing.T) { +func TestRestrictedRoomsLocalJoinNoCreatorsUsesPowerLevelsV12(t *testing.T) { doTestRestrictedRoomsLocalJoinNoCreatorsUsesPowerLevels(t, "12", "restricted") +} + +func TestRestrictedRoomsLocalJoinNoCreatorsUsesPowerLevelsV11(t *testing.T) { doTestRestrictedRoomsLocalJoinNoCreatorsUsesPowerLevels(t, "11", "restricted") } From ee6936d39ea07a0de72459250962005309d77c6b Mon Sep 17 00:00:00 2001 From: timedout Date: Thu, 8 Jan 2026 20:03:28 +0000 Subject: [PATCH 5/6] Clarify comments in new test & fix v11 test Signed-off-by: timedout --- tests/restricted_rooms_test.go | 44 +++++++++++++++++++++++----------- 1 file changed, 30 insertions(+), 14 deletions(-) diff --git a/tests/restricted_rooms_test.go b/tests/restricted_rooms_test.go index 22d54abb..aa8f4b6a 100644 --- a/tests/restricted_rooms_test.go +++ b/tests/restricted_rooms_test.go @@ -481,7 +481,7 @@ func doTestRestrictedRoomsLocalJoinNoCreatorsUsesPowerLevels(t *testing.T, roomV deployment.GetFullyQualifiedHomeserverName(t, "hs1"), }) // Bob joins the restricted room. This join should go remotely - // and consequently be authorised by Alice as she is the only + // and consequently be authorised by Alice (on hs1) as she is the only // member. bob.JoinRoom(t, room, []spec.ServerName{ deployment.GetFullyQualifiedHomeserverName(t, "hs1"), @@ -497,26 +497,42 @@ func doTestRestrictedRoomsLocalJoinNoCreatorsUsesPowerLevels(t *testing.T, roomV // Alice restricts the invite power level to moderators and promotes Bob to // moderator. state_key := "" - alice.SendEventSynced(t, room, b.Event{ - Type: "m.room.power_levels", - StateKey: &state_key, - Content: map[string]interface{}{ - "invite": 50, - "users": map[string]interface{}{ - bob.UserID: 50, + if roomVersion == "12" { + // Alice is a creator and cannot appear in the power levels + alice.SendEventSynced(t, room, b.Event{ + Type: "m.room.power_levels", + StateKey: &state_key, + Content: map[string]interface{}{ + "invite": 50, + "users": map[string]interface{}{ + bob.UserID: 50, + }, }, - }, - }) + }) + } else { + // rooms Date: Thu, 8 Jan 2026 20:10:56 +0000 Subject: [PATCH 6/6] Add associated tests for knock_restricted Signed-off-by: timedout --- tests/restricted_rooms_test.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tests/restricted_rooms_test.go b/tests/restricted_rooms_test.go index aa8f4b6a..99d63b9c 100644 --- a/tests/restricted_rooms_test.go +++ b/tests/restricted_rooms_test.go @@ -467,6 +467,14 @@ func TestRestrictedRoomsLocalJoinNoCreatorsUsesPowerLevelsV11(t *testing.T) { doTestRestrictedRoomsLocalJoinNoCreatorsUsesPowerLevels(t, "11", "restricted") } +func TestKnockRestrictedRoomsLocalJoinNoCreatorsUsesPowerLevelsV12(t *testing.T) { + doTestRestrictedRoomsLocalJoinNoCreatorsUsesPowerLevels(t, "12", "knock_restricted") +} + +func TestKnockRestrictedRoomsLocalJoinNoCreatorsUsesPowerLevelsV11(t *testing.T) { + doTestRestrictedRoomsLocalJoinNoCreatorsUsesPowerLevels(t, "11", "knock_restricted") +} + func doTestRestrictedRoomsLocalJoinNoCreatorsUsesPowerLevels(t *testing.T, roomVersion string, joinRule string) { deployment := complement.Deploy(t, 2) defer deployment.Destroy(t)