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
124 changes: 122 additions & 2 deletions tests/federation_acl_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,15 @@ import (
"github.com/matrix-org/complement/runtime"
"github.com/matrix-org/complement/should"
"github.com/matrix-org/gomatrixserverlib/spec"
"github.com/tidwall/gjson"
)

// Test for https://github.com/matrix-org/dendrite/issues/3004
func TestACLs(t *testing.T) {
runtime.SkipIf(t, runtime.Dendrite) // needs https://github.com/matrix-org/dendrite/pull/3008
// 1. Prepare 3 or more servers. 1st will be room host, 2nd will be blocked with m.room.server_acl and 3rd server will be affected by this issue. 1st and 2nd servers don't have to be powered by dendrite.
// 1. Prepare 3 or more servers. 1st will be room host, 2nd will be blocked with
// m.room.server_acl and 3rd server will be affected by this issue. 1st and 2nd
// servers don't have to be powered by dendrite.
deployment := complement.Deploy(t, 3)
defer deployment.Destroy(t)

Expand Down Expand Up @@ -74,7 +77,8 @@ func TestACLs(t *testing.T) {
aliceSince = alice.MustSyncUntil(t, client.SyncReq{Since: aliceSince}, client.SyncJoinedTo(charlie.UserID, roomID))
charlieSince := charlie.MustSyncUntil(t, client.SyncReq{}, client.SyncJoinedTo(charlie.UserID, roomID))

// 6. Any events sent by 2nd server will not appear for users from 1st server, but will be visible for users from 3rd server
// 6. Any events sent by 2nd server will not appear for users from 1st or 3rd server, as
// m.room.server_acl blocks everyone from accepting hs2's events
eventID = bob.SendEventSynced(t, roomID, b.Event{
Type: "m.room.message",
Sender: bob.UserID,
Expand Down Expand Up @@ -120,3 +124,119 @@ func TestACLs(t *testing.T) {
)
}
}

// MSC4163: TestACLsForEDUs checks that ACLs are applied to EDUs (typing notifications and read receipts)
func TestACLsForEDUs(t *testing.T) {
runtime.SkipIf(t, runtime.Dendrite)
// 1. Prepare 3 or more servers. 1st will be room host, 2nd will be blocked with
// m.room.server_acl and 3rd server will be affected by this issue. 1st and 2nd
// servers don't have to be powered by dendrite.
deployment := complement.Deploy(t, 3)
Comment thread
FrenchGithubUser marked this conversation as resolved.
defer deployment.Destroy(t)

alice := deployment.Register(t, "hs1", helpers.RegistrationOpts{})
bob := deployment.Register(t, "hs2", helpers.RegistrationOpts{})
charlie := deployment.Register(t, "hs3", helpers.RegistrationOpts{})

// Create a room where hs2 will be blocked by `m.room.server_acl` later on
roomID := alice.MustCreateRoom(t, map[string]interface{}{"preset": "public_chat"})
aliceSince := alice.MustSyncUntil(t, client.SyncReq{}, client.SyncJoinedTo(alice.UserID, roomID))

bob.MustJoinRoom(t, roomID, []spec.ServerName{
deployment.GetFullyQualifiedHomeserverName(t, "hs1"),
})
aliceSince = alice.MustSyncUntil(t, client.SyncReq{Since: aliceSince}, client.SyncJoinedTo(bob.UserID, roomID))
bobSince := bob.MustSyncUntil(t, client.SyncReq{}, client.SyncJoinedTo(bob.UserID, roomID))

// Create a sentinel room without ACLs to confirm federation is working
sentinelRoom := alice.MustCreateRoom(t, map[string]interface{}{"preset": "public_chat"})
aliceSince = alice.MustSyncUntil(t, client.SyncReq{Since: aliceSince}, client.SyncJoinedTo(alice.UserID, sentinelRoom))
bob.MustJoinRoom(t, sentinelRoom, []spec.ServerName{
deployment.GetFullyQualifiedHomeserverName(t, "hs1"),
})
charlie.MustJoinRoom(t, sentinelRoom, []spec.ServerName{
deployment.GetFullyQualifiedHomeserverName(t, "hs1"),
})
aliceSince = alice.MustSyncUntil(t, client.SyncReq{Since: aliceSince},
client.SyncJoinedTo(bob.UserID, sentinelRoom),
client.SyncJoinedTo(charlie.UserID, sentinelRoom),
)

// Block hs2 from participating in the roomID via `m.room.server_acl` rules
stateKey := ""
aclEventID := alice.SendEventSynced(t, roomID, b.Event{
Type: "m.room.server_acl",
Sender: alice.UserID,
StateKey: &stateKey,
Content: map[string]interface{}{
"allow": []string{"*"},
"allow_ip_literals": true,
"deny": []string{
string(deployment.GetFullyQualifiedHomeserverName(t, "hs2")),
},
},
})
// Wait for the ACL to reach hs2 before sending EDUs
bob.MustSyncUntil(t, client.SyncReq{Since: bobSince}, client.SyncTimelineHasEventID(roomID, aclEventID))

// Join charlie to roomID after the ACL is set up
charlie.MustJoinRoom(t, roomID, []spec.ServerName{
deployment.GetFullyQualifiedHomeserverName(t, "hs1"),
})
aliceSince = alice.MustSyncUntil(t, client.SyncReq{Since: aliceSince}, client.SyncJoinedTo(charlie.UserID, roomID))
charlieSince := charlie.MustSyncUntil(t, client.SyncReq{}, client.SyncJoinedTo(charlie.UserID, roomID))

// Bob starts typing (kind of EDU) in both rooms; typing in roomID should be dropped by ACLs
bob.SendTyping(t, roomID, true, 10000)
bob.SendTyping(t, sentinelRoom, true, 10000)

// Bob sets read receipts (another kind of EDU) on both rooms; receipts in roomID should be dropped by ACLs
bob.MustDo(t, "POST", []string{"_matrix", "client", "v3", "rooms", roomID, "read_markers"},
client.WithJSONBody(t, map[string]interface{}{"m.read": aclEventID}))

// Send a sentinel message to sentinelRoom to use as a receipt anchor
sentinelEventID := bob.SendEventSynced(t, sentinelRoom, b.Event{
Type: "m.room.message",
Sender: bob.UserID,
Content: map[string]interface{}{
"msgtype": "m.text",
"body": "sentinel",
},
})
bob.MustDo(t, "POST", []string{"_matrix", "client", "v3", "rooms", sentinelRoom, "read_markers"},
client.WithJSONBody(t, map[string]interface{}{"m.read": sentinelEventID}))

// Wait for the sentinel message and EDUs in sentinelRoom to arrive, proving the federation
// transaction containing hs2's EDUs was processed
alice.MustSyncUntil(t, client.SyncReq{Since: aliceSince},
client.SyncTimelineHasEventID(sentinelRoom, sentinelEventID),
client.SyncEphemeralHas(sentinelRoom, func(result gjson.Result) bool {
return result.Get("type").Str == "m.receipt"
}),
client.SyncEphemeralHas(sentinelRoom, func(result gjson.Result) bool {
return result.Get("type").Str == "m.typing"
}),
)
charlie.MustSyncUntil(t, client.SyncReq{Since: charlieSince},
client.SyncTimelineHasEventID(sentinelRoom, sentinelEventID),
client.SyncEphemeralHas(sentinelRoom, func(result gjson.Result) bool {
return result.Get("type").Str == "m.receipt"
}),
client.SyncEphemeralHas(sentinelRoom, func(result gjson.Result) bool {
return result.Get("type").Str == "m.typing"
}),
)

// Verify with alice (hs1) and charlie (hs3) that we never received EDU's from hs2
for _, user := range []*client.CSAPI{alice, charlie} {
Comment thread
FrenchGithubUser marked this conversation as resolved.
syncResp, _ := user.MustSync(t, client.SyncReq{})

// No typing or read receipts from the blocked server should appear in room with id roomID
ephemerals := syncResp.Get("rooms.join." + client.GjsonEscape(roomID) + ".ephemeral")
must.MatchGJSON(t, ephemerals, match.JSONKeyArrayOfSize("events", 0))

// sentinelRoom should have received the read receipt and typing notification
ephemerals = syncResp.Get("rooms.join." + client.GjsonEscape(sentinelRoom) + ".ephemeral")
must.MatchGJSON(t, ephemerals, match.JSONKeyArrayOfSize("events", 2))
}
}
Loading