From 9f5b13c452c82822e86f0f9cc1d37f6689501445 Mon Sep 17 00:00:00 2001 From: Half-Shot Date: Thu, 29 May 2025 15:25:56 +0100 Subject: [PATCH 1/8] Add tests for MSC4155 --- tests/msc4155/invite_filter_test.go | 203 ++++++++++++++++++++++++++++ tests/msc4155/main_test.go | 11 ++ 2 files changed, 214 insertions(+) create mode 100644 tests/msc4155/invite_filter_test.go create mode 100644 tests/msc4155/main_test.go diff --git a/tests/msc4155/invite_filter_test.go b/tests/msc4155/invite_filter_test.go new file mode 100644 index 00000000..c31fd216 --- /dev/null +++ b/tests/msc4155/invite_filter_test.go @@ -0,0 +1,203 @@ +package tests + +import ( + "testing" + "log" + "github.com/matrix-org/complement" + "github.com/matrix-org/complement/helpers" + "github.com/matrix-org/complement/must" + "github.com/matrix-org/complement/match" + "github.com/matrix-org/gomatrixserverlib/spec" + "github.com/matrix-org/complement/client" + "encoding/json" + "github.com/matrix-org/complement/ct" + "io" +) + +const hs1Name = "hs1" +const hs2Name = "hs2" +const inviteFilterAccountData = "org.matrix.msc4155.invite_permission_config" + +// TODO: Test pagination of `GET /_matrix/client/v1/delayed_events` once +// it is implemented in a homeserver. + +// As described in https://github.com/Johennes/matrix-spec-proposals/blob/johannes/invite-filtering/proposals/4155-invite-filtering.md#proposal +type InviteFilterConfig struct { + AllowedUsers []string `json:"allowed_users,omitempty"` + IgnoredUsers []string `json:"ignored_users,omitempty"` + BlockedUsers []string `json:"blocked_users,omitempty"` + AllowedServers []string `json:"allowed_servers,omitempty"` + IgnoredServers []string `json:"ignored_servers,omitempty"` + BlockedServers []string `json:"blocked_servers,omitempty"` +} + +func TestInviteFiltering(t *testing.T) { + deployment := complement.Deploy(t, 2) + defer deployment.Destroy(t) + + // Invitee + alice := deployment.Register(t, hs1Name, helpers.RegistrationOpts{}) + evil_alice := deployment.Register(t, hs1Name, helpers.RegistrationOpts{}) + + bob := deployment.Register(t, hs2Name, helpers.RegistrationOpts{}) + evil_bob := deployment.Register(t, hs2Name, helpers.RegistrationOpts{}) + + t.Run("Can invite users normally without any rules", func(t *testing.T) { + mustSetInviteConfig(t, alice, InviteFilterConfig{}) + roomID := bob.MustCreateRoom(t, map[string]interface{}{ + "preset": "private_chat", + }) + bob.MustInviteRoom(t, roomID, alice.UserID) + alice.MustSyncUntil(t, client.SyncReq{}, client.SyncInvitedTo(alice.UserID, roomID)) + alice.MustJoinRoom(t, roomID, []spec.ServerName{}) + alice.MustSyncUntil(t, client.SyncReq{}, client.SyncJoinedTo(alice.UserID, roomID)) + bob.MustSyncUntil(t, client.SyncReq{}, client.SyncJoinedTo(alice.UserID, roomID)) + }) + t.Run("Can block a single user", func(t *testing.T) { + mustSetInviteConfig(t, alice, InviteFilterConfig{ + BlockedUsers: []string{bob.UserID}, + }) + roomID := bob.MustCreateRoom(t, map[string]interface{}{ + "preset": "private_chat", + }) + MustInviteRoomAndFail(t, bob, roomID, alice.UserID) + MustHaveNoInviteInSyncResponse(t, alice) + }) + t.Run("Can ignore a single user", func(t *testing.T) { + mustSetInviteConfig(t, alice, InviteFilterConfig{ + IgnoredUsers: []string{bob.UserID}, + }) + roomID := bob.MustCreateRoom(t, map[string]interface{}{ + "preset": "private_chat", + }) + // Note, this invite failed invisibly. + bob.MustInviteRoom(t, roomID, alice.UserID) + MustHaveNoInviteInSyncResponse(t, alice) + }) + t.Run("Can block a whole server", func(t *testing.T) { + mustSetInviteConfig(t, alice, InviteFilterConfig{ + BlockedServers: []string{hs2Name}, + }) + roomIDA := bob.MustCreateRoom(t, map[string]interface{}{ + "preset": "private_chat", + }) + MustInviteRoomAndFail(t, bob, roomIDA, alice.UserID) + roomIDB := evil_bob.MustCreateRoom(t, map[string]interface{}{ + "preset": "private_chat", + }) + MustInviteRoomAndFail(t, evil_bob, roomIDB, alice.UserID) + MustHaveNoInviteInSyncResponse(t, alice) + }) + t.Run("Can ignore a whole server", func(t *testing.T) { + mustSetInviteConfig(t, alice, InviteFilterConfig{ + IgnoredServers: []string{hs2Name}, + }) + roomIDA := bob.MustCreateRoom(t, map[string]interface{}{ + "preset": "private_chat", + }) + bob.MustInviteRoom(t, roomIDA, alice.UserID) + roomIDB := evil_bob.MustCreateRoom(t, map[string]interface{}{ + "preset": "private_chat", + }) + evil_bob.MustInviteRoom(t, roomIDB, alice.UserID) + MustHaveNoInviteInSyncResponse(t, alice) + }) + t.Run("Can glob serveral servers", func(t *testing.T) { + mustSetInviteConfig(t, alice, InviteFilterConfig{ + BlockedServers: []string{"hs*"}, + }) + + roomIDA := bob.MustCreateRoom(t, map[string]interface{}{ + "preset": "private_chat", + }) + MustInviteRoomAndFail(t, bob, roomIDA, alice.UserID) + roomIDB := evil_bob.MustCreateRoom(t, map[string]interface{}{ + "preset": "private_chat", + }) + MustInviteRoomAndFail(t, evil_bob, roomIDB, alice.UserID) + roomIDC := evil_alice.MustCreateRoom(t, map[string]interface{}{ + "preset": "private_chat", + }) + MustInviteRoomAndFail(t, evil_alice, roomIDC, alice.UserID) + + MustHaveNoInviteInSyncResponse(t, alice) + }) + t.Run("Can allow a user from a blocked server", func(t *testing.T) { + mustSetInviteConfig(t, alice, InviteFilterConfig{ + AllowedUsers: []string{bob.UserID}, + BlockedServers: []string{hs2Name}, + }) + + roomIDBlocked := evil_bob.MustCreateRoom(t, map[string]interface{}{ + "preset": "private_chat", + }) + MustInviteRoomAndFail(t, evil_bob, roomIDBlocked, alice.UserID) + MustHaveNoInviteInSyncResponse(t, alice) + roomIDAllowed := bob.MustCreateRoom(t, map[string]interface{}{ + "preset": "private_chat", + }) + bob.MustInviteRoom(t, roomIDAllowed, alice.UserID) + alice.MustSyncUntil(t, client.SyncReq{}, client.SyncInvitedTo(alice.UserID, roomIDAllowed)) + alice.MustJoinRoom(t, roomIDAllowed, []spec.ServerName{}) + alice.MustSyncUntil(t, client.SyncReq{}, client.SyncJoinedTo(alice.UserID, roomIDAllowed)) + bob.MustSyncUntil(t, client.SyncReq{}, client.SyncJoinedTo(alice.UserID, roomIDAllowed)) + }) + t.Run("Can block a user from an allowed server", func(t *testing.T) { + mustSetInviteConfig(t, alice, InviteFilterConfig{ + BlockedUsers: []string{evil_bob.UserID}, + AllowedServers: []string{hs2Name}, + }) + + roomIDBlocked := evil_bob.MustCreateRoom(t, map[string]interface{}{ + "preset": "private_chat", + }) + MustInviteRoomAndFail(t, evil_bob, roomIDBlocked, alice.UserID) + MustHaveNoInviteInSyncResponse(t, alice) + roomIDAllowed := bob.MustCreateRoom(t, map[string]interface{}{ + "preset": "private_chat", + }) + bob.MustInviteRoom(t, roomIDAllowed, alice.UserID) + alice.MustSyncUntil(t, client.SyncReq{}, client.SyncInvitedTo(alice.UserID, roomIDAllowed)) + alice.MustJoinRoom(t, roomIDAllowed, []spec.ServerName{}) + alice.MustSyncUntil(t, client.SyncReq{}, client.SyncJoinedTo(alice.UserID, roomIDAllowed)) + bob.MustSyncUntil(t, client.SyncReq{}, client.SyncJoinedTo(alice.UserID, roomIDAllowed)) + }) +} + +func mustSetInviteConfig(t *testing.T, c *client.CSAPI, cfg InviteFilterConfig) { + log.Printf("Setting invite config A %+v\n", cfg) + + b, err := json.Marshal(&cfg) + if err != nil { + panic(err) + } + var m map[string]interface{} + err = json.Unmarshal(b, &m) + if err != nil { + panic(err) + } + log.Printf("Setting invite config B %+v\n", m) + c.MustSetGlobalAccountData(t, inviteFilterAccountData, m) +} + +// InviteRoom invites userID to the room ID, else fails the test. +func MustInviteRoomAndFail(t *testing.T, c *client.CSAPI,roomID string, userID string) { + t.Helper() + res := c.InviteRoom(t, roomID, userID) + if res.StatusCode == 403 { + return + } + defer res.Body.Close() + body, _ := io.ReadAll(res.Body) + ct.Fatalf(t, "CSAPI.Must: %s %s returned non-403 code: %s - body: %s", res.Request.Method, res.Request.URL.String(), res.Status, string(body)) +} + +// InviteRoom invites userID to the room ID, else fails the test. +func MustHaveNoInviteInSyncResponse(t *testing.T, c *client.CSAPI) { + initialSyncResponse, _ := c.MustSync(t, client.SyncReq{}) + must.MatchGJSON( + t, + initialSyncResponse, + match.JSONKeyMissing("rooms.invite"), + ) +} diff --git a/tests/msc4155/main_test.go b/tests/msc4155/main_test.go new file mode 100644 index 00000000..f1404d3a --- /dev/null +++ b/tests/msc4155/main_test.go @@ -0,0 +1,11 @@ +package tests + +import ( + "testing" + + "github.com/matrix-org/complement" +) + +func TestMain(m *testing.M) { + complement.TestMain(m, "msc4140") +} From f7e14a6e58732d3cc1bb86704a5865b41de09ef1 Mon Sep 17 00:00:00 2001 From: Half-Shot Date: Mon, 2 Jun 2025 13:39:49 +0100 Subject: [PATCH 2/8] fix typo --- tests/msc4155/main_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/msc4155/main_test.go b/tests/msc4155/main_test.go index f1404d3a..61c5b91a 100644 --- a/tests/msc4155/main_test.go +++ b/tests/msc4155/main_test.go @@ -7,5 +7,5 @@ import ( ) func TestMain(m *testing.M) { - complement.TestMain(m, "msc4140") + complement.TestMain(m, "msc4155") } From 583f7c86550e5f3228e43c1cde752841aef37395 Mon Sep 17 00:00:00 2001 From: Half-Shot Date: Mon, 2 Jun 2025 13:45:58 +0100 Subject: [PATCH 3/8] Refactor to ensure no racing. --- tests/msc4155/invite_filter_test.go | 106 ++++++++++++---------------- 1 file changed, 44 insertions(+), 62 deletions(-) diff --git a/tests/msc4155/invite_filter_test.go b/tests/msc4155/invite_filter_test.go index c31fd216..7ff8b9da 100644 --- a/tests/msc4155/invite_filter_test.go +++ b/tests/msc4155/invite_filter_test.go @@ -18,9 +18,6 @@ const hs1Name = "hs1" const hs2Name = "hs2" const inviteFilterAccountData = "org.matrix.msc4155.invite_permission_config" -// TODO: Test pagination of `GET /_matrix/client/v1/delayed_events` once -// it is implemented in a homeserver. - // As described in https://github.com/Johennes/matrix-spec-proposals/blob/johannes/invite-filtering/proposals/4155-invite-filtering.md#proposal type InviteFilterConfig struct { AllowedUsers []string `json:"allowed_users,omitempty"` @@ -44,9 +41,8 @@ func TestInviteFiltering(t *testing.T) { t.Run("Can invite users normally without any rules", func(t *testing.T) { mustSetInviteConfig(t, alice, InviteFilterConfig{}) - roomID := bob.MustCreateRoom(t, map[string]interface{}{ - "preset": "private_chat", - }) + roomID := mustCreateRoomAndSync(t, bob) + bob.MustSyncUntil(t, client.SyncReq{}, client.SyncJoinedTo(bob.UserID, roomID)) bob.MustInviteRoom(t, roomID, alice.UserID) alice.MustSyncUntil(t, client.SyncReq{}, client.SyncInvitedTo(alice.UserID, roomID)) alice.MustJoinRoom(t, roomID, []spec.ServerName{}) @@ -57,70 +53,53 @@ func TestInviteFiltering(t *testing.T) { mustSetInviteConfig(t, alice, InviteFilterConfig{ BlockedUsers: []string{bob.UserID}, }) - roomID := bob.MustCreateRoom(t, map[string]interface{}{ - "preset": "private_chat", - }) - MustInviteRoomAndFail(t, bob, roomID, alice.UserID) - MustHaveNoInviteInSyncResponse(t, alice) + roomID := mustCreateRoomAndSync(t, bob) + mustInviteRoomAndFail(t, bob, roomID, alice.UserID) + mustHaveNoInviteInSyncResponse(t, alice) }) t.Run("Can ignore a single user", func(t *testing.T) { mustSetInviteConfig(t, alice, InviteFilterConfig{ IgnoredUsers: []string{bob.UserID}, }) - roomID := bob.MustCreateRoom(t, map[string]interface{}{ - "preset": "private_chat", - }) + roomID := mustCreateRoomAndSync(t, bob) // Note, this invite failed invisibly. bob.MustInviteRoom(t, roomID, alice.UserID) - MustHaveNoInviteInSyncResponse(t, alice) + mustHaveNoInviteInSyncResponse(t, alice) }) t.Run("Can block a whole server", func(t *testing.T) { mustSetInviteConfig(t, alice, InviteFilterConfig{ BlockedServers: []string{hs2Name}, }) - roomIDA := bob.MustCreateRoom(t, map[string]interface{}{ - "preset": "private_chat", - }) - MustInviteRoomAndFail(t, bob, roomIDA, alice.UserID) - roomIDB := evil_bob.MustCreateRoom(t, map[string]interface{}{ - "preset": "private_chat", - }) - MustInviteRoomAndFail(t, evil_bob, roomIDB, alice.UserID) - MustHaveNoInviteInSyncResponse(t, alice) + roomIDA := mustCreateRoomAndSync(t, bob) + mustInviteRoomAndFail(t, bob, roomIDA, alice.UserID) + roomIDB := mustCreateRoomAndSync(t, evil_bob) + mustInviteRoomAndFail(t, evil_bob, roomIDB, alice.UserID) + mustHaveNoInviteInSyncResponse(t, alice) }) t.Run("Can ignore a whole server", func(t *testing.T) { mustSetInviteConfig(t, alice, InviteFilterConfig{ IgnoredServers: []string{hs2Name}, }) - roomIDA := bob.MustCreateRoom(t, map[string]interface{}{ - "preset": "private_chat", - }) + roomIDA := mustCreateRoomAndSync(t, bob) bob.MustInviteRoom(t, roomIDA, alice.UserID) - roomIDB := evil_bob.MustCreateRoom(t, map[string]interface{}{ - "preset": "private_chat", - }) + roomIDB := mustCreateRoomAndSync(t, evil_bob) evil_bob.MustInviteRoom(t, roomIDB, alice.UserID) - MustHaveNoInviteInSyncResponse(t, alice) + mustHaveNoInviteInSyncResponse(t, alice) }) t.Run("Can glob serveral servers", func(t *testing.T) { mustSetInviteConfig(t, alice, InviteFilterConfig{ BlockedServers: []string{"hs*"}, }) - - roomIDA := bob.MustCreateRoom(t, map[string]interface{}{ - "preset": "private_chat", - }) - MustInviteRoomAndFail(t, bob, roomIDA, alice.UserID) - roomIDB := evil_bob.MustCreateRoom(t, map[string]interface{}{ - "preset": "private_chat", - }) - MustInviteRoomAndFail(t, evil_bob, roomIDB, alice.UserID) + roomIDA := mustCreateRoomAndSync(t, bob) + mustInviteRoomAndFail(t, bob, roomIDA, alice.UserID) + roomIDB := mustCreateRoomAndSync(t, evil_bob) + mustInviteRoomAndFail(t, evil_bob, roomIDB, alice.UserID) roomIDC := evil_alice.MustCreateRoom(t, map[string]interface{}{ "preset": "private_chat", }) - MustInviteRoomAndFail(t, evil_alice, roomIDC, alice.UserID) + mustInviteRoomAndFail(t, evil_alice, roomIDC, alice.UserID) - MustHaveNoInviteInSyncResponse(t, alice) + mustHaveNoInviteInSyncResponse(t, alice) }) t.Run("Can allow a user from a blocked server", func(t *testing.T) { mustSetInviteConfig(t, alice, InviteFilterConfig{ @@ -128,14 +107,10 @@ func TestInviteFiltering(t *testing.T) { BlockedServers: []string{hs2Name}, }) - roomIDBlocked := evil_bob.MustCreateRoom(t, map[string]interface{}{ - "preset": "private_chat", - }) - MustInviteRoomAndFail(t, evil_bob, roomIDBlocked, alice.UserID) - MustHaveNoInviteInSyncResponse(t, alice) - roomIDAllowed := bob.MustCreateRoom(t, map[string]interface{}{ - "preset": "private_chat", - }) + roomIDBlocked := mustCreateRoomAndSync(t, evil_bob) + mustInviteRoomAndFail(t, evil_bob, roomIDBlocked, alice.UserID) + mustHaveNoInviteInSyncResponse(t, alice) + roomIDAllowed := mustCreateRoomAndSync(t, bob) bob.MustInviteRoom(t, roomIDAllowed, alice.UserID) alice.MustSyncUntil(t, client.SyncReq{}, client.SyncInvitedTo(alice.UserID, roomIDAllowed)) alice.MustJoinRoom(t, roomIDAllowed, []spec.ServerName{}) @@ -148,14 +123,10 @@ func TestInviteFiltering(t *testing.T) { AllowedServers: []string{hs2Name}, }) - roomIDBlocked := evil_bob.MustCreateRoom(t, map[string]interface{}{ - "preset": "private_chat", - }) - MustInviteRoomAndFail(t, evil_bob, roomIDBlocked, alice.UserID) - MustHaveNoInviteInSyncResponse(t, alice) - roomIDAllowed := bob.MustCreateRoom(t, map[string]interface{}{ - "preset": "private_chat", - }) + roomIDBlocked := mustCreateRoomAndSync(t, evil_bob) + mustInviteRoomAndFail(t, evil_bob, roomIDBlocked, alice.UserID) + mustHaveNoInviteInSyncResponse(t, alice) + roomIDAllowed := mustCreateRoomAndSync(t, bob) bob.MustInviteRoom(t, roomIDAllowed, alice.UserID) alice.MustSyncUntil(t, client.SyncReq{}, client.SyncInvitedTo(alice.UserID, roomIDAllowed)) alice.MustJoinRoom(t, roomIDAllowed, []spec.ServerName{}) @@ -164,6 +135,7 @@ func TestInviteFiltering(t *testing.T) { }) } +// Tests that a given invite filter config is properly set func mustSetInviteConfig(t *testing.T, c *client.CSAPI, cfg InviteFilterConfig) { log.Printf("Setting invite config A %+v\n", cfg) @@ -180,8 +152,18 @@ func mustSetInviteConfig(t *testing.T, c *client.CSAPI, cfg InviteFilterConfig) c.MustSetGlobalAccountData(t, inviteFilterAccountData, m) } -// InviteRoom invites userID to the room ID, else fails the test. -func MustInviteRoomAndFail(t *testing.T, c *client.CSAPI,roomID string, userID string) { +// Tests that a room is created and appears down the creators sync +func mustCreateRoomAndSync(t *testing.T, c *client.CSAPI) string { + t.Helper() + roomID := c.MustCreateRoom(t, map[string]interface{}{ + "preset": "private_chat", + }) + c.MustSyncUntil(t, client.SyncReq{}, client.SyncJoinedTo(c.UserID, roomID)) + return roomID +} + +// Test that requests to invite a given user fail with a 403 response +func mustInviteRoomAndFail(t *testing.T, c *client.CSAPI,roomID string, userID string) { t.Helper() res := c.InviteRoom(t, roomID, userID) if res.StatusCode == 403 { @@ -192,8 +174,8 @@ func MustInviteRoomAndFail(t *testing.T, c *client.CSAPI,roomID string, userID s ct.Fatalf(t, "CSAPI.Must: %s %s returned non-403 code: %s - body: %s", res.Request.Method, res.Request.URL.String(), res.Status, string(body)) } -// InviteRoom invites userID to the room ID, else fails the test. -func MustHaveNoInviteInSyncResponse(t *testing.T, c *client.CSAPI) { +// Test that no invites appear down sync +func mustHaveNoInviteInSyncResponse(t *testing.T, c *client.CSAPI) { initialSyncResponse, _ := c.MustSync(t, client.SyncReq{}) must.MatchGJSON( t, From e322611c8b2e24090b05e16412bc84902702c6b4 Mon Sep 17 00:00:00 2001 From: Half-Shot Date: Mon, 2 Jun 2025 13:48:28 +0100 Subject: [PATCH 4/8] remove log line --- tests/msc4155/invite_filter_test.go | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/msc4155/invite_filter_test.go b/tests/msc4155/invite_filter_test.go index 7ff8b9da..582eb2c3 100644 --- a/tests/msc4155/invite_filter_test.go +++ b/tests/msc4155/invite_filter_test.go @@ -148,7 +148,6 @@ func mustSetInviteConfig(t *testing.T, c *client.CSAPI, cfg InviteFilterConfig) if err != nil { panic(err) } - log.Printf("Setting invite config B %+v\n", m) c.MustSetGlobalAccountData(t, inviteFilterAccountData, m) } From af9add96c39b870384d7ac45eaf42c472ce28ab0 Mon Sep 17 00:00:00 2001 From: Half-Shot Date: Mon, 2 Jun 2025 13:48:41 +0100 Subject: [PATCH 5/8] and the other one --- tests/msc4155/invite_filter_test.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/msc4155/invite_filter_test.go b/tests/msc4155/invite_filter_test.go index 582eb2c3..5b7d9435 100644 --- a/tests/msc4155/invite_filter_test.go +++ b/tests/msc4155/invite_filter_test.go @@ -137,8 +137,6 @@ func TestInviteFiltering(t *testing.T) { // Tests that a given invite filter config is properly set func mustSetInviteConfig(t *testing.T, c *client.CSAPI, cfg InviteFilterConfig) { - log.Printf("Setting invite config A %+v\n", cfg) - b, err := json.Marshal(&cfg) if err != nil { panic(err) From 33cce610457afbe88fd446c6422d29dcf5f82b0b Mon Sep 17 00:00:00 2001 From: Half-Shot Date: Mon, 2 Jun 2025 13:48:55 +0100 Subject: [PATCH 6/8] and the import :) --- tests/msc4155/invite_filter_test.go | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/msc4155/invite_filter_test.go b/tests/msc4155/invite_filter_test.go index 5b7d9435..b5f86fb3 100644 --- a/tests/msc4155/invite_filter_test.go +++ b/tests/msc4155/invite_filter_test.go @@ -2,7 +2,6 @@ package tests import ( "testing" - "log" "github.com/matrix-org/complement" "github.com/matrix-org/complement/helpers" "github.com/matrix-org/complement/must" From 684b12cc86edb6303d9c927e15d6a703bc116c00 Mon Sep 17 00:00:00 2001 From: Half-Shot Date: Tue, 3 Jun 2025 12:23:04 +0100 Subject: [PATCH 7/8] update tests --- tests/msc4155/invite_filter_test.go | 83 +++++++++++++++++++++++++---- 1 file changed, 74 insertions(+), 9 deletions(-) diff --git a/tests/msc4155/invite_filter_test.go b/tests/msc4155/invite_filter_test.go index b5f86fb3..7fbd19c9 100644 --- a/tests/msc4155/invite_filter_test.go +++ b/tests/msc4155/invite_filter_test.go @@ -1,16 +1,16 @@ package tests import ( - "testing" + "encoding/json" "github.com/matrix-org/complement" + "github.com/matrix-org/complement/client" + "github.com/matrix-org/complement/ct" "github.com/matrix-org/complement/helpers" - "github.com/matrix-org/complement/must" "github.com/matrix-org/complement/match" + "github.com/matrix-org/complement/must" "github.com/matrix-org/gomatrixserverlib/spec" - "github.com/matrix-org/complement/client" - "encoding/json" - "github.com/matrix-org/complement/ct" "io" + "testing" ) const hs1Name = "hs1" @@ -32,23 +32,23 @@ func TestInviteFiltering(t *testing.T) { defer deployment.Destroy(t) // Invitee - alice := deployment.Register(t, hs1Name, helpers.RegistrationOpts{}) evil_alice := deployment.Register(t, hs1Name, helpers.RegistrationOpts{}) bob := deployment.Register(t, hs2Name, helpers.RegistrationOpts{}) evil_bob := deployment.Register(t, hs2Name, helpers.RegistrationOpts{}) t.Run("Can invite users normally without any rules", func(t *testing.T) { + // Use a fresh alice each time to ensure we don't have any test cross-contaimination. + alice := deployment.Register(t, hs1Name, helpers.RegistrationOpts{}) mustSetInviteConfig(t, alice, InviteFilterConfig{}) roomID := mustCreateRoomAndSync(t, bob) - bob.MustSyncUntil(t, client.SyncReq{}, client.SyncJoinedTo(bob.UserID, roomID)) bob.MustInviteRoom(t, roomID, alice.UserID) - alice.MustSyncUntil(t, client.SyncReq{}, client.SyncInvitedTo(alice.UserID, roomID)) alice.MustJoinRoom(t, roomID, []spec.ServerName{}) alice.MustSyncUntil(t, client.SyncReq{}, client.SyncJoinedTo(alice.UserID, roomID)) bob.MustSyncUntil(t, client.SyncReq{}, client.SyncJoinedTo(alice.UserID, roomID)) }) t.Run("Can block a single user", func(t *testing.T) { + alice := deployment.Register(t, hs1Name, helpers.RegistrationOpts{}) mustSetInviteConfig(t, alice, InviteFilterConfig{ BlockedUsers: []string{bob.UserID}, }) @@ -57,6 +57,7 @@ func TestInviteFiltering(t *testing.T) { mustHaveNoInviteInSyncResponse(t, alice) }) t.Run("Can ignore a single user", func(t *testing.T) { + alice := deployment.Register(t, hs1Name, helpers.RegistrationOpts{}) mustSetInviteConfig(t, alice, InviteFilterConfig{ IgnoredUsers: []string{bob.UserID}, }) @@ -66,6 +67,7 @@ func TestInviteFiltering(t *testing.T) { mustHaveNoInviteInSyncResponse(t, alice) }) t.Run("Can block a whole server", func(t *testing.T) { + alice := deployment.Register(t, hs1Name, helpers.RegistrationOpts{}) mustSetInviteConfig(t, alice, InviteFilterConfig{ BlockedServers: []string{hs2Name}, }) @@ -73,9 +75,9 @@ func TestInviteFiltering(t *testing.T) { mustInviteRoomAndFail(t, bob, roomIDA, alice.UserID) roomIDB := mustCreateRoomAndSync(t, evil_bob) mustInviteRoomAndFail(t, evil_bob, roomIDB, alice.UserID) - mustHaveNoInviteInSyncResponse(t, alice) }) t.Run("Can ignore a whole server", func(t *testing.T) { + alice := deployment.Register(t, hs1Name, helpers.RegistrationOpts{}) mustSetInviteConfig(t, alice, InviteFilterConfig{ IgnoredServers: []string{hs2Name}, }) @@ -86,6 +88,7 @@ func TestInviteFiltering(t *testing.T) { mustHaveNoInviteInSyncResponse(t, alice) }) t.Run("Can glob serveral servers", func(t *testing.T) { + alice := deployment.Register(t, hs1Name, helpers.RegistrationOpts{}) mustSetInviteConfig(t, alice, InviteFilterConfig{ BlockedServers: []string{"hs*"}, }) @@ -100,7 +103,27 @@ func TestInviteFiltering(t *testing.T) { mustHaveNoInviteInSyncResponse(t, alice) }) + t.Run("Can glob serveral users", func(t *testing.T) { + alice := deployment.Register(t, hs1Name, helpers.RegistrationOpts{}) + mustSetInviteConfig(t, alice, InviteFilterConfig{ + // Test glob behaviours + BlockedUsers: []string{ + "@user-?*", + }, + }) + roomIDA := mustCreateRoomAndSync(t, bob) + mustInviteRoomAndFail(t, bob, roomIDA, alice.UserID) + roomIDB := mustCreateRoomAndSync(t, evil_bob) + mustInviteRoomAndFail(t, evil_bob, roomIDB, alice.UserID) + roomIDC := evil_alice.MustCreateRoom(t, map[string]interface{}{ + "preset": "private_chat", + }) + mustInviteRoomAndFail(t, evil_alice, roomIDC, alice.UserID) + + mustHaveNoInviteInSyncResponse(t, alice) + }) t.Run("Can allow a user from a blocked server", func(t *testing.T) { + alice := deployment.Register(t, hs1Name, helpers.RegistrationOpts{}) mustSetInviteConfig(t, alice, InviteFilterConfig{ AllowedUsers: []string{bob.UserID}, BlockedServers: []string{hs2Name}, @@ -117,6 +140,7 @@ func TestInviteFiltering(t *testing.T) { bob.MustSyncUntil(t, client.SyncReq{}, client.SyncJoinedTo(alice.UserID, roomIDAllowed)) }) t.Run("Can block a user from an allowed server", func(t *testing.T) { + alice := deployment.Register(t, hs1Name, helpers.RegistrationOpts{}) mustSetInviteConfig(t, alice, InviteFilterConfig{ BlockedUsers: []string{evil_bob.UserID}, AllowedServers: []string{hs2Name}, @@ -132,10 +156,50 @@ func TestInviteFiltering(t *testing.T) { alice.MustSyncUntil(t, client.SyncReq{}, client.SyncJoinedTo(alice.UserID, roomIDAllowed)) bob.MustSyncUntil(t, client.SyncReq{}, client.SyncJoinedTo(alice.UserID, roomIDAllowed)) }) + t.Run("Will ignore null fields", func(t *testing.T) { + alice := deployment.Register(t, hs1Name, helpers.RegistrationOpts{}) + alice.MustSetGlobalAccountData(t, inviteFilterAccountData, map[string]interface{}{ + "allowed_users": nil, + "ignored_users": nil, + "blocked_users": nil, + "allowed_servers": nil, + "ignored_servers": nil, + "blocked_servers": nil, + }) + roomIDAllowed := mustCreateRoomAndSync(t, bob) + bob.MustInviteRoom(t, roomIDAllowed, alice.UserID) + alice.MustSyncUntil(t, client.SyncReq{}, client.SyncInvitedTo(alice.UserID, roomIDAllowed)) + alice.MustJoinRoom(t, roomIDAllowed, []spec.ServerName{}) + alice.MustSyncUntil(t, client.SyncReq{}, client.SyncJoinedTo(alice.UserID, roomIDAllowed)) + bob.MustSyncUntil(t, client.SyncReq{}, client.SyncJoinedTo(alice.UserID, roomIDAllowed)) + }) + t.Run("Will allow users when a user appears in multiple fields", func(t *testing.T) { + alice := deployment.Register(t, hs1Name, helpers.RegistrationOpts{}) + mustSetInviteConfig(t, alice, InviteFilterConfig{ + IgnoredUsers: []string{bob.UserID}, + BlockedUsers: []string{evil_bob.UserID}, + AllowedUsers: []string{bob.UserID, evil_bob.UserID}, + }) + roomIDBob := mustCreateRoomAndSync(t, bob) + bob.MustInviteRoom(t, roomIDBob, alice.UserID) + alice.MustSyncUntil(t, client.SyncReq{}, client.SyncInvitedTo(alice.UserID, roomIDBob)) + alice.MustJoinRoom(t, roomIDBob, []spec.ServerName{}) + alice.MustSyncUntil(t, client.SyncReq{}, client.SyncJoinedTo(alice.UserID, roomIDBob)) + bob.MustSyncUntil(t, client.SyncReq{}, client.SyncJoinedTo(alice.UserID, roomIDBob)) + + roomIDEvilBob := mustCreateRoomAndSync(t, evil_bob) + evil_bob.MustInviteRoom(t, roomIDEvilBob, alice.UserID) + alice.MustSyncUntil(t, client.SyncReq{}, client.SyncInvitedTo(alice.UserID, roomIDEvilBob)) + alice.MustJoinRoom(t, roomIDEvilBob, []spec.ServerName{}) + alice.MustSyncUntil(t, client.SyncReq{}, client.SyncJoinedTo(alice.UserID, roomIDEvilBob)) + evil_bob.MustSyncUntil(t, client.SyncReq{}, client.SyncJoinedTo(alice.UserID, roomIDEvilBob)) + }) } // Tests that a given invite filter config is properly set func mustSetInviteConfig(t *testing.T, c *client.CSAPI, cfg InviteFilterConfig) { + // This marshalling section transforms the InviteFilterConfig struct into JSON + // we can send to the server. b, err := json.Marshal(&cfg) if err != nil { panic(err) @@ -145,6 +209,7 @@ func mustSetInviteConfig(t *testing.T, c *client.CSAPI, cfg InviteFilterConfig) if err != nil { panic(err) } + c.MustSetGlobalAccountData(t, inviteFilterAccountData, m) } From 984ff5f3f8651d0a4b5d2b24144cbab78ab84d64 Mon Sep 17 00:00:00 2001 From: Half-Shot Date: Tue, 3 Jun 2025 12:29:06 +0100 Subject: [PATCH 8/8] add comment --- tests/msc4155/invite_filter_test.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/msc4155/invite_filter_test.go b/tests/msc4155/invite_filter_test.go index 7fbd19c9..f217c2cf 100644 --- a/tests/msc4155/invite_filter_test.go +++ b/tests/msc4155/invite_filter_test.go @@ -194,6 +194,8 @@ func TestInviteFiltering(t *testing.T) { alice.MustSyncUntil(t, client.SyncReq{}, client.SyncJoinedTo(alice.UserID, roomIDEvilBob)) evil_bob.MustSyncUntil(t, client.SyncReq{}, client.SyncJoinedTo(alice.UserID, roomIDEvilBob)) }) + // TODO: Escaping a glob is not possible, so no tests exist for that. + // See https://github.com/matrix-org/matrix-spec/issues/2156 } // Tests that a given invite filter config is properly set