From cd709e81e14f341b6f257b43ba9978b3e1a24994 Mon Sep 17 00:00:00 2001 From: David Robertson Date: Mon, 23 Aug 2021 16:37:34 +0100 Subject: [PATCH 01/26] Add test case reproducing matrix-org/synapse#5677 --- .../user_directory_display_names_test.go | 63 +++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 tests/csapi/user_directory_display_names_test.go diff --git a/tests/csapi/user_directory_display_names_test.go b/tests/csapi/user_directory_display_names_test.go new file mode 100644 index 00000000..40674224 --- /dev/null +++ b/tests/csapi/user_directory_display_names_test.go @@ -0,0 +1,63 @@ +// +build !synapse_blacklist + +// Rationale for being included in Synapse's blacklist: https://github.com/matrix-org/synapse/issues/5677 +package csapi_tests + +import ( + "github.com/matrix-org/complement/internal/b" + "github.com/matrix-org/complement/internal/match" + "github.com/matrix-org/complement/internal/must" + "testing" +) + +func TestRoomSpecificUsernameNotLeaked(t *testing.T) { + // Reproduces https://github.com/matrix-org/synapse/issues/5677 + // In that bug report, Alice has revealed a private name to a friend X, + // and Bob can see that private name when he shouldn't be able to. + // I've tweaked the names to be more traditional: Alice reveals a private name + // to Bob, and Eve shouldn't be able to see that name. + deployment := Deploy(t, b.BlueprintAlice) + defer deployment.Destroy(t) + + alice := deployment.Client(t, "hs1", "@alice:hs1") + bob := deployment.RegisterUser(t, "hs1", "bob", "bob-pw") + eve := deployment.RegisterUser(t, "hs1", "eve", "eve-pw") + + t.Run("Usernames specific to a room aren't leaked in the user directory", func(t *testing.T) { + // Bob creates a new room and invites Alice. She accepts. + privateRoom := bob.CreateRoom(t, map[string]interface{}{ + "m.federate": false, + }) + bob.InviteRoom(t, privateRoom, "@alice:hs1") + alice.JoinRoom(t, privateRoom, nil) + + // Alice reveals her private name to Bob + alice.MustDo( + t, + "PUT", + []string{"_matrix", "client", "r0", "rooms", privateRoom, "state", "m.room.member", + "@alice:hs1"}, + map[string]interface{}{ + "displayname": "Alice Cooper", + "membership": "join", + }, + ) + + // Eve looks up alice in the directory using her public name + res := eve.MustDo( + t, + "POST", + []string{"_matrix", "client", "r0", "user_directory", "search"}, + map[string]interface{}{ + "search_term": "alice", + }, + ) + + must.MatchResponse(t, res, match.HTTPResponse{ + JSON: []match.JSON{ + match.JSONKeyEqual("results.0.display_name", "alice"), + match.JSONKeyEqual("results.0.user_id", "@alice:hs1"), + }, + }) + }) +} From 05d148af7d1cff788ac3d387d97686435e897c4e Mon Sep 17 00:00:00 2001 From: David Robertson Date: Mon, 23 Aug 2021 16:54:08 +0100 Subject: [PATCH 02/26] goimports surprised that's not part of the standard "go" executable if go fmt is! --- tests/csapi/user_directory_display_names_test.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/csapi/user_directory_display_names_test.go b/tests/csapi/user_directory_display_names_test.go index 40674224..1695c793 100644 --- a/tests/csapi/user_directory_display_names_test.go +++ b/tests/csapi/user_directory_display_names_test.go @@ -4,10 +4,11 @@ package csapi_tests import ( + "testing" + "github.com/matrix-org/complement/internal/b" "github.com/matrix-org/complement/internal/match" "github.com/matrix-org/complement/internal/must" - "testing" ) func TestRoomSpecificUsernameNotLeaked(t *testing.T) { From 877f6a1a2071205c1616f4fa36cb16a6f869a7b6 Mon Sep 17 00:00:00 2001 From: David Robertson Date: Mon, 23 Aug 2021 19:35:28 +0100 Subject: [PATCH 03/26] Mark MustDo as Deprecated --- internal/client/client.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/internal/client/client.go b/internal/client/client.go index ed3b174f..bb7bca0c 100644 --- a/internal/client/client.go +++ b/internal/client/client.go @@ -213,6 +213,9 @@ func (c *CSAPI) RegisterUser(t *testing.T, localpart, password string) (userID, } // MustDo will do the HTTP request and fail the test if the response is not 2xx +// +// Deprecated: Prefer MustDoFunc. MustDo is the older format which doesn't allow for vargs +// and will be removed in the future. MustDoFunc also logs HTTP response bodies on error. func (c *CSAPI) MustDo(t *testing.T, method string, paths []string, jsonBody interface{}) *http.Response { t.Helper() res := c.DoFunc(t, method, paths, WithJSONBody(t, jsonBody)) From 82092860455cc7fc8c058352f43e809cc8eba20e Mon Sep 17 00:00:00 2001 From: David Robertson Date: Tue, 24 Aug 2021 16:11:22 +0100 Subject: [PATCH 04/26] Introduce SyncUntilInvitedTo --- internal/client/client.go | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/internal/client/client.go b/internal/client/client.go index bb7bca0c..04556088 100644 --- a/internal/client/client.go +++ b/internal/client/client.go @@ -127,7 +127,10 @@ func (c *CSAPI) SendEventSynced(t *testing.T, roomID string, e b.Event) string { return eventID } -// SyncUntilTimelineHas blocks and continually calls /sync until the `check` function returns true. +// SyncUntilTimelineHas is a wrapper around `SyncUntil`. +// It blocks and continually calls `/sync` until +// - we have joined the given room +// - we see an event in the room for which the `check` function returns True // If the `check` function fails the test, the failing event will be automatically logged. // Will time out after CSAPI.SyncUntilTimeout. func (c *CSAPI) SyncUntilTimelineHas(t *testing.T, roomID string, check func(gjson.Result) bool) { @@ -135,7 +138,23 @@ func (c *CSAPI) SyncUntilTimelineHas(t *testing.T, roomID string, check func(gjs c.SyncUntil(t, "", "", "rooms.join."+GjsonEscape(roomID)+".timeline.events", check) } -// SyncUntil blocks and continually calls /sync until the `check` function returns true. +// SyncUntilInvitedTo is a wrapper around SyncUntil. +// It blocks and continually calls `/sync` until we've been invited to the given room. +// Will time out after CSAPI.SyncUntilTimeout. +func (c *CSAPI) SyncUntilInvitedTo(t *testing.T, roomID string) { + t.Helper() + check := func(event gjson.Result) bool { + return event.Get("type").Str == "m.room.member" && + event.Get("content.membership").Str == "invite" && + event.Get("state_key").Str == c.UserID + } + c.SyncUntil(t, "", "", "rooms.invite."+GjsonEscape(roomID)+".invite_state.events", check) +} + +// SyncUntil blocks and continually calls /sync until +// - the response contains a particular `key`, and +// - its corresponding value is an array +// - some element in that array makes the `check` function return true. // If the `check` function fails the test, the failing event will be automatically logged. // Will time out after CSAPI.SyncUntilTimeout. func (c *CSAPI) SyncUntil(t *testing.T, since, filter, key string, check func(gjson.Result) bool) { From 07afbaa7ee1268ae15292e82b70355e1dfcbdc18 Mon Sep 17 00:00:00 2001 From: David Robertson Date: Tue, 24 Aug 2021 18:00:52 +0100 Subject: [PATCH 05/26] client: Helper function SearchUserDirectory Probably bloat that belongs in the specific test? --- internal/client/client.go | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/internal/client/client.go b/internal/client/client.go index 04556088..c7dcf8b0 100644 --- a/internal/client/client.go +++ b/internal/client/client.go @@ -108,6 +108,21 @@ func (c *CSAPI) InviteRoom(t *testing.T, roomID string, userID string) { c.MustDo(t, "POST", []string{"_matrix", "client", "r0", "rooms", roomID, "invite"}, body) } +// SearchUserDirectory makes a request to search the user directory with a given query. +// We don't include an explicit limit on the number of results returned, relying on +// the spec's default of 10. +func (c *CSAPI) SearchUserDirectory(t *testing.T, query string) *http.Response { + t.Helper() + return c.MustDoFunc( + t, + "POST", + []string{"_matrix", "client", "r0", "user_directory", "search"}, + WithJSONBody(t, map[string]interface{}{ + "search_term": query, + }), + ) +} + // SendEventSynced sends `e` into the room and waits for its event ID to come down /sync. // Returns the event ID of the sent event. func (c *CSAPI) SendEventSynced(t *testing.T, roomID string, e b.Event) string { From 75ef397bd0b4a5a6e5bb47f57edd5f9a4ac481b8 Mon Sep 17 00:00:00 2001 From: David Robertson Date: Tue, 24 Aug 2021 18:01:37 +0100 Subject: [PATCH 06/26] match: use rs.Exists() in JSONKeyEqual for consistency with other matchers --- internal/match/json.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/match/json.go b/internal/match/json.go index 84432ce5..345f68e8 100644 --- a/internal/match/json.go +++ b/internal/match/json.go @@ -17,7 +17,7 @@ type JSON func(body []byte) error func JSONKeyEqual(wantKey string, wantValue interface{}) JSON { return func(body []byte) error { res := gjson.GetBytes(body, wantKey) - if res.Index == 0 { + if !res.Exists() { return fmt.Errorf("key '%s' missing", wantKey) } gotValue := res.Value() From 68cf89a8ca99c676a40b947967ea0e68076b7fec Mon Sep 17 00:00:00 2001 From: David Robertson Date: Tue, 24 Aug 2021 18:01:55 +0100 Subject: [PATCH 07/26] match: matcher that seeks an array of a fixed size --- internal/match/json.go | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/internal/match/json.go b/internal/match/json.go index 345f68e8..12cd7459 100644 --- a/internal/match/json.go +++ b/internal/match/json.go @@ -55,6 +55,26 @@ func JSONKeyTypeEqual(wantKey string, wantType gjson.Type) JSON { } } +// JSONKeyArrayOfSize returns a matcher which will check that `wantKey` is present and +// its value is an array with the given size. +// `wantKey` can be nested, see https://godoc.org/github.com/tidwall/gjson#Get for details. +func JSONKeyArrayOfSize(wantKey string, wantSize int) JSON { + return func(body []byte) error { + res := gjson.GetBytes(body, wantKey) + if !res.Exists() { + return fmt.Errorf("key '%s' missing", wantKey) + } + if !res.IsArray() { + return fmt.Errorf("key '%s' is not an array", wantKey) + } + entries := res.Array() + if len(entries) != wantSize { + return fmt.Errorf("key '%s' is an array of the wrong size, got %s want %s", wantKey, len(entries), wantSize) + } + return nil + } +} + func jsonCheckOffInternal(wantKey string, wantItems []interface{}, allowUnwantedItems bool, mapper func(gjson.Result) interface{}, fn func(interface{}, gjson.Result) error) JSON { return func(body []byte) error { res := gjson.GetBytes(body, wantKey) From ce8f3a9c54a6b416bbef021273b7637eb6247b00 Mon Sep 17 00:00:00 2001 From: David Robertson Date: Tue, 24 Aug 2021 18:05:13 +0100 Subject: [PATCH 08/26] Update tests after review hopefully will now pass on dendrite --- .../user_directory_display_names_test.go | 80 +++++++++++-------- 1 file changed, 46 insertions(+), 34 deletions(-) diff --git a/tests/csapi/user_directory_display_names_test.go b/tests/csapi/user_directory_display_names_test.go index 1695c793..044df20a 100644 --- a/tests/csapi/user_directory_display_names_test.go +++ b/tests/csapi/user_directory_display_names_test.go @@ -7,58 +7,70 @@ import ( "testing" "github.com/matrix-org/complement/internal/b" + "github.com/matrix-org/complement/internal/client" "github.com/matrix-org/complement/internal/match" "github.com/matrix-org/complement/internal/must" ) func TestRoomSpecificUsernameNotLeaked(t *testing.T) { // Reproduces https://github.com/matrix-org/synapse/issues/5677 - // In that bug report, Alice has revealed a private name to a friend X, - // and Bob can see that private name when he shouldn't be able to. - // I've tweaked the names to be more traditional: Alice reveals a private name - // to Bob, and Eve shouldn't be able to see that name. + // In that bug report, + // - Bob knows about Alice, and + // - Alice has revealed a private name to another friend X, + // - Bob can see that private name when he shouldn't be able to. + // + // I've tweaked the names to be more traditional: + // - Even knows about Alice, + // - Alice reveals a private name to another friend Bob + // - Eve shouldn't be able to see that private name. deployment := Deploy(t, b.BlueprintAlice) defer deployment.Destroy(t) alice := deployment.Client(t, "hs1", "@alice:hs1") - bob := deployment.RegisterUser(t, "hs1", "bob", "bob-pw") - eve := deployment.RegisterUser(t, "hs1", "eve", "eve-pw") + bob := deployment.RegisterUser(t, "hs1", "bob", "bob-has-a-very-secret-pw") + eve := deployment.RegisterUser(t, "hs1", "eve", "eve-has-a-very-secret-pw") - t.Run("Usernames specific to a room aren't leaked in the user directory", func(t *testing.T) { - // Bob creates a new room and invites Alice. She accepts. - privateRoom := bob.CreateRoom(t, map[string]interface{}{ - "m.federate": false, - }) - bob.InviteRoom(t, privateRoom, "@alice:hs1") - alice.JoinRoom(t, privateRoom, nil) + // Alice creates a public room (so Eve can see that Alice exists) + alice.CreateRoom(t, map[string]interface{}{"visibility": "public"}) - // Alice reveals her private name to Bob - alice.MustDo( - t, - "PUT", - []string{"_matrix", "client", "r0", "rooms", privateRoom, "state", "m.room.member", - "@alice:hs1"}, - map[string]interface{}{ - "displayname": "Alice Cooper", - "membership": "join", - }, - ) + // Bob creates a new room and invites Alice. + privateRoom := bob.CreateRoom(t, map[string]interface{}{ + "visibility": "private", + "invite": []string{alice.UserID}, + }) - // Eve looks up alice in the directory using her public name - res := eve.MustDo( - t, - "POST", - []string{"_matrix", "client", "r0", "user_directory", "search"}, - map[string]interface{}{ - "search_term": "alice", - }, - ) + // Alice waits until she sees the invite, then accepts. + alice.SyncUntilInvitedTo(t, privateRoom) + alice.JoinRoom(t, privateRoom, nil) + + // Alice reveals her private name to Bob + alice.MustDoFunc( + t, + "PUT", + []string{"_matrix", "client", "r0", "rooms", privateRoom, "state", "m.room.member", "@alice:hs1"}, + client.WithJSONBody(t, map[string]interface{}{ + "displayname": "Alice Cooper", + "membership": "join", + }), + ) + // Eve should be able to find alice by her public name and mxid + for _, name := range []string{"alice", alice.UserID} { + res := eve.SearchUserDirectory(t, name) must.MatchResponse(t, res, match.HTTPResponse{ JSON: []match.JSON{ + match.JSONKeyArrayOfSize("results", 1), match.JSONKeyEqual("results.0.display_name", "alice"), - match.JSONKeyEqual("results.0.user_id", "@alice:hs1"), + match.JSONKeyEqual("results.0.user_id", alice.UserID), }, }) + } + + // Eve should not be able to find Alice by her private name + res := eve.SearchUserDirectory(t, "Alice Cooper") + must.MatchResponse(t, res, match.HTTPResponse{ + JSON: []match.JSON{ + match.JSONKeyArrayOfSize("results", 0), + }, }) } From ed7d66e821a3af383c9896e03ba10a815fdf60b5 Mon Sep 17 00:00:00 2001 From: David Robertson Date: Tue, 24 Aug 2021 18:35:15 +0100 Subject: [PATCH 09/26] Fix format string --- internal/match/json.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/match/json.go b/internal/match/json.go index 12cd7459..8972de9b 100644 --- a/internal/match/json.go +++ b/internal/match/json.go @@ -69,7 +69,7 @@ func JSONKeyArrayOfSize(wantKey string, wantSize int) JSON { } entries := res.Array() if len(entries) != wantSize { - return fmt.Errorf("key '%s' is an array of the wrong size, got %s want %s", wantKey, len(entries), wantSize) + return fmt.Errorf("key '%s' is an array of the wrong size, got %v want %v", wantKey, len(entries), wantSize) } return nil } From 5435622b4b2fd0d5779e9a240594dbfc95b866e3 Mon Sep 17 00:00:00 2001 From: David Robertson Date: Tue, 24 Aug 2021 18:35:27 +0100 Subject: [PATCH 10/26] Make lint accept use of deprecated things tried to make this just a warning but I couldn't figure out the config. --- .golangci.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.golangci.yml b/.golangci.yml index 38038f32..0961ec9f 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -438,6 +438,11 @@ issues: - G107 # gosec: Error on TLS InsecureSkipVerify set to true. - G402 + # staticcheck: Using a deprecated function, variable, constant or field + # Tried to make this a warning rather than error in the severity section. + # I failed. But it's nice to have goland know that certain things are deprecated + # so it can strike them through. + - SA1019 # Excluding configuration per-path, per-linter, per-text and per-source exclude-rules: From 6ce2fc47c535a48eb7eb35ede42e6f6fc24b13c3 Mon Sep 17 00:00:00 2001 From: David Robertson Date: Tue, 24 Aug 2021 18:52:44 +0100 Subject: [PATCH 11/26] Remove from synapse blacklist --- tests/csapi/user_directory_display_names_test.go | 3 --- 1 file changed, 3 deletions(-) diff --git a/tests/csapi/user_directory_display_names_test.go b/tests/csapi/user_directory_display_names_test.go index 044df20a..2692f141 100644 --- a/tests/csapi/user_directory_display_names_test.go +++ b/tests/csapi/user_directory_display_names_test.go @@ -1,6 +1,3 @@ -// +build !synapse_blacklist - -// Rationale for being included in Synapse's blacklist: https://github.com/matrix-org/synapse/issues/5677 package csapi_tests import ( From 26aad2089a8a05ab886e2e2ff23433d9f1cdd31f Mon Sep 17 00:00:00 2001 From: David Robertson Date: Wed, 25 Aug 2021 10:56:31 +0100 Subject: [PATCH 12/26] Introduce `AnyOf` matcher --- internal/match/json.go | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/internal/match/json.go b/internal/match/json.go index 8972de9b..b61190fa 100644 --- a/internal/match/json.go +++ b/internal/match/json.go @@ -3,6 +3,7 @@ package match import ( "fmt" "reflect" + "strings" "github.com/tidwall/gjson" ) @@ -224,3 +225,29 @@ func JSONMapEach(wantKey string, fn func(k, v gjson.Result) error) JSON { return err } } + +// AnyOf takes 1 or more `checkers`, and builds a new checker which accepts a given +// json body iff it's accepted by at least one of the original `checkers`. +func AnyOf(checkers ...JSON) JSON { + return func(body []byte) error { + if len(checkers) == 0 { + return fmt.Errorf("must provide at least one checker to AnyOf") + } + + errors := make([]error, len(checkers)) + for i, check := range checkers { + errors[i] = check(body) + if errors[i] == nil { + return nil + } + } + + builder := strings.Builder{} + builder.WriteString("all checks failed:") + for _, err := range errors { + builder.WriteString("\n ") + builder.WriteString(err.Error()) + } + return fmt.Errorf(builder.String()) + } +} From 9f4b9a01ad65034e08049492fbeeac7ba6b40664 Mon Sep 17 00:00:00 2001 From: David Robertson Date: Wed, 25 Aug 2021 10:56:47 +0100 Subject: [PATCH 13/26] Expand test cases to inspect Bob's behaviour too --- .../user_directory_display_names_test.go | 87 +++++++++++++++---- 1 file changed, 69 insertions(+), 18 deletions(-) diff --git a/tests/csapi/user_directory_display_names_test.go b/tests/csapi/user_directory_display_names_test.go index 2692f141..2f0850e0 100644 --- a/tests/csapi/user_directory_display_names_test.go +++ b/tests/csapi/user_directory_display_names_test.go @@ -9,8 +9,8 @@ import ( "github.com/matrix-org/complement/internal/must" ) -func TestRoomSpecificUsernameNotLeaked(t *testing.T) { - // Reproduces https://github.com/matrix-org/synapse/issues/5677 +func TestRoomSpecificUsernameHandling(t *testing.T) { + // Originally written to reproduce https://github.com/matrix-org/synapse/issues/5677 // In that bug report, // - Bob knows about Alice, and // - Alice has revealed a private name to another friend X, @@ -51,23 +51,74 @@ func TestRoomSpecificUsernameNotLeaked(t *testing.T) { }), ) - // Eve should be able to find alice by her public name and mxid - for _, name := range []string{"alice", alice.UserID} { - res := eve.SearchUserDirectory(t, name) - must.MatchResponse(t, res, match.HTTPResponse{ - JSON: []match.JSON{ - match.JSONKeyArrayOfSize("results", 1), - match.JSONKeyEqual("results.0.display_name", "alice"), - match.JSONKeyEqual("results.0.user_id", alice.UserID), - }, + justAliceByPublicName := []match.JSON{ + match.JSONKeyArrayOfSize("results", 1), + match.JSONKeyEqual("results.0.display_name", "alice"), + match.JSONKeyEqual("results.0.user_id", alice.UserID), + } + + t.Run("Searcher can find target by display name "+ + "when searcher and target have no room in common, share a homeserver, and "+ + "target is in a public room on that homeserver", + func(t *testing.T) { + res := eve.SearchUserDirectory(t, "alice") + must.MatchResponse(t, res, match.HTTPResponse{JSON: justAliceByPublicName}) + }) + + t.Run("Searcher can find target by mxid "+ + "when searcher and target have no room in common, share a homeserver, and "+ + "target is in a public room on that homeserver", + func(t *testing.T) { + res := eve.SearchUserDirectory(t, alice.UserID) + must.MatchResponse(t, res, match.HTTPResponse{JSON: justAliceByPublicName}) }) + + noResults := []match.JSON{ + match.JSONKeyArrayOfSize("results", 0), } - // Eve should not be able to find Alice by her private name - res := eve.SearchUserDirectory(t, "Alice Cooper") - must.MatchResponse(t, res, match.HTTPResponse{ - JSON: []match.JSON{ - match.JSONKeyArrayOfSize("results", 0), - }, - }) + t.Run("Searcher cannot find target by room-specific name they are not privy to "+ + "when searcher and target have no room in common, share a homeserver, and "+ + "target is in a public room on that homeserver", + func(t *testing.T) { + res := eve.SearchUserDirectory(t, "Alice Cooper") + must.MatchResponse(t, res, match.HTTPResponse{JSON: noResults}) + }) + + justAliceByPublicOrPrivateName := []match.JSON{ + match.JSONKeyArrayOfSize("results", 1), + // TODO should bob find alice by her public or private name? + match.AnyOf( + match.JSONKeyEqual("results.0.display_name", "Alice Cooper"), + match.JSONKeyEqual("results.0.display_name", "alice"), + ), + match.JSONKeyEqual("results.0.user_id", alice.UserID), + } + + t.Run("Searcher can find target by public display name "+ + "when searcher and target share a private room with a specific display_name for the target", + func(t *testing.T) { + res := bob.SearchUserDirectory(t, "alice") + must.MatchResponse(t, res, match.HTTPResponse{ + JSON: justAliceByPublicOrPrivateName, + }) + }) + + t.Run("Searcher can find target by mxid "+ + "when searcher and target share a private room with a specific display_name for the target", + func(t *testing.T) { + res := bob.SearchUserDirectory(t, alice.UserID) + must.MatchResponse(t, res, match.HTTPResponse{ + JSON: justAliceByPublicOrPrivateName, + }) + }) + + t.Run("Searcher can find target by room-specific name"+ + "when searcher and target share a private room with a specific display_name for the target", + func(t *testing.T) { + res := bob.SearchUserDirectory(t, "Alice Cooper") + must.MatchResponse(t, res, match.HTTPResponse{ + JSON: justAliceByPublicOrPrivateName, + }) + }) } From a2e0d0602c81ec085b497883bc129c38dbd43617 Mon Sep 17 00:00:00 2001 From: David Robertson Date: Thu, 26 Aug 2021 13:11:38 +0100 Subject: [PATCH 14/26] Tweak expected behaviour It's not required that you can search for people by per-room names (and per-room names are kind of an accident anyway right now) --- .../user_directory_display_names_test.go | 51 +++++-------------- 1 file changed, 12 insertions(+), 39 deletions(-) diff --git a/tests/csapi/user_directory_display_names_test.go b/tests/csapi/user_directory_display_names_test.go index 2f0850e0..c68cc8a2 100644 --- a/tests/csapi/user_directory_display_names_test.go +++ b/tests/csapi/user_directory_display_names_test.go @@ -46,28 +46,24 @@ func TestRoomSpecificUsernameHandling(t *testing.T) { "PUT", []string{"_matrix", "client", "r0", "rooms", privateRoom, "state", "m.room.member", "@alice:hs1"}, client.WithJSONBody(t, map[string]interface{}{ - "displayname": "Alice Cooper", + "displayname": "Freddy", "membership": "join", }), ) justAliceByPublicName := []match.JSON{ match.JSONKeyArrayOfSize("results", 1), - match.JSONKeyEqual("results.0.display_name", "alice"), + match.JSONKeyEqual("results.0.display_name", "Alice"), match.JSONKeyEqual("results.0.user_id", alice.UserID), } - t.Run("Searcher can find target by display name "+ - "when searcher and target have no room in common, share a homeserver, and "+ - "target is in a public room on that homeserver", + t.Run("Eve can find Alice by profile display name", func(t *testing.T) { - res := eve.SearchUserDirectory(t, "alice") + res := eve.SearchUserDirectory(t, "Alice") must.MatchResponse(t, res, match.HTTPResponse{JSON: justAliceByPublicName}) }) - t.Run("Searcher can find target by mxid "+ - "when searcher and target have no room in common, share a homeserver, and "+ - "target is in a public room on that homeserver", + t.Run("Eve can find Alice by mxid", func(t *testing.T) { res := eve.SearchUserDirectory(t, alice.UserID) must.MatchResponse(t, res, match.HTTPResponse{JSON: justAliceByPublicName}) @@ -77,48 +73,25 @@ func TestRoomSpecificUsernameHandling(t *testing.T) { match.JSONKeyArrayOfSize("results", 0), } - t.Run("Searcher cannot find target by room-specific name they are not privy to "+ - "when searcher and target have no room in common, share a homeserver, and "+ - "target is in a public room on that homeserver", + t.Run("Eve cannot find Alice by room-specific name that Eve is not privy to", func(t *testing.T) { - res := eve.SearchUserDirectory(t, "Alice Cooper") + res := eve.SearchUserDirectory(t, "Freddy") must.MatchResponse(t, res, match.HTTPResponse{JSON: noResults}) }) - justAliceByPublicOrPrivateName := []match.JSON{ - match.JSONKeyArrayOfSize("results", 1), - // TODO should bob find alice by her public or private name? - match.AnyOf( - match.JSONKeyEqual("results.0.display_name", "Alice Cooper"), - match.JSONKeyEqual("results.0.display_name", "alice"), - ), - match.JSONKeyEqual("results.0.user_id", alice.UserID), - } - - t.Run("Searcher can find target by public display name "+ - "when searcher and target share a private room with a specific display_name for the target", + t.Run("Bob can find Alice by profile display name", func(t *testing.T) { - res := bob.SearchUserDirectory(t, "alice") + res := bob.SearchUserDirectory(t, "Alice") must.MatchResponse(t, res, match.HTTPResponse{ - JSON: justAliceByPublicOrPrivateName, + JSON: justAliceByPublicName, }) }) - t.Run("Searcher can find target by mxid "+ - "when searcher and target share a private room with a specific display_name for the target", + t.Run("Bob can find Alice by mxid", func(t *testing.T) { res := bob.SearchUserDirectory(t, alice.UserID) must.MatchResponse(t, res, match.HTTPResponse{ - JSON: justAliceByPublicOrPrivateName, - }) - }) - - t.Run("Searcher can find target by room-specific name"+ - "when searcher and target share a private room with a specific display_name for the target", - func(t *testing.T) { - res := bob.SearchUserDirectory(t, "Alice Cooper") - must.MatchResponse(t, res, match.HTTPResponse{ - JSON: justAliceByPublicOrPrivateName, + JSON: justAliceByPublicName, }) }) } From b10655f820125f4ae5ab854365d1dca73f878d77 Mon Sep 17 00:00:00 2001 From: David Robertson Date: Tue, 31 Aug 2021 16:57:21 +0100 Subject: [PATCH 15/26] Enforce Displaynames in blueprints --- internal/instruction/runner.go | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/internal/instruction/runner.go b/internal/instruction/runner.go index 4635d5da..20ec4900 100644 --- a/internal/instruction/runner.go +++ b/internal/instruction/runner.go @@ -289,6 +289,9 @@ func calculateUserInstructionSets(r *Runner, hs b.Homeserver) [][]instruction { instrs = append(instrs, instructionLogin(hs, user)) } else { instrs = append(instrs, instructionRegister(hs, user)) + if user.DisplayName != "" { + instrs = append(instrs, instructionDisplayName(hs, user)) + } } createdUsers[user.Localpart] = true @@ -440,6 +443,21 @@ func instructionRegister(hs b.Homeserver, user b.User) instruction { } } +func instructionDisplayName(hs b.Homeserver, user b.User) instruction { + body := map[string]interface{}{ + "displayname": user.DisplayName, + } + return instruction{ + method: "PUT", + path: fmt.Sprintf( + "/_matrix/client/r0/profile/@%s:%s/displayname", + user.Localpart, hs.Name, + ), + accessToken: fmt.Sprintf("user_@%s:%s", user.Localpart, hs.Name), + body: body, + } +} + func instructionLogin(hs b.Homeserver, user b.User) instruction { body := map[string]interface{}{ "type": "m.login.password", From b52c712078d381ccbd48957beb9085654c0d097d Mon Sep 17 00:00:00 2001 From: David Robertson Date: Thu, 2 Sep 2021 13:59:25 +0100 Subject: [PATCH 16/26] Fix typo --- tests/csapi/user_directory_display_names_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/csapi/user_directory_display_names_test.go b/tests/csapi/user_directory_display_names_test.go index c68cc8a2..0feb5106 100644 --- a/tests/csapi/user_directory_display_names_test.go +++ b/tests/csapi/user_directory_display_names_test.go @@ -17,7 +17,7 @@ func TestRoomSpecificUsernameHandling(t *testing.T) { // - Bob can see that private name when he shouldn't be able to. // // I've tweaked the names to be more traditional: - // - Even knows about Alice, + // - Eve knows about Alice, // - Alice reveals a private name to another friend Bob // - Eve shouldn't be able to see that private name. deployment := Deploy(t, b.BlueprintAlice) From c95b011e49d28823b6e271602f4491a2c6940e2e Mon Sep 17 00:00:00 2001 From: David Robertson Date: Thu, 2 Sep 2021 13:59:32 +0100 Subject: [PATCH 17/26] Add case for remote user with per-room nickname --- ..._directory_federated_display_names_test.go | 115 ++++++++++++++++++ 1 file changed, 115 insertions(+) create mode 100644 tests/user_directory_federated_display_names_test.go diff --git a/tests/user_directory_federated_display_names_test.go b/tests/user_directory_federated_display_names_test.go new file mode 100644 index 00000000..751898cd --- /dev/null +++ b/tests/user_directory_federated_display_names_test.go @@ -0,0 +1,115 @@ +package tests + +import ( + "testing" + + "github.com/tidwall/gjson" + + "github.com/matrix-org/complement/internal/b" + "github.com/matrix-org/complement/internal/client" + "github.com/matrix-org/complement/internal/match" + "github.com/matrix-org/complement/internal/must" +) + +func TestRoomSpecificUsernameHandlingOverFederation(t *testing.T) { + // Another test case for https://github.com/matrix-org/synapse/issues/5677 + // Now we're checking that we don't leak per-room names of a remote user. + + // The scenario: + // - Charlie (hs2) and Bob (hs1) are in a public room. + // - Charlie and Bob are also in a private room, and Charlie has a per-room name there. + // - Eve (hs1) shouldn't be able to see that private name. + deployment := Deploy(t, b.BlueprintFederationTwoLocalOneRemote) + defer deployment.Destroy(t) + //defer time.Sleep(2 * time.Hour) + + bob := deployment.Client(t, "hs1", "@bob:hs1") + remoteCharlie := deployment.Client(t, "hs2", "@charlie:hs2") + eve := deployment.RegisterUser(t, "hs1", "eve", "eve-has-a-very-secret-pw") + + // Charlie creates a public room and invites Bob (so Eve can see that Charlie exists) + publicRoom := remoteCharlie.CreateRoom(t, map[string]interface{}{ + "visibility": "public", + "invite": []string{bob.UserID}, + }) + + // Charlie also creates a private room with Bob. + privateRoom := remoteCharlie.CreateRoom(t, map[string]interface{}{ + "visibility": "private", + "invite": []string{bob.UserID}, + }) + + // Bob accepts both invites. + bob.SyncUntilInvitedTo(t, privateRoom) + bob.SyncUntilInvitedTo(t, publicRoom) + bob.JoinRoom(t, publicRoom, []string{"hs2"}) + bob.JoinRoom(t, privateRoom, []string{"hs2"}) + + // Charlie reveals her private name to Bob + remoteCharlie.MustDoFunc( + t, + "PUT", + []string{"_matrix", "client", "r0", "rooms", privateRoom, "state", "m.room.member", remoteCharlie.UserID}, + client.WithJSONBody(t, map[string]interface{}{ + "displayname": "Freddy", + "membership": "join", + }), + ) + + // Wait for Bob to see the name change + bob.SyncUntilTimelineHas(t, privateRoom, func(ev gjson.Result) bool { + return ev.Get("type").Str == "m.room.member" && + ev.Get("state_key").Str == remoteCharlie.UserID && + ev.Get("content.displayname").Str == "Freddy" + }) + + // There's no way to know what a remote user's "public profile" is + // without making a federation request. Accept either + // mxid or their profile's name as the displayname. + justCharlieByPublicNameOrMxid := []match.JSON{ + match.JSONKeyArrayOfSize("results", 1), + match.AnyOf( + match.JSONKeyEqual("results.0.display_name", "Charlie"), + match.JSONKeyEqual("results.0.display_name", remoteCharlie.UserID), + ), + match.JSONKeyEqual("results.0.user_id", remoteCharlie.UserID), + } + + t.Run("Eve can find Charlie by profile display name", + func(t *testing.T) { + res := eve.SearchUserDirectory(t, "Charlie") + must.MatchResponse(t, res, match.HTTPResponse{JSON: justCharlieByPublicNameOrMxid}) + }) + + t.Run("Eve can find Charlie by mxid", + func(t *testing.T) { + res := eve.SearchUserDirectory(t, remoteCharlie.UserID) + must.MatchResponse(t, res, match.HTTPResponse{JSON: justCharlieByPublicNameOrMxid}) + }) + + noResults := []match.JSON{ + match.JSONKeyArrayOfSize("results", 0), + } + + t.Run("Eve cannot find Charlie by room-specific name that Eve is not privy to", + func(t *testing.T) { + res := eve.SearchUserDirectory(t, "Freddy") + must.MatchResponse(t, res, match.HTTPResponse{JSON: noResults}) + }) + + t.Run("Bob can find Charlie by profile display name", + func(t *testing.T) { + res := bob.SearchUserDirectory(t, "Charlie") + must.MatchResponse(t, res, match.HTTPResponse{ + JSON: justCharlieByPublicNameOrMxid, + }) + }) + + t.Run("Bob can find Charlie by mxid", + func(t *testing.T) { + res := bob.SearchUserDirectory(t, remoteCharlie.UserID) + must.MatchResponse(t, res, match.HTTPResponse{ + JSON: justCharlieByPublicNameOrMxid, + }) + }) +} From d650c5aace743c8ad98e7f3f7a1680bb5d77d481 Mon Sep 17 00:00:00 2001 From: David Robertson Date: Thu, 2 Sep 2021 14:36:06 +0100 Subject: [PATCH 18/26] Ensure pub name, priv name & localpart all differ --- .../user_directory_display_names_test.go | 24 +++++++++++++---- ..._directory_federated_display_names_test.go | 26 ++++++++++++++----- 2 files changed, 39 insertions(+), 11 deletions(-) diff --git a/tests/csapi/user_directory_display_names_test.go b/tests/csapi/user_directory_display_names_test.go index 0feb5106..97bcf25f 100644 --- a/tests/csapi/user_directory_display_names_test.go +++ b/tests/csapi/user_directory_display_names_test.go @@ -27,6 +27,19 @@ func TestRoomSpecificUsernameHandling(t *testing.T) { bob := deployment.RegisterUser(t, "hs1", "bob", "bob-has-a-very-secret-pw") eve := deployment.RegisterUser(t, "hs1", "eve", "eve-has-a-very-secret-pw") + // Alice sets her profile displayname. This ensures that her + // public name, private name and userid localpart are all + // distinguishable, even case-insensitively. + const alicePublicName = "Alice Cooper" + alice.MustDoFunc( + t, + "PUT", + []string{"profile", alice.UserID, "displayname"}, + client.WithJSONBody(t, map[string]interface{}{ + "displayname": alicePublicName, + }), + ) + // Alice creates a public room (so Eve can see that Alice exists) alice.CreateRoom(t, map[string]interface{}{"visibility": "public"}) @@ -41,25 +54,26 @@ func TestRoomSpecificUsernameHandling(t *testing.T) { alice.JoinRoom(t, privateRoom, nil) // Alice reveals her private name to Bob + const alicePrivateName = "Freddy" alice.MustDoFunc( t, "PUT", []string{"_matrix", "client", "r0", "rooms", privateRoom, "state", "m.room.member", "@alice:hs1"}, client.WithJSONBody(t, map[string]interface{}{ - "displayname": "Freddy", + "displayname": alicePrivateName, "membership": "join", }), ) justAliceByPublicName := []match.JSON{ match.JSONKeyArrayOfSize("results", 1), - match.JSONKeyEqual("results.0.display_name", "Alice"), + match.JSONKeyEqual("results.0.display_name", alicePublicName), match.JSONKeyEqual("results.0.user_id", alice.UserID), } t.Run("Eve can find Alice by profile display name", func(t *testing.T) { - res := eve.SearchUserDirectory(t, "Alice") + res := eve.SearchUserDirectory(t, alicePublicName) must.MatchResponse(t, res, match.HTTPResponse{JSON: justAliceByPublicName}) }) @@ -75,13 +89,13 @@ func TestRoomSpecificUsernameHandling(t *testing.T) { t.Run("Eve cannot find Alice by room-specific name that Eve is not privy to", func(t *testing.T) { - res := eve.SearchUserDirectory(t, "Freddy") + res := eve.SearchUserDirectory(t, alicePrivateName) must.MatchResponse(t, res, match.HTTPResponse{JSON: noResults}) }) t.Run("Bob can find Alice by profile display name", func(t *testing.T) { - res := bob.SearchUserDirectory(t, "Alice") + res := bob.SearchUserDirectory(t, alicePublicName) must.MatchResponse(t, res, match.HTTPResponse{ JSON: justAliceByPublicName, }) diff --git a/tests/user_directory_federated_display_names_test.go b/tests/user_directory_federated_display_names_test.go index 751898cd..0174454b 100644 --- a/tests/user_directory_federated_display_names_test.go +++ b/tests/user_directory_federated_display_names_test.go @@ -27,6 +27,19 @@ func TestRoomSpecificUsernameHandlingOverFederation(t *testing.T) { remoteCharlie := deployment.Client(t, "hs2", "@charlie:hs2") eve := deployment.RegisterUser(t, "hs1", "eve", "eve-has-a-very-secret-pw") + // Charlie sets her profile displayname. This ensures that her + // public name, private name and userid localpart are all + // distinguishable, even case-insensitively. + const charliePublicName = "Charlie Cooper" + remoteCharlie.MustDoFunc( + t, + "PUT", + []string{"profile", remoteCharlie.UserID, "displayname"}, + client.WithJSONBody(t, map[string]interface{}{ + "displayname": charliePublicName, + }), + ) + // Charlie creates a public room and invites Bob (so Eve can see that Charlie exists) publicRoom := remoteCharlie.CreateRoom(t, map[string]interface{}{ "visibility": "public", @@ -46,12 +59,13 @@ func TestRoomSpecificUsernameHandlingOverFederation(t *testing.T) { bob.JoinRoom(t, privateRoom, []string{"hs2"}) // Charlie reveals her private name to Bob + const charliePrivateName = "Freddy" remoteCharlie.MustDoFunc( t, "PUT", []string{"_matrix", "client", "r0", "rooms", privateRoom, "state", "m.room.member", remoteCharlie.UserID}, client.WithJSONBody(t, map[string]interface{}{ - "displayname": "Freddy", + "displayname": charliePrivateName, "membership": "join", }), ) @@ -60,7 +74,7 @@ func TestRoomSpecificUsernameHandlingOverFederation(t *testing.T) { bob.SyncUntilTimelineHas(t, privateRoom, func(ev gjson.Result) bool { return ev.Get("type").Str == "m.room.member" && ev.Get("state_key").Str == remoteCharlie.UserID && - ev.Get("content.displayname").Str == "Freddy" + ev.Get("content.displayname").Str == charliePrivateName }) // There's no way to know what a remote user's "public profile" is @@ -69,7 +83,7 @@ func TestRoomSpecificUsernameHandlingOverFederation(t *testing.T) { justCharlieByPublicNameOrMxid := []match.JSON{ match.JSONKeyArrayOfSize("results", 1), match.AnyOf( - match.JSONKeyEqual("results.0.display_name", "Charlie"), + match.JSONKeyEqual("results.0.display_name", charliePublicName), match.JSONKeyEqual("results.0.display_name", remoteCharlie.UserID), ), match.JSONKeyEqual("results.0.user_id", remoteCharlie.UserID), @@ -77,7 +91,7 @@ func TestRoomSpecificUsernameHandlingOverFederation(t *testing.T) { t.Run("Eve can find Charlie by profile display name", func(t *testing.T) { - res := eve.SearchUserDirectory(t, "Charlie") + res := eve.SearchUserDirectory(t, charliePublicName) must.MatchResponse(t, res, match.HTTPResponse{JSON: justCharlieByPublicNameOrMxid}) }) @@ -93,13 +107,13 @@ func TestRoomSpecificUsernameHandlingOverFederation(t *testing.T) { t.Run("Eve cannot find Charlie by room-specific name that Eve is not privy to", func(t *testing.T) { - res := eve.SearchUserDirectory(t, "Freddy") + res := eve.SearchUserDirectory(t, charliePrivateName) must.MatchResponse(t, res, match.HTTPResponse{JSON: noResults}) }) t.Run("Bob can find Charlie by profile display name", func(t *testing.T) { - res := bob.SearchUserDirectory(t, "Charlie") + res := bob.SearchUserDirectory(t, charliePublicName) must.MatchResponse(t, res, match.HTTPResponse{ JSON: justCharlieByPublicNameOrMxid, }) From 815cf7b07d72c43b23030d66546a739034bef89e Mon Sep 17 00:00:00 2001 From: David Robertson Date: Thu, 2 Sep 2021 14:36:46 +0100 Subject: [PATCH 19/26] Remove testing comment --- tests/user_directory_federated_display_names_test.go | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/user_directory_federated_display_names_test.go b/tests/user_directory_federated_display_names_test.go index 0174454b..3a611fd5 100644 --- a/tests/user_directory_federated_display_names_test.go +++ b/tests/user_directory_federated_display_names_test.go @@ -21,7 +21,6 @@ func TestRoomSpecificUsernameHandlingOverFederation(t *testing.T) { // - Eve (hs1) shouldn't be able to see that private name. deployment := Deploy(t, b.BlueprintFederationTwoLocalOneRemote) defer deployment.Destroy(t) - //defer time.Sleep(2 * time.Hour) bob := deployment.Client(t, "hs1", "@bob:hs1") remoteCharlie := deployment.Client(t, "hs2", "@charlie:hs2") From 4c95d45b5439272d851c3115c5f26496ec078ad6 Mon Sep 17 00:00:00 2001 From: David Robertson Date: Mon, 6 Sep 2021 17:46:46 +0100 Subject: [PATCH 20/26] Prefer MustDoFunc; test joining with private name this unfortunately got bundled in with changes I'd already started. --- internal/client/client.go | 15 -- .../user_directory_display_names_test.go | 160 +++++++++++++----- ..._directory_federated_display_names_test.go | 77 ++++++--- 3 files changed, 169 insertions(+), 83 deletions(-) diff --git a/internal/client/client.go b/internal/client/client.go index c7dcf8b0..04556088 100644 --- a/internal/client/client.go +++ b/internal/client/client.go @@ -108,21 +108,6 @@ func (c *CSAPI) InviteRoom(t *testing.T, roomID string, userID string) { c.MustDo(t, "POST", []string{"_matrix", "client", "r0", "rooms", roomID, "invite"}, body) } -// SearchUserDirectory makes a request to search the user directory with a given query. -// We don't include an explicit limit on the number of results returned, relying on -// the spec's default of 10. -func (c *CSAPI) SearchUserDirectory(t *testing.T, query string) *http.Response { - t.Helper() - return c.MustDoFunc( - t, - "POST", - []string{"_matrix", "client", "r0", "user_directory", "search"}, - WithJSONBody(t, map[string]interface{}{ - "search_term": query, - }), - ) -} - // SendEventSynced sends `e` into the room and waits for its event ID to come down /sync. // Returns the event ID of the sent event. func (c *CSAPI) SendEventSynced(t *testing.T, roomID string, e b.Event) string { diff --git a/tests/csapi/user_directory_display_names_test.go b/tests/csapi/user_directory_display_names_test.go index 97bcf25f..0c93ddb5 100644 --- a/tests/csapi/user_directory_display_names_test.go +++ b/tests/csapi/user_directory_display_names_test.go @@ -9,7 +9,21 @@ import ( "github.com/matrix-org/complement/internal/must" ) -func TestRoomSpecificUsernameHandling(t *testing.T) { +const aliceId = "@alice:hs1" +const alicePublicName = "Alice Cooper" +const alicePrivateName = "Freddy" + +var justAliceByPublicName = []match.JSON{ + match.JSONKeyArrayOfSize("results", 1), + match.JSONKeyEqual("results.0.display_name", alicePublicName), + match.JSONKeyEqual("results.0.user_id", aliceId), +} + +var noResults = []match.JSON{ + match.JSONKeyArrayOfSize("results", 0), +} + +func setupUsers(t *testing.T) (*client.CSAPI, *client.CSAPI, *client.CSAPI) { // Originally written to reproduce https://github.com/matrix-org/synapse/issues/5677 // In that bug report, // - Bob knows about Alice, and @@ -19,18 +33,17 @@ func TestRoomSpecificUsernameHandling(t *testing.T) { // I've tweaked the names to be more traditional: // - Eve knows about Alice, // - Alice reveals a private name to another friend Bob - // - Eve shouldn't be able to see that private name. + // - Eve shouldn't be able to see that private name via the directory. deployment := Deploy(t, b.BlueprintAlice) defer deployment.Destroy(t) - alice := deployment.Client(t, "hs1", "@alice:hs1") + alice := deployment.Client(t, "hs1", aliceId) bob := deployment.RegisterUser(t, "hs1", "bob", "bob-has-a-very-secret-pw") eve := deployment.RegisterUser(t, "hs1", "eve", "eve-has-a-very-secret-pw") // Alice sets her profile displayname. This ensures that her // public name, private name and userid localpart are all // distinguishable, even case-insensitively. - const alicePublicName = "Alice Cooper" alice.MustDoFunc( t, "PUT", @@ -40,9 +53,81 @@ func TestRoomSpecificUsernameHandling(t *testing.T) { }), ) - // Alice creates a public room (so Eve can see that Alice exists) + // Alice creates a public room (so when Eve searches, she can see that Alice exists) alice.CreateRoom(t, map[string]interface{}{"visibility": "public"}) + return alice, bob, eve +} + +func checkExpectations(t *testing.T, bob, eve *client.CSAPI) { + t.Run("Eve can find Alice by profile display name", func(t *testing.T) { + res := eve.MustDoFunc( + t, + "POST", + []string{"_matrix", "client", "r0", "user_directory", "search"}, + client.WithJSONBody(t, map[string]interface{}{ + "search_term": alicePublicName, + }), + ) + must.MatchResponse(t, res, match.HTTPResponse{JSON: justAliceByPublicName}) + }) + + t.Run("Eve can find Alice by mxid", func(t *testing.T) { + res := eve.MustDoFunc( + t, + "POST", + []string{"_matrix", "client", "r0", "user_directory", "search"}, + client.WithJSONBody(t, map[string]interface{}{ + "search_term": aliceId, + }), + ) + must.MatchResponse(t, res, match.HTTPResponse{JSON: justAliceByPublicName}) + }) + + t.Run("Eve cannot find Alice by room-specific name that Eve is not privy to", func(t *testing.T) { + res := eve.MustDoFunc( + t, + "POST", + []string{"_matrix", "client", "r0", "user_directory", "search"}, + client.WithJSONBody(t, map[string]interface{}{ + "search_term": alicePrivateName, + }), + ) + must.MatchResponse(t, res, match.HTTPResponse{JSON: noResults}) + }) + + t.Run("Bob can find Alice by profile display name", func(t *testing.T) { + res := bob.MustDoFunc( + t, + "POST", + []string{"_matrix", "client", "r0", "user_directory", "search"}, + client.WithJSONBody(t, map[string]interface{}{ + "search_term": alicePublicName, + }), + ) + must.MatchResponse(t, res, match.HTTPResponse{ + JSON: justAliceByPublicName, + }) + }) + + t.Run("Bob can find Alice by mxid", func(t *testing.T) { + res := bob.MustDoFunc( + t, + "POST", + []string{"_matrix", "client", "r0", "user_directory", "search"}, + client.WithJSONBody(t, map[string]interface{}{ + "search_term": aliceId, + }), + ) + must.MatchResponse(t, res, match.HTTPResponse{ + JSON: justAliceByPublicName, + }) + }) +} + +func TestRoomSpecificUsernameChange(t *testing.T) { + alice, bob, eve := setupUsers(t) + // Bob creates a new room and invites Alice. privateRoom := bob.CreateRoom(t, map[string]interface{}{ "visibility": "private", @@ -54,58 +139,43 @@ func TestRoomSpecificUsernameHandling(t *testing.T) { alice.JoinRoom(t, privateRoom, nil) // Alice reveals her private name to Bob - const alicePrivateName = "Freddy" alice.MustDoFunc( t, "PUT", - []string{"_matrix", "client", "r0", "rooms", privateRoom, "state", "m.room.member", "@alice:hs1"}, + []string{"_matrix", "client", "r0", "rooms", privateRoom, "state", "m.room.member", alice.UserID}, client.WithJSONBody(t, map[string]interface{}{ "displayname": alicePrivateName, "membership": "join", }), ) - justAliceByPublicName := []match.JSON{ - match.JSONKeyArrayOfSize("results", 1), - match.JSONKeyEqual("results.0.display_name", alicePublicName), - match.JSONKeyEqual("results.0.user_id", alice.UserID), - } - - t.Run("Eve can find Alice by profile display name", - func(t *testing.T) { - res := eve.SearchUserDirectory(t, alicePublicName) - must.MatchResponse(t, res, match.HTTPResponse{JSON: justAliceByPublicName}) - }) + checkExpectations(t, bob, eve) +} - t.Run("Eve can find Alice by mxid", - func(t *testing.T) { - res := eve.SearchUserDirectory(t, alice.UserID) - must.MatchResponse(t, res, match.HTTPResponse{JSON: justAliceByPublicName}) - }) +func TestRoomSpecificUsernameAtJoin(t *testing.T) { + alice, bob, eve := setupUsers(t) - noResults := []match.JSON{ - match.JSONKeyArrayOfSize("results", 0), - } + // Bob creates a new room and invites Alice. + privateRoom := bob.CreateRoom(t, map[string]interface{}{ + "visibility": "private", + "invite": []string{alice.UserID}, + }) - t.Run("Eve cannot find Alice by room-specific name that Eve is not privy to", - func(t *testing.T) { - res := eve.SearchUserDirectory(t, alicePrivateName) - must.MatchResponse(t, res, match.HTTPResponse{JSON: noResults}) - }) + // Alice waits until she sees the invite, then accepts. + // When she accepts, she does so with a specific displayname. + alice.SyncUntilInvitedTo(t, privateRoom) + alice.JoinRoom(t, privateRoom, nil) - t.Run("Bob can find Alice by profile display name", - func(t *testing.T) { - res := bob.SearchUserDirectory(t, alicePublicName) - must.MatchResponse(t, res, match.HTTPResponse{ - JSON: justAliceByPublicName, - }) - }) + // Alice reveals her private name to Bob + alice.MustDoFunc( + t, + "PUT", + []string{"_matrix", "client", "r0", "rooms", privateRoom, "state", "m.room.member", alice.UserID}, + client.WithJSONBody(t, map[string]interface{}{ + "displayname": alicePrivateName, + "membership": "join", + }), + ) - t.Run("Bob can find Alice by mxid", - func(t *testing.T) { - res := bob.SearchUserDirectory(t, alice.UserID) - must.MatchResponse(t, res, match.HTTPResponse{ - JSON: justAliceByPublicName, - }) - }) + checkExpectations(t, bob, eve) } diff --git a/tests/user_directory_federated_display_names_test.go b/tests/user_directory_federated_display_names_test.go index 3a611fd5..a7ad5765 100644 --- a/tests/user_directory_federated_display_names_test.go +++ b/tests/user_directory_federated_display_names_test.go @@ -88,17 +88,29 @@ func TestRoomSpecificUsernameHandlingOverFederation(t *testing.T) { match.JSONKeyEqual("results.0.user_id", remoteCharlie.UserID), } - t.Run("Eve can find Charlie by profile display name", - func(t *testing.T) { - res := eve.SearchUserDirectory(t, charliePublicName) - must.MatchResponse(t, res, match.HTTPResponse{JSON: justCharlieByPublicNameOrMxid}) - }) + t.Run("Eve can find Charlie by profile display name", func(t *testing.T) { + res := eve.MustDoFunc( + t, + "POST", + []string{"_matrix", "client", "r0", "user_directory", "search"}, + client.WithJSONBody(t, map[string]interface{}{ + "search_term": charliePublicName, + }), + ) + must.MatchResponse(t, res, match.HTTPResponse{JSON: justCharlieByPublicNameOrMxid}) + }) - t.Run("Eve can find Charlie by mxid", - func(t *testing.T) { - res := eve.SearchUserDirectory(t, remoteCharlie.UserID) - must.MatchResponse(t, res, match.HTTPResponse{JSON: justCharlieByPublicNameOrMxid}) - }) + t.Run("Eve can find Charlie by mxid", func(t *testing.T) { + res := eve.MustDoFunc( + t, + "POST", + []string{"_matrix", "client", "r0", "user_directory", "search"}, + client.WithJSONBody(t, map[string]interface{}{ + "search_term": remoteCharlie.UserID, + }), + ) + must.MatchResponse(t, res, match.HTTPResponse{JSON: justCharlieByPublicNameOrMxid}) + }) noResults := []match.JSON{ match.JSONKeyArrayOfSize("results", 0), @@ -106,23 +118,42 @@ func TestRoomSpecificUsernameHandlingOverFederation(t *testing.T) { t.Run("Eve cannot find Charlie by room-specific name that Eve is not privy to", func(t *testing.T) { - res := eve.SearchUserDirectory(t, charliePrivateName) + res := eve.MustDoFunc( + t, + "POST", + []string{"_matrix", "client", "r0", "user_directory", "search"}, + client.WithJSONBody(t, map[string]interface{}{ + "search_term": charliePrivateName, + }), + ) must.MatchResponse(t, res, match.HTTPResponse{JSON: noResults}) }) - t.Run("Bob can find Charlie by profile display name", - func(t *testing.T) { - res := bob.SearchUserDirectory(t, charliePublicName) - must.MatchResponse(t, res, match.HTTPResponse{ - JSON: justCharlieByPublicNameOrMxid, - }) + t.Run("Bob can find Charlie by profile display name", func(t *testing.T) { + res := bob.MustDoFunc( + t, + "POST", + []string{"_matrix", "client", "r0", "user_directory", "search"}, + client.WithJSONBody(t, map[string]interface{}{ + "search_term": charliePublicName, + }), + ) + must.MatchResponse(t, res, match.HTTPResponse{ + JSON: justCharlieByPublicNameOrMxid, }) + }) - t.Run("Bob can find Charlie by mxid", - func(t *testing.T) { - res := bob.SearchUserDirectory(t, remoteCharlie.UserID) - must.MatchResponse(t, res, match.HTTPResponse{ - JSON: justCharlieByPublicNameOrMxid, - }) + t.Run("Bob can find Charlie by mxid", func(t *testing.T) { + res := bob.MustDoFunc( + t, + "POST", + []string{"_matrix", "client", "r0", "user_directory", "search"}, + client.WithJSONBody(t, map[string]interface{}{ + "search_term": remoteCharlie.UserID, + }), + ) + must.MatchResponse(t, res, match.HTTPResponse{ + JSON: justCharlieByPublicNameOrMxid, }) + }) } From ef171f395f07406315e8a7bdda0b05d193f4b2a9 Mon Sep 17 00:00:00 2001 From: David Robertson Date: Mon, 6 Sep 2021 18:11:24 +0100 Subject: [PATCH 21/26] Fix PUT call to set displayname --- tests/csapi/user_directory_display_names_test.go | 2 +- tests/user_directory_federated_display_names_test.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/csapi/user_directory_display_names_test.go b/tests/csapi/user_directory_display_names_test.go index 0c93ddb5..a392783f 100644 --- a/tests/csapi/user_directory_display_names_test.go +++ b/tests/csapi/user_directory_display_names_test.go @@ -47,7 +47,7 @@ func setupUsers(t *testing.T) (*client.CSAPI, *client.CSAPI, *client.CSAPI) { alice.MustDoFunc( t, "PUT", - []string{"profile", alice.UserID, "displayname"}, + []string{"_matrix", "client", "r0", "profile", alice.UserID, "displayname"}, client.WithJSONBody(t, map[string]interface{}{ "displayname": alicePublicName, }), diff --git a/tests/user_directory_federated_display_names_test.go b/tests/user_directory_federated_display_names_test.go index a7ad5765..b844eafd 100644 --- a/tests/user_directory_federated_display_names_test.go +++ b/tests/user_directory_federated_display_names_test.go @@ -33,7 +33,7 @@ func TestRoomSpecificUsernameHandlingOverFederation(t *testing.T) { remoteCharlie.MustDoFunc( t, "PUT", - []string{"profile", remoteCharlie.UserID, "displayname"}, + []string{"_matrix", "client", "r0", "profile", remoteCharlie.UserID, "displayname"}, client.WithJSONBody(t, map[string]interface{}{ "displayname": charliePublicName, }), From 707b53080bbcae46076d0712448314f4d0362095 Mon Sep 17 00:00:00 2001 From: David Robertson Date: Mon, 6 Sep 2021 18:12:09 +0100 Subject: [PATCH 22/26] Cleanup deployment after test, not after setup! --- tests/csapi/user_directory_display_names_test.go | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/tests/csapi/user_directory_display_names_test.go b/tests/csapi/user_directory_display_names_test.go index a392783f..c9345f0d 100644 --- a/tests/csapi/user_directory_display_names_test.go +++ b/tests/csapi/user_directory_display_names_test.go @@ -23,7 +23,7 @@ var noResults = []match.JSON{ match.JSONKeyArrayOfSize("results", 0), } -func setupUsers(t *testing.T) (*client.CSAPI, *client.CSAPI, *client.CSAPI) { +func setupUsers(t *testing.T) (*client.CSAPI, *client.CSAPI, *client.CSAPI, func(*testing.T)) { // Originally written to reproduce https://github.com/matrix-org/synapse/issues/5677 // In that bug report, // - Bob knows about Alice, and @@ -35,7 +35,9 @@ func setupUsers(t *testing.T) (*client.CSAPI, *client.CSAPI, *client.CSAPI) { // - Alice reveals a private name to another friend Bob // - Eve shouldn't be able to see that private name via the directory. deployment := Deploy(t, b.BlueprintAlice) - defer deployment.Destroy(t) + cleanup := func(t *testing.T) { + deployment.Destroy(t) + } alice := deployment.Client(t, "hs1", aliceId) bob := deployment.RegisterUser(t, "hs1", "bob", "bob-has-a-very-secret-pw") @@ -55,8 +57,7 @@ func setupUsers(t *testing.T) (*client.CSAPI, *client.CSAPI, *client.CSAPI) { // Alice creates a public room (so when Eve searches, she can see that Alice exists) alice.CreateRoom(t, map[string]interface{}{"visibility": "public"}) - - return alice, bob, eve + return alice, bob, eve, cleanup } func checkExpectations(t *testing.T, bob, eve *client.CSAPI) { @@ -126,7 +127,8 @@ func checkExpectations(t *testing.T, bob, eve *client.CSAPI) { } func TestRoomSpecificUsernameChange(t *testing.T) { - alice, bob, eve := setupUsers(t) + alice, bob, eve, cleanup := setupUsers(t) + defer cleanup(t) // Bob creates a new room and invites Alice. privateRoom := bob.CreateRoom(t, map[string]interface{}{ @@ -153,7 +155,8 @@ func TestRoomSpecificUsernameChange(t *testing.T) { } func TestRoomSpecificUsernameAtJoin(t *testing.T) { - alice, bob, eve := setupUsers(t) + alice, bob, eve, cleanup := setupUsers(t) + defer cleanup(t) // Bob creates a new room and invites Alice. privateRoom := bob.CreateRoom(t, map[string]interface{}{ From 2b4609ad1d77ffbbd8622d341dd68ef04bbf7d59 Mon Sep 17 00:00:00 2001 From: David Robertson Date: Tue, 7 Sep 2021 11:49:30 +0100 Subject: [PATCH 23/26] aliceId -> aliceUserID --- tests/csapi/user_directory_display_names_test.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/csapi/user_directory_display_names_test.go b/tests/csapi/user_directory_display_names_test.go index c9345f0d..09194c09 100644 --- a/tests/csapi/user_directory_display_names_test.go +++ b/tests/csapi/user_directory_display_names_test.go @@ -9,14 +9,14 @@ import ( "github.com/matrix-org/complement/internal/must" ) -const aliceId = "@alice:hs1" +const aliceUserID = "@alice:hs1" const alicePublicName = "Alice Cooper" const alicePrivateName = "Freddy" var justAliceByPublicName = []match.JSON{ match.JSONKeyArrayOfSize("results", 1), match.JSONKeyEqual("results.0.display_name", alicePublicName), - match.JSONKeyEqual("results.0.user_id", aliceId), + match.JSONKeyEqual("results.0.user_id", aliceUserID), } var noResults = []match.JSON{ @@ -39,7 +39,7 @@ func setupUsers(t *testing.T) (*client.CSAPI, *client.CSAPI, *client.CSAPI, func deployment.Destroy(t) } - alice := deployment.Client(t, "hs1", aliceId) + alice := deployment.Client(t, "hs1", aliceUserID) bob := deployment.RegisterUser(t, "hs1", "bob", "bob-has-a-very-secret-pw") eve := deployment.RegisterUser(t, "hs1", "eve", "eve-has-a-very-secret-pw") @@ -79,7 +79,7 @@ func checkExpectations(t *testing.T, bob, eve *client.CSAPI) { "POST", []string{"_matrix", "client", "r0", "user_directory", "search"}, client.WithJSONBody(t, map[string]interface{}{ - "search_term": aliceId, + "search_term": aliceUserID, }), ) must.MatchResponse(t, res, match.HTTPResponse{JSON: justAliceByPublicName}) @@ -117,7 +117,7 @@ func checkExpectations(t *testing.T, bob, eve *client.CSAPI) { "POST", []string{"_matrix", "client", "r0", "user_directory", "search"}, client.WithJSONBody(t, map[string]interface{}{ - "search_term": aliceId, + "search_term": aliceUserID, }), ) must.MatchResponse(t, res, match.HTTPResponse{ From 700e300529403e3b1a66938d825aa35b7908371f Mon Sep 17 00:00:00 2001 From: David Robertson Date: Tue, 7 Sep 2021 11:49:42 +0100 Subject: [PATCH 24/26] Allow remote users' displaynames to be localparts I think "alice" rather than "@alice:example.com" is nicer to display in the UI until we've confirmed their public profile. --- tests/user_directory_federated_display_names_test.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/user_directory_federated_display_names_test.go b/tests/user_directory_federated_display_names_test.go index b844eafd..793fb1f9 100644 --- a/tests/user_directory_federated_display_names_test.go +++ b/tests/user_directory_federated_display_names_test.go @@ -30,6 +30,7 @@ func TestRoomSpecificUsernameHandlingOverFederation(t *testing.T) { // public name, private name and userid localpart are all // distinguishable, even case-insensitively. const charliePublicName = "Charlie Cooper" + const charlieLocalPart = "charlie" remoteCharlie.MustDoFunc( t, "PUT", @@ -83,6 +84,7 @@ func TestRoomSpecificUsernameHandlingOverFederation(t *testing.T) { match.JSONKeyArrayOfSize("results", 1), match.AnyOf( match.JSONKeyEqual("results.0.display_name", charliePublicName), + match.JSONKeyEqual("results.0.display_name", charlieLocalPart), match.JSONKeyEqual("results.0.display_name", remoteCharlie.UserID), ), match.JSONKeyEqual("results.0.user_id", remoteCharlie.UserID), From 56bf3e3892b59e7652f8c28906c2f771fef03967 Mon Sep 17 00:00:00 2001 From: David Robertson Date: Wed, 10 Nov 2021 11:52:13 +0000 Subject: [PATCH 25/26] Remove the tests which apply over federation Will return to these when I pick up matrix-org/synapse#5677 again. --- ..._directory_federated_display_names_test.go | 161 ------------------ 1 file changed, 161 deletions(-) delete mode 100644 tests/user_directory_federated_display_names_test.go diff --git a/tests/user_directory_federated_display_names_test.go b/tests/user_directory_federated_display_names_test.go deleted file mode 100644 index 793fb1f9..00000000 --- a/tests/user_directory_federated_display_names_test.go +++ /dev/null @@ -1,161 +0,0 @@ -package tests - -import ( - "testing" - - "github.com/tidwall/gjson" - - "github.com/matrix-org/complement/internal/b" - "github.com/matrix-org/complement/internal/client" - "github.com/matrix-org/complement/internal/match" - "github.com/matrix-org/complement/internal/must" -) - -func TestRoomSpecificUsernameHandlingOverFederation(t *testing.T) { - // Another test case for https://github.com/matrix-org/synapse/issues/5677 - // Now we're checking that we don't leak per-room names of a remote user. - - // The scenario: - // - Charlie (hs2) and Bob (hs1) are in a public room. - // - Charlie and Bob are also in a private room, and Charlie has a per-room name there. - // - Eve (hs1) shouldn't be able to see that private name. - deployment := Deploy(t, b.BlueprintFederationTwoLocalOneRemote) - defer deployment.Destroy(t) - - bob := deployment.Client(t, "hs1", "@bob:hs1") - remoteCharlie := deployment.Client(t, "hs2", "@charlie:hs2") - eve := deployment.RegisterUser(t, "hs1", "eve", "eve-has-a-very-secret-pw") - - // Charlie sets her profile displayname. This ensures that her - // public name, private name and userid localpart are all - // distinguishable, even case-insensitively. - const charliePublicName = "Charlie Cooper" - const charlieLocalPart = "charlie" - remoteCharlie.MustDoFunc( - t, - "PUT", - []string{"_matrix", "client", "r0", "profile", remoteCharlie.UserID, "displayname"}, - client.WithJSONBody(t, map[string]interface{}{ - "displayname": charliePublicName, - }), - ) - - // Charlie creates a public room and invites Bob (so Eve can see that Charlie exists) - publicRoom := remoteCharlie.CreateRoom(t, map[string]interface{}{ - "visibility": "public", - "invite": []string{bob.UserID}, - }) - - // Charlie also creates a private room with Bob. - privateRoom := remoteCharlie.CreateRoom(t, map[string]interface{}{ - "visibility": "private", - "invite": []string{bob.UserID}, - }) - - // Bob accepts both invites. - bob.SyncUntilInvitedTo(t, privateRoom) - bob.SyncUntilInvitedTo(t, publicRoom) - bob.JoinRoom(t, publicRoom, []string{"hs2"}) - bob.JoinRoom(t, privateRoom, []string{"hs2"}) - - // Charlie reveals her private name to Bob - const charliePrivateName = "Freddy" - remoteCharlie.MustDoFunc( - t, - "PUT", - []string{"_matrix", "client", "r0", "rooms", privateRoom, "state", "m.room.member", remoteCharlie.UserID}, - client.WithJSONBody(t, map[string]interface{}{ - "displayname": charliePrivateName, - "membership": "join", - }), - ) - - // Wait for Bob to see the name change - bob.SyncUntilTimelineHas(t, privateRoom, func(ev gjson.Result) bool { - return ev.Get("type").Str == "m.room.member" && - ev.Get("state_key").Str == remoteCharlie.UserID && - ev.Get("content.displayname").Str == charliePrivateName - }) - - // There's no way to know what a remote user's "public profile" is - // without making a federation request. Accept either - // mxid or their profile's name as the displayname. - justCharlieByPublicNameOrMxid := []match.JSON{ - match.JSONKeyArrayOfSize("results", 1), - match.AnyOf( - match.JSONKeyEqual("results.0.display_name", charliePublicName), - match.JSONKeyEqual("results.0.display_name", charlieLocalPart), - match.JSONKeyEqual("results.0.display_name", remoteCharlie.UserID), - ), - match.JSONKeyEqual("results.0.user_id", remoteCharlie.UserID), - } - - t.Run("Eve can find Charlie by profile display name", func(t *testing.T) { - res := eve.MustDoFunc( - t, - "POST", - []string{"_matrix", "client", "r0", "user_directory", "search"}, - client.WithJSONBody(t, map[string]interface{}{ - "search_term": charliePublicName, - }), - ) - must.MatchResponse(t, res, match.HTTPResponse{JSON: justCharlieByPublicNameOrMxid}) - }) - - t.Run("Eve can find Charlie by mxid", func(t *testing.T) { - res := eve.MustDoFunc( - t, - "POST", - []string{"_matrix", "client", "r0", "user_directory", "search"}, - client.WithJSONBody(t, map[string]interface{}{ - "search_term": remoteCharlie.UserID, - }), - ) - must.MatchResponse(t, res, match.HTTPResponse{JSON: justCharlieByPublicNameOrMxid}) - }) - - noResults := []match.JSON{ - match.JSONKeyArrayOfSize("results", 0), - } - - t.Run("Eve cannot find Charlie by room-specific name that Eve is not privy to", - func(t *testing.T) { - res := eve.MustDoFunc( - t, - "POST", - []string{"_matrix", "client", "r0", "user_directory", "search"}, - client.WithJSONBody(t, map[string]interface{}{ - "search_term": charliePrivateName, - }), - ) - must.MatchResponse(t, res, match.HTTPResponse{JSON: noResults}) - }) - - t.Run("Bob can find Charlie by profile display name", func(t *testing.T) { - res := bob.MustDoFunc( - t, - "POST", - []string{"_matrix", "client", "r0", "user_directory", "search"}, - client.WithJSONBody(t, map[string]interface{}{ - "search_term": charliePublicName, - }), - ) - must.MatchResponse(t, res, match.HTTPResponse{ - JSON: justCharlieByPublicNameOrMxid, - }) - }) - - t.Run("Bob can find Charlie by mxid", func(t *testing.T) { - res := bob.MustDoFunc( - t, - "POST", - []string{"_matrix", "client", "r0", "user_directory", "search"}, - client.WithJSONBody(t, map[string]interface{}{ - "search_term": remoteCharlie.UserID, - }), - ) - must.MatchResponse(t, res, match.HTTPResponse{ - JSON: justCharlieByPublicNameOrMxid, - }) - }) -} From c0b3ecd3660bfe42c890d3530df838045efc8e52 Mon Sep 17 00:00:00 2001 From: David Robertson Date: Wed, 10 Nov 2021 11:55:15 +0000 Subject: [PATCH 26/26] Blacklist tests for dendrite --- tests/csapi/user_directory_display_names_test.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/csapi/user_directory_display_names_test.go b/tests/csapi/user_directory_display_names_test.go index 09194c09..fee4fdc6 100644 --- a/tests/csapi/user_directory_display_names_test.go +++ b/tests/csapi/user_directory_display_names_test.go @@ -1,3 +1,6 @@ +// +build !dendrite_blacklist + +// Rationale for being included in Dendrite's blacklist: https://github.com/matrix-org/complement/pull/199#issuecomment-904852233 package csapi_tests import (