From aa105dc1a2d6ca85c2eaa25787e6a21cf9e5d21b Mon Sep 17 00:00:00 2001 From: Joshua Hong Date: Wed, 2 Jun 2021 22:18:24 -0400 Subject: [PATCH] WIP User in private room doesn't appear in user directory --- clientapi/routing/userdirectory.go | 30 ++++++++----------- federationapi/routing/send_test.go | 4 +++ roomserver/api/api.go | 2 ++ roomserver/api/api_trace.go | 7 +++++ roomserver/api/query.go | 9 ++++++ roomserver/internal/query/query.go | 13 ++++++++ roomserver/inthttp/client.go | 11 +++++++ roomserver/inthttp/server.go | 13 ++++++++ roomserver/storage/interface.go | 2 ++ .../storage/postgres/membership_table.go | 26 ++++++++++++++++ roomserver/storage/shared/storage.go | 5 ++++ .../storage/sqlite3/membership_table.go | 26 ++++++++++++++++ roomserver/storage/tables/interface.go | 1 + sytest-whitelist | 1 + 14 files changed, 132 insertions(+), 18 deletions(-) diff --git a/clientapi/routing/userdirectory.go b/clientapi/routing/userdirectory.go index 2659bc9cc..ae954fef9 100644 --- a/clientapi/routing/userdirectory.go +++ b/clientapi/routing/userdirectory.go @@ -19,7 +19,7 @@ import ( "fmt" "github.com/matrix-org/dendrite/clientapi/auth/authtypes" - "github.com/matrix-org/dendrite/roomserver/api" + rsapi "github.com/matrix-org/dendrite/roomserver/api" userapi "github.com/matrix-org/dendrite/userapi/api" "github.com/matrix-org/gomatrixserverlib" "github.com/matrix-org/util" @@ -34,7 +34,7 @@ func SearchUserDirectory( ctx context.Context, device *userapi.Device, userAPI userapi.UserInternalAPI, - rsAPI api.RoomserverInternalAPI, + rsAPI rsapi.RoomserverInternalAPI, serverName gomatrixserverlib.ServerName, searchString string, limit int, @@ -49,31 +49,25 @@ func SearchUserDirectory( Limited: false, } - // First start searching local users. - - userReq := &userapi.QuerySearchProfilesRequest{ + // First start searching users in public rooms + userReq := &rsapi.QueryPublicUsersRequest{ 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)) + userRes := &rsapi.QueryPublicUsersResponse{} + if err := rsAPI.QueryPublicUsers(ctx, userReq, userRes); err != nil { + errRes := util.ErrorResponse(fmt.Errorf("rsAPI.QueryPublicUsers: %w", err)) return &errRes } - for _, user := range userRes.Profiles { + for _, user := range userRes.Users { 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, - } + if _, ok := results[user.UserID]; !ok { + results[user.UserID] = user } } @@ -81,12 +75,12 @@ func SearchUserDirectory( // start searching for known users from joined rooms. if len(results) <= limit { - stateReq := &api.QueryKnownUsersRequest{ + stateReq := &rsapi.QueryKnownUsersRequest{ UserID: device.UserID, SearchString: searchString, Limit: limit - len(results), } - stateRes := &api.QueryKnownUsersResponse{} + stateRes := &rsapi.QueryKnownUsersResponse{} if err := rsAPI.QueryKnownUsers(ctx, stateReq, stateRes); err != nil { errRes := util.ErrorResponse(fmt.Errorf("rsAPI.QueryKnownUsers: %w", err)) return &errRes diff --git a/federationapi/routing/send_test.go b/federationapi/routing/send_test.go index b14cbd35a..4195c2e27 100644 --- a/federationapi/routing/send_test.go +++ b/federationapi/routing/send_test.go @@ -315,6 +315,10 @@ func (t *testRoomserverAPI) QueryKnownUsers(ctx context.Context, req *api.QueryK return fmt.Errorf("not implemented") } +func (t *testRoomserverAPI) QueryPublicUsers(ctx context.Context, req *api.QueryPublicUsersRequest, res *api.QueryPublicUsersResponse) error { + return fmt.Errorf("not implemented") +} + func (t *testRoomserverAPI) QueryServerBannedFromRoom(ctx context.Context, req *api.QueryServerBannedFromRoomRequest, res *api.QueryServerBannedFromRoomResponse) error { return nil } diff --git a/roomserver/api/api.go b/roomserver/api/api.go index 72e406ee8..2ba1a1b9f 100644 --- a/roomserver/api/api.go +++ b/roomserver/api/api.go @@ -160,6 +160,8 @@ type RoomserverInternalAPI interface { QuerySharedUsers(ctx context.Context, req *QuerySharedUsersRequest, res *QuerySharedUsersResponse) error // QueryKnownUsers returns a list of users that we know about from our joined rooms. QueryKnownUsers(ctx context.Context, req *QueryKnownUsersRequest, res *QueryKnownUsersResponse) error + // QueryPublicUsers returns a list of users in at least 1 public room. + QueryPublicUsers(ctx context.Context, req *QueryPublicUsersRequest, res *QueryPublicUsersResponse) error // QueryServerBannedFromRoom returns whether a server is banned from a room by server ACLs. QueryServerBannedFromRoom(ctx context.Context, req *QueryServerBannedFromRoomRequest, res *QueryServerBannedFromRoomResponse) error diff --git a/roomserver/api/api_trace.go b/roomserver/api/api_trace.go index 1a2b9a490..6da433c8d 100644 --- a/roomserver/api/api_trace.go +++ b/roomserver/api/api_trace.go @@ -332,6 +332,13 @@ func (t *RoomserverInternalAPITrace) QueryKnownUsers(ctx context.Context, req *Q return err } +// QueryPublicUsers returns a list of users that are in at least one public room. +func (t *RoomserverInternalAPITrace) QueryPublicUsers(ctx context.Context, req *QueryPublicUsersRequest, res *QueryPublicUsersResponse) error { + err := t.Impl.QueryPublicUsers(ctx, req, res) + util.GetLogger(ctx).WithError(err).Infof("QueryPublicUsers req=%+v res=%+v", js(req), js(res)) + return err +} + // QueryServerBannedFromRoom returns whether a server is banned from a room by server ACLs. func (t *RoomserverInternalAPITrace) QueryServerBannedFromRoom(ctx context.Context, req *QueryServerBannedFromRoomRequest, res *QueryServerBannedFromRoomResponse) error { err := t.Impl.QueryServerBannedFromRoom(ctx, req, res) diff --git a/roomserver/api/query.go b/roomserver/api/query.go index af35f7e72..f1223e362 100644 --- a/roomserver/api/query.go +++ b/roomserver/api/query.go @@ -348,6 +348,15 @@ type QueryKnownUsersResponse struct { Users []authtypes.FullyQualifiedProfile `json:"profiles"` } +type QueryPublicUsersRequest struct { + SearchString string `json:"search_string"` + Limit int `json:"limit"` +} + +type QueryPublicUsersResponse struct { + Users []authtypes.FullyQualifiedProfile `json:"profiles"` +} + type QueryServerBannedFromRoomRequest struct { ServerName gomatrixserverlib.ServerName `json:"server_name"` RoomID string `json:"room_id"` diff --git a/roomserver/internal/query/query.go b/roomserver/internal/query/query.go index 408f9766e..357b24066 100644 --- a/roomserver/internal/query/query.go +++ b/roomserver/internal/query/query.go @@ -683,6 +683,19 @@ func (r *Queryer) QueryKnownUsers(ctx context.Context, req *api.QueryKnownUsersR return nil } +func (r *Queryer) QueryPublicUsers(ctx context.Context, req *api.QueryPublicUsersRequest, res *api.QueryPublicUsersResponse) error { + users, err := r.DB.GetPublicUsers(ctx, req.SearchString, req.Limit) + if err != nil { + return err + } + for _, user := range users { + res.Users = append(res.Users, authtypes.FullyQualifiedProfile{ + UserID: user, + }) + } + return nil +} + func (r *Queryer) QueryBulkStateContent(ctx context.Context, req *api.QueryBulkStateContentRequest, res *api.QueryBulkStateContentResponse) error { events, err := r.DB.GetBulkStateContent(ctx, req.RoomIDs, req.StateTuples, req.AllowWildcards) if err != nil { diff --git a/roomserver/inthttp/client.go b/roomserver/inthttp/client.go index 6774d102d..ba65b9749 100644 --- a/roomserver/inthttp/client.go +++ b/roomserver/inthttp/client.go @@ -55,6 +55,7 @@ const ( RoomserverQueryBulkStateContentPath = "/roomserver/queryBulkStateContent" RoomserverQuerySharedUsersPath = "/roomserver/querySharedUsers" RoomserverQueryKnownUsersPath = "/roomserver/queryKnownUsers" + RoomserverQueryPublicUsersPath = "/roomserver/queryPublicUsers" RoomserverQueryServerBannedFromRoomPath = "/roomserver/queryServerBannedFromRoom" RoomserverQueryAuthChainPath = "/roomserver/queryAuthChain" ) @@ -521,6 +522,16 @@ func (h *httpRoomserverInternalAPI) QueryKnownUsers( return httputil.PostJSON(ctx, span, h.httpClient, apiURL, req, res) } +func (h *httpRoomserverInternalAPI) QueryPublicUsers( + ctx context.Context, req *api.QueryPublicUsersRequest, res *api.QueryPublicUsersResponse, +) error { + span, ctx := opentracing.StartSpanFromContext(ctx, "QueryPublicUsers") + defer span.Finish() + + apiURL := h.roomserverURL + RoomserverQueryPublicUsersPath + return httputil.PostJSON(ctx, span, h.httpClient, apiURL, req, res) +} + func (h *httpRoomserverInternalAPI) QueryAuthChain( ctx context.Context, req *api.QueryAuthChainRequest, res *api.QueryAuthChainResponse, ) error { diff --git a/roomserver/inthttp/server.go b/roomserver/inthttp/server.go index bf319262f..7f008d8d7 100644 --- a/roomserver/inthttp/server.go +++ b/roomserver/inthttp/server.go @@ -452,6 +452,19 @@ func AddRoutes(r api.RoomserverInternalAPI, internalAPIMux *mux.Router) { return util.JSONResponse{Code: http.StatusOK, JSON: &response} }), ) + internalAPIMux.Handle(RoomserverQueryPublicUsersPath, + httputil.MakeInternalAPI("queryPublicUsers", func(req *http.Request) util.JSONResponse { + request := api.QueryPublicUsersRequest{} + response := api.QueryPublicUsersResponse{} + if err := json.NewDecoder(req.Body).Decode(&request); err != nil { + return util.MessageResponse(http.StatusBadRequest, err.Error()) + } + if err := r.QueryPublicUsers(req.Context(), &request, &response); err != nil { + return util.ErrorResponse(err) + } + return util.JSONResponse{Code: http.StatusOK, JSON: &response} + }), + ) internalAPIMux.Handle(RoomserverQueryServerBannedFromRoomPath, httputil.MakeInternalAPI("queryServerBannedFromRoom", func(req *http.Request) util.JSONResponse { request := api.QueryServerBannedFromRoomRequest{} diff --git a/roomserver/storage/interface.go b/roomserver/storage/interface.go index d2b0e75c9..d3e16485e 100644 --- a/roomserver/storage/interface.go +++ b/roomserver/storage/interface.go @@ -156,6 +156,8 @@ type Database interface { JoinedUsersSetInRooms(ctx context.Context, roomIDs []string) (map[string]int, error) // GetKnownUsers searches all users that userID knows about. GetKnownUsers(ctx context.Context, userID, searchString string, limit int) ([]string, error) + // GetPublicUsers searches all users that are in a public room. + GetPublicUsers(ctx context.Context, searchString string, limit int) ([]string, error) // GetKnownRooms returns a list of all rooms we know about. GetKnownRooms(ctx context.Context) ([]string, error) // ForgetRoom sets a flag in the membership table, that the user wishes to forget a specific room diff --git a/roomserver/storage/postgres/membership_table.go b/roomserver/storage/postgres/membership_table.go index 3466da6d2..c51bd5674 100644 --- a/roomserver/storage/postgres/membership_table.go +++ b/roomserver/storage/postgres/membership_table.go @@ -124,6 +124,13 @@ var selectKnownUsersSQL = "" + " SELECT DISTINCT room_nid FROM roomserver_membership WHERE target_nid=$1 AND membership_nid = " + fmt.Sprintf("%d", tables.MembershipStateJoin) + ") AND membership_nid = " + fmt.Sprintf("%d", tables.MembershipStateJoin) + " AND event_state_key LIKE $2 LIMIT $3" +// Select users in public rooms +const selectPublicUsersSQL = "" + + "SELECT DISTINCT event_state_key FROM roomserver_event_state_keys INNER JOIN roomserver_membership ON " + + "roomserver_membership.target_nid = roomserver_event_state_keys.event_state_key_nid INNER JOIN roomserver_rooms ON " + + "roomserver_rooms.room_nid = roomserver_membership.room_nid INNER JOIN roomserver_published ON roomserver_published.room_id = roomserver_rooms.room_id " + + "AND event_state_key LIKE $1 LIMIT $2" + type membershipStatements struct { insertMembershipStmt *sql.Stmt selectMembershipForUpdateStmt *sql.Stmt @@ -136,6 +143,7 @@ type membershipStatements struct { selectRoomsWithMembershipStmt *sql.Stmt selectJoinedUsersSetForRoomsStmt *sql.Stmt selectKnownUsersStmt *sql.Stmt + selectPublicUsersStmt *sql.Stmt updateMembershipForgetRoomStmt *sql.Stmt } @@ -159,6 +167,7 @@ func prepareMembershipTable(db *sql.DB) (tables.Membership, error) { {&s.selectRoomsWithMembershipStmt, selectRoomsWithMembershipSQL}, {&s.selectJoinedUsersSetForRoomsStmt, selectJoinedUsersSetForRoomsSQL}, {&s.selectKnownUsersStmt, selectKnownUsersSQL}, + {&s.selectPublicUsersStmt, selectPublicUsersSQL}, {&s.updateMembershipForgetRoomStmt, updateMembershipForgetRoom}, }.Prepare(db) } @@ -314,6 +323,23 @@ func (s *membershipStatements) SelectKnownUsers(ctx context.Context, userID type return result, rows.Err() } +func (s *membershipStatements) SelectPublicUsers(ctx context.Context, searchString string, limit int) ([]string, error) { + rows, err := s.selectPublicUsersStmt.QueryContext(ctx, fmt.Sprintf("%%%s%%", searchString), limit) + if err != nil { + return nil, err + } + result := []string{} + defer internal.CloseAndLogIfError(ctx, rows, "SelectPublicUsers: rows.close() failed") + for rows.Next() { + var userID string + if err := rows.Scan(&userID); err != nil { + return nil, err + } + result = append(result, userID) + } + return result, rows.Err() +} + func (s *membershipStatements) UpdateForgetMembership( ctx context.Context, txn *sql.Tx, roomNID types.RoomNID, targetUserNID types.EventStateKeyNID, diff --git a/roomserver/storage/shared/storage.go b/roomserver/storage/shared/storage.go index 096d5d7a8..5092ca8a2 100644 --- a/roomserver/storage/shared/storage.go +++ b/roomserver/storage/shared/storage.go @@ -1068,6 +1068,11 @@ func (d *Database) GetKnownUsers(ctx context.Context, userID, searchString strin return d.MembershipTable.SelectKnownUsers(ctx, stateKeyNID, searchString, limit) } +// Get users in public rooms +func (d *Database) GetPublicUsers(ctx context.Context, searchString string, limit int) ([]string, error) { + return d.MembershipTable.SelectPublicUsers(ctx, searchString, limit) +} + // GetKnownRooms returns a list of all rooms we know about. func (d *Database) GetKnownRooms(ctx context.Context) ([]string, error) { return d.RoomsTable.SelectRoomIDs(ctx) diff --git a/roomserver/storage/sqlite3/membership_table.go b/roomserver/storage/sqlite3/membership_table.go index d9fe32cf8..d9e8b76db 100644 --- a/roomserver/storage/sqlite3/membership_table.go +++ b/roomserver/storage/sqlite3/membership_table.go @@ -100,6 +100,13 @@ var selectKnownUsersSQL = "" + " SELECT DISTINCT room_nid FROM roomserver_membership WHERE target_nid=$1 AND membership_nid = " + fmt.Sprintf("%d", tables.MembershipStateJoin) + ") AND membership_nid = " + fmt.Sprintf("%d", tables.MembershipStateJoin) + " AND event_state_key LIKE $2 LIMIT $3" +// Select users in public rooms +const selectPublicUsersSQL = "" + + "SELECT DISTINCT event_state_key FROM roomserver_event_state_keys INNER JOIN roomserver_membership ON " + + "roomserver_membership.target_nid = roomserver_event_state_keys.event_state_key_nid INNER JOIN roomserver_rooms ON " + + "roomserver_rooms.room_nid = roomserver_membership.room_nid INNER JOIN roomserver_published ON roomserver_published.room_id = roomserver_rooms.room_id " + + "AND event_state_key LIKE $1 LIMIT $2" + type membershipStatements struct { db *sql.DB insertMembershipStmt *sql.Stmt @@ -112,6 +119,7 @@ type membershipStatements struct { selectRoomsWithMembershipStmt *sql.Stmt updateMembershipStmt *sql.Stmt selectKnownUsersStmt *sql.Stmt + selectPublicUsersStmt *sql.Stmt updateMembershipForgetRoomStmt *sql.Stmt } @@ -136,6 +144,7 @@ func prepareMembershipTable(db *sql.DB) (tables.Membership, error) { {&s.updateMembershipStmt, updateMembershipSQL}, {&s.selectRoomsWithMembershipStmt, selectRoomsWithMembershipSQL}, {&s.selectKnownUsersStmt, selectKnownUsersSQL}, + {&s.selectPublicUsersStmt, selectPublicUsersSQL}, {&s.updateMembershipForgetRoomStmt, updateMembershipForgetRoom}, }.Prepare(db) } @@ -294,6 +303,23 @@ func (s *membershipStatements) SelectKnownUsers(ctx context.Context, userID type return result, rows.Err() } +func (s *membershipStatements) SelectPublicUsers(ctx context.Context, searchString string, limit int) ([]string, error) { + rows, err := s.selectPublicUsersStmt.QueryContext(ctx, fmt.Sprintf("%%%s%%", searchString), limit) + if err != nil { + return nil, err + } + result := []string{} + defer internal.CloseAndLogIfError(ctx, rows, "SelectPublicUsers: rows.close() failed") + for rows.Next() { + var userID string + if err := rows.Scan(&userID); err != nil { + return nil, err + } + result = append(result, userID) + } + return result, rows.Err() +} + func (s *membershipStatements) UpdateForgetMembership( ctx context.Context, txn *sql.Tx, roomNID types.RoomNID, targetUserNID types.EventStateKeyNID, diff --git a/roomserver/storage/tables/interface.go b/roomserver/storage/tables/interface.go index dd486873a..1dcf16f2e 100644 --- a/roomserver/storage/tables/interface.go +++ b/roomserver/storage/tables/interface.go @@ -134,6 +134,7 @@ type Membership interface { // counts of how many rooms they are joined. SelectJoinedUsersSetForRooms(ctx context.Context, roomNIDs []types.RoomNID) (map[types.EventStateKeyNID]int, error) SelectKnownUsers(ctx context.Context, userID types.EventStateKeyNID, searchString string, limit int) ([]string, error) + SelectPublicUsers(ctx context.Context, searchString string, limit int) ([]string, error) UpdateForgetMembership(ctx context.Context, txn *sql.Tx, roomNID types.RoomNID, targetUserNID types.EventStateKeyNID, forget bool) error } diff --git a/sytest-whitelist b/sytest-whitelist index 8c4585716..35a1e321a 100644 --- a/sytest-whitelist +++ b/sytest-whitelist @@ -520,3 +520,4 @@ Inviting an AS-hosted user asks the AS server Can generate a openid access_token that can be exchanged for information about a user Invalid openid access tokens are rejected Requests to userinfo without access tokens are rejected +User in private room doesn't appear in user directory