Show/hide users in user directory (#2637)

* CS API changes

* Query remote profiles

* Add passing tests

* Don't create a new FullyQualifiedProfile

* Handle sql.ErrNoRows

Co-authored-by: Neil Alexander <neilalexander@users.noreply.github.com>
This commit is contained in:
Till 2022-08-12 13:33:31 +02:00 committed by GitHub
parent 48600d5540
commit b4647fbb7e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 75 additions and 52 deletions

View file

@ -935,12 +935,12 @@ func Setup(
return SearchUserDirectory( return SearchUserDirectory(
req.Context(), req.Context(),
device, device,
userAPI,
rsAPI, rsAPI,
userDirectoryProvider, userDirectoryProvider,
cfg.Matrix.ServerName,
postContent.SearchString, postContent.SearchString,
postContent.Limit, postContent.Limit,
federation,
cfg.Matrix.ServerName,
) )
}), }),
).Methods(http.MethodPost, http.MethodOptions) ).Methods(http.MethodPost, http.MethodOptions)

View file

@ -18,10 +18,13 @@ import (
"context" "context"
"database/sql" "database/sql"
"fmt" "fmt"
"net/http"
"strings"
"github.com/matrix-org/dendrite/clientapi/auth/authtypes" "github.com/matrix-org/dendrite/clientapi/auth/authtypes"
"github.com/matrix-org/dendrite/roomserver/api" "github.com/matrix-org/dendrite/roomserver/api"
userapi "github.com/matrix-org/dendrite/userapi/api" userapi "github.com/matrix-org/dendrite/userapi/api"
"github.com/matrix-org/gomatrix"
"github.com/matrix-org/gomatrixserverlib" "github.com/matrix-org/gomatrixserverlib"
"github.com/matrix-org/util" "github.com/matrix-org/util"
) )
@ -34,12 +37,12 @@ type UserDirectoryResponse struct {
func SearchUserDirectory( func SearchUserDirectory(
ctx context.Context, ctx context.Context,
device *userapi.Device, device *userapi.Device,
userAPI userapi.ClientUserAPI,
rsAPI api.ClientRoomserverAPI, rsAPI api.ClientRoomserverAPI,
provider userapi.QuerySearchProfilesAPI, provider userapi.QuerySearchProfilesAPI,
serverName gomatrixserverlib.ServerName,
searchString string, searchString string,
limit int, limit int,
federation *gomatrixserverlib.FederationClient,
localServerName gomatrixserverlib.ServerName,
) util.JSONResponse { ) util.JSONResponse {
if limit < 10 { if limit < 10 {
limit = 10 limit = 10
@ -51,59 +54,74 @@ func SearchUserDirectory(
Limited: false, Limited: false,
} }
// First start searching local users. // Get users we share a room with
knownUsersReq := &api.QueryKnownUsersRequest{
UserID: device.UserID,
Limit: limit,
}
knownUsersRes := &api.QueryKnownUsersResponse{}
if err := rsAPI.QueryKnownUsers(ctx, knownUsersReq, knownUsersRes); err != nil && err != sql.ErrNoRows {
return util.ErrorResponse(fmt.Errorf("rsAPI.QueryKnownUsers: %w", err))
}
knownUsersLoop:
for _, profile := range knownUsersRes.Users {
if len(results) == limit {
response.Limited = true
break
}
userID := profile.UserID
// get the full profile of the local user
localpart, serverName, _ := gomatrixserverlib.SplitID('@', userID)
if serverName == localServerName {
userReq := &userapi.QuerySearchProfilesRequest{ userReq := &userapi.QuerySearchProfilesRequest{
SearchString: searchString, SearchString: localpart,
Limit: limit, Limit: limit,
} }
userRes := &userapi.QuerySearchProfilesResponse{} userRes := &userapi.QuerySearchProfilesResponse{}
if err := provider.QuerySearchProfiles(ctx, userReq, userRes); err != nil { if err := provider.QuerySearchProfiles(ctx, userReq, userRes); err != nil {
return util.ErrorResponse(fmt.Errorf("userAPI.QuerySearchProfiles: %w", err)) return util.ErrorResponse(fmt.Errorf("userAPI.QuerySearchProfiles: %w", err))
} }
for _, p := range userRes.Profiles {
for _, user := range userRes.Profiles { if strings.Contains(p.DisplayName, searchString) ||
strings.Contains(p.Localpart, searchString) {
profile.DisplayName = p.DisplayName
profile.AvatarURL = p.AvatarURL
results[userID] = profile
if len(results) == limit { if len(results) == limit {
response.Limited = true response.Limited = true
break break knownUsersLoop
}
}
} }
var userID string
if user.ServerName != "" {
userID = fmt.Sprintf("@%s:%s", user.Localpart, user.ServerName)
} else { } else {
userID = fmt.Sprintf("@%s:%s", user.Localpart, serverName) // If the username already contains the search string, don't bother hitting federation.
} // This will result in missing avatars and displaynames, but saves the federation roundtrip.
if _, ok := results[userID]; !ok { if strings.Contains(localpart, searchString) {
results[userID] = authtypes.FullyQualifiedProfile{ results[userID] = profile
UserID: userID,
DisplayName: user.DisplayName,
AvatarURL: user.AvatarURL,
}
}
}
// Then, if we have enough room left in the response,
// start searching for known users from joined rooms.
if len(results) <= limit {
stateReq := &api.QueryKnownUsersRequest{
UserID: device.UserID,
SearchString: searchString,
Limit: limit - len(results),
}
stateRes := &api.QueryKnownUsersResponse{}
if err := rsAPI.QueryKnownUsers(ctx, stateReq, stateRes); err != nil && err != sql.ErrNoRows {
return util.ErrorResponse(fmt.Errorf("rsAPI.QueryKnownUsers: %w", err))
}
for _, user := range stateRes.Users {
if len(results) == limit { if len(results) == limit {
response.Limited = true response.Limited = true
break break knownUsersLoop
}
continue
}
// TODO: We should probably cache/store this
fedProfile, fedErr := federation.LookupProfile(ctx, serverName, userID, "")
if fedErr != nil {
if x, ok := fedErr.(gomatrix.HTTPError); ok {
if x.Code == http.StatusNotFound {
continue
}
}
}
if strings.Contains(fedProfile.DisplayName, searchString) {
profile.DisplayName = fedProfile.DisplayName
profile.AvatarURL = fedProfile.AvatarURL
results[userID] = profile
if len(results) == limit {
response.Limited = true
break knownUsersLoop
} }
if _, ok := results[user.UserID]; !ok {
results[user.UserID] = user
} }
} }
} }

View file

@ -21,6 +21,10 @@ import (
"errors" "errors"
"fmt" "fmt"
"github.com/matrix-org/gomatrixserverlib"
"github.com/matrix-org/util"
"github.com/sirupsen/logrus"
"github.com/matrix-org/dendrite/clientapi/auth/authtypes" "github.com/matrix-org/dendrite/clientapi/auth/authtypes"
"github.com/matrix-org/dendrite/internal/caching" "github.com/matrix-org/dendrite/internal/caching"
"github.com/matrix-org/dendrite/roomserver/acls" "github.com/matrix-org/dendrite/roomserver/acls"
@ -30,9 +34,6 @@ import (
"github.com/matrix-org/dendrite/roomserver/storage" "github.com/matrix-org/dendrite/roomserver/storage"
"github.com/matrix-org/dendrite/roomserver/types" "github.com/matrix-org/dendrite/roomserver/types"
"github.com/matrix-org/dendrite/roomserver/version" "github.com/matrix-org/dendrite/roomserver/version"
"github.com/matrix-org/gomatrixserverlib"
"github.com/matrix-org/util"
"github.com/sirupsen/logrus"
) )
type Queryer struct { type Queryer struct {
@ -732,7 +733,7 @@ func (r *Queryer) QueryRoomsForUser(ctx context.Context, req *api.QueryRoomsForU
func (r *Queryer) QueryKnownUsers(ctx context.Context, req *api.QueryKnownUsersRequest, res *api.QueryKnownUsersResponse) error { func (r *Queryer) QueryKnownUsers(ctx context.Context, req *api.QueryKnownUsersRequest, res *api.QueryKnownUsersResponse) error {
users, err := r.DB.GetKnownUsers(ctx, req.UserID, req.SearchString, req.Limit) users, err := r.DB.GetKnownUsers(ctx, req.UserID, req.SearchString, req.Limit)
if err != nil { if err != nil && err != sql.ErrNoRows {
return err return err
} }
for _, user := range users { for _, user := range users {

View file

@ -740,3 +740,7 @@ Current state appears in timeline in private history
Current state appears in timeline in private history with many messages before Current state appears in timeline in private history with many messages before
Local users can peek into world_readable rooms by room ID Local users can peek into world_readable rooms by room ID
Newly joined room includes presence in incremental sync Newly joined room includes presence in incremental sync
User in private room doesn't appear in user directory
User joining then leaving public room appears and dissappears from directory
User in remote room doesn't appear in user directory after server left room
User in shared private room does appear in user directory until leave