diff --git a/api4/channel_test.go b/api4/channel_test.go index a79554cb8f5..8a94d201775 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 0250cb75c0e..94df003d2ac 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 21fd45b0c1b..4e336132743 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 b7c4a5f2440..ed83a0e5b37 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 c25bc1cf7ac..433d9bbf2a9 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 fec9dd46ff1..c338049db8e 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 32ce4f60379..853cc74764f 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 ccad681ab40..08641516412 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 70d6553fbdd..75c12c9975d 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 440dbcb79f6..072f765c492 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 ff4c6fee556..790fb2637ba 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 8d7843a6150..fed8e3166a1 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 d990481ee5e..f99995ffa4b 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 8eeaa492ef4..e8dc0397d29 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 {