diff --git a/api4/channel_test.go b/api4/channel_test.go index a79554cb8f..8a94d20177 100644 --- a/api4/channel_test.go +++ b/api4/channel_test.go @@ -1339,6 +1339,46 @@ func TestSearchChannels(t *testing.T) { } require.NotContains(t, channelNames, th.BasicChannel.Name) }) + + t.Run("Guests only receive autocompletion for which accounts they are a member of", func(t *testing.T) { + th.App.Srv().SetLicense(model.NewTestLicense("")) + defer th.App.Srv().SetLicense(nil) + + enableGuestAccounts := *th.App.Config().GuestAccountsSettings.Enable + defer func() { + th.App.UpdateConfig(func(cfg *model.Config) { cfg.GuestAccountsSettings.Enable = &enableGuestAccounts }) + }() + th.App.UpdateConfig(func(cfg *model.Config) { *cfg.GuestAccountsSettings.Enable = true }) + + guest := th.CreateUser() + _, appErr := th.SystemAdminClient.DemoteUserToGuest(guest.Id) + require.NoError(t, appErr) + + _, resp, err := th.SystemAdminClient.AddTeamMember(th.BasicTeam.Id, guest.Id) + require.NoError(t, err) + CheckCreatedStatus(t, resp) + + _, resp, err = client.Login(guest.Username, guest.Password) + require.NoError(t, err) + CheckOKStatus(t, resp) + + search.Term = th.BasicChannel2.Name + channelList, _, err := client.SearchChannels(th.BasicTeam.Id, search) + require.NoError(t, err) + + require.Empty(t, channelList) + + _, resp, err = th.SystemAdminClient.AddChannelMember(th.BasicChannel2.Id, guest.Id) + require.NoError(t, err) + CheckCreatedStatus(t, resp) + + search.Term = th.BasicChannel2.Name + channelList, _, err = client.SearchChannels(th.BasicTeam.Id, search) + require.NoError(t, err) + + require.NotEmpty(t, channelList) + require.Equal(t, th.BasicChannel2.Id, channelList[0].Id) + }) } func TestSearchArchivedChannels(t *testing.T) { diff --git a/app/channel.go b/app/channel.go index 0250cb75c0..94df003d2a 100644 --- a/app/channel.go +++ b/app/channel.go @@ -2781,7 +2781,12 @@ func (a *App) AutocompleteChannels(userID, term string) (model.ChannelListWithTe includeDeleted := *a.Config().TeamSettings.ExperimentalViewArchivedChannels term = strings.TrimSpace(term) - channelList, err := a.Srv().Store.Channel().Autocomplete(userID, term, includeDeleted) + user, appErr := a.GetUser(userID) + if appErr != nil { + return nil, appErr + } + + channelList, err := a.Srv().Store.Channel().Autocomplete(userID, term, includeDeleted, user.IsGuest()) if err != nil { return nil, model.NewAppError("AutocompleteChannels", "app.channel.search.app_error", nil, err.Error(), http.StatusInternalServerError) } @@ -2793,7 +2798,12 @@ func (a *App) AutocompleteChannelsForTeam(teamID, userID, term string) (model.Ch includeDeleted := *a.Config().TeamSettings.ExperimentalViewArchivedChannels term = strings.TrimSpace(term) - channelList, err := a.Srv().Store.Channel().AutocompleteInTeam(teamID, userID, term, includeDeleted) + user, appErr := a.GetUser(userID) + if appErr != nil { + return nil, appErr + } + + channelList, err := a.Srv().Store.Channel().AutocompleteInTeam(teamID, userID, term, includeDeleted, user.IsGuest()) if err != nil { return nil, model.NewAppError("AutocompleteChannels", "app.channel.search.app_error", nil, err.Error(), http.StatusInternalServerError) } diff --git a/services/searchengine/bleveengine/search.go b/services/searchengine/bleveengine/search.go index 21fd45b0c1..4e33613274 100644 --- a/services/searchengine/bleveengine/search.go +++ b/services/searchengine/bleveengine/search.go @@ -314,7 +314,7 @@ func (b *BleveEngine) IndexChannel(channel *model.Channel, userIDs, teamMemberID return nil } -func (b *BleveEngine) SearchChannels(teamId, userID, term string) ([]string, *model.AppError) { +func (b *BleveEngine) SearchChannels(teamId, userID, term string, isGuest bool) ([]string, *model.AppError) { // This query essentially boils down to (if teamID is passed): // match teamID == <> // AND @@ -329,6 +329,13 @@ func (b *BleveEngine) SearchChannels(teamId, userID, term string) ([]string, *mo // AND // match (channelType != 'P' || (<> in userIDs && channelType == 'P')) + // (or if isGuest is true) + // <> in teamMemberIds + // AND + // match term == <> + // AND + // match (<> in userIDs) + queries := []query.Query{} if teamId != "" { teamIdQ := bleve.NewTermQuery(teamId) @@ -340,22 +347,29 @@ func (b *BleveEngine) SearchChannels(teamId, userID, term string) ([]string, *mo queries = append(queries, teamMemberQ) } - boolNotPrivate := bleve.NewBooleanQuery() - privateQ := bleve.NewTermQuery(string(model.ChannelTypePrivate)) - privateQ.SetField("Type") - boolNotPrivate.AddMustNot(privateQ) + if isGuest { + userQ := bleve.NewBooleanQuery() + userIDQ := bleve.NewTermQuery(userID) + userIDQ.SetField("UserIDs") + userQ.AddMust(userIDQ) + queries = append(queries, userIDQ) + } else { + boolNotPrivate := bleve.NewBooleanQuery() + privateQ := bleve.NewTermQuery(string(model.ChannelTypePrivate)) + privateQ.SetField("Type") + boolNotPrivate.AddMustNot(privateQ) - userQ := bleve.NewBooleanQuery() - userIDQ := bleve.NewTermQuery(userID) - userIDQ.SetField("UserIDs") - userQ.AddMust(userIDQ) - userQ.AddMust(privateQ) + userQ := bleve.NewBooleanQuery() + userIDQ := bleve.NewTermQuery(userID) + userIDQ.SetField("UserIDs") + userQ.AddMust(userIDQ) + userQ.AddMust(privateQ) - channelTypeQ := bleve.NewDisjunctionQuery() - channelTypeQ.AddQuery(boolNotPrivate) - channelTypeQ.AddQuery(userQ) // userID && 'p' - - queries = append(queries, channelTypeQ) + channelTypeQ := bleve.NewDisjunctionQuery() + channelTypeQ.AddQuery(boolNotPrivate) + channelTypeQ.AddQuery(userQ) // userID && 'p' + queries = append(queries, channelTypeQ) + } if term != "" { nameSuggestQ := bleve.NewPrefixQuery(strings.ToLower(term)) diff --git a/services/searchengine/interface.go b/services/searchengine/interface.go index b7c4a5f244..ed83a0e5b3 100644 --- a/services/searchengine/interface.go +++ b/services/searchengine/interface.go @@ -30,7 +30,7 @@ type SearchEngineInterface interface { // IndexChannel indexes a given channel. The userIDs are only populated // for private channels. IndexChannel(channel *model.Channel, userIDs, teamMemberIDs []string) *model.AppError - SearchChannels(teamId, userID, term string) ([]string, *model.AppError) + SearchChannels(teamId, userID, term string, isGuest bool) ([]string, *model.AppError) DeleteChannel(channel *model.Channel) *model.AppError IndexUser(user *model.User, teamsIds, channelsIds []string) *model.AppError SearchUsersInChannel(teamId, channelId string, restrictedToChannels []string, term string, options *model.UserSearchOptions) ([]string, []string, *model.AppError) diff --git a/services/searchengine/mocks/SearchEngineInterface.go b/services/searchengine/mocks/SearchEngineInterface.go index c25bc1cf7a..433d9bbf2a 100644 --- a/services/searchengine/mocks/SearchEngineInterface.go +++ b/services/searchengine/mocks/SearchEngineInterface.go @@ -400,13 +400,13 @@ func (_m *SearchEngineInterface) RefreshIndexes() *model.AppError { return r0 } -// SearchChannels provides a mock function with given fields: teamId, userID, term -func (_m *SearchEngineInterface) SearchChannels(teamId string, userID string, term string) ([]string, *model.AppError) { - ret := _m.Called(teamId, userID, term) +// SearchChannels provides a mock function with given fields: teamId, userID, term, isGuest +func (_m *SearchEngineInterface) SearchChannels(teamId string, userID string, term string, isGuest bool) ([]string, *model.AppError) { + ret := _m.Called(teamId, userID, term, isGuest) var r0 []string - if rf, ok := ret.Get(0).(func(string, string, string) []string); ok { - r0 = rf(teamId, userID, term) + if rf, ok := ret.Get(0).(func(string, string, string, bool) []string); ok { + r0 = rf(teamId, userID, term, isGuest) } else { if ret.Get(0) != nil { r0 = ret.Get(0).([]string) @@ -414,8 +414,8 @@ func (_m *SearchEngineInterface) SearchChannels(teamId string, userID string, te } var r1 *model.AppError - if rf, ok := ret.Get(1).(func(string, string, string) *model.AppError); ok { - r1 = rf(teamId, userID, term) + if rf, ok := ret.Get(1).(func(string, string, string, bool) *model.AppError); ok { + r1 = rf(teamId, userID, term, isGuest) } else { if ret.Get(1) != nil { r1 = ret.Get(1).(*model.AppError) diff --git a/store/opentracinglayer/opentracinglayer.go b/store/opentracinglayer/opentracinglayer.go index fec9dd46ff..c338049db8 100644 --- a/store/opentracinglayer/opentracinglayer.go +++ b/store/opentracinglayer/opentracinglayer.go @@ -570,7 +570,7 @@ func (s *OpenTracingLayerChannelStore) AnalyticsTypeCount(teamID string, channel return result, err } -func (s *OpenTracingLayerChannelStore) Autocomplete(userID string, term string, includeDeleted bool) (model.ChannelListWithTeamData, error) { +func (s *OpenTracingLayerChannelStore) Autocomplete(userID string, term string, includeDeleted bool, isGuest bool) (model.ChannelListWithTeamData, error) { origCtx := s.Root.Store.Context() span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "ChannelStore.Autocomplete") s.Root.Store.SetContext(newCtx) @@ -579,7 +579,7 @@ func (s *OpenTracingLayerChannelStore) Autocomplete(userID string, term string, }() defer span.Finish() - result, err := s.ChannelStore.Autocomplete(userID, term, includeDeleted) + result, err := s.ChannelStore.Autocomplete(userID, term, includeDeleted, isGuest) if err != nil { span.LogFields(spanlog.Error(err)) ext.Error.Set(span, true) @@ -588,7 +588,7 @@ func (s *OpenTracingLayerChannelStore) Autocomplete(userID string, term string, return result, err } -func (s *OpenTracingLayerChannelStore) AutocompleteInTeam(teamID string, userID string, term string, includeDeleted bool) (model.ChannelList, error) { +func (s *OpenTracingLayerChannelStore) AutocompleteInTeam(teamID string, userID string, term string, includeDeleted bool, isGuest bool) (model.ChannelList, error) { origCtx := s.Root.Store.Context() span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "ChannelStore.AutocompleteInTeam") s.Root.Store.SetContext(newCtx) @@ -597,7 +597,7 @@ func (s *OpenTracingLayerChannelStore) AutocompleteInTeam(teamID string, userID }() defer span.Finish() - result, err := s.ChannelStore.AutocompleteInTeam(teamID, userID, term, includeDeleted) + result, err := s.ChannelStore.AutocompleteInTeam(teamID, userID, term, includeDeleted, isGuest) if err != nil { span.LogFields(spanlog.Error(err)) ext.Error.Set(span, true) diff --git a/store/retrylayer/retrylayer.go b/store/retrylayer/retrylayer.go index 32ce4f6037..853cc74764 100644 --- a/store/retrylayer/retrylayer.go +++ b/store/retrylayer/retrylayer.go @@ -619,11 +619,11 @@ func (s *RetryLayerChannelStore) AnalyticsTypeCount(teamID string, channelType m } -func (s *RetryLayerChannelStore) Autocomplete(userID string, term string, includeDeleted bool) (model.ChannelListWithTeamData, error) { +func (s *RetryLayerChannelStore) Autocomplete(userID string, term string, includeDeleted bool, isGuest bool) (model.ChannelListWithTeamData, error) { tries := 0 for { - result, err := s.ChannelStore.Autocomplete(userID, term, includeDeleted) + result, err := s.ChannelStore.Autocomplete(userID, term, includeDeleted, isGuest) if err == nil { return result, nil } @@ -640,11 +640,11 @@ func (s *RetryLayerChannelStore) Autocomplete(userID string, term string, includ } -func (s *RetryLayerChannelStore) AutocompleteInTeam(teamID string, userID string, term string, includeDeleted bool) (model.ChannelList, error) { +func (s *RetryLayerChannelStore) AutocompleteInTeam(teamID string, userID string, term string, includeDeleted bool, isGuest bool) (model.ChannelList, error) { tries := 0 for { - result, err := s.ChannelStore.AutocompleteInTeam(teamID, userID, term, includeDeleted) + result, err := s.ChannelStore.AutocompleteInTeam(teamID, userID, term, includeDeleted, isGuest) if err == nil { return result, nil } diff --git a/store/searchlayer/channel_layer.go b/store/searchlayer/channel_layer.go index ccad681ab4..0864151641 100644 --- a/store/searchlayer/channel_layer.go +++ b/store/searchlayer/channel_layer.go @@ -161,14 +161,14 @@ func (c *SearchChannelStore) SaveDirectChannel(directchannel *model.Channel, mem return channel, err } -func (c *SearchChannelStore) Autocomplete(userID, term string, includeDeleted bool) (model.ChannelListWithTeamData, error) { +func (c *SearchChannelStore) Autocomplete(userID, term string, includeDeleted, isGuest bool) (model.ChannelListWithTeamData, error) { var channelList model.ChannelListWithTeamData var err error allFailed := true for _, engine := range c.rootStore.searchEngine.GetActiveEngines() { if engine.IsAutocompletionEnabled() { - channelList, err = c.searchAutocompleteChannelsAllTeams(engine, userID, term, includeDeleted) + channelList, err = c.searchAutocompleteChannelsAllTeams(engine, userID, term, includeDeleted, isGuest) if err != nil { mlog.Warn("Encountered error on AutocompleteChannels through SearchEngine. Falling back to default autocompletion.", mlog.String("search_engine", engine.GetName()), mlog.Err(err)) continue @@ -181,7 +181,7 @@ func (c *SearchChannelStore) Autocomplete(userID, term string, includeDeleted bo if allFailed { mlog.Debug("Using database search because no other search engine is available") - channelList, err = c.ChannelStore.Autocomplete(userID, term, includeDeleted) + channelList, err = c.ChannelStore.Autocomplete(userID, term, includeDeleted, isGuest) if err != nil { return nil, errors.Wrap(err, "Failed to autocomplete channels in team") } @@ -194,14 +194,14 @@ func (c *SearchChannelStore) Autocomplete(userID, term string, includeDeleted bo return channelList, nil } -func (c *SearchChannelStore) AutocompleteInTeam(teamID, userID, term string, includeDeleted bool) (model.ChannelList, error) { +func (c *SearchChannelStore) AutocompleteInTeam(teamID, userID, term string, includeDeleted, isGuest bool) (model.ChannelList, error) { var channelList model.ChannelList var err error allFailed := true for _, engine := range c.rootStore.searchEngine.GetActiveEngines() { if engine.IsAutocompletionEnabled() { - channelList, err = c.searchAutocompleteChannels(engine, teamID, userID, term, includeDeleted) + channelList, err = c.searchAutocompleteChannels(engine, teamID, userID, term, includeDeleted, isGuest) if err != nil { mlog.Warn("Encountered error on AutocompleteChannels through SearchEngine. Falling back to default autocompletion.", mlog.String("search_engine", engine.GetName()), mlog.Err(err)) continue @@ -214,7 +214,7 @@ func (c *SearchChannelStore) AutocompleteInTeam(teamID, userID, term string, inc if allFailed { mlog.Debug("Using database search because no other search engine is available") - channelList, err = c.ChannelStore.AutocompleteInTeam(teamID, userID, term, includeDeleted) + channelList, err = c.ChannelStore.AutocompleteInTeam(teamID, userID, term, includeDeleted, isGuest) if err != nil { return nil, errors.Wrap(err, "Failed to autocomplete channels in team") } @@ -227,8 +227,8 @@ func (c *SearchChannelStore) AutocompleteInTeam(teamID, userID, term string, inc return channelList, nil } -func (c *SearchChannelStore) searchAutocompleteChannels(engine searchengine.SearchEngineInterface, teamId, userID, term string, includeDeleted bool) (model.ChannelList, error) { - channelIds, err := engine.SearchChannels(teamId, userID, term) +func (c *SearchChannelStore) searchAutocompleteChannels(engine searchengine.SearchEngineInterface, teamId, userID, term string, includeDeleted, isGuest bool) (model.ChannelList, error) { + channelIds, err := engine.SearchChannels(teamId, userID, term, isGuest) if err != nil { return nil, err } @@ -245,8 +245,8 @@ func (c *SearchChannelStore) searchAutocompleteChannels(engine searchengine.Sear return channelList, nil } -func (c *SearchChannelStore) searchAutocompleteChannelsAllTeams(engine searchengine.SearchEngineInterface, userID, term string, includeDeleted bool) (model.ChannelListWithTeamData, error) { - channelIds, err := engine.SearchChannels("", userID, term) +func (c *SearchChannelStore) searchAutocompleteChannelsAllTeams(engine searchengine.SearchEngineInterface, userID, term string, includeDeleted, isGuest bool) (model.ChannelListWithTeamData, error) { + channelIds, err := engine.SearchChannels("", userID, term, isGuest) if err != nil { return nil, err } diff --git a/store/searchtest/channel_layer.go b/store/searchtest/channel_layer.go index 70d6553fbd..75c12c9975 100644 --- a/store/searchtest/channel_layer.go +++ b/store/searchtest/channel_layer.go @@ -94,11 +94,11 @@ func testAutocompleteChannelByName(t *testing.T, th *SearchTestHelper) { require.NoError(t, err) defer th.deleteChannel(private) - res, err := th.Store.Channel().AutocompleteInTeam(th.Team.Id, th.User.Id, "channel-a", false) + res, err := th.Store.Channel().AutocompleteInTeam(th.Team.Id, th.User.Id, "channel-a", false, false) require.NoError(t, err) th.checkChannelIdsMatch(t, []string{th.ChannelBasic.Id, alternate.Id, private.Id}, res) - res2, err := th.Store.Channel().Autocomplete(th.User.Id, "channel-a", false) + res2, err := th.Store.Channel().Autocomplete(th.User.Id, "channel-a", false, false) require.NoError(t, err) th.checkChannelIdsMatchWithTeamData(t, []string{th.ChannelBasic.Id, alternate.Id, private.Id, th.ChannelAnotherTeam.Id}, res2) } @@ -107,7 +107,7 @@ func testAutocompleteChannelByNamePostgres(t *testing.T, th *SearchTestHelper) { alternate, err := th.createChannel(th.Team.Id, "channel-alternate", "Channel Alternate", "Channel Alternate", model.ChannelTypeOpen, th.User, false) require.NoError(t, err) defer th.deleteChannel(alternate) - res, err := th.Store.Channel().AutocompleteInTeam(th.Team.Id, th.User.Id, "channel-a", false) + res, err := th.Store.Channel().AutocompleteInTeam(th.Team.Id, th.User.Id, "channel-a", false, false) require.NoError(t, err) th.checkChannelIdsMatch(t, []string{th.ChannelBasic.Id, th.ChannelPrivate.Id, alternate.Id}, res) } @@ -121,11 +121,11 @@ func testAutocompleteChannelByDisplayName(t *testing.T, th *SearchTestHelper) { require.NoError(t, err) defer th.deleteChannel(private) - res, err := th.Store.Channel().AutocompleteInTeam(th.Team.Id, th.User.Id, "ChannelA", false) + res, err := th.Store.Channel().AutocompleteInTeam(th.Team.Id, th.User.Id, "ChannelA", false, false) require.NoError(t, err) th.checkChannelIdsMatch(t, []string{th.ChannelBasic.Id, alternate.Id, private.Id}, res) - res2, err := th.Store.Channel().Autocomplete(th.User.Id, "ChannelA", false) + res2, err := th.Store.Channel().Autocomplete(th.User.Id, "ChannelA", false, false) require.NoError(t, err) th.checkChannelIdsMatchWithTeamData(t, []string{th.ChannelBasic.Id, alternate.Id, private.Id, th.ChannelAnotherTeam.Id}, res2) } @@ -134,7 +134,7 @@ func testAutocompleteChannelByNameSplittedWithDashChar(t *testing.T, th *SearchT alternate, err := th.createChannel(th.Team.Id, "channel-alternate", "ChannelAlternate", "", model.ChannelTypeOpen, th.User, false) require.NoError(t, err) defer th.deleteChannel(alternate) - res, err := th.Store.Channel().AutocompleteInTeam(th.Team.Id, th.User.Id, "channel-a", false) + res, err := th.Store.Channel().AutocompleteInTeam(th.Team.Id, th.User.Id, "channel-a", false, false) require.NoError(t, err) th.checkChannelIdsMatch(t, []string{th.ChannelBasic.Id, alternate.Id}, res) } @@ -143,7 +143,7 @@ func testAutocompleteChannelByNameSplittedWithDashCharPostgres(t *testing.T, th alternate, err := th.createChannel(th.Team.Id, "channel-alternate", "ChannelAlternate", "", model.ChannelTypeOpen, th.User, false) require.NoError(t, err) defer th.deleteChannel(alternate) - res, err := th.Store.Channel().AutocompleteInTeam(th.Team.Id, th.User.Id, "channel-a", false) + res, err := th.Store.Channel().AutocompleteInTeam(th.Team.Id, th.User.Id, "channel-a", false, false) require.NoError(t, err) th.checkChannelIdsMatch(t, []string{th.ChannelBasic.Id, th.ChannelPrivate.Id, alternate.Id}, res) } @@ -152,11 +152,11 @@ func testAutocompleteChannelByNameSplittedWithUnderscoreChar(t *testing.T, th *S alternate, err := th.createChannel(th.Team.Id, "channel_alternate", "ChannelAlternate", "", model.ChannelTypeOpen, th.User, false) require.NoError(t, err) defer th.deleteChannel(alternate) - res, err := th.Store.Channel().AutocompleteInTeam(th.Team.Id, th.User.Id, "channel_a", false) + res, err := th.Store.Channel().AutocompleteInTeam(th.Team.Id, th.User.Id, "channel_a", false, false) require.NoError(t, err) th.checkChannelIdsMatch(t, []string{alternate.Id}, res) - res2, err := th.Store.Channel().Autocomplete(th.User.Id, "channel_a", false) + res2, err := th.Store.Channel().Autocomplete(th.User.Id, "channel_a", false, false) require.NoError(t, err) th.checkChannelIdsMatchWithTeamData(t, []string{alternate.Id}, res2) } @@ -166,7 +166,7 @@ func testAutocompleteChannelByDisplayNameSplittedByWhitespaces(t *testing.T, th require.NoError(t, err) defer th.deleteChannel(alternate) - res, err := th.Store.Channel().AutocompleteInTeam(th.Team.Id, th.User.Id, "Channel A", false) + res, err := th.Store.Channel().AutocompleteInTeam(th.Team.Id, th.User.Id, "Channel A", false, false) require.NoError(t, err) th.checkChannelIdsMatch(t, []string{alternate.Id}, res) } @@ -177,7 +177,7 @@ func testAutocompleteAllChannelsIfTermIsEmpty(t *testing.T, th *SearchTestHelper require.NoError(t, err) defer th.deleteChannel(alternate) defer th.deleteChannel(other) - res, err := th.Store.Channel().AutocompleteInTeam(th.Team.Id, th.User.Id, "", false) + res, err := th.Store.Channel().AutocompleteInTeam(th.Team.Id, th.User.Id, "", false, false) require.NoError(t, err) th.checkChannelIdsMatch(t, []string{th.ChannelBasic.Id, th.ChannelPrivate.Id, alternate.Id, other.Id}, res) } @@ -186,17 +186,17 @@ func testSearchChannelsInCaseInsensitiveManner(t *testing.T, th *SearchTestHelpe alternate, err := th.createChannel(th.Team.Id, "channel-alternate", "ChannelAlternate", "", model.ChannelTypeOpen, th.User, false) require.NoError(t, err) defer th.deleteChannel(alternate) - res, err := th.Store.Channel().AutocompleteInTeam(th.Team.Id, th.User.Id, "channela", false) + res, err := th.Store.Channel().AutocompleteInTeam(th.Team.Id, th.User.Id, "channela", false, false) require.NoError(t, err) th.checkChannelIdsMatch(t, []string{th.ChannelBasic.Id, alternate.Id}, res) - res, err = th.Store.Channel().AutocompleteInTeam(th.Team.Id, th.User.Id, "ChAnNeL-a", false) + res, err = th.Store.Channel().AutocompleteInTeam(th.Team.Id, th.User.Id, "ChAnNeL-a", false, false) require.NoError(t, err) th.checkChannelIdsMatch(t, []string{th.ChannelBasic.Id, alternate.Id}, res) - res2, err := th.Store.Channel().Autocomplete(th.User.Id, "channela", false) + res2, err := th.Store.Channel().Autocomplete(th.User.Id, "channela", false, false) require.NoError(t, err) th.checkChannelIdsMatchWithTeamData(t, []string{th.ChannelAnotherTeam.Id, th.ChannelBasic.Id, alternate.Id}, res2) - res2, err = th.Store.Channel().Autocomplete(th.User.Id, "ChAnNeL-a", false) + res2, err = th.Store.Channel().Autocomplete(th.User.Id, "ChAnNeL-a", false, false) require.NoError(t, err) th.checkChannelIdsMatchWithTeamData(t, []string{th.ChannelAnotherTeam.Id, th.ChannelBasic.Id, alternate.Id}, res2) } @@ -205,10 +205,10 @@ func testSearchChannelsInCaseInsensitiveMannerPostgres(t *testing.T, th *SearchT alternate, err := th.createChannel(th.Team.Id, "channel-alternate", "ChannelAlternate", "", model.ChannelTypeOpen, th.User, false) require.NoError(t, err) defer th.deleteChannel(alternate) - res, err := th.Store.Channel().AutocompleteInTeam(th.Team.Id, th.User.Id, "channela", false) + res, err := th.Store.Channel().AutocompleteInTeam(th.Team.Id, th.User.Id, "channela", false, false) require.NoError(t, err) th.checkChannelIdsMatch(t, []string{th.ChannelBasic.Id, alternate.Id}, res) - res, err = th.Store.Channel().AutocompleteInTeam(th.Team.Id, th.User.Id, "ChAnNeL-a", false) + res, err = th.Store.Channel().AutocompleteInTeam(th.Team.Id, th.User.Id, "ChAnNeL-a", false, false) require.NoError(t, err) th.checkChannelIdsMatch(t, []string{th.ChannelBasic.Id, th.ChannelPrivate.Id, alternate.Id}, res) } @@ -217,17 +217,17 @@ func testSearchShouldSupportHavingHyphenAsLastCharacter(t *testing.T, th *Search alternate, err := th.createChannel(th.Team.Id, "channel-alternate", "ChannelAlternate", "", model.ChannelTypeOpen, th.User, false) require.NoError(t, err) defer th.deleteChannel(alternate) - res, err := th.Store.Channel().AutocompleteInTeam(th.Team.Id, th.User.Id, "channel-", false) + res, err := th.Store.Channel().AutocompleteInTeam(th.Team.Id, th.User.Id, "channel-", false, false) require.NoError(t, err) th.checkChannelIdsMatch(t, []string{th.ChannelBasic.Id, th.ChannelPrivate.Id, alternate.Id}, res) - res2, err := th.Store.Channel().Autocomplete(th.User.Id, "channel-", false) + res2, err := th.Store.Channel().Autocomplete(th.User.Id, "channel-", false, false) require.NoError(t, err) th.checkChannelIdsMatchWithTeamData(t, []string{th.ChannelAnotherTeam.Id, th.ChannelBasic.Id, th.ChannelPrivate.Id, alternate.Id}, res2) } func testSearchShouldSupportAutocompleteWithArchivedChannels(t *testing.T, th *SearchTestHelper) { - res, err := th.Store.Channel().AutocompleteInTeam(th.Team.Id, th.User.Id, "channel-", true) + res, err := th.Store.Channel().AutocompleteInTeam(th.Team.Id, th.User.Id, "channel-", true, false) require.NoError(t, err) th.checkChannelIdsMatch(t, []string{th.ChannelBasic.Id, th.ChannelPrivate.Id, th.ChannelDeleted.Id}, res) } diff --git a/store/sqlstore/channel_store.go b/store/sqlstore/channel_store.go index 440dbcb79f..072f765c49 100644 --- a/store/sqlstore/channel_store.go +++ b/store/sqlstore/channel_store.go @@ -2921,7 +2921,7 @@ func (s SqlChannelStore) GetTeamMembersForChannel(channelID string) ([]string, e return teamMemberIDs, nil } -func (s SqlChannelStore) Autocomplete(userID, term string, includeDeleted bool) (model.ChannelListWithTeamData, error) { +func (s SqlChannelStore) Autocomplete(userID, term string, includeDeleted, isGuest bool) (model.ChannelListWithTeamData, error) { query := s.getQueryBuilder().Select("c.*", "t.DisplayName AS TeamDisplayName", "t.Name AS TeamName", @@ -2931,15 +2931,6 @@ func (s SqlChannelStore) Autocomplete(userID, term string, includeDeleted bool) sq.Expr("c.TeamId = t.id"), sq.Expr("t.id = tm.TeamId"), sq.Eq{"tm.UserId": userID}, - sq.Or{ - sq.NotEq{"c.Type": model.ChannelTypePrivate}, - sq.And{ - sq.Eq{"c.Type": model.ChannelTypePrivate}, - sq.Expr("c.Id IN (?)", sq.Select("ChannelId"). - From("ChannelMembers"). - Where(sq.Eq{"UserId": userID})), - }, - }, }). OrderBy("c.DisplayName") @@ -2949,6 +2940,23 @@ func (s SqlChannelStore) Autocomplete(userID, term string, includeDeleted bool) sq.Eq{"tm.DeleteAt": 0}, }) } + + if isGuest { + query = query.Where(sq.Expr("c.Id IN (?)", sq.Select("ChannelId"). + From("ChannelMembers"). + Where(sq.Eq{"UserId": userID}))) + } else { + query = query.Where(sq.Or{ + sq.NotEq{"c.Type": model.ChannelTypePrivate}, + sq.And{ + sq.Eq{"c.Type": model.ChannelTypePrivate}, + sq.Expr("c.Id IN (?)", sq.Select("ChannelId"). + From("ChannelMembers"). + Where(sq.Eq{"UserId": userID})), + }, + }) + } + searchClause := s.searchClause(term) if searchClause != nil { query = query.Where(searchClause) @@ -2967,21 +2975,10 @@ func (s SqlChannelStore) Autocomplete(userID, term string, includeDeleted bool) return channels, nil } -func (s SqlChannelStore) AutocompleteInTeam(teamID, userID, term string, includeDeleted bool) (model.ChannelList, error) { +func (s SqlChannelStore) AutocompleteInTeam(teamID, userID, term string, includeDeleted, isGuest bool) (model.ChannelList, error) { query := s.getQueryBuilder().Select("*"). From("Channels c"). - Where(sq.And{ - sq.Eq{"c.TeamId": teamID}, - sq.Or{ - sq.NotEq{"c.Type": model.ChannelTypePrivate}, - sq.And{ - sq.Eq{"c.Type": model.ChannelTypePrivate}, - sq.Expr("c.Id IN (?)", sq.Select("ChannelId"). - From("ChannelMembers"). - Where(sq.Eq{"UserId": userID})), - }, - }, - }). + Where(sq.Eq{"c.TeamId": teamID}). OrderBy("c.DisplayName"). Limit(model.ChannelSearchDefaultLimit) @@ -2989,6 +2986,22 @@ func (s SqlChannelStore) AutocompleteInTeam(teamID, userID, term string, include query = query.Where(sq.Eq{"c.DeleteAt": 0}) } + if isGuest { + query = query.Where(sq.Expr("c.Id IN (?)", sq.Select("ChannelId"). + From("ChannelMembers"). + Where(sq.Eq{"UserId": userID}))) + } else { + query = query.Where(sq.Or{ + sq.NotEq{"c.Type": model.ChannelTypePrivate}, + sq.And{ + sq.Eq{"c.Type": model.ChannelTypePrivate}, + sq.Expr("c.Id IN (?)", sq.Select("ChannelId"). + From("ChannelMembers"). + Where(sq.Eq{"UserId": userID})), + }, + }) + } + searchClause := s.searchClause(term) if searchClause != nil { query = query.Where(searchClause) diff --git a/store/store.go b/store/store.go index ff4c6fee55..790fb2637b 100644 --- a/store/store.go +++ b/store/store.go @@ -235,8 +235,8 @@ type ChannelStore interface { GetTeamMembersForChannel(channelID string) ([]string, error) GetMembersForUserWithPagination(userID string, page, perPage int) (model.ChannelMembersWithTeamData, error) GetMembersForUserWithCursor(userID, teamID string, opts *ChannelMemberGraphQLSearchOpts) (model.ChannelMembers, error) - Autocomplete(userID, term string, includeDeleted bool) (model.ChannelListWithTeamData, error) - AutocompleteInTeam(teamID, userID, term string, includeDeleted bool) (model.ChannelList, error) + Autocomplete(userID, term string, includeDeleted, isGuest bool) (model.ChannelListWithTeamData, error) + AutocompleteInTeam(teamID, userID, term string, includeDeleted, isGuest bool) (model.ChannelList, error) AutocompleteInTeamForSearch(teamID string, userID string, term string, includeDeleted bool) (model.ChannelList, error) SearchAllChannels(term string, opts ChannelSearchOpts) (model.ChannelListWithTeamData, int64, error) SearchInTeam(teamID string, term string, includeDeleted bool) (model.ChannelList, error) diff --git a/store/storetest/channel_store.go b/store/storetest/channel_store.go index 8d7843a615..fed8e3166a 100644 --- a/store/storetest/channel_store.go +++ b/store/storetest/channel_store.go @@ -5899,7 +5899,7 @@ func testChannelStoreSearchInTeam(t *testing.T, ss store.Store) { for _, testCase := range testCases { t.Run("AutoCompleteInTeam/"+testCase.Description, func(t *testing.T) { - channels, err := ss.Channel().AutocompleteInTeam(testCase.TeamID, testCase.UserID, testCase.Term, testCase.IncludeDeleted) + channels, err := ss.Channel().AutocompleteInTeam(testCase.TeamID, testCase.UserID, testCase.Term, testCase.IncludeDeleted, false) require.NoError(t, err) sort.Sort(ByChannelDisplayName(channels)) require.Equal(t, testCase.ExpectedResults, channels) @@ -5946,6 +5946,15 @@ func testAutocomplete(t *testing.T, ss store.Store) { _, err = ss.Channel().Save(&o2, -1) require.NoError(t, err) + o6 := model.Channel{ + TeamId: teamID, + DisplayName: "ChannelA3", + Name: NewTestId(), + Type: model.ChannelTypeOpen, + } + _, err = ss.Channel().Save(&o6, -1) + require.NoError(t, err) + m1 := model.ChannelMember{ ChannelId: o1.Id, UserId: model.NewId(), @@ -6063,20 +6072,22 @@ func testAutocomplete(t *testing.T, ss store.Store) { UserID string Term string IncludeDeleted bool + IsGuest bool ExpectedChannelIds []string ExpectedTeamNames []string }{ - {"user 1, Channel A", m1.UserId, "ChannelA", false, []string{o1.Id, o2.Id}, []string{t1.Name, t2.Name}}, - {"user 1, Channel B", m1.UserId, "ChannelB", false, []string{o4.Id}, []string{t2.Name}}, - {"user 2, Channel A", m3.UserId, "ChannelA", false, []string{o3.Id, o1.Id, o2.Id}, []string{t2.Name, t1.Name, t1.Name}}, - {"user 2, Channel B", m3.UserId, "ChannelB", false, nil, nil}, - {"user 1, empty string", m1.UserId, "", false, []string{o1.Id, o2.Id, o4.Id}, []string{t1.Name, t2.Name, t2.Name}}, - {"user 2, empty string", m3.UserId, "", false, []string{o1.Id, o2.Id, o3.Id}, []string{t1.Name, t2.Name, t1.Name}}, + {"user 1, Channel A", m1.UserId, "ChannelA", false, false, []string{o1.Id, o2.Id, o6.Id}, []string{t1.Name, t2.Name, t1.Name}}, + {"user 1, Channel B", m1.UserId, "ChannelB", false, false, []string{o4.Id}, []string{t2.Name}}, + {"user 2, Channel A", m3.UserId, "ChannelA", false, false, []string{o3.Id, o1.Id, o2.Id, o6.Id}, []string{t2.Name, t1.Name, t1.Name, t1.Name}}, + {"user 2 guest, Channel A", m3.UserId, "ChannelA", false, true, []string{o2.Id, o3.Id}, []string{t2.Name, t1.Name}}, + {"user 2, Channel B", m3.UserId, "ChannelB", false, false, nil, nil}, + {"user 1, empty string", m1.UserId, "", false, false, []string{o1.Id, o2.Id, o4.Id, o6.Id}, []string{t1.Name, t2.Name, t2.Name, t1.Name}}, + {"user 2, empty string", m3.UserId, "", false, false, []string{o1.Id, o2.Id, o3.Id, o6.Id}, []string{t1.Name, t2.Name, t1.Name, t1.Name}}, } for _, testCase := range testCases { t.Run("Autocomplete/"+testCase.Description, func(t *testing.T) { - channels, err := ss.Channel().Autocomplete(testCase.UserID, testCase.Term, testCase.IncludeDeleted) + channels, err := ss.Channel().Autocomplete(testCase.UserID, testCase.Term, testCase.IncludeDeleted, testCase.IsGuest) require.NoError(t, err) var gotChannelIds []string var gotTeamNames []string @@ -6084,8 +6095,8 @@ func testAutocomplete(t *testing.T, ss store.Store) { gotChannelIds = append(gotChannelIds, ch.Id) gotTeamNames = append(gotTeamNames, ch.TeamName) } - require.ElementsMatch(t, testCase.ExpectedChannelIds, gotChannelIds) - require.ElementsMatch(t, testCase.ExpectedTeamNames, gotTeamNames) + require.ElementsMatch(t, testCase.ExpectedChannelIds, gotChannelIds, "channels IDs are not as expected") + require.ElementsMatch(t, testCase.ExpectedTeamNames, gotTeamNames, "team names are not as expected") }) } } diff --git a/store/storetest/mocks/ChannelStore.go b/store/storetest/mocks/ChannelStore.go index d990481ee5..f99995ffa4 100644 --- a/store/storetest/mocks/ChannelStore.go +++ b/store/storetest/mocks/ChannelStore.go @@ -60,13 +60,13 @@ func (_m *ChannelStore) AnalyticsTypeCount(teamID string, channelType model.Chan return r0, r1 } -// Autocomplete provides a mock function with given fields: userID, term, includeDeleted -func (_m *ChannelStore) Autocomplete(userID string, term string, includeDeleted bool) (model.ChannelListWithTeamData, error) { - ret := _m.Called(userID, term, includeDeleted) +// Autocomplete provides a mock function with given fields: userID, term, includeDeleted, isGuest +func (_m *ChannelStore) Autocomplete(userID string, term string, includeDeleted bool, isGuest bool) (model.ChannelListWithTeamData, error) { + ret := _m.Called(userID, term, includeDeleted, isGuest) var r0 model.ChannelListWithTeamData - if rf, ok := ret.Get(0).(func(string, string, bool) model.ChannelListWithTeamData); ok { - r0 = rf(userID, term, includeDeleted) + if rf, ok := ret.Get(0).(func(string, string, bool, bool) model.ChannelListWithTeamData); ok { + r0 = rf(userID, term, includeDeleted, isGuest) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(model.ChannelListWithTeamData) @@ -74,8 +74,8 @@ func (_m *ChannelStore) Autocomplete(userID string, term string, includeDeleted } var r1 error - if rf, ok := ret.Get(1).(func(string, string, bool) error); ok { - r1 = rf(userID, term, includeDeleted) + if rf, ok := ret.Get(1).(func(string, string, bool, bool) error); ok { + r1 = rf(userID, term, includeDeleted, isGuest) } else { r1 = ret.Error(1) } @@ -83,13 +83,13 @@ func (_m *ChannelStore) Autocomplete(userID string, term string, includeDeleted return r0, r1 } -// AutocompleteInTeam provides a mock function with given fields: teamID, userID, term, includeDeleted -func (_m *ChannelStore) AutocompleteInTeam(teamID string, userID string, term string, includeDeleted bool) (model.ChannelList, error) { - ret := _m.Called(teamID, userID, term, includeDeleted) +// AutocompleteInTeam provides a mock function with given fields: teamID, userID, term, includeDeleted, isGuest +func (_m *ChannelStore) AutocompleteInTeam(teamID string, userID string, term string, includeDeleted bool, isGuest bool) (model.ChannelList, error) { + ret := _m.Called(teamID, userID, term, includeDeleted, isGuest) var r0 model.ChannelList - if rf, ok := ret.Get(0).(func(string, string, string, bool) model.ChannelList); ok { - r0 = rf(teamID, userID, term, includeDeleted) + if rf, ok := ret.Get(0).(func(string, string, string, bool, bool) model.ChannelList); ok { + r0 = rf(teamID, userID, term, includeDeleted, isGuest) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(model.ChannelList) @@ -97,8 +97,8 @@ func (_m *ChannelStore) AutocompleteInTeam(teamID string, userID string, term st } var r1 error - if rf, ok := ret.Get(1).(func(string, string, string, bool) error); ok { - r1 = rf(teamID, userID, term, includeDeleted) + if rf, ok := ret.Get(1).(func(string, string, string, bool, bool) error); ok { + r1 = rf(teamID, userID, term, includeDeleted, isGuest) } else { r1 = ret.Error(1) } diff --git a/store/timerlayer/timerlayer.go b/store/timerlayer/timerlayer.go index 8eeaa492ef..e8dc0397d2 100644 --- a/store/timerlayer/timerlayer.go +++ b/store/timerlayer/timerlayer.go @@ -550,10 +550,10 @@ func (s *TimerLayerChannelStore) AnalyticsTypeCount(teamID string, channelType m return result, err } -func (s *TimerLayerChannelStore) Autocomplete(userID string, term string, includeDeleted bool) (model.ChannelListWithTeamData, error) { +func (s *TimerLayerChannelStore) Autocomplete(userID string, term string, includeDeleted bool, isGuest bool) (model.ChannelListWithTeamData, error) { start := timemodule.Now() - result, err := s.ChannelStore.Autocomplete(userID, term, includeDeleted) + result, err := s.ChannelStore.Autocomplete(userID, term, includeDeleted, isGuest) elapsed := float64(timemodule.Since(start)) / float64(timemodule.Second) if s.Root.Metrics != nil { @@ -566,10 +566,10 @@ func (s *TimerLayerChannelStore) Autocomplete(userID string, term string, includ return result, err } -func (s *TimerLayerChannelStore) AutocompleteInTeam(teamID string, userID string, term string, includeDeleted bool) (model.ChannelList, error) { +func (s *TimerLayerChannelStore) AutocompleteInTeam(teamID string, userID string, term string, includeDeleted bool, isGuest bool) (model.ChannelList, error) { start := timemodule.Now() - result, err := s.ChannelStore.AutocompleteInTeam(teamID, userID, term, includeDeleted) + result, err := s.ChannelStore.AutocompleteInTeam(teamID, userID, term, includeDeleted, isGuest) elapsed := float64(timemodule.Since(start)) / float64(timemodule.Second) if s.Root.Metrics != nil {