From 098dbc84cc83a8338872eae7de82c83a697c7353 Mon Sep 17 00:00:00 2001 From: Miguel de la Cruz Date: Thu, 16 May 2019 10:12:06 +0100 Subject: [PATCH] [MM-14751] Adds group_constrained filter to user list and search endpoints (#10678) --- api4/user.go | 16 +++-- app/user.go | 20 +++--- app/user_viewmembers_test.go | 4 +- model/user_get.go | 2 + model/user_search.go | 21 +++--- store/sqlstore/user_store.go | 104 +++++++++++++++++++---------- store/store.go | 4 +- store/storetest/mocks/UserStore.go | 20 +++--- store/storetest/user_store.go | 100 ++++++++++++++++++++++++--- 9 files changed, 206 insertions(+), 85 deletions(-) diff --git a/api4/user.go b/api4/user.go index 1b647d088f..3063a7fb13 100644 --- a/api4/user.go +++ b/api4/user.go @@ -452,6 +452,7 @@ func getUsers(c *Context, w http.ResponseWriter, r *http.Request) { notInTeamId := r.URL.Query().Get("not_in_team") inChannelId := r.URL.Query().Get("in_channel") notInChannelId := r.URL.Query().Get("not_in_channel") + groupConstrained := r.URL.Query().Get("group_constrained") withoutTeam := r.URL.Query().Get("without_team") inactive := r.URL.Query().Get("inactive") role := r.URL.Query().Get("role") @@ -479,6 +480,7 @@ func getUsers(c *Context, w http.ResponseWriter, r *http.Request) { } withoutTeamBool, _ := strconv.ParseBool(withoutTeam) + groupConstrainedBool, _ := strconv.ParseBool(groupConstrained) inactiveBool, _ := strconv.ParseBool(inactive) restrictions, err := c.App.GetViewUsersRestrictions(c.App.Session.UserId) @@ -492,6 +494,7 @@ func getUsers(c *Context, w http.ResponseWriter, r *http.Request) { InChannelId: inChannelId, NotInTeamId: notInTeamId, NotInChannelId: notInChannelId, + GroupConstrained: groupConstrainedBool, WithoutTeam: withoutTeamBool, Inactive: inactiveBool, Role: role, @@ -518,7 +521,7 @@ func getUsers(c *Context, w http.ResponseWriter, r *http.Request) { return } - profiles, err = c.App.GetUsersNotInChannelPage(inTeamId, notInChannelId, c.Params.Page, c.Params.PerPage, c.IsSystemAdmin(), restrictions) + profiles, err = c.App.GetUsersNotInChannelPage(inTeamId, notInChannelId, groupConstrainedBool, c.Params.Page, c.Params.PerPage, c.IsSystemAdmin(), restrictions) } else if len(notInTeamId) > 0 { if !c.App.SessionHasPermissionToTeam(c.App.Session, notInTeamId, model.PERMISSION_VIEW_TEAM) { c.SetPermissionError(model.PERMISSION_VIEW_TEAM) @@ -530,7 +533,7 @@ func getUsers(c *Context, w http.ResponseWriter, r *http.Request) { return } - profiles, err = c.App.GetUsersNotInTeamPage(notInTeamId, c.Params.Page, c.Params.PerPage, c.IsSystemAdmin(), restrictions) + profiles, err = c.App.GetUsersNotInTeamPage(notInTeamId, groupConstrainedBool, c.Params.Page, c.Params.PerPage, c.IsSystemAdmin(), restrictions) } else if len(inTeamId) > 0 { if !c.App.SessionHasPermissionToTeam(c.App.Session, inTeamId, model.PERMISSION_VIEW_TEAM) { c.SetPermissionError(model.PERMISSION_VIEW_TEAM) @@ -673,10 +676,11 @@ func searchUsers(c *Context, w http.ResponseWriter, r *http.Request) { } options := &model.UserSearchOptions{ - IsAdmin: c.IsSystemAdmin(), - AllowInactive: props.AllowInactive, - Limit: props.Limit, - Role: props.Role, + IsAdmin: c.IsSystemAdmin(), + AllowInactive: props.AllowInactive, + GroupConstrained: props.GroupConstrained, + Limit: props.Limit, + Role: props.Role, } if c.App.SessionHasPermissionTo(c.App.Session, model.PERMISSION_MANAGE_SYSTEM) { diff --git a/app/user.go b/app/user.go index 8ddc428e8b..537debe927 100644 --- a/app/user.go +++ b/app/user.go @@ -472,8 +472,8 @@ func (a *App) GetUsersInTeam(options *model.UserGetOptions) ([]*model.User, *mod return result.Data.([]*model.User), nil } -func (a *App) GetUsersNotInTeam(teamId string, offset int, limit int, viewRestrictions *model.ViewUsersRestrictions) ([]*model.User, *model.AppError) { - result := <-a.Srv.Store.User().GetProfilesNotInTeam(teamId, offset, limit, viewRestrictions) +func (a *App) GetUsersNotInTeam(teamId string, groupConstrained bool, offset int, limit int, viewRestrictions *model.ViewUsersRestrictions) ([]*model.User, *model.AppError) { + result := <-a.Srv.Store.User().GetProfilesNotInTeam(teamId, groupConstrained, offset, limit, viewRestrictions) if result.Err != nil { return nil, result.Err } @@ -489,8 +489,8 @@ func (a *App) GetUsersInTeamPage(options *model.UserGetOptions, asAdmin bool) ([ return a.sanitizeProfiles(users, asAdmin), nil } -func (a *App) GetUsersNotInTeamPage(teamId string, page int, perPage int, asAdmin bool, viewRestrictions *model.ViewUsersRestrictions) ([]*model.User, *model.AppError) { - users, err := a.GetUsersNotInTeam(teamId, page*perPage, perPage, viewRestrictions) +func (a *App) GetUsersNotInTeamPage(teamId string, groupConstrained bool, page int, perPage int, asAdmin bool, viewRestrictions *model.ViewUsersRestrictions) ([]*model.User, *model.AppError) { + users, err := a.GetUsersNotInTeam(teamId, groupConstrained, page*perPage, perPage, viewRestrictions) if err != nil { return nil, err } @@ -554,16 +554,16 @@ func (a *App) GetUsersInChannelPageByStatus(channelId string, page int, perPage return a.sanitizeProfiles(users, asAdmin), nil } -func (a *App) GetUsersNotInChannel(teamId string, channelId string, offset int, limit int, viewRestrictions *model.ViewUsersRestrictions) ([]*model.User, *model.AppError) { - result := <-a.Srv.Store.User().GetProfilesNotInChannel(teamId, channelId, offset, limit, viewRestrictions) +func (a *App) GetUsersNotInChannel(teamId string, channelId string, groupConstrained bool, offset int, limit int, viewRestrictions *model.ViewUsersRestrictions) ([]*model.User, *model.AppError) { + result := <-a.Srv.Store.User().GetProfilesNotInChannel(teamId, channelId, groupConstrained, offset, limit, viewRestrictions) if result.Err != nil { return nil, result.Err } return result.Data.([]*model.User), nil } -func (a *App) GetUsersNotInChannelMap(teamId string, channelId string, offset int, limit int, asAdmin bool, viewRestrictions *model.ViewUsersRestrictions) (map[string]*model.User, *model.AppError) { - users, err := a.GetUsersNotInChannel(teamId, channelId, offset, limit, viewRestrictions) +func (a *App) GetUsersNotInChannelMap(teamId string, channelId string, groupConstrained bool, offset int, limit int, asAdmin bool, viewRestrictions *model.ViewUsersRestrictions) (map[string]*model.User, *model.AppError) { + users, err := a.GetUsersNotInChannel(teamId, channelId, groupConstrained, offset, limit, viewRestrictions) if err != nil { return nil, err } @@ -578,8 +578,8 @@ func (a *App) GetUsersNotInChannelMap(teamId string, channelId string, offset in return userMap, nil } -func (a *App) GetUsersNotInChannelPage(teamId string, channelId string, page int, perPage int, asAdmin bool, viewRestrictions *model.ViewUsersRestrictions) ([]*model.User, *model.AppError) { - users, err := a.GetUsersNotInChannel(teamId, channelId, page*perPage, perPage, viewRestrictions) +func (a *App) GetUsersNotInChannelPage(teamId string, channelId string, groupConstrained bool, page int, perPage int, asAdmin bool, viewRestrictions *model.ViewUsersRestrictions) ([]*model.User, *model.AppError) { + users, err := a.GetUsersNotInChannel(teamId, channelId, groupConstrained, page*perPage, perPage, viewRestrictions) if err != nil { return nil, err } diff --git a/app/user_viewmembers_test.go b/app/user_viewmembers_test.go index ab14647ee2..d2ddfae8f1 100644 --- a/app/user_viewmembers_test.go +++ b/app/user_viewmembers_test.go @@ -692,7 +692,7 @@ func TestResctrictedViewMembers(t *testing.T) { for _, tc := range testCases { t.Run(tc.Name, func(t *testing.T) { - results, err := th.App.GetUsersNotInTeam(tc.TeamId, 0, 100, tc.Restrictions) + results, err := th.App.GetUsersNotInTeam(tc.TeamId, false, 0, 100, tc.Restrictions) require.Nil(t, err) ids := []string{} for _, result := range results { @@ -775,7 +775,7 @@ func TestResctrictedViewMembers(t *testing.T) { for _, tc := range testCases { t.Run(tc.Name, func(t *testing.T) { - results, err := th.App.GetUsersNotInChannel(tc.TeamId, tc.ChannelId, 0, 100, tc.Restrictions) + results, err := th.App.GetUsersNotInChannel(tc.TeamId, tc.ChannelId, false, 0, 100, tc.Restrictions) require.Nil(t, err) ids := []string{} for _, result := range results { diff --git a/model/user_get.go b/model/user_get.go index a291140e4f..ec72eb4972 100644 --- a/model/user_get.go +++ b/model/user_get.go @@ -12,6 +12,8 @@ type UserGetOptions struct { InChannelId string // Filters the users not in the channel NotInChannelId string + // Filters the users group constrained + GroupConstrained bool // Filters the users without a team WithoutTeam bool // Filters the inactive users diff --git a/model/user_search.go b/model/user_search.go index f6ca6c247e..9ea69c46e1 100644 --- a/model/user_search.go +++ b/model/user_search.go @@ -13,15 +13,16 @@ const USER_SEARCH_DEFAULT_LIMIT = 100 // UserSearch captures the parameters provided by a client for initiating a user search. type UserSearch struct { - Term string `json:"term"` - TeamId string `json:"team_id"` - NotInTeamId string `json:"not_in_team_id"` - InChannelId string `json:"in_channel_id"` - NotInChannelId string `json:"not_in_channel_id"` - AllowInactive bool `json:"allow_inactive"` - WithoutTeam bool `json:"without_team"` - Limit int `json:"limit"` - Role string `json:"role"` + Term string `json:"term"` + TeamId string `json:"team_id"` + NotInTeamId string `json:"not_in_team_id"` + InChannelId string `json:"in_channel_id"` + NotInChannelId string `json:"not_in_channel_id"` + GroupConstrained bool `json:"group_constrained"` + AllowInactive bool `json:"allow_inactive"` + WithoutTeam bool `json:"without_team"` + Limit int `json:"limit"` + Role string `json:"role"` } // ToJson convert a User to a json string @@ -53,6 +54,8 @@ type UserSearchOptions struct { AllowFullNames bool // AllowInactive configures whether or not to return inactive users in the search results. AllowInactive bool + // Narrows the search to the group constrained users + GroupConstrained bool // Limit limits the total number of results returned. Limit int // Filters for the given role diff --git a/store/sqlstore/user_store.go b/store/sqlstore/user_store.go index c713c6fb3e..0acb94de98 100644 --- a/store/sqlstore/user_store.go +++ b/store/sqlstore/user_store.go @@ -448,6 +448,54 @@ func applyRoleFilter(query sq.SelectBuilder, role string, isPostgreSQL bool) sq. return query.Where("u.Roles LIKE ? ESCAPE '*'", roleParam) } +func applyChannelGroupConstrainedFilter(query sq.SelectBuilder, channelId string) sq.SelectBuilder { + if channelId == "" { + return query + } + + return query. + Where(`u.Id IN ( + SELECT + GroupMembers.UserId + FROM + Channels + JOIN GroupChannels ON GroupChannels.ChannelId = Channels.Id + JOIN UserGroups ON UserGroups.Id = GroupChannels.GroupId + JOIN GroupMembers ON GroupMembers.GroupId = UserGroups.Id + WHERE + Channels.Id = ? + AND GroupChannels.DeleteAt = 0 + AND UserGroups.DeleteAt = 0 + AND GroupMembers.DeleteAt = 0 + GROUP BY + GroupMembers.UserId + )`, channelId) +} + +func applyTeamGroupConstrainedFilter(query sq.SelectBuilder, teamId string) sq.SelectBuilder { + if teamId == "" { + return query + } + + return query. + Where(`u.Id IN ( + SELECT + GroupMembers.UserId + FROM + Teams + JOIN GroupTeams ON GroupTeams.TeamId = Teams.Id + JOIN UserGroups ON UserGroups.Id = GroupTeams.GroupId + JOIN GroupMembers ON GroupMembers.GroupId = UserGroups.Id + WHERE + Teams.Id = ? + AND GroupTeams.DeleteAt = 0 + AND UserGroups.DeleteAt = 0 + AND GroupMembers.DeleteAt = 0 + GROUP BY + GroupMembers.UserId + )`, teamId) +} + func (s SqlUserStore) GetEtagForProfiles(teamId string) store.StoreChannel { return store.Do(func(result *store.StoreResult) { updateAt, err := s.GetReplica().SelectInt("SELECT UpdateAt FROM Users, TeamMembers WHERE TeamMembers.TeamId = :TeamId AND Users.Id = TeamMembers.UserId ORDER BY UpdateAt DESC LIMIT 1", map[string]interface{}{"TeamId": teamId}) @@ -636,7 +684,7 @@ func (us SqlUserStore) GetAllProfilesInChannel(channelId string, allowFromCache }) } -func (us SqlUserStore) GetProfilesNotInChannel(teamId string, channelId string, offset int, limit int, viewRestrictions *model.ViewUsersRestrictions) store.StoreChannel { +func (us SqlUserStore) GetProfilesNotInChannel(teamId string, channelId string, groupConstrained bool, offset int, limit int, viewRestrictions *model.ViewUsersRestrictions) store.StoreChannel { return store.Do(func(result *store.StoreResult) { query := us.usersQuery. Join("TeamMembers tm ON ( tm.UserId = u.Id AND tm.DeleteAt = 0 AND tm.TeamId = ? )", teamId). @@ -647,6 +695,10 @@ func (us SqlUserStore) GetProfilesNotInChannel(teamId string, channelId string, query = applyViewRestrictionsFilter(query, viewRestrictions, true) + if groupConstrained { + query = applyChannelGroupConstrainedFilter(query, channelId) + } + queryString, args, err := query.ToSql() if err != nil { result.Err = model.NewAppError("SqlUserStore.GetProfilesNotInChannel", "store.sql_user.app_error", nil, err.Error(), http.StatusInternalServerError) @@ -1185,6 +1237,10 @@ func (us SqlUserStore) SearchNotInTeam(notInTeamId string, term string, options OrderBy("u.Username ASC"). Limit(uint64(options.Limit)) + if options.GroupConstrained { + query = applyTeamGroupConstrainedFilter(query, notInTeamId) + } + *result = us.performSearch(query, term, options) }) } @@ -1201,6 +1257,10 @@ func (us SqlUserStore) SearchNotInChannel(teamId string, channelId string, term query = query.Join("TeamMembers tm ON ( tm.UserId = u.Id AND tm.DeleteAt = 0 AND tm.TeamId = ? )", teamId) } + if options.GroupConstrained { + query = applyChannelGroupConstrainedFilter(query, channelId) + } + *result = us.performSearch(query, term, options) }) } @@ -1341,7 +1401,7 @@ func (us SqlUserStore) AnalyticsGetSystemAdminCount() store.StoreChannel { }) } -func (us SqlUserStore) GetProfilesNotInTeam(teamId string, offset int, limit int, viewRestrictions *model.ViewUsersRestrictions) store.StoreChannel { +func (us SqlUserStore) GetProfilesNotInTeam(teamId string, groupConstrained bool, offset int, limit int, viewRestrictions *model.ViewUsersRestrictions) store.StoreChannel { return store.Do(func(result *store.StoreResult) { query := us.usersQuery. LeftJoin("TeamMembers tm ON ( tm.UserId = u.Id AND tm.DeleteAt = 0 AND tm.TeamId = ? )", teamId). @@ -1351,6 +1411,10 @@ func (us SqlUserStore) GetProfilesNotInTeam(teamId string, offset int, limit int query = applyViewRestrictionsFilter(query, viewRestrictions, true) + if groupConstrained { + query = applyTeamGroupConstrainedFilter(query, teamId) + } + queryString, args, err := query.ToSql() if err != nil { result.Err = model.NewAppError("SqlUserStore.GetProfilesNotInTeam", "store.sql_user.app_error", nil, err.Error(), http.StatusInternalServerError) @@ -1551,23 +1615,7 @@ func (us SqlUserStore) GetUsersBatchForIndexing(startTime, endTime int64, limit func (us SqlUserStore) GetTeamGroupUsers(teamID string) store.StoreChannel { return store.Do(func(result *store.StoreResult) { - query := us.usersQuery. - Where(`Id IN ( - SELECT - GroupMembers.UserId - FROM - Teams - JOIN GroupTeams ON GroupTeams.TeamId = Teams.Id - JOIN UserGroups ON UserGroups.Id = GroupTeams.GroupId - JOIN GroupMembers ON GroupMembers.GroupId = UserGroups.Id - WHERE - Teams.Id = ? - AND GroupTeams.DeleteAt = 0 - AND UserGroups.DeleteAt = 0 - AND GroupMembers.DeleteAt = 0 - GROUP BY - GroupMembers.UserId - )`, teamID) + query := applyTeamGroupConstrainedFilter(us.usersQuery, teamID) queryString, args, err := query.ToSql() if err != nil { @@ -1591,23 +1639,7 @@ func (us SqlUserStore) GetTeamGroupUsers(teamID string) store.StoreChannel { func (us SqlUserStore) GetChannelGroupUsers(channelID string) store.StoreChannel { return store.Do(func(result *store.StoreResult) { - query := us.usersQuery. - Where(`Id IN ( - SELECT - GroupMembers.UserId - FROM - Channels - JOIN GroupChannels ON GroupChannels.ChannelId = Channels.Id - JOIN UserGroups ON UserGroups.Id = GroupChannels.GroupId - JOIN GroupMembers ON GroupMembers.GroupId = UserGroups.Id - WHERE - Channels.Id = ? - AND GroupChannels.DeleteAt = 0 - AND UserGroups.DeleteAt = 0 - AND GroupMembers.DeleteAt = 0 - GROUP BY - GroupMembers.UserId - )`, channelID) + query := applyChannelGroupConstrainedFilter(us.usersQuery, channelID) queryString, args, err := query.ToSql() if err != nil { diff --git a/store/store.go b/store/store.go index 462f79c0ad..20df6c5ad1 100644 --- a/store/store.go +++ b/store/store.go @@ -261,7 +261,7 @@ type UserStore interface { GetProfilesInChannel(channelId string, offset int, limit int) StoreChannel GetProfilesInChannelByStatus(channelId string, offset int, limit int) StoreChannel GetAllProfilesInChannel(channelId string, allowFromCache bool) StoreChannel - GetProfilesNotInChannel(teamId string, channelId string, offset int, limit int, viewRestrictions *model.ViewUsersRestrictions) StoreChannel + GetProfilesNotInChannel(teamId string, channelId string, groupConstrained bool, offset int, limit int, viewRestrictions *model.ViewUsersRestrictions) StoreChannel GetProfilesWithoutTeam(offset int, limit int, viewRestrictions *model.ViewUsersRestrictions) StoreChannel GetProfilesByUsernames(usernames []string, viewRestrictions *model.ViewUsersRestrictions) StoreChannel GetAllProfiles(options *model.UserGetOptions) StoreChannel @@ -292,7 +292,7 @@ type UserStore interface { SearchWithoutTeam(term string, options *model.UserSearchOptions) StoreChannel AnalyticsGetInactiveUsersCount() StoreChannel AnalyticsGetSystemAdminCount() StoreChannel - GetProfilesNotInTeam(teamId string, offset int, limit int, viewRestrictions *model.ViewUsersRestrictions) StoreChannel + GetProfilesNotInTeam(teamId string, groupConstrained bool, offset int, limit int, viewRestrictions *model.ViewUsersRestrictions) StoreChannel GetEtagForProfilesNotInTeam(teamId string) StoreChannel ClearAllCustomRoleAssignments() StoreChannel InferSystemInstallDate() StoreChannel diff --git a/store/storetest/mocks/UserStore.go b/store/storetest/mocks/UserStore.go index e2305ea8c3..d1a075224a 100644 --- a/store/storetest/mocks/UserStore.go +++ b/store/storetest/mocks/UserStore.go @@ -443,13 +443,13 @@ func (_m *UserStore) GetProfilesInChannelByStatus(channelId string, offset int, return r0 } -// GetProfilesNotInChannel provides a mock function with given fields: teamId, channelId, offset, limit, viewRestrictions -func (_m *UserStore) GetProfilesNotInChannel(teamId string, channelId string, offset int, limit int, viewRestrictions *model.ViewUsersRestrictions) store.StoreChannel { - ret := _m.Called(teamId, channelId, offset, limit, viewRestrictions) +// GetProfilesNotInChannel provides a mock function with given fields: teamId, channelId, groupConstrained, offset, limit, viewRestrictions +func (_m *UserStore) GetProfilesNotInChannel(teamId string, channelId string, groupConstrained bool, offset int, limit int, viewRestrictions *model.ViewUsersRestrictions) store.StoreChannel { + ret := _m.Called(teamId, channelId, groupConstrained, offset, limit, viewRestrictions) var r0 store.StoreChannel - if rf, ok := ret.Get(0).(func(string, string, int, int, *model.ViewUsersRestrictions) store.StoreChannel); ok { - r0 = rf(teamId, channelId, offset, limit, viewRestrictions) + if rf, ok := ret.Get(0).(func(string, string, bool, int, int, *model.ViewUsersRestrictions) store.StoreChannel); ok { + r0 = rf(teamId, channelId, groupConstrained, offset, limit, viewRestrictions) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(store.StoreChannel) @@ -459,13 +459,13 @@ func (_m *UserStore) GetProfilesNotInChannel(teamId string, channelId string, of return r0 } -// GetProfilesNotInTeam provides a mock function with given fields: teamId, offset, limit, viewRestrictions -func (_m *UserStore) GetProfilesNotInTeam(teamId string, offset int, limit int, viewRestrictions *model.ViewUsersRestrictions) store.StoreChannel { - ret := _m.Called(teamId, offset, limit, viewRestrictions) +// GetProfilesNotInTeam provides a mock function with given fields: teamId, groupConstrained, offset, limit, viewRestrictions +func (_m *UserStore) GetProfilesNotInTeam(teamId string, groupConstrained bool, offset int, limit int, viewRestrictions *model.ViewUsersRestrictions) store.StoreChannel { + ret := _m.Called(teamId, groupConstrained, offset, limit, viewRestrictions) var r0 store.StoreChannel - if rf, ok := ret.Get(0).(func(string, int, int, *model.ViewUsersRestrictions) store.StoreChannel); ok { - r0 = rf(teamId, offset, limit, viewRestrictions) + if rf, ok := ret.Get(0).(func(string, bool, int, int, *model.ViewUsersRestrictions) store.StoreChannel); ok { + r0 = rf(teamId, groupConstrained, offset, limit, viewRestrictions) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(store.StoreChannel) diff --git a/store/storetest/user_store.go b/store/storetest/user_store.go index ddc678f6d0..cf98d8aac8 100644 --- a/store/storetest/user_store.go +++ b/store/storetest/user_store.go @@ -1031,7 +1031,7 @@ func testUserStoreGetProfilesNotInChannel(t *testing.T, ss store.Store) { }, -1)).(*model.Channel) t.Run("get team 1, channel 1, offset 0, limit 100", func(t *testing.T) { - result := <-ss.User().GetProfilesNotInChannel(teamId, c1.Id, 0, 100, nil) + result := <-ss.User().GetProfilesNotInChannel(teamId, c1.Id, false, 0, 100, nil) require.Nil(t, result.Err) assert.Equal(t, []*model.User{ sanitized(u1), @@ -1041,7 +1041,7 @@ func testUserStoreGetProfilesNotInChannel(t *testing.T, ss store.Store) { }) t.Run("get team 1, channel 2, offset 0, limit 100", func(t *testing.T) { - result := <-ss.User().GetProfilesNotInChannel(teamId, c2.Id, 0, 100, nil) + result := <-ss.User().GetProfilesNotInChannel(teamId, c2.Id, false, 0, 100, nil) require.Nil(t, result.Err) assert.Equal(t, []*model.User{ sanitized(u1), @@ -1075,19 +1075,55 @@ func testUserStoreGetProfilesNotInChannel(t *testing.T, ss store.Store) { })) t.Run("get team 1, channel 1, offset 0, limit 100, after update", func(t *testing.T) { - result := <-ss.User().GetProfilesNotInChannel(teamId, c1.Id, 0, 100, nil) + result := <-ss.User().GetProfilesNotInChannel(teamId, c1.Id, false, 0, 100, nil) require.Nil(t, result.Err) assert.Equal(t, []*model.User{}, result.Data.([]*model.User)) }) t.Run("get team 1, channel 2, offset 0, limit 100, after update", func(t *testing.T) { - result := <-ss.User().GetProfilesNotInChannel(teamId, c2.Id, 0, 100, nil) + result := <-ss.User().GetProfilesNotInChannel(teamId, c2.Id, false, 0, 100, nil) require.Nil(t, result.Err) assert.Equal(t, []*model.User{ sanitized(u2), sanitized(u3), }, result.Data.([]*model.User)) }) + + t.Run("get team 1, channel 2, offset 0, limit 0, setting group constrained when it's not", func(t *testing.T) { + result := <-ss.User().GetProfilesNotInChannel(teamId, c2.Id, true, 0, 100, nil) + require.Nil(t, result.Err) + assert.Empty(t, result.Data.([]*model.User)) + }) + + // create a group + group := store.Must(ss.Group().Create(&model.Group{ + Name: "n_" + model.NewId(), + DisplayName: "dn_" + model.NewId(), + Source: model.GroupSourceLdap, + RemoteId: "ri_" + model.NewId(), + })).(*model.Group) + + // add two members to the group + for _, u := range []*model.User{u1, u2} { + res := <-ss.Group().CreateOrRestoreMember(group.Id, u.Id) + require.Nil(t, res.Err) + } + + // associate the group with the channel + res := <-ss.Group().CreateGroupSyncable(&model.GroupSyncable{ + GroupId: group.Id, + SyncableId: c2.Id, + Type: model.GroupSyncableTypeChannel, + }) + require.Nil(t, res.Err) + + t.Run("get team 1, channel 2, offset 0, limit 0, setting group constrained", func(t *testing.T) { + result := <-ss.User().GetProfilesNotInChannel(teamId, c2.Id, true, 0, 100, nil) + require.Nil(t, result.Err) + assert.Equal(t, []*model.User{ + sanitized(u2), + }, result.Data.([]*model.User)) + }) } func testUserStoreGetProfilesByIds(t *testing.T, ss store.Store) { @@ -3069,7 +3105,14 @@ func testUserStoreAnalyticsGetSystemAdminCount(t *testing.T, ss store.Store) { } func testUserStoreGetProfilesNotInTeam(t *testing.T, ss store.Store) { - teamId := model.NewId() + team, err := ss.Team().Save(&model.Team{ + DisplayName: "Team", + Name: model.NewId(), + Type: model.TEAM_OPEN, + }) + require.Nil(t, err) + + teamId := team.Id teamId2 := model.NewId() u1 := store.Must(ss.User().Save(&model.User{ @@ -3114,7 +3157,7 @@ func testUserStoreGetProfilesNotInTeam(t *testing.T, ss store.Store) { }) t.Run("get not in team 1, offset 0, limit 100000", func(t *testing.T) { - result := <-ss.User().GetProfilesNotInTeam(teamId, 0, 100000, nil) + result := <-ss.User().GetProfilesNotInTeam(teamId, false, 0, 100000, nil) require.Nil(t, result.Err) assert.Equal(t, []*model.User{ sanitized(u2), @@ -3123,7 +3166,7 @@ func testUserStoreGetProfilesNotInTeam(t *testing.T, ss store.Store) { }) t.Run("get not in team 1, offset 1, limit 1", func(t *testing.T) { - result := <-ss.User().GetProfilesNotInTeam(teamId, 1, 1, nil) + result := <-ss.User().GetProfilesNotInTeam(teamId, false, 1, 1, nil) require.Nil(t, result.Err) assert.Equal(t, []*model.User{ sanitized(u3), @@ -3131,7 +3174,7 @@ func testUserStoreGetProfilesNotInTeam(t *testing.T, ss store.Store) { }) t.Run("get not in team 2, offset 0, limit 100", func(t *testing.T) { - result := <-ss.User().GetProfilesNotInTeam(teamId2, 0, 100, nil) + result := <-ss.User().GetProfilesNotInTeam(teamId2, false, 0, 100, nil) require.Nil(t, result.Err) assert.Equal(t, []*model.User{ sanitized(u1), @@ -3154,7 +3197,7 @@ func testUserStoreGetProfilesNotInTeam(t *testing.T, ss store.Store) { }) t.Run("get not in team 1, offset 0, limit 100000 after update", func(t *testing.T) { - result := <-ss.User().GetProfilesNotInTeam(teamId, 0, 100000, nil) + result := <-ss.User().GetProfilesNotInTeam(teamId, false, 0, 100000, nil) require.Nil(t, result.Err) assert.Equal(t, []*model.User{ sanitized(u3), @@ -3178,7 +3221,7 @@ func testUserStoreGetProfilesNotInTeam(t *testing.T, ss store.Store) { }) t.Run("get not in team 1, offset 0, limit 100000 after second update", func(t *testing.T) { - result := <-ss.User().GetProfilesNotInTeam(teamId, 0, 100000, nil) + result := <-ss.User().GetProfilesNotInTeam(teamId, false, 0, 100000, nil) require.Nil(t, result.Err) assert.Equal(t, []*model.User{ sanitized(u1), @@ -3220,6 +3263,43 @@ func testUserStoreGetProfilesNotInTeam(t *testing.T, ss store.Store) { etag4 := result.Data.(string) require.Equal(t, etag3, etag4, "etag should not have changed") }) + + t.Run("get not in team 1, offset 0, limit 100000 after second update, setting group constrained when it's not", func(t *testing.T) { + result := <-ss.User().GetProfilesNotInTeam(teamId, true, 0, 100000, nil) + require.Nil(t, result.Err) + assert.Empty(t, result.Data.([]*model.User)) + }) + + // create a group + group := store.Must(ss.Group().Create(&model.Group{ + Name: "n_" + model.NewId(), + DisplayName: "dn_" + model.NewId(), + Source: model.GroupSourceLdap, + RemoteId: "ri_" + model.NewId(), + })).(*model.Group) + + // add two members to the group + for _, u := range []*model.User{u1, u2} { + res := <-ss.Group().CreateOrRestoreMember(group.Id, u.Id) + require.Nil(t, res.Err) + } + + // associate the group with the team + res := <-ss.Group().CreateGroupSyncable(&model.GroupSyncable{ + GroupId: group.Id, + SyncableId: teamId, + Type: model.GroupSyncableTypeTeam, + }) + require.Nil(t, res.Err) + + t.Run("get not in team 1, offset 0, limit 100000 after second update, setting group constrained", func(t *testing.T) { + result := <-ss.User().GetProfilesNotInTeam(teamId, true, 0, 100000, nil) + require.Nil(t, result.Err) + assert.Equal(t, []*model.User{ + sanitized(u1), + sanitized(u2), + }, result.Data.([]*model.User)) + }) } func testUserStoreClearAllCustomRoleAssignments(t *testing.T, ss store.Store) {