Skip to content

Commit d764e23

Browse files
Half-Shotjevolk
authored andcommitted
Add tests for MSC4155 (matrix-org#782)
1 parent b0a58e5 commit d764e23

File tree

2 files changed

+259
-0
lines changed

2 files changed

+259
-0
lines changed

tests/msc4155/invite_filter_test.go

Lines changed: 248 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,248 @@
1+
package tests
2+
3+
import (
4+
"encoding/json"
5+
"github.com/matrix-org/complement"
6+
"github.com/matrix-org/complement/client"
7+
"github.com/matrix-org/complement/ct"
8+
"github.com/matrix-org/complement/helpers"
9+
"github.com/matrix-org/complement/match"
10+
"github.com/matrix-org/complement/must"
11+
"github.com/matrix-org/gomatrixserverlib/spec"
12+
"io"
13+
"testing"
14+
)
15+
16+
const hs1Name = "hs1"
17+
const hs2Name = "hs2"
18+
const inviteFilterAccountData = "org.matrix.msc4155.invite_permission_config"
19+
20+
// As described in https://github.com/Johennes/matrix-spec-proposals/blob/johannes/invite-filtering/proposals/4155-invite-filtering.md#proposal
21+
type InviteFilterConfig struct {
22+
AllowedUsers []string `json:"allowed_users,omitempty"`
23+
IgnoredUsers []string `json:"ignored_users,omitempty"`
24+
BlockedUsers []string `json:"blocked_users,omitempty"`
25+
AllowedServers []string `json:"allowed_servers,omitempty"`
26+
IgnoredServers []string `json:"ignored_servers,omitempty"`
27+
BlockedServers []string `json:"blocked_servers,omitempty"`
28+
}
29+
30+
func TestInviteFiltering(t *testing.T) {
31+
deployment := complement.Deploy(t, 2)
32+
defer deployment.Destroy(t)
33+
34+
// Invitee
35+
evil_alice := deployment.Register(t, hs1Name, helpers.RegistrationOpts{})
36+
37+
bob := deployment.Register(t, hs2Name, helpers.RegistrationOpts{})
38+
evil_bob := deployment.Register(t, hs2Name, helpers.RegistrationOpts{})
39+
40+
t.Run("Can invite users normally without any rules", func(t *testing.T) {
41+
// Use a fresh alice each time to ensure we don't have any test cross-contaimination.
42+
alice := deployment.Register(t, hs1Name, helpers.RegistrationOpts{})
43+
mustSetInviteConfig(t, alice, InviteFilterConfig{})
44+
roomID := mustCreateRoomAndSync(t, bob)
45+
bob.MustInviteRoom(t, roomID, alice.UserID)
46+
alice.MustJoinRoom(t, roomID, []spec.ServerName{})
47+
alice.MustSyncUntil(t, client.SyncReq{}, client.SyncJoinedTo(alice.UserID, roomID))
48+
bob.MustSyncUntil(t, client.SyncReq{}, client.SyncJoinedTo(alice.UserID, roomID))
49+
})
50+
t.Run("Can block a single user", func(t *testing.T) {
51+
alice := deployment.Register(t, hs1Name, helpers.RegistrationOpts{})
52+
mustSetInviteConfig(t, alice, InviteFilterConfig{
53+
BlockedUsers: []string{bob.UserID},
54+
})
55+
roomID := mustCreateRoomAndSync(t, bob)
56+
mustInviteRoomAndFail(t, bob, roomID, alice.UserID)
57+
mustHaveNoInviteInSyncResponse(t, alice)
58+
})
59+
t.Run("Can ignore a single user", func(t *testing.T) {
60+
alice := deployment.Register(t, hs1Name, helpers.RegistrationOpts{})
61+
mustSetInviteConfig(t, alice, InviteFilterConfig{
62+
IgnoredUsers: []string{bob.UserID},
63+
})
64+
roomID := mustCreateRoomAndSync(t, bob)
65+
// Note, this invite failed invisibly.
66+
bob.MustInviteRoom(t, roomID, alice.UserID)
67+
mustHaveNoInviteInSyncResponse(t, alice)
68+
})
69+
t.Run("Can block a whole server", func(t *testing.T) {
70+
alice := deployment.Register(t, hs1Name, helpers.RegistrationOpts{})
71+
mustSetInviteConfig(t, alice, InviteFilterConfig{
72+
BlockedServers: []string{hs2Name},
73+
})
74+
roomIDA := mustCreateRoomAndSync(t, bob)
75+
mustInviteRoomAndFail(t, bob, roomIDA, alice.UserID)
76+
roomIDB := mustCreateRoomAndSync(t, evil_bob)
77+
mustInviteRoomAndFail(t, evil_bob, roomIDB, alice.UserID)
78+
})
79+
t.Run("Can ignore a whole server", func(t *testing.T) {
80+
alice := deployment.Register(t, hs1Name, helpers.RegistrationOpts{})
81+
mustSetInviteConfig(t, alice, InviteFilterConfig{
82+
IgnoredServers: []string{hs2Name},
83+
})
84+
roomIDA := mustCreateRoomAndSync(t, bob)
85+
bob.MustInviteRoom(t, roomIDA, alice.UserID)
86+
roomIDB := mustCreateRoomAndSync(t, evil_bob)
87+
evil_bob.MustInviteRoom(t, roomIDB, alice.UserID)
88+
mustHaveNoInviteInSyncResponse(t, alice)
89+
})
90+
t.Run("Can glob serveral servers", func(t *testing.T) {
91+
alice := deployment.Register(t, hs1Name, helpers.RegistrationOpts{})
92+
mustSetInviteConfig(t, alice, InviteFilterConfig{
93+
BlockedServers: []string{"hs*"},
94+
})
95+
roomIDA := mustCreateRoomAndSync(t, bob)
96+
mustInviteRoomAndFail(t, bob, roomIDA, alice.UserID)
97+
roomIDB := mustCreateRoomAndSync(t, evil_bob)
98+
mustInviteRoomAndFail(t, evil_bob, roomIDB, alice.UserID)
99+
roomIDC := evil_alice.MustCreateRoom(t, map[string]interface{}{
100+
"preset": "private_chat",
101+
})
102+
mustInviteRoomAndFail(t, evil_alice, roomIDC, alice.UserID)
103+
104+
mustHaveNoInviteInSyncResponse(t, alice)
105+
})
106+
t.Run("Can glob serveral users", func(t *testing.T) {
107+
alice := deployment.Register(t, hs1Name, helpers.RegistrationOpts{})
108+
mustSetInviteConfig(t, alice, InviteFilterConfig{
109+
// Test glob behaviours
110+
BlockedUsers: []string{
111+
"@user-?*",
112+
},
113+
})
114+
roomIDA := mustCreateRoomAndSync(t, bob)
115+
mustInviteRoomAndFail(t, bob, roomIDA, alice.UserID)
116+
roomIDB := mustCreateRoomAndSync(t, evil_bob)
117+
mustInviteRoomAndFail(t, evil_bob, roomIDB, alice.UserID)
118+
roomIDC := evil_alice.MustCreateRoom(t, map[string]interface{}{
119+
"preset": "private_chat",
120+
})
121+
mustInviteRoomAndFail(t, evil_alice, roomIDC, alice.UserID)
122+
123+
mustHaveNoInviteInSyncResponse(t, alice)
124+
})
125+
t.Run("Can allow a user from a blocked server", func(t *testing.T) {
126+
alice := deployment.Register(t, hs1Name, helpers.RegistrationOpts{})
127+
mustSetInviteConfig(t, alice, InviteFilterConfig{
128+
AllowedUsers: []string{bob.UserID},
129+
BlockedServers: []string{hs2Name},
130+
})
131+
132+
roomIDBlocked := mustCreateRoomAndSync(t, evil_bob)
133+
mustInviteRoomAndFail(t, evil_bob, roomIDBlocked, alice.UserID)
134+
mustHaveNoInviteInSyncResponse(t, alice)
135+
roomIDAllowed := mustCreateRoomAndSync(t, bob)
136+
bob.MustInviteRoom(t, roomIDAllowed, alice.UserID)
137+
alice.MustSyncUntil(t, client.SyncReq{}, client.SyncInvitedTo(alice.UserID, roomIDAllowed))
138+
alice.MustJoinRoom(t, roomIDAllowed, []spec.ServerName{})
139+
alice.MustSyncUntil(t, client.SyncReq{}, client.SyncJoinedTo(alice.UserID, roomIDAllowed))
140+
bob.MustSyncUntil(t, client.SyncReq{}, client.SyncJoinedTo(alice.UserID, roomIDAllowed))
141+
})
142+
t.Run("Can block a user from an allowed server", func(t *testing.T) {
143+
alice := deployment.Register(t, hs1Name, helpers.RegistrationOpts{})
144+
mustSetInviteConfig(t, alice, InviteFilterConfig{
145+
BlockedUsers: []string{evil_bob.UserID},
146+
AllowedServers: []string{hs2Name},
147+
})
148+
149+
roomIDBlocked := mustCreateRoomAndSync(t, evil_bob)
150+
mustInviteRoomAndFail(t, evil_bob, roomIDBlocked, alice.UserID)
151+
mustHaveNoInviteInSyncResponse(t, alice)
152+
roomIDAllowed := mustCreateRoomAndSync(t, bob)
153+
bob.MustInviteRoom(t, roomIDAllowed, alice.UserID)
154+
alice.MustSyncUntil(t, client.SyncReq{}, client.SyncInvitedTo(alice.UserID, roomIDAllowed))
155+
alice.MustJoinRoom(t, roomIDAllowed, []spec.ServerName{})
156+
alice.MustSyncUntil(t, client.SyncReq{}, client.SyncJoinedTo(alice.UserID, roomIDAllowed))
157+
bob.MustSyncUntil(t, client.SyncReq{}, client.SyncJoinedTo(alice.UserID, roomIDAllowed))
158+
})
159+
t.Run("Will ignore null fields", func(t *testing.T) {
160+
alice := deployment.Register(t, hs1Name, helpers.RegistrationOpts{})
161+
alice.MustSetGlobalAccountData(t, inviteFilterAccountData, map[string]interface{}{
162+
"allowed_users": nil,
163+
"ignored_users": nil,
164+
"blocked_users": nil,
165+
"allowed_servers": nil,
166+
"ignored_servers": nil,
167+
"blocked_servers": nil,
168+
})
169+
roomIDAllowed := mustCreateRoomAndSync(t, bob)
170+
bob.MustInviteRoom(t, roomIDAllowed, alice.UserID)
171+
alice.MustSyncUntil(t, client.SyncReq{}, client.SyncInvitedTo(alice.UserID, roomIDAllowed))
172+
alice.MustJoinRoom(t, roomIDAllowed, []spec.ServerName{})
173+
alice.MustSyncUntil(t, client.SyncReq{}, client.SyncJoinedTo(alice.UserID, roomIDAllowed))
174+
bob.MustSyncUntil(t, client.SyncReq{}, client.SyncJoinedTo(alice.UserID, roomIDAllowed))
175+
})
176+
t.Run("Will allow users when a user appears in multiple fields", func(t *testing.T) {
177+
alice := deployment.Register(t, hs1Name, helpers.RegistrationOpts{})
178+
mustSetInviteConfig(t, alice, InviteFilterConfig{
179+
IgnoredUsers: []string{bob.UserID},
180+
BlockedUsers: []string{evil_bob.UserID},
181+
AllowedUsers: []string{bob.UserID, evil_bob.UserID},
182+
})
183+
roomIDBob := mustCreateRoomAndSync(t, bob)
184+
bob.MustInviteRoom(t, roomIDBob, alice.UserID)
185+
alice.MustSyncUntil(t, client.SyncReq{}, client.SyncInvitedTo(alice.UserID, roomIDBob))
186+
alice.MustJoinRoom(t, roomIDBob, []spec.ServerName{})
187+
alice.MustSyncUntil(t, client.SyncReq{}, client.SyncJoinedTo(alice.UserID, roomIDBob))
188+
bob.MustSyncUntil(t, client.SyncReq{}, client.SyncJoinedTo(alice.UserID, roomIDBob))
189+
190+
roomIDEvilBob := mustCreateRoomAndSync(t, evil_bob)
191+
evil_bob.MustInviteRoom(t, roomIDEvilBob, alice.UserID)
192+
alice.MustSyncUntil(t, client.SyncReq{}, client.SyncInvitedTo(alice.UserID, roomIDEvilBob))
193+
alice.MustJoinRoom(t, roomIDEvilBob, []spec.ServerName{})
194+
alice.MustSyncUntil(t, client.SyncReq{}, client.SyncJoinedTo(alice.UserID, roomIDEvilBob))
195+
evil_bob.MustSyncUntil(t, client.SyncReq{}, client.SyncJoinedTo(alice.UserID, roomIDEvilBob))
196+
})
197+
// TODO: Escaping a glob is not possible, so no tests exist for that.
198+
// See https://github.com/matrix-org/matrix-spec/issues/2156
199+
}
200+
201+
// Tests that a given invite filter config is properly set
202+
func mustSetInviteConfig(t *testing.T, c *client.CSAPI, cfg InviteFilterConfig) {
203+
// This marshalling section transforms the InviteFilterConfig struct into JSON
204+
// we can send to the server.
205+
b, err := json.Marshal(&cfg)
206+
if err != nil {
207+
panic(err)
208+
}
209+
var m map[string]interface{}
210+
err = json.Unmarshal(b, &m)
211+
if err != nil {
212+
panic(err)
213+
}
214+
215+
c.MustSetGlobalAccountData(t, inviteFilterAccountData, m)
216+
}
217+
218+
// Tests that a room is created and appears down the creators sync
219+
func mustCreateRoomAndSync(t *testing.T, c *client.CSAPI) string {
220+
t.Helper()
221+
roomID := c.MustCreateRoom(t, map[string]interface{}{
222+
"preset": "private_chat",
223+
})
224+
c.MustSyncUntil(t, client.SyncReq{}, client.SyncJoinedTo(c.UserID, roomID))
225+
return roomID
226+
}
227+
228+
// Test that requests to invite a given user fail with a 403 response
229+
func mustInviteRoomAndFail(t *testing.T, c *client.CSAPI,roomID string, userID string) {
230+
t.Helper()
231+
res := c.InviteRoom(t, roomID, userID)
232+
if res.StatusCode == 403 {
233+
return
234+
}
235+
defer res.Body.Close()
236+
body, _ := io.ReadAll(res.Body)
237+
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))
238+
}
239+
240+
// Test that no invites appear down sync
241+
func mustHaveNoInviteInSyncResponse(t *testing.T, c *client.CSAPI) {
242+
initialSyncResponse, _ := c.MustSync(t, client.SyncReq{})
243+
must.MatchGJSON(
244+
t,
245+
initialSyncResponse,
246+
match.JSONKeyMissing("rooms.invite"),
247+
)
248+
}

tests/msc4155/main_test.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package tests
2+
3+
import (
4+
"testing"
5+
6+
"github.com/matrix-org/complement"
7+
)
8+
9+
func TestMain(m *testing.M) {
10+
complement.TestMain(m, "msc4155")
11+
}

0 commit comments

Comments
 (0)