User directory (#1225)

* User directory

* Fix syncapi unit test

* Make user directory only show remote users you know about from your joined rooms

* Update sytest-whitelist

* Review comments
This commit is contained in:
Neil Alexander 2020-07-28 10:53:17 +01:00 committed by GitHub
parent c632867135
commit acc8e80a51
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
23 changed files with 402 additions and 8 deletions

View file

@ -16,7 +16,14 @@ package authtypes
// Profile represents the profile for a Matrix account.
type Profile struct {
Localpart string
DisplayName string
AvatarURL string
Localpart string `json:"local_part"`
DisplayName string `json:"display_name"`
AvatarURL string `json:"avatar_url"`
}
// FullyQualifiedProfile represents the profile for a Matrix account.
type FullyQualifiedProfile struct {
UserID string `json:"user_id"`
DisplayName string `json:"display_name"`
AvatarURL string `json:"avatar_url"`
}

View file

@ -574,6 +574,27 @@ func Setup(
}),
).Methods(http.MethodGet)
r0mux.Handle("/user_directory/search",
httputil.MakeAuthAPI("userdirectory_search", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
postContent := struct {
SearchString string `json:"search_term"`
Limit int `json:"limit"`
}{}
if err := json.NewDecoder(req.Body).Decode(&postContent); err != nil {
return util.ErrorResponse(err)
}
return *SearchUserDirectory(
req.Context(),
device,
userAPI,
stateAPI,
cfg.Matrix.ServerName,
postContent.SearchString,
postContent.Limit,
)
}),
).Methods(http.MethodPost, http.MethodOptions)
r0mux.Handle("/rooms/{roomID}/members",
httputil.MakeAuthAPI("rooms_members", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
vars, err := httputil.URLDecodeMapValues(mux.Vars(req))

View file

@ -0,0 +1,115 @@
// Copyright 2020 The Matrix.org Foundation C.I.C.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package routing
import (
"context"
"fmt"
"github.com/matrix-org/dendrite/clientapi/auth/authtypes"
currentstateAPI "github.com/matrix-org/dendrite/currentstateserver/api"
userapi "github.com/matrix-org/dendrite/userapi/api"
"github.com/matrix-org/gomatrixserverlib"
"github.com/matrix-org/util"
)
type UserDirectoryResponse struct {
Results []authtypes.FullyQualifiedProfile `json:"results"`
Limited bool `json:"limited"`
}
func SearchUserDirectory(
ctx context.Context,
device *userapi.Device,
userAPI userapi.UserInternalAPI,
stateAPI currentstateAPI.CurrentStateInternalAPI,
serverName gomatrixserverlib.ServerName,
searchString string,
limit int,
) *util.JSONResponse {
if limit < 10 {
limit = 10
}
results := map[string]authtypes.FullyQualifiedProfile{}
response := &UserDirectoryResponse{
Results: []authtypes.FullyQualifiedProfile{},
Limited: false,
}
// First start searching local users.
userReq := &userapi.QuerySearchProfilesRequest{
SearchString: searchString,
Limit: limit,
}
userRes := &userapi.QuerySearchProfilesResponse{}
if err := userAPI.QuerySearchProfiles(ctx, userReq, userRes); err != nil {
errRes := util.ErrorResponse(fmt.Errorf("userAPI.QuerySearchProfiles: %w", err))
return &errRes
}
for _, user := range userRes.Profiles {
if len(results) == limit {
response.Limited = true
break
}
userID := fmt.Sprintf("@%s:%s", user.Localpart, serverName)
if _, ok := results[userID]; !ok {
results[userID] = authtypes.FullyQualifiedProfile{
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 := &currentstateAPI.QueryKnownUsersRequest{
UserID: device.UserID,
SearchString: searchString,
Limit: limit - len(results),
}
stateRes := &currentstateAPI.QueryKnownUsersResponse{}
if err := stateAPI.QueryKnownUsers(ctx, stateReq, stateRes); err != nil {
errRes := util.ErrorResponse(fmt.Errorf("stateAPI.QueryKnownUsers: %w", err))
return &errRes
}
for _, user := range stateRes.Users {
if len(results) == limit {
response.Limited = true
break
}
if _, ok := results[user.UserID]; !ok {
results[user.UserID] = user
}
}
}
for _, result := range results {
response.Results = append(response.Results, result)
}
return &util.JSONResponse{
Code: 200,
JSON: response,
}
}