From 605040c59715be1b06671e9b62de8b5a10989d70 Mon Sep 17 00:00:00 2001 From: Martin Kraft Date: Fri, 10 Jan 2020 12:19:39 -0500 Subject: [PATCH] MM-20644: Add users to teams as a SchemeAdmin based on a new configuration field on GroupTeams and GroupChannels records. (#13361) * MM-20644: Add users to teams as a SchemeAdmin based on a new configuration field on GroupTeams and GroupChannels records. * MM-20644: Adds SchemeAdmin to mapping of the GroupSyncable struct fields. * MM-2064: Adds test to ensure SchemeAdmin field value is mapped. * MM-20644: Adds missing index creation for fresh DBs. * MM-20644: Duplicates UpdateMembersRole across Team and Channel stores. Adds tests. * MM-20644: Fixes some old method name references. * MM-20644: Moves variable declaration; removes Println statement. * MM-20644: Use a SQL query instead of two to update Team and Channel members. * MM-20644: Fixes tests; updates query. * MM-20644: Fix permission check for patching a group syncable. * MM-20644: Fixes test for change of permissions verification in group patch API request. * MM-20644: Fix for ORM select vs insert. * MM-20644: Linting fixes. * MM-20644: Fixes some tests. * MM-20644: Skips changing the role of guests. * MM-20644: Added syncableID filtering --- api4/group.go | 13 +- api4/group_test.go | 45 +- app/channel.go | 10 + app/channel_test.go | 52 ++ app/group.go | 19 +- app/group_test.go | 58 ++- app/team.go | 8 + app/team_test.go | 54 +- model/client4.go | 12 +- model/group.go | 5 + model/group_syncable.go | 17 +- store/sqlstore/channel_store.go | 22 + store/sqlstore/group_store.go | 59 ++- store/sqlstore/team_store.go | 22 + store/sqlstore/upgrade.go | 6 + store/store.go | 20 +- store/storetest/group_store.go | 689 +++++++++++++++++++++++++- store/storetest/mocks/ChannelStore.go | 16 + store/storetest/mocks/GroupStore.go | 66 ++- store/storetest/mocks/TeamStore.go | 16 + store/timer_layer.go | 68 ++- 21 files changed, 1207 insertions(+), 70 deletions(-) diff --git a/api4/group.go b/api4/group.go index f39045e74b..1dd3eabaa7 100644 --- a/api4/group.go +++ b/api4/group.go @@ -320,8 +320,9 @@ func patchGroupSyncable(c *Context, w http.ResponseWriter, r *http.Request) { return } - if !c.App.SessionHasPermissionTo(c.App.Session, model.PERMISSION_MANAGE_SYSTEM) { - c.SetPermissionError(model.PERMISSION_MANAGE_SYSTEM) + appErr := verifyLinkUnlinkPermission(c, syncableType, syncableID) + if appErr != nil { + c.Err = appErr return } @@ -492,8 +493,8 @@ func getGroupsByChannel(c *Context, w http.ResponseWriter, r *http.Request) { } b, marshalErr := json.Marshal(struct { - Groups []*model.Group `json:"groups"` - Count int `json:"total_group_count"` + Groups []*model.GroupWithSchemeAdmin `json:"groups"` + Count int `json:"total_group_count"` }{ Groups: groups, Count: totalCount, @@ -538,8 +539,8 @@ func getGroupsByTeam(c *Context, w http.ResponseWriter, r *http.Request) { } b, marshalErr := json.Marshal(struct { - Groups []*model.Group `json:"groups"` - Count int `json:"total_group_count"` + Groups []*model.GroupWithSchemeAdmin `json:"groups"` + Count int `json:"total_group_count"` }{ Groups: groups, Count: totalCount, diff --git a/api4/group_test.go b/api4/group_test.go index 1006a2edc3..bccd5cef98 100644 --- a/api4/group_test.go +++ b/api4/group_test.go @@ -591,9 +591,18 @@ func TestPatchGroupChannel(t *testing.T) { assert.NotNil(t, groupSyncable) assert.True(t, groupSyncable.AutoAdd) + role, err := th.App.GetRoleByName("channel_user") + require.Nil(t, err) + originalPermissions := role.Permissions + _, err = th.App.PatchRole(role, &model.RolePatch{Permissions: &[]string{}}) + require.Nil(t, err) + _, response = th.Client.PatchGroupSyncable(g.Id, th.BasicChannel.Id, model.GroupSyncableTypeChannel, patch) assert.Equal(t, http.StatusForbidden, response.StatusCode) + _, err = th.App.PatchRole(role, &model.RolePatch{Permissions: &originalPermissions}) + require.Nil(t, err) + th.App.SetLicense(nil) _, response = th.SystemAdminClient.PatchGroupSyncable(g.Id, th.BasicChannel.Id, model.GroupSyncableTypeChannel, patch) @@ -645,7 +654,7 @@ func TestGetGroupsByChannel(t *testing.T) { }) assert.Nil(t, err) - _, err = th.App.UpsertGroupSyncable(&model.GroupSyncable{ + groupSyncable, err := th.App.UpsertGroupSyncable(&model.GroupSyncable{ AutoAdd: true, SyncableId: th.BasicChannel.Id, Type: model.GroupSyncableTypeChannel, @@ -677,7 +686,21 @@ func TestGetGroupsByChannel(t *testing.T) { groups, _, response := th.SystemAdminClient.GetGroupsByChannel(th.BasicChannel.Id, opts) assert.Nil(t, response.Error) - assert.ElementsMatch(t, []*model.Group{group}, groups) + assert.ElementsMatch(t, []*model.GroupWithSchemeAdmin{{Group: *group, SchemeAdmin: model.NewBool(false)}}, groups) + require.NotNil(t, groups[0].SchemeAdmin) + require.False(t, *groups[0].SchemeAdmin) + + // set syncable to true + groupSyncable.SchemeAdmin = true + _, err = th.App.UpdateGroupSyncable(groupSyncable) + require.Nil(t, err) + + // ensure that SchemeAdmin field is updated + groups, _, response = th.SystemAdminClient.GetGroupsByChannel(th.BasicChannel.Id, opts) + assert.Nil(t, response.Error) + assert.ElementsMatch(t, []*model.GroupWithSchemeAdmin{{Group: *group, SchemeAdmin: model.NewBool(true)}}, groups) + require.NotNil(t, groups[0].SchemeAdmin) + require.True(t, *groups[0].SchemeAdmin) groups, _, response = th.SystemAdminClient.GetGroupsByChannel(model.NewId(), opts) assert.Equal(t, "store.sql_channel.get.existing.app_error", response.Error.Id) @@ -698,7 +721,7 @@ func TestGetGroupsByTeam(t *testing.T) { }) assert.Nil(t, err) - _, err = th.App.UpsertGroupSyncable(&model.GroupSyncable{ + groupSyncable, err := th.App.UpsertGroupSyncable(&model.GroupSyncable{ AutoAdd: true, SyncableId: th.BasicTeam.Id, Type: model.GroupSyncableTypeTeam, @@ -728,7 +751,21 @@ func TestGetGroupsByTeam(t *testing.T) { groups, _, response := th.SystemAdminClient.GetGroupsByTeam(th.BasicTeam.Id, opts) assert.Nil(t, response.Error) - assert.ElementsMatch(t, []*model.Group{group}, groups) + assert.ElementsMatch(t, []*model.GroupWithSchemeAdmin{{Group: *group, SchemeAdmin: model.NewBool(false)}}, groups) + require.NotNil(t, groups[0].SchemeAdmin) + require.False(t, *groups[0].SchemeAdmin) + + // set syncable to true + groupSyncable.SchemeAdmin = true + _, err = th.App.UpdateGroupSyncable(groupSyncable) + require.Nil(t, err) + + // ensure that SchemeAdmin field is updated + groups, _, response = th.SystemAdminClient.GetGroupsByTeam(th.BasicTeam.Id, opts) + assert.Nil(t, response.Error) + assert.ElementsMatch(t, []*model.GroupWithSchemeAdmin{{Group: *group, SchemeAdmin: model.NewBool(true)}}, groups) + require.NotNil(t, groups[0].SchemeAdmin) + require.True(t, *groups[0].SchemeAdmin) groups, _, response = th.SystemAdminClient.GetGroupsByTeam(model.NewId(), opts) assert.Nil(t, response.Error) diff --git a/app/channel.go b/app/channel.go index 180f839cfe..f5a840ab8d 100644 --- a/app/channel.go +++ b/app/channel.go @@ -971,6 +971,16 @@ func (a *App) addUserToChannel(user *model.User, channel *model.Channel, teamMem SchemeGuest: user.IsGuest(), SchemeUser: !user.IsGuest(), } + + if !user.IsGuest() { + var userShouldBeAdmin bool + userShouldBeAdmin, err = a.UserIsInAdminRoleGroup(user.Id, channel.Id, model.GroupSyncableTypeChannel) + if err != nil { + return nil, err + } + newMember.SchemeAdmin = userShouldBeAdmin + } + if _, err = a.Srv.Store.Channel().SaveMember(newMember); err != nil { mlog.Error("Failed to add member", mlog.String("user_id", user.Id), mlog.String("channel_id", channel.Id), mlog.Err(err)) return nil, model.NewAppError("AddUserToChannel", "api.channel.add_user.to.channel.failed.app_error", nil, "", http.StatusInternalServerError) diff --git a/app/channel_test.go b/app/channel_test.go index a0b3d46024..afc166bdfd 100644 --- a/app/channel_test.go +++ b/app/channel_test.go @@ -1161,3 +1161,55 @@ func TestMarkChannelAsUnreadFromPost(t *testing.T) { assert.Nil(t, response) }) } + +func TestAddUserToChannel(t *testing.T) { + th := Setup(t).InitBasic() + defer th.TearDown() + + user1 := model.User{Email: strings.ToLower(model.NewId()) + "success+test@example.com", Nickname: "Darth Vader", Username: "vader" + model.NewId(), Password: "passwd1", AuthService: ""} + ruser1, _ := th.App.CreateUser(&user1) + defer th.App.PermanentDeleteUser(&user1) + th.App.AddTeamMember(th.BasicTeam.Id, ruser1.Id) + + group := th.CreateGroup() + + _, err := th.App.UpsertGroupMember(group.Id, user1.Id) + require.Nil(t, err) + + gs, err := th.App.UpsertGroupSyncable(&model.GroupSyncable{ + AutoAdd: true, + SyncableId: th.BasicChannel.Id, + Type: model.GroupSyncableTypeChannel, + GroupId: group.Id, + SchemeAdmin: false, + }) + require.Nil(t, err) + + err = th.App.JoinChannel(th.BasicChannel, ruser1.Id) + require.Nil(t, err) + + // verify user was added as a non-admin + cm1, err := th.App.GetChannelMember(th.BasicChannel.Id, ruser1.Id) + require.Nil(t, err) + require.False(t, cm1.SchemeAdmin) + + user2 := model.User{Email: strings.ToLower(model.NewId()) + "success+test@example.com", Nickname: "Darth Vader", Username: "vader" + model.NewId(), Password: "passwd1", AuthService: ""} + ruser2, _ := th.App.CreateUser(&user2) + defer th.App.PermanentDeleteUser(&user2) + th.App.AddTeamMember(th.BasicTeam.Id, ruser2.Id) + + _, err = th.App.UpsertGroupMember(group.Id, user2.Id) + require.Nil(t, err) + + gs.SchemeAdmin = true + _, err = th.App.UpdateGroupSyncable(gs) + require.Nil(t, err) + + err = th.App.JoinChannel(th.BasicChannel, ruser2.Id) + require.Nil(t, err) + + // verify user was added as an admin + cm2, err := th.App.GetChannelMember(th.BasicChannel.Id, ruser2.Id) + require.Nil(t, err) + require.True(t, cm2.SchemeAdmin) +} diff --git a/app/group.go b/app/group.go index 0f22d0d76b..d7366c5c57 100644 --- a/app/group.go +++ b/app/group.go @@ -168,7 +168,7 @@ func (a *App) ChannelMembersToRemove() ([]*model.ChannelMember, *model.AppError) return a.Srv.Store.Group().ChannelMembersToRemove() } -func (a *App) GetGroupsByChannel(channelId string, opts model.GroupSearchOpts) ([]*model.Group, int, *model.AppError) { +func (a *App) GetGroupsByChannel(channelId string, opts model.GroupSearchOpts) ([]*model.GroupWithSchemeAdmin, int, *model.AppError) { groups, err := a.Srv.Store.Group().GetGroupsByChannel(channelId, opts) if err != nil { return nil, 0, err @@ -182,7 +182,7 @@ func (a *App) GetGroupsByChannel(channelId string, opts model.GroupSearchOpts) ( return groups, int(count), nil } -func (a *App) GetGroupsByTeam(teamId string, opts model.GroupSearchOpts) ([]*model.Group, int, *model.AppError) { +func (a *App) GetGroupsByTeam(teamId string, opts model.GroupSearchOpts) ([]*model.GroupWithSchemeAdmin, int, *model.AppError) { groups, err := a.Srv.Store.Group().GetGroupsByTeam(teamId, opts) if err != nil { return nil, 0, err @@ -313,3 +313,18 @@ func (a *App) ChannelMembersMinusGroupMembers(channelID string, groupIDs []strin } return users, totalCount, nil } + +// UserIsInAdminRoleGroup returns true at least one of the user's groups are configured to set the members as +// admins in the given syncable. +func (a *App) UserIsInAdminRoleGroup(userID, syncableID string, syncableType model.GroupSyncableType) (bool, *model.AppError) { + groupIDs, err := a.Srv.Store.Group().AdminRoleGroupsForSyncableMember(userID, syncableID, syncableType) + if err != nil { + return false, err + } + + if len(groupIDs) == 0 { + return false, nil + } + + return true, nil +} diff --git a/app/group_test.go b/app/group_test.go index 0a8ca75b66..fa928c32d9 100644 --- a/app/group_test.go +++ b/app/group_test.go @@ -235,7 +235,8 @@ func TestGetGroupsByChannel(t *testing.T) { groups, _, err := th.App.GetGroupsByChannel(th.BasicChannel.Id, opts) require.Nil(t, err) - require.ElementsMatch(t, []*model.Group{group}, groups) + require.ElementsMatch(t, []*model.GroupWithSchemeAdmin{{Group: *group, SchemeAdmin: model.NewBool(false)}}, groups) + require.NotNil(t, groups[0].SchemeAdmin) groups, _, err = th.App.GetGroupsByChannel(model.NewId(), opts) require.Nil(t, err) @@ -261,7 +262,8 @@ func TestGetGroupsByTeam(t *testing.T) { groups, _, err := th.App.GetGroupsByTeam(th.BasicTeam.Id, model.GroupSearchOpts{}) require.Nil(t, err) - require.ElementsMatch(t, []*model.Group{group}, groups) + require.ElementsMatch(t, []*model.GroupWithSchemeAdmin{{Group: *group, SchemeAdmin: model.NewBool(false)}}, groups) + require.NotNil(t, groups[0].SchemeAdmin) groups, _, err = th.App.GetGroupsByTeam(model.NewId(), model.GroupSearchOpts{}) require.Nil(t, err) @@ -277,3 +279,55 @@ func TestGetGroups(t *testing.T) { require.Nil(t, err) require.ElementsMatch(t, []*model.Group{group}, groups) } + +func TestUserIsInAdminRoleGroup(t *testing.T) { + th := Setup(t).InitBasic() + defer th.TearDown() + group1 := th.CreateGroup() + group2 := th.CreateGroup() + + g, err := th.App.UpsertGroupMember(group1.Id, th.BasicUser.Id) + require.Nil(t, err) + require.NotNil(t, g) + + g, err = th.App.UpsertGroupMember(group2.Id, th.BasicUser.Id) + require.Nil(t, err) + require.NotNil(t, g) + + _, err = th.App.UpsertGroupSyncable(&model.GroupSyncable{ + GroupId: group1.Id, + AutoAdd: false, + SyncableId: th.BasicTeam.Id, + Type: model.GroupSyncableTypeTeam, + }) + require.Nil(t, err) + + groupSyncable2, err := th.App.UpsertGroupSyncable(&model.GroupSyncable{ + GroupId: group2.Id, + AutoAdd: false, + SyncableId: th.BasicTeam.Id, + Type: model.GroupSyncableTypeTeam, + }) + require.Nil(t, err) + + // no syncables are set to scheme admin true, so this returns false + actual, err := th.App.UserIsInAdminRoleGroup(th.BasicUser.Id, th.BasicTeam.Id, model.GroupSyncableTypeTeam) + require.Nil(t, err) + require.False(t, actual) + + // set a syncable to be scheme admins + groupSyncable2.SchemeAdmin = true + _, err = th.App.UpdateGroupSyncable(groupSyncable2) + require.Nil(t, err) + + // a syncable is set to scheme admin true, so this returns true + actual, err = th.App.UserIsInAdminRoleGroup(th.BasicUser.Id, th.BasicTeam.Id, model.GroupSyncableTypeTeam) + require.Nil(t, err) + require.True(t, actual) + + // delete the syncable, should be false again + th.App.DeleteGroupSyncable(group2.Id, th.BasicTeam.Id, model.GroupSyncableTypeTeam) + actual, err = th.App.UserIsInAdminRoleGroup(th.BasicUser.Id, th.BasicTeam.Id, model.GroupSyncableTypeTeam) + require.Nil(t, err) + require.False(t, actual) +} diff --git a/app/team.go b/app/team.go index 341a392ee7..03fdcc0c0b 100644 --- a/app/team.go +++ b/app/team.go @@ -543,6 +543,14 @@ func (a *App) joinUserToTeam(team *model.Team, user *model.User) (*model.TeamMem SchemeUser: !user.IsGuest(), } + if !user.IsGuest() { + userShouldBeAdmin, err := a.UserIsInAdminRoleGroup(user.Id, team.Id, model.GroupSyncableTypeTeam) + if err != nil { + return nil, false, err + } + tm.SchemeAdmin = userShouldBeAdmin + } + if team.Email == user.Email { tm.SchemeAdmin = true } diff --git a/app/team_test.go b/app/team_test.go index 5fc63d0933..dac5da2496 100644 --- a/app/team_test.go +++ b/app/team_test.go @@ -639,7 +639,8 @@ func TestJoinUserToTeam(t *testing.T) { ruser, _ := th.App.CreateUser(&user) defer th.App.PermanentDeleteUser(&user) - _, alreadyAdded, err := th.App.joinUserToTeam(team, ruser) + var alreadyAdded bool + _, alreadyAdded, err = th.App.joinUserToTeam(team, ruser) require.False(t, alreadyAdded, "Should return already added equal to false") require.Nil(t, err, "Should return no error") }) @@ -651,7 +652,8 @@ func TestJoinUserToTeam(t *testing.T) { th.App.joinUserToTeam(team, ruser) - _, alreadyAdded, err := th.App.joinUserToTeam(team, ruser) + var alreadyAdded bool + _, alreadyAdded, err = th.App.joinUserToTeam(team, ruser) require.True(t, alreadyAdded, "Should return already added") require.Nil(t, err, "Should return no error") }) @@ -664,7 +666,8 @@ func TestJoinUserToTeam(t *testing.T) { th.App.joinUserToTeam(team, ruser) th.App.LeaveTeam(team, ruser, ruser.Id) - _, alreadyAdded, err := th.App.joinUserToTeam(team, ruser) + var alreadyAdded bool + _, alreadyAdded, err = th.App.joinUserToTeam(team, ruser) require.False(t, alreadyAdded, "Should return already added equal to false") require.Nil(t, err, "Should return no error") }) @@ -679,7 +682,7 @@ func TestJoinUserToTeam(t *testing.T) { defer th.App.PermanentDeleteUser(&user2) th.App.joinUserToTeam(team, ruser1) - _, _, err := th.App.joinUserToTeam(team, ruser2) + _, _, err = th.App.joinUserToTeam(team, ruser2) require.NotNil(t, err, "Should fail") }) @@ -697,9 +700,50 @@ func TestJoinUserToTeam(t *testing.T) { th.App.LeaveTeam(team, ruser1, ruser1.Id) th.App.joinUserToTeam(team, ruser2) - _, _, err := th.App.joinUserToTeam(team, ruser1) + _, _, err = th.App.joinUserToTeam(team, ruser1) require.NotNil(t, err, "Should fail") }) + + t.Run("new join with correct scheme_admin value from group syncable", func(t *testing.T) { + user1 := model.User{Email: strings.ToLower(model.NewId()) + "success+test@example.com", Nickname: "Darth Vader", Username: "vader" + model.NewId(), Password: "passwd1", AuthService: ""} + ruser1, _ := th.App.CreateUser(&user1) + defer th.App.PermanentDeleteUser(&user1) + + group := th.CreateGroup() + + _, err = th.App.UpsertGroupMember(group.Id, user1.Id) + require.Nil(t, err) + + gs, err := th.App.UpsertGroupSyncable(&model.GroupSyncable{ + AutoAdd: true, + SyncableId: team.Id, + Type: model.GroupSyncableTypeTeam, + GroupId: group.Id, + SchemeAdmin: false, + }) + require.Nil(t, err) + + th.App.UpdateConfig(func(cfg *model.Config) { cfg.TeamSettings.MaxUsersPerTeam = model.NewInt(999) }) + + tm1, _, err := th.App.joinUserToTeam(team, ruser1) + require.Nil(t, err) + require.False(t, tm1.SchemeAdmin) + + user2 := model.User{Email: strings.ToLower(model.NewId()) + "success+test@example.com", Nickname: "Darth Vader", Username: "vader" + model.NewId(), Password: "passwd1", AuthService: ""} + ruser2, _ := th.App.CreateUser(&user2) + defer th.App.PermanentDeleteUser(&user2) + + _, err = th.App.UpsertGroupMember(group.Id, user2.Id) + require.Nil(t, err) + + gs.SchemeAdmin = true + _, err = th.App.UpdateGroupSyncable(gs) + require.Nil(t, err) + + tm2, _, err := th.App.joinUserToTeam(team, ruser2) + require.Nil(t, err) + require.True(t, tm2.SchemeAdmin) + }) } func TestAppUpdateTeamScheme(t *testing.T) { diff --git a/model/client4.go b/model/client4.go index 9b2fdbc257..b322510ccc 100644 --- a/model/client4.go +++ b/model/client4.go @@ -3612,7 +3612,7 @@ func (c *Client4) UnlinkLdapGroup(dn string) (*Group, *Response) { } // GetGroupsByChannel retrieves the Mattermost Groups associated with a given channel -func (c *Client4) GetGroupsByChannel(channelId string, opts GroupSearchOpts) ([]*Group, int, *Response) { +func (c *Client4) GetGroupsByChannel(channelId string, opts GroupSearchOpts) ([]*GroupWithSchemeAdmin, int, *Response) { path := fmt.Sprintf("%s/groups?q=%v&include_member_count=%v", c.GetChannelRoute(channelId), opts.Q, opts.IncludeMemberCount) if opts.PageOpts != nil { path = fmt.Sprintf("%s&page=%v&per_page=%v", path, opts.PageOpts.Page, opts.PageOpts.PerPage) @@ -3624,8 +3624,8 @@ func (c *Client4) GetGroupsByChannel(channelId string, opts GroupSearchOpts) ([] defer closeBody(r) responseData := struct { - Groups []*Group `json:"groups"` - Count int `json:"total_group_count"` + Groups []*GroupWithSchemeAdmin `json:"groups"` + Count int `json:"total_group_count"` }{} if err := json.NewDecoder(r.Body).Decode(&responseData); err != nil { appErr := NewAppError("Api4.GetGroupsByChannel", "api.marshal_error", nil, err.Error(), http.StatusInternalServerError) @@ -3636,7 +3636,7 @@ func (c *Client4) GetGroupsByChannel(channelId string, opts GroupSearchOpts) ([] } // GetGroupsByTeam retrieves the Mattermost Groups associated with a given team -func (c *Client4) GetGroupsByTeam(teamId string, opts GroupSearchOpts) ([]*Group, int, *Response) { +func (c *Client4) GetGroupsByTeam(teamId string, opts GroupSearchOpts) ([]*GroupWithSchemeAdmin, int, *Response) { path := fmt.Sprintf("%s/groups?q=%v&include_member_count=%v", c.GetTeamRoute(teamId), opts.Q, opts.IncludeMemberCount) if opts.PageOpts != nil { path = fmt.Sprintf("%s&page=%v&per_page=%v", path, opts.PageOpts.Page, opts.PageOpts.PerPage) @@ -3648,8 +3648,8 @@ func (c *Client4) GetGroupsByTeam(teamId string, opts GroupSearchOpts) ([]*Group defer closeBody(r) responseData := struct { - Groups []*Group `json:"groups"` - Count int `json:"total_group_count"` + Groups []*GroupWithSchemeAdmin `json:"groups"` + Count int `json:"total_group_count"` }{} if err := json.NewDecoder(r.Body).Decode(&responseData); err != nil { appErr := NewAppError("Api4.GetGroupsByTeam", "api.marshal_error", nil, err.Error(), http.StatusInternalServerError) diff --git a/model/group.go b/model/group.go index 3143192b61..ec69c4041b 100644 --- a/model/group.go +++ b/model/group.go @@ -43,6 +43,11 @@ type Group struct { MemberCount *int `db:"-" json:"member_count,omitempty"` } +type GroupWithSchemeAdmin struct { + Group + SchemeAdmin *bool `db:"SyncableSchemeAdmin" json:"scheme_admin,omitempty"` +} + type GroupPatch struct { Name *string `json:"name"` DisplayName *string `json:"display_name"` diff --git a/model/group_syncable.go b/model/group_syncable.go index 227e00e32b..6a4d402371 100644 --- a/model/group_syncable.go +++ b/model/group_syncable.go @@ -29,11 +29,12 @@ type GroupSyncable struct { // TeamId. SyncableId string `db:"-" json:"-"` - AutoAdd bool `json:"auto_add"` - CreateAt int64 `json:"create_at"` - DeleteAt int64 `json:"delete_at"` - UpdateAt int64 `json:"update_at"` - Type GroupSyncableType `db:"-" json:"-"` + AutoAdd bool `json:"auto_add"` + SchemeAdmin bool `json:"scheme_admin"` + CreateAt int64 `json:"create_at"` + DeleteAt int64 `json:"delete_at"` + UpdateAt int64 `json:"update_at"` + Type GroupSyncableType `db:"-" json:"-"` // Values joined in from the associated team and/or channel ChannelDisplayName string `db:"-" json:"-"` @@ -123,13 +124,17 @@ func (syncable *GroupSyncable) MarshalJSON() ([]byte, error) { } type GroupSyncablePatch struct { - AutoAdd *bool `json:"auto_add"` + AutoAdd *bool `json:"auto_add"` + SchemeAdmin *bool `json:"scheme_admin"` } func (syncable *GroupSyncable) Patch(patch *GroupSyncablePatch) { if patch.AutoAdd != nil { syncable.AutoAdd = *patch.AutoAdd } + if patch.SchemeAdmin != nil { + syncable.SchemeAdmin = *patch.SchemeAdmin + } } type UserTeamIDPair struct { diff --git a/store/sqlstore/channel_store.go b/store/sqlstore/channel_store.go index 5af36cee85..f918df9998 100644 --- a/store/sqlstore/channel_store.go +++ b/store/sqlstore/channel_store.go @@ -2838,3 +2838,25 @@ func (s SqlChannelStore) UserBelongsToChannels(userId string, channelIds []strin } return c > 0, nil } + +func (s SqlChannelStore) UpdateMembersRole(channelID string, userIDs []string) *model.AppError { + sql := fmt.Sprintf(` + UPDATE + ChannelMembers + SET + SchemeAdmin = CASE WHEN UserId IN ('%s') THEN + TRUE + ELSE + FALSE + END + WHERE + ChannelId = :ChannelId + AND (SchemeGuest = false OR SchemeGuest IS NULL) + `, strings.Join(userIDs, "', '")) + + if _, err := s.GetMaster().Exec(sql, map[string]interface{}{"ChannelId": channelID}); err != nil { + return model.NewAppError("SqlChannelStore.UpdateMembersRole", "store.update_error", nil, err.Error(), http.StatusInternalServerError) + } + + return nil +} diff --git a/store/sqlstore/group_store.go b/store/sqlstore/group_store.go index 61b3ca6286..37350a8d00 100644 --- a/store/sqlstore/group_store.go +++ b/store/sqlstore/group_store.go @@ -86,6 +86,8 @@ func (s *SqlGroupStore) CreateIndexesIfNotExists() { s.CreateIndexIfNotExists("idx_groupchannels_channelid", "GroupChannels", "ChannelId") s.CreateColumnIfNotExistsNoDefault("Channels", "GroupConstrained", "tinyint(1)", "boolean") s.CreateColumnIfNotExistsNoDefault("Teams", "GroupConstrained", "tinyint(1)", "boolean") + s.CreateIndexIfNotExists("idx_groupteams_schemeadmin", "GroupTeams", "SchemeAdmin") + s.CreateIndexIfNotExists("idx_groupchannels_schemeadmin", "GroupChannels", "SchemeAdmin") } func (s *SqlGroupStore) Create(group *model.Group) (*model.Group, *model.AppError) { @@ -512,6 +514,7 @@ func (s *SqlGroupStore) GetAllGroupSyncablesByGroupId(groupID string, syncableTy Type: syncableType, TeamDisplayName: result.TeamDisplayName, TeamType: result.TeamType, + SchemeAdmin: result.SchemeAdmin, } groupSyncables = append(groupSyncables, groupSyncable) } @@ -550,6 +553,7 @@ func (s *SqlGroupStore) GetAllGroupSyncablesByGroupId(groupID string, syncableTy TeamDisplayName: result.TeamDisplayName, TeamType: result.TeamType, TeamID: result.TeamID, + SchemeAdmin: result.SchemeAdmin, } groupSyncables = append(groupSyncables, groupSyncable) } @@ -784,7 +788,7 @@ func (s *SqlGroupStore) CountGroupsByChannel(channelId string, opts model.GroupS return count, nil } -func (s *SqlGroupStore) GetGroupsByChannel(channelId string, opts model.GroupSearchOpts) ([]*model.Group, *model.AppError) { +func (s *SqlGroupStore) GetGroupsByChannel(channelId string, opts model.GroupSearchOpts) ([]*model.GroupWithSchemeAdmin, *model.AppError) { query := s.groupsBySyncableBaseQuery(model.GroupSyncableTypeChannel, selectGroups, channelId, opts) if opts.PageOpts != nil { @@ -797,7 +801,7 @@ func (s *SqlGroupStore) GetGroupsByChannel(channelId string, opts model.GroupSea return nil, model.NewAppError("SqlGroupStore.GetGroupsByChannel", "store.sql_group.app_error", nil, err.Error(), http.StatusInternalServerError) } - var groups []*model.Group + var groups []*model.GroupWithSchemeAdmin _, err = s.GetReplica().Select(&groups, queryString, args...) if err != nil { @@ -861,7 +865,7 @@ func (s *SqlGroupStore) ChannelMembersToRemove() ([]*model.ChannelMember, *model func (s *SqlGroupStore) groupsBySyncableBaseQuery(st model.GroupSyncableType, t selectType, syncableID string, opts model.GroupSearchOpts) sq.SelectBuilder { selectStrs := map[selectType]string{ - selectGroups: "ug.*", + selectGroups: "ug.*, gs.SchemeAdmin AS SyncableSchemeAdmin", selectCountGroups: "COUNT(*)", } @@ -883,7 +887,7 @@ func (s *SqlGroupStore) groupsBySyncableBaseQuery(st model.GroupSyncableType, t if opts.IncludeMemberCount && t == selectGroups { query = s.getQueryBuilder(). - Select("ug.*, coalesce(Members.MemberCount, 0) AS MemberCount"). + Select(fmt.Sprintf("ug.*, coalesce(Members.MemberCount, 0) AS MemberCount, Group%ss.SchemeAdmin AS SyncableSchemeAdmin", st)). From("UserGroups ug"). LeftJoin("(SELECT GroupMembers.GroupId, COUNT(*) AS MemberCount FROM GroupMembers LEFT JOIN Users ON Users.Id = GroupMembers.UserId WHERE GroupMembers.DeleteAt = 0 AND Users.DeleteAt = 0 GROUP BY GroupId) AS Members ON Members.GroupId = ug.Id"). LeftJoin(fmt.Sprintf("%[1]s ON %[1]s.GroupId = ug.Id", table)). @@ -919,7 +923,7 @@ func (s *SqlGroupStore) CountGroupsByTeam(teamId string, opts model.GroupSearchO return count, nil } -func (s *SqlGroupStore) GetGroupsByTeam(teamId string, opts model.GroupSearchOpts) ([]*model.Group, *model.AppError) { +func (s *SqlGroupStore) GetGroupsByTeam(teamId string, opts model.GroupSearchOpts) ([]*model.GroupWithSchemeAdmin, *model.AppError) { query := s.groupsBySyncableBaseQuery(model.GroupSyncableTypeTeam, selectGroups, teamId, opts) if opts.PageOpts != nil { @@ -932,7 +936,7 @@ func (s *SqlGroupStore) GetGroupsByTeam(teamId string, opts model.GroupSearchOpt return nil, model.NewAppError("SqlGroupStore.GetGroupsByTeam", "store.sql_group.app_error", nil, err.Error(), http.StatusInternalServerError) } - var groups []*model.Group + var groups []*model.GroupWithSchemeAdmin _, err = s.GetReplica().Select(&groups, queryString, args...) if err != nil { @@ -1164,3 +1168,46 @@ func (s *SqlGroupStore) CountChannelMembersMinusGroupMembers(channelID string, g return count, nil } + +func (s *SqlGroupStore) AdminRoleGroupsForSyncableMember(userID, syncableID string, syncableType model.GroupSyncableType) ([]string, *model.AppError) { + var groupIds []string + + sql := fmt.Sprintf(` + SELECT + GroupMembers.GroupId + FROM + GroupMembers + INNER JOIN + Group%[1]ss ON Group%[1]ss.GroupId = GroupMembers.GroupId + WHERE + GroupMembers.UserId = :UserId + AND GroupMembers.DeleteAt = 0 + AND %[1]sId = :%[1]sId + AND Group%[1]ss.DeleteAt = 0 + AND Group%[1]ss.SchemeAdmin = TRUE`, syncableType) + + _, err := s.GetReplica().Select(&groupIds, sql, map[string]interface{}{"UserId": userID, fmt.Sprintf("%sId", syncableType): syncableID}) + if err != nil { + return nil, model.NewAppError("SqlGroupStore AdminRoleGroupsForSyncableMember", "store.select_error", nil, err.Error(), http.StatusInternalServerError) + } + + return groupIds, nil +} + +func (s *SqlGroupStore) PermittedSyncableAdmins(syncableID string, syncableType model.GroupSyncableType) ([]string, *model.AppError) { + query := s.getQueryBuilder().Select("UserId"). + From(fmt.Sprintf("Group%ss", syncableType)). + Join(fmt.Sprintf("GroupMembers ON GroupMembers.GroupId = Group%ss.GroupId AND Group%[1]ss.SchemeAdmin = TRUE AND GroupMembers.DeleteAt = 0", syncableType.String())).Where(fmt.Sprintf("Group%[1]ss.%[1]sId = ?", syncableType.String()), syncableID) + + sql, args, err := query.ToSql() + if err != nil { + return nil, model.NewAppError("SqlGroupStore.PermittedSyncableAdmins", "store.sql_group.app_error", nil, err.Error(), http.StatusInternalServerError) + } + + var userIDs []string + if _, err = s.GetReplica().Select(&userIDs, sql, args...); err != nil { + return nil, model.NewAppError("SqlGroupStore.PermittedSyncableAdmins", "store.select_error", nil, err.Error(), http.StatusInternalServerError) + } + + return userIDs, nil +} diff --git a/store/sqlstore/team_store.go b/store/sqlstore/team_store.go index 2e7a676388..745e9e4f44 100644 --- a/store/sqlstore/team_store.go +++ b/store/sqlstore/team_store.go @@ -1080,6 +1080,28 @@ func (s SqlTeamStore) UserBelongsToTeams(userId string, teamIds []string) (bool, return c > 0, nil } +func (s SqlTeamStore) UpdateMembersRole(teamID string, userIDs []string) *model.AppError { + sql := fmt.Sprintf(` + UPDATE + TeamMembers + SET + SchemeAdmin = CASE WHEN UserId IN ('%s') THEN + TRUE + ELSE + FALSE + END + WHERE + TeamId = :TeamId + AND (SchemeGuest = false OR SchemeGuest IS NULL) + AND DeleteAt = 0`, strings.Join(userIDs, "', '")) + + if _, err := s.GetMaster().Exec(sql, map[string]interface{}{"TeamId": teamID}); err != nil { + return model.NewAppError("SqlTeamStore.UpdateMembersRole", "store.update_error", nil, err.Error(), http.StatusInternalServerError) + } + + return nil +} + func applyTeamMemberViewRestrictionsFilter(query sq.SelectBuilder, teamId string, restrictions *model.ViewUsersRestrictions) sq.SelectBuilder { if restrictions == nil { return query diff --git a/store/sqlstore/upgrade.go b/store/sqlstore/upgrade.go index 9a61726fb2..f78e402dc3 100644 --- a/store/sqlstore/upgrade.go +++ b/store/sqlstore/upgrade.go @@ -754,6 +754,12 @@ func upgradeDatabaseToVersion520(sqlStore SqlStore) { sqlStore.CreateColumnIfNotExistsNoDefault("Bots", "LastIconUpdate", "bigint", "bigint") + sqlStore.CreateColumnIfNotExists("GroupTeams", "SchemeAdmin", "boolean", "boolean", "0") + sqlStore.CreateIndexIfNotExists("idx_groupteams_schemeadmin", "GroupTeams", "SchemeAdmin") + + sqlStore.CreateColumnIfNotExists("GroupChannels", "SchemeAdmin", "boolean", "boolean", "0") + sqlStore.CreateIndexIfNotExists("idx_groupchannels_schemeadmin", "GroupChannels", "SchemeAdmin") + // saveSchemaVersion(sqlStore, VERSION_5_20_0) // } } diff --git a/store/store.go b/store/store.go index 92c96779ea..cb318ae807 100644 --- a/store/store.go +++ b/store/store.go @@ -106,6 +106,10 @@ type TeamStore interface { GetUserTeamIds(userId string, allowFromCache bool) ([]string, *model.AppError) InvalidateAllTeamIdsForUser(userId string) ClearCaches() + + // UpdateMembersRole sets all of the given team members to admins and all of the other members of the team to + // non-admin members. + UpdateMembersRole(teamID string, userIDs []string) *model.AppError } type ChannelStore interface { @@ -190,6 +194,10 @@ type ChannelStore interface { RemoveAllDeactivatedMembers(channelId string) *model.AppError GetChannelsBatchForIndexing(startTime, endTime int64, limit int) ([]*model.Channel, *model.AppError) UserBelongsToChannels(userId string, channelIds []string) (bool, *model.AppError) + + // UpdateMembersRole sets all of the given team members to admins and all of the other members of the team to + // non-admin members. + UpdateMembersRole(channelID string, userIDs []string) *model.AppError } type ChannelMemberHistoryStore interface { @@ -599,10 +607,10 @@ type GroupStore interface { TeamMembersToRemove() ([]*model.TeamMember, *model.AppError) ChannelMembersToRemove() ([]*model.ChannelMember, *model.AppError) - GetGroupsByChannel(channelId string, opts model.GroupSearchOpts) ([]*model.Group, *model.AppError) + GetGroupsByChannel(channelId string, opts model.GroupSearchOpts) ([]*model.GroupWithSchemeAdmin, *model.AppError) CountGroupsByChannel(channelId string, opts model.GroupSearchOpts) (int64, *model.AppError) - GetGroupsByTeam(teamId string, opts model.GroupSearchOpts) ([]*model.Group, *model.AppError) + GetGroupsByTeam(teamId string, opts model.GroupSearchOpts) ([]*model.GroupWithSchemeAdmin, *model.AppError) CountGroupsByTeam(teamId string, opts model.GroupSearchOpts) (int64, *model.AppError) GetGroups(page, perPage int, opts model.GroupSearchOpts) ([]*model.Group, *model.AppError) @@ -611,6 +619,14 @@ type GroupStore interface { CountTeamMembersMinusGroupMembers(teamID string, groupIDs []string) (int64, *model.AppError) ChannelMembersMinusGroupMembers(channelID string, groupIDs []string, page, perPage int) ([]*model.UserWithGroups, *model.AppError) CountChannelMembersMinusGroupMembers(channelID string, groupIDs []string) (int64, *model.AppError) + + // AdminRoleGroupsForSyncableMember returns the IDs of all of the groups that the user is a member of that are + // configured as SchemeAdmin: true for the given syncable. + AdminRoleGroupsForSyncableMember(userID, syncableID string, syncableType model.GroupSyncableType) ([]string, *model.AppError) + + // PermittedSyncableAdmins returns the IDs of all of the user who are permitted by the group syncable to have + // the admin role for the given syncable. + PermittedSyncableAdmins(syncableID string, syncableType model.GroupSyncableType) ([]string, *model.AppError) } type LinkMetadataStore interface { diff --git a/store/storetest/group_store.go b/store/storetest/group_store.go index 39157078dd..df48ceb70c 100644 --- a/store/storetest/group_store.go +++ b/store/storetest/group_store.go @@ -56,6 +56,13 @@ func TestGroupStore(t *testing.T, ss store.Store) { t.Run("ChannelMembersMinusGroupMembers", func(t *testing.T) { testChannelMembersMinusGroupMembers(t, ss) }) t.Run("GetMemberCount", func(t *testing.T) { groupTestGetMemberCount(t, ss) }) + + t.Run("AdminRoleGroupsForSyncableMember_Channel", func(t *testing.T) { groupTestAdminRoleGroupsForSyncableMemberChannel(t, ss) }) + t.Run("AdminRoleGroupsForSyncableMember_Team", func(t *testing.T) { groupTestAdminRoleGroupsForSyncableMemberTeam(t, ss) }) + t.Run("PermittedSyncableAdmins_Team", func(t *testing.T) { groupTestPermittedSyncableAdminsTeam(t, ss) }) + t.Run("PermittedSyncableAdmins_Channel", func(t *testing.T) { groupTestPermittedSyncableAdminsChannel(t, ss) }) + t.Run("UpdateMembersRole_Team", func(t *testing.T) { groupTestpUpdateMembersRoleTeam(t, ss) }) + t.Run("UpdateMembersRole_Channel", func(t *testing.T) { groupTestpUpdateMembersRoleChannel(t, ss) }) } func testGroupStoreCreate(t *testing.T, ss store.Store) { @@ -916,7 +923,9 @@ func testGetAllGroupSyncablesByGroup(t *testing.T, ss store.Store) { // create groupteam var groupTeam *model.GroupSyncable - groupTeam, err = ss.Group().CreateGroupSyncable(model.NewGroupTeam(group.Id, team.Id, false)) + gt := model.NewGroupTeam(group.Id, team.Id, false) + gt.SchemeAdmin = true + groupTeam, err = ss.Group().CreateGroupSyncable(gt) require.Nil(t, err) groupTeams = append(groupTeams, groupTeam) } @@ -929,6 +938,7 @@ func testGetAllGroupSyncablesByGroup(t *testing.T, ss store.Store) { present := false for _, dbGroupTeam := range d1 { if dbGroupTeam.GroupId == expectedGroupTeam.GroupId && dbGroupTeam.SyncableId == expectedGroupTeam.SyncableId { + require.True(t, dbGroupTeam.SchemeAdmin) present = true break } @@ -1720,6 +1730,7 @@ func testGetGroupsByChannel(t *testing.T, ss store.Store) { DisplayName: "group-1", RemoteId: model.NewId(), Source: model.GroupSourceLdap, + // SchemeAdmin: model.NewBool(false), }) require.Nil(t, err) @@ -1728,6 +1739,7 @@ func testGetGroupsByChannel(t *testing.T, ss store.Store) { DisplayName: "group-2", RemoteId: model.NewId(), Source: model.GroupSourceLdap, + // SchemeAdmin: model.NewBool(false), }) require.Nil(t, err) @@ -1758,6 +1770,7 @@ func testGetGroupsByChannel(t *testing.T, ss store.Store) { DisplayName: "group-3", RemoteId: model.NewId(), Source: model.GroupSourceLdap, + // SchemeAdmin: model.NewBool(false), }) require.Nil(t, err) @@ -1801,12 +1814,16 @@ func testGetGroupsByChannel(t *testing.T, ss store.Store) { group2WithMemberCount := *group2 group2WithMemberCount.MemberCount = model.NewInt(0) + group1WSA := &model.GroupWithSchemeAdmin{Group: *group1, SchemeAdmin: model.NewBool(false)} + group2WSA := &model.GroupWithSchemeAdmin{Group: *group2, SchemeAdmin: model.NewBool(false)} + group3WSA := &model.GroupWithSchemeAdmin{Group: *group3, SchemeAdmin: model.NewBool(false)} + testCases := []struct { Name string ChannelId string Page int PerPage int - Result []*model.Group + Result []*model.GroupWithSchemeAdmin Opts model.GroupSearchOpts TotalCount *int64 }{ @@ -1816,7 +1833,7 @@ func testGetGroupsByChannel(t *testing.T, ss store.Store) { Opts: model.GroupSearchOpts{}, Page: 0, PerPage: 60, - Result: []*model.Group{group1, group2}, + Result: []*model.GroupWithSchemeAdmin{group1WSA, group2WSA}, TotalCount: model.NewInt64(2), }, { @@ -1825,7 +1842,7 @@ func testGetGroupsByChannel(t *testing.T, ss store.Store) { Opts: model.GroupSearchOpts{}, Page: 0, PerPage: 1, - Result: []*model.Group{group1}, + Result: []*model.GroupWithSchemeAdmin{group1WSA}, }, { Name: "Get second Group for Channel1 with page 1 with 1 element", @@ -1833,7 +1850,7 @@ func testGetGroupsByChannel(t *testing.T, ss store.Store) { Opts: model.GroupSearchOpts{}, Page: 1, PerPage: 1, - Result: []*model.Group{group2}, + Result: []*model.GroupWithSchemeAdmin{group2WSA}, }, { Name: "Get third Group for Channel2", @@ -1841,7 +1858,7 @@ func testGetGroupsByChannel(t *testing.T, ss store.Store) { Opts: model.GroupSearchOpts{}, Page: 0, PerPage: 60, - Result: []*model.Group{group3}, + Result: []*model.GroupWithSchemeAdmin{group3WSA}, }, { Name: "Get empty Groups for a fake id", @@ -1849,7 +1866,7 @@ func testGetGroupsByChannel(t *testing.T, ss store.Store) { Opts: model.GroupSearchOpts{}, Page: 0, PerPage: 60, - Result: []*model.Group{}, + Result: []*model.GroupWithSchemeAdmin{}, TotalCount: model.NewInt64(0), }, { @@ -1858,7 +1875,7 @@ func testGetGroupsByChannel(t *testing.T, ss store.Store) { Opts: model.GroupSearchOpts{Q: string([]rune(group1.Name)[2:10])}, // very low change of a name collision Page: 0, PerPage: 100, - Result: []*model.Group{group1}, + Result: []*model.GroupWithSchemeAdmin{group1WSA}, TotalCount: model.NewInt64(1), }, { @@ -1867,7 +1884,7 @@ func testGetGroupsByChannel(t *testing.T, ss store.Store) { Opts: model.GroupSearchOpts{Q: "rouP-1"}, Page: 0, PerPage: 100, - Result: []*model.Group{group1}, + Result: []*model.GroupWithSchemeAdmin{group1WSA}, TotalCount: model.NewInt64(1), }, { @@ -1876,7 +1893,7 @@ func testGetGroupsByChannel(t *testing.T, ss store.Store) { Opts: model.GroupSearchOpts{Q: "roUp-"}, Page: 0, PerPage: 100, - Result: []*model.Group{group1, group2}, + Result: []*model.GroupWithSchemeAdmin{group1WSA, group2WSA}, TotalCount: model.NewInt64(2), }, { @@ -1885,7 +1902,10 @@ func testGetGroupsByChannel(t *testing.T, ss store.Store) { Opts: model.GroupSearchOpts{IncludeMemberCount: true}, Page: 0, PerPage: 2, - Result: []*model.Group{&group1WithMemberCount, &group2WithMemberCount}, + Result: []*model.GroupWithSchemeAdmin{ + {Group: group1WithMemberCount, SchemeAdmin: model.NewBool(false)}, + {Group: group2WithMemberCount, SchemeAdmin: model.NewBool(false)}, + }, }, } @@ -2015,13 +2035,17 @@ func testGetGroupsByTeam(t *testing.T, ss store.Store) { group2WithMemberCount := *group2 group2WithMemberCount.MemberCount = model.NewInt(0) + group1WSA := &model.GroupWithSchemeAdmin{Group: *group1, SchemeAdmin: model.NewBool(false)} + group2WSA := &model.GroupWithSchemeAdmin{Group: *group2, SchemeAdmin: model.NewBool(false)} + group3WSA := &model.GroupWithSchemeAdmin{Group: *group3, SchemeAdmin: model.NewBool(false)} + testCases := []struct { Name string TeamId string Page int PerPage int Opts model.GroupSearchOpts - Result []*model.Group + Result []*model.GroupWithSchemeAdmin TotalCount *int64 }{ { @@ -2030,7 +2054,7 @@ func testGetGroupsByTeam(t *testing.T, ss store.Store) { Opts: model.GroupSearchOpts{}, Page: 0, PerPage: 60, - Result: []*model.Group{group1, group2}, + Result: []*model.GroupWithSchemeAdmin{group1WSA, group2WSA}, TotalCount: model.NewInt64(2), }, { @@ -2039,7 +2063,7 @@ func testGetGroupsByTeam(t *testing.T, ss store.Store) { Opts: model.GroupSearchOpts{}, Page: 0, PerPage: 1, - Result: []*model.Group{group1}, + Result: []*model.GroupWithSchemeAdmin{group1WSA}, }, { Name: "Get second Group for Team1 with page 1 with 1 element", @@ -2047,7 +2071,7 @@ func testGetGroupsByTeam(t *testing.T, ss store.Store) { Opts: model.GroupSearchOpts{}, Page: 1, PerPage: 1, - Result: []*model.Group{group2}, + Result: []*model.GroupWithSchemeAdmin{group2WSA}, }, { Name: "Get third Group for Team2", @@ -2055,7 +2079,7 @@ func testGetGroupsByTeam(t *testing.T, ss store.Store) { Opts: model.GroupSearchOpts{}, Page: 0, PerPage: 60, - Result: []*model.Group{group3}, + Result: []*model.GroupWithSchemeAdmin{group3WSA}, TotalCount: model.NewInt64(1), }, { @@ -2064,7 +2088,7 @@ func testGetGroupsByTeam(t *testing.T, ss store.Store) { Opts: model.GroupSearchOpts{}, Page: 0, PerPage: 60, - Result: []*model.Group{}, + Result: []*model.GroupWithSchemeAdmin{}, TotalCount: model.NewInt64(0), }, { @@ -2073,7 +2097,7 @@ func testGetGroupsByTeam(t *testing.T, ss store.Store) { Opts: model.GroupSearchOpts{Q: string([]rune(group1.Name)[2:10])}, // very low change of a name collision Page: 0, PerPage: 100, - Result: []*model.Group{group1}, + Result: []*model.GroupWithSchemeAdmin{group1WSA}, TotalCount: model.NewInt64(1), }, { @@ -2082,7 +2106,7 @@ func testGetGroupsByTeam(t *testing.T, ss store.Store) { Opts: model.GroupSearchOpts{Q: "rouP-1"}, Page: 0, PerPage: 100, - Result: []*model.Group{group1}, + Result: []*model.GroupWithSchemeAdmin{group1WSA}, TotalCount: model.NewInt64(1), }, { @@ -2091,7 +2115,7 @@ func testGetGroupsByTeam(t *testing.T, ss store.Store) { Opts: model.GroupSearchOpts{Q: "roUp-"}, Page: 0, PerPage: 100, - Result: []*model.Group{group1, group2}, + Result: []*model.GroupWithSchemeAdmin{group1WSA, group2WSA}, TotalCount: model.NewInt64(2), }, { @@ -2100,7 +2124,10 @@ func testGetGroupsByTeam(t *testing.T, ss store.Store) { Opts: model.GroupSearchOpts{IncludeMemberCount: true}, Page: 0, PerPage: 2, - Result: []*model.Group{&group1WithMemberCount, &group2WithMemberCount}, + Result: []*model.GroupWithSchemeAdmin{ + {Group: group1WithMemberCount, SchemeAdmin: model.NewBool(false)}, + {Group: group2WithMemberCount, SchemeAdmin: model.NewBool(false)}, + }, }, } @@ -2760,3 +2787,623 @@ func groupTestGetMemberCount(t *testing.T, ss store.Store) { require.Nil(t, err) require.Equal(t, int64(1), count) } + +func groupTestAdminRoleGroupsForSyncableMemberChannel(t *testing.T, ss store.Store) { + user := &model.User{ + Email: MakeEmail(), + Username: model.NewId(), + } + user, err := ss.User().Save(user) + require.Nil(t, err) + + group1 := &model.Group{ + Name: model.NewId(), + DisplayName: model.NewId(), + Source: model.GroupSourceLdap, + Description: model.NewId(), + RemoteId: model.NewId(), + } + group1, err = ss.Group().Create(group1) + require.Nil(t, err) + + _, err = ss.Group().UpsertMember(group1.Id, user.Id) + require.Nil(t, err) + + group2 := &model.Group{ + Name: model.NewId(), + DisplayName: model.NewId(), + Source: model.GroupSourceLdap, + Description: model.NewId(), + RemoteId: model.NewId(), + } + group2, err = ss.Group().Create(group2) + require.Nil(t, err) + + _, err = ss.Group().UpsertMember(group2.Id, user.Id) + require.Nil(t, err) + + channel := &model.Channel{ + TeamId: model.NewId(), + DisplayName: "A Name", + Name: model.NewId(), + Type: model.CHANNEL_OPEN, + } + channel, err = ss.Channel().Save(channel, 9999) + require.Nil(t, err) + + _, err = ss.Group().CreateGroupSyncable(&model.GroupSyncable{ + AutoAdd: true, + SyncableId: channel.Id, + Type: model.GroupSyncableTypeChannel, + GroupId: group1.Id, + SchemeAdmin: true, + }) + require.Nil(t, err) + + groupSyncable2, err := ss.Group().CreateGroupSyncable(&model.GroupSyncable{ + AutoAdd: true, + SyncableId: channel.Id, + Type: model.GroupSyncableTypeChannel, + GroupId: group2.Id, + }) + require.Nil(t, err) + + // User is a member of both groups but only one is SchmeAdmin: true + actualGroupIDs, err := ss.Group().AdminRoleGroupsForSyncableMember(user.Id, channel.Id, model.GroupSyncableTypeChannel) + require.Nil(t, err) + require.ElementsMatch(t, []string{group1.Id}, actualGroupIDs) + + // Update the second group syncable to be SchemeAdmin: true and both groups should be returned + groupSyncable2.SchemeAdmin = true + _, err = ss.Group().UpdateGroupSyncable(groupSyncable2) + require.Nil(t, err) + actualGroupIDs, err = ss.Group().AdminRoleGroupsForSyncableMember(user.Id, channel.Id, model.GroupSyncableTypeChannel) + require.Nil(t, err) + require.ElementsMatch(t, []string{group1.Id, group2.Id}, actualGroupIDs) + + // Deleting membership from group should stop the group from being returned + _, err = ss.Group().DeleteMember(group1.Id, user.Id) + require.Nil(t, err) + actualGroupIDs, err = ss.Group().AdminRoleGroupsForSyncableMember(user.Id, channel.Id, model.GroupSyncableTypeChannel) + require.Nil(t, err) + require.ElementsMatch(t, []string{group2.Id}, actualGroupIDs) + + // Deleting group syncable should stop it being returned + _, err = ss.Group().DeleteGroupSyncable(group2.Id, channel.Id, model.GroupSyncableTypeChannel) + require.Nil(t, err) + actualGroupIDs, err = ss.Group().AdminRoleGroupsForSyncableMember(user.Id, channel.Id, model.GroupSyncableTypeChannel) + require.Nil(t, err) + require.ElementsMatch(t, []string{}, actualGroupIDs) +} + +func groupTestAdminRoleGroupsForSyncableMemberTeam(t *testing.T, ss store.Store) { + user := &model.User{ + Email: MakeEmail(), + Username: model.NewId(), + } + user, err := ss.User().Save(user) + require.Nil(t, err) + + group1 := &model.Group{ + Name: model.NewId(), + DisplayName: model.NewId(), + Source: model.GroupSourceLdap, + Description: model.NewId(), + RemoteId: model.NewId(), + } + group1, err = ss.Group().Create(group1) + require.Nil(t, err) + + _, err = ss.Group().UpsertMember(group1.Id, user.Id) + require.Nil(t, err) + + group2 := &model.Group{ + Name: model.NewId(), + DisplayName: model.NewId(), + Source: model.GroupSourceLdap, + Description: model.NewId(), + RemoteId: model.NewId(), + } + group2, err = ss.Group().Create(group2) + require.Nil(t, err) + + _, err = ss.Group().UpsertMember(group2.Id, user.Id) + require.Nil(t, err) + + team := &model.Team{ + DisplayName: "A Name", + Name: model.NewId(), + Type: model.CHANNEL_OPEN, + } + team, err = ss.Team().Save(team) + require.Nil(t, err) + + _, err = ss.Group().CreateGroupSyncable(&model.GroupSyncable{ + AutoAdd: true, + SyncableId: team.Id, + Type: model.GroupSyncableTypeTeam, + GroupId: group1.Id, + SchemeAdmin: true, + }) + require.Nil(t, err) + + groupSyncable2, err := ss.Group().CreateGroupSyncable(&model.GroupSyncable{ + AutoAdd: true, + SyncableId: team.Id, + Type: model.GroupSyncableTypeTeam, + GroupId: group2.Id, + }) + require.Nil(t, err) + + // User is a member of both groups but only one is SchmeAdmin: true + actualGroupIDs, err := ss.Group().AdminRoleGroupsForSyncableMember(user.Id, team.Id, model.GroupSyncableTypeTeam) + require.Nil(t, err) + require.ElementsMatch(t, []string{group1.Id}, actualGroupIDs) + + // Update the second group syncable to be SchemeAdmin: true and both groups should be returned + groupSyncable2.SchemeAdmin = true + _, err = ss.Group().UpdateGroupSyncable(groupSyncable2) + require.Nil(t, err) + actualGroupIDs, err = ss.Group().AdminRoleGroupsForSyncableMember(user.Id, team.Id, model.GroupSyncableTypeTeam) + require.Nil(t, err) + require.ElementsMatch(t, []string{group1.Id, group2.Id}, actualGroupIDs) + + // Deleting membership from group should stop the group from being returned + _, err = ss.Group().DeleteMember(group1.Id, user.Id) + require.Nil(t, err) + actualGroupIDs, err = ss.Group().AdminRoleGroupsForSyncableMember(user.Id, team.Id, model.GroupSyncableTypeTeam) + require.Nil(t, err) + require.ElementsMatch(t, []string{group2.Id}, actualGroupIDs) + + // Deleting group syncable should stop it being returned + _, err = ss.Group().DeleteGroupSyncable(group2.Id, team.Id, model.GroupSyncableTypeTeam) + require.Nil(t, err) + actualGroupIDs, err = ss.Group().AdminRoleGroupsForSyncableMember(user.Id, team.Id, model.GroupSyncableTypeTeam) + require.Nil(t, err) + require.ElementsMatch(t, []string{}, actualGroupIDs) +} + +func groupTestPermittedSyncableAdminsTeam(t *testing.T, ss store.Store) { + user1 := &model.User{ + Email: MakeEmail(), + Username: model.NewId(), + } + user1, err := ss.User().Save(user1) + require.Nil(t, err) + + user2 := &model.User{ + Email: MakeEmail(), + Username: model.NewId(), + } + user2, err = ss.User().Save(user2) + require.Nil(t, err) + + user3 := &model.User{ + Email: MakeEmail(), + Username: model.NewId(), + } + user3, err = ss.User().Save(user3) + require.Nil(t, err) + + group1 := &model.Group{ + Name: model.NewId(), + DisplayName: model.NewId(), + Source: model.GroupSourceLdap, + Description: model.NewId(), + RemoteId: model.NewId(), + } + group1, err = ss.Group().Create(group1) + require.Nil(t, err) + + _, err = ss.Group().UpsertMember(group1.Id, user1.Id) + require.Nil(t, err) + _, err = ss.Group().UpsertMember(group1.Id, user2.Id) + require.Nil(t, err) + + group2 := &model.Group{ + Name: model.NewId(), + DisplayName: model.NewId(), + Source: model.GroupSourceLdap, + Description: model.NewId(), + RemoteId: model.NewId(), + } + group2, err = ss.Group().Create(group2) + require.Nil(t, err) + + _, err = ss.Group().UpsertMember(group2.Id, user3.Id) + require.Nil(t, err) + + team := &model.Team{ + DisplayName: "A Name", + Name: model.NewId(), + Type: model.CHANNEL_OPEN, + } + team, err = ss.Team().Save(team) + require.Nil(t, err) + + _, err = ss.Group().CreateGroupSyncable(&model.GroupSyncable{ + AutoAdd: true, + SyncableId: team.Id, + Type: model.GroupSyncableTypeTeam, + GroupId: group1.Id, + SchemeAdmin: true, + }) + require.Nil(t, err) + + groupSyncable2, err := ss.Group().CreateGroupSyncable(&model.GroupSyncable{ + AutoAdd: true, + SyncableId: team.Id, + Type: model.GroupSyncableTypeTeam, + GroupId: group2.Id, + SchemeAdmin: false, + }) + require.Nil(t, err) + + // group 1's users are returned because groupsyncable 2 has SchemeAdmin false. + actualUserIDs, err := ss.Group().PermittedSyncableAdmins(team.Id, model.GroupSyncableTypeTeam) + require.Nil(t, err) + require.ElementsMatch(t, []string{user1.Id, user2.Id}, actualUserIDs) + + // update groupsyncable 2 to be SchemeAdmin true + groupSyncable2.SchemeAdmin = true + _, err = ss.Group().UpdateGroupSyncable(groupSyncable2) + require.Nil(t, err) + + // group 2's users are now included in return value + actualUserIDs, err = ss.Group().PermittedSyncableAdmins(team.Id, model.GroupSyncableTypeTeam) + require.Nil(t, err) + require.ElementsMatch(t, []string{user1.Id, user2.Id, user3.Id}, actualUserIDs) + + // deleted group member should not be included + ss.Group().DeleteMember(group1.Id, user2.Id) + require.Nil(t, err) + actualUserIDs, err = ss.Group().PermittedSyncableAdmins(team.Id, model.GroupSyncableTypeTeam) + require.Nil(t, err) + require.ElementsMatch(t, []string{user1.Id, user3.Id}, actualUserIDs) + + // deleted group syncable no longer includes group members + _, err = ss.Group().DeleteGroupSyncable(group1.Id, team.Id, model.GroupSyncableTypeTeam) + require.Nil(t, err) + actualUserIDs, err = ss.Group().PermittedSyncableAdmins(team.Id, model.GroupSyncableTypeTeam) + require.Nil(t, err) + require.ElementsMatch(t, []string{user3.Id}, actualUserIDs) +} + +func groupTestPermittedSyncableAdminsChannel(t *testing.T, ss store.Store) { + user1 := &model.User{ + Email: MakeEmail(), + Username: model.NewId(), + } + user1, err := ss.User().Save(user1) + require.Nil(t, err) + + user2 := &model.User{ + Email: MakeEmail(), + Username: model.NewId(), + } + user2, err = ss.User().Save(user2) + require.Nil(t, err) + + user3 := &model.User{ + Email: MakeEmail(), + Username: model.NewId(), + } + user3, err = ss.User().Save(user3) + require.Nil(t, err) + + group1 := &model.Group{ + Name: model.NewId(), + DisplayName: model.NewId(), + Source: model.GroupSourceLdap, + Description: model.NewId(), + RemoteId: model.NewId(), + } + group1, err = ss.Group().Create(group1) + require.Nil(t, err) + + _, err = ss.Group().UpsertMember(group1.Id, user1.Id) + require.Nil(t, err) + _, err = ss.Group().UpsertMember(group1.Id, user2.Id) + require.Nil(t, err) + + group2 := &model.Group{ + Name: model.NewId(), + DisplayName: model.NewId(), + Source: model.GroupSourceLdap, + Description: model.NewId(), + RemoteId: model.NewId(), + } + group2, err = ss.Group().Create(group2) + require.Nil(t, err) + + _, err = ss.Group().UpsertMember(group2.Id, user3.Id) + require.Nil(t, err) + + channel := &model.Channel{ + TeamId: model.NewId(), + DisplayName: "A Name", + Name: model.NewId(), + Type: model.CHANNEL_OPEN, + } + channel, err = ss.Channel().Save(channel, 9999) + require.Nil(t, err) + + _, err = ss.Group().CreateGroupSyncable(&model.GroupSyncable{ + AutoAdd: true, + SyncableId: channel.Id, + Type: model.GroupSyncableTypeChannel, + GroupId: group1.Id, + SchemeAdmin: true, + }) + require.Nil(t, err) + + groupSyncable2, err := ss.Group().CreateGroupSyncable(&model.GroupSyncable{ + AutoAdd: true, + SyncableId: channel.Id, + Type: model.GroupSyncableTypeChannel, + GroupId: group2.Id, + SchemeAdmin: false, + }) + require.Nil(t, err) + + // group 1's users are returned because groupsyncable 2 has SchemeAdmin false. + actualUserIDs, err := ss.Group().PermittedSyncableAdmins(channel.Id, model.GroupSyncableTypeChannel) + require.Nil(t, err) + require.ElementsMatch(t, []string{user1.Id, user2.Id}, actualUserIDs) + + // update groupsyncable 2 to be SchemeAdmin true + groupSyncable2.SchemeAdmin = true + _, err = ss.Group().UpdateGroupSyncable(groupSyncable2) + require.Nil(t, err) + + // group 2's users are now included in return value + actualUserIDs, err = ss.Group().PermittedSyncableAdmins(channel.Id, model.GroupSyncableTypeChannel) + require.Nil(t, err) + require.ElementsMatch(t, []string{user1.Id, user2.Id, user3.Id}, actualUserIDs) + + // deleted group member should not be included + ss.Group().DeleteMember(group1.Id, user2.Id) + require.Nil(t, err) + actualUserIDs, err = ss.Group().PermittedSyncableAdmins(channel.Id, model.GroupSyncableTypeChannel) + require.Nil(t, err) + require.ElementsMatch(t, []string{user1.Id, user3.Id}, actualUserIDs) + + // deleted group syncable no longer includes group members + _, err = ss.Group().DeleteGroupSyncable(group1.Id, channel.Id, model.GroupSyncableTypeChannel) + require.Nil(t, err) + actualUserIDs, err = ss.Group().PermittedSyncableAdmins(channel.Id, model.GroupSyncableTypeChannel) + require.Nil(t, err) + require.ElementsMatch(t, []string{user3.Id}, actualUserIDs) +} + +func groupTestpUpdateMembersRoleTeam(t *testing.T, ss store.Store) { + team := &model.Team{ + DisplayName: "Name", + Description: "Some description", + CompanyName: "Some company name", + AllowOpenInvite: false, + InviteId: "inviteid0", + Name: "z-z-" + model.NewId() + "a", + Email: "success+" + model.NewId() + "@simulator.amazonses.com", + Type: model.TEAM_OPEN, + } + team, err := ss.Team().Save(team) + require.Nil(t, err) + + user1 := &model.User{ + Email: MakeEmail(), + Username: model.NewId(), + } + user1, err = ss.User().Save(user1) + require.Nil(t, err) + + user2 := &model.User{ + Email: MakeEmail(), + Username: model.NewId(), + } + user2, err = ss.User().Save(user2) + require.Nil(t, err) + + user3 := &model.User{ + Email: MakeEmail(), + Username: model.NewId(), + } + user3, err = ss.User().Save(user3) + require.Nil(t, err) + + user4 := &model.User{ + Email: MakeEmail(), + Username: model.NewId(), + } + user4, err = ss.User().Save(user4) + require.Nil(t, err) + + for _, user := range []*model.User{user1, user2, user3} { + _, err = ss.Team().SaveMember(&model.TeamMember{TeamId: team.Id, UserId: user.Id}, 9999) + require.Nil(t, err) + } + + _, err = ss.Team().SaveMember(&model.TeamMember{TeamId: team.Id, UserId: user4.Id, SchemeGuest: true}, 9999) + require.Nil(t, err) + + tests := []struct { + testName string + inUserIDs []string + targetSchemeAdminValue bool + }{ + { + "Given users are admins", + []string{user1.Id, user2.Id}, + true, + }, + { + "Given users are members", + []string{user2.Id}, + false, + }, + { + "Non-given users are admins", + []string{user2.Id}, + false, + }, + { + "Non-given users are members", + []string{user2.Id}, + false, + }, + } + + includes := func(list []string, item string) bool { + for _, it := range list { + if it == item { + return true + } + } + return false + } + + for _, tt := range tests { + t.Run(tt.testName, func(t *testing.T) { + err = ss.Team().UpdateMembersRole(team.Id, tt.inUserIDs) + require.Nil(t, err) + + members, err := ss.Team().GetMembers(team.Id, 0, 100, nil) + require.Nil(t, err) + require.GreaterOrEqual(t, len(members), 4) // sanity check for team membership + + for _, member := range members { + if includes(tt.inUserIDs, member.UserId) { + require.True(t, member.SchemeAdmin) + } else { + require.False(t, member.SchemeAdmin) + } + + // Ensure guest account never changes. + if member.UserId == user4.Id { + require.False(t, member.SchemeUser) + require.False(t, member.SchemeAdmin) + require.True(t, member.SchemeGuest) + } + } + }) + } +} + +func groupTestpUpdateMembersRoleChannel(t *testing.T, ss store.Store) { + channel := &model.Channel{ + TeamId: model.NewId(), + DisplayName: "A Name", + Name: model.NewId(), + Type: model.CHANNEL_OPEN, // Query does not look at type so this shouldn't matter. + } + channel, err := ss.Channel().Save(channel, 9999) + require.Nil(t, err) + + user1 := &model.User{ + Email: MakeEmail(), + Username: model.NewId(), + } + user1, err = ss.User().Save(user1) + require.Nil(t, err) + + user2 := &model.User{ + Email: MakeEmail(), + Username: model.NewId(), + } + user2, err = ss.User().Save(user2) + require.Nil(t, err) + + user3 := &model.User{ + Email: MakeEmail(), + Username: model.NewId(), + } + user3, err = ss.User().Save(user3) + require.Nil(t, err) + + user4 := &model.User{ + Email: MakeEmail(), + Username: model.NewId(), + } + user4, err = ss.User().Save(user4) + require.Nil(t, err) + + for _, user := range []*model.User{user1, user2, user3} { + _, err = ss.Channel().SaveMember(&model.ChannelMember{ + ChannelId: channel.Id, + UserId: user.Id, + NotifyProps: model.GetDefaultChannelNotifyProps(), + }) + require.Nil(t, err) + } + + _, err = ss.Channel().SaveMember(&model.ChannelMember{ + ChannelId: channel.Id, + UserId: user4.Id, + NotifyProps: model.GetDefaultChannelNotifyProps(), + SchemeGuest: true, + }) + require.Nil(t, err) + + tests := []struct { + testName string + inUserIDs []string + targetSchemeAdminValue bool + }{ + { + "Given users are admins", + []string{user1.Id, user2.Id}, + true, + }, + { + "Given users are members", + []string{user2.Id}, + false, + }, + { + "Non-given users are admins", + []string{user2.Id}, + false, + }, + { + "Non-given users are members", + []string{user2.Id}, + false, + }, + } + + includes := func(list []string, item string) bool { + for _, it := range list { + if it == item { + return true + } + } + return false + } + + for _, tt := range tests { + t.Run(tt.testName, func(t *testing.T) { + err = ss.Channel().UpdateMembersRole(channel.Id, tt.inUserIDs) + require.Nil(t, err) + + members, err := ss.Channel().GetMembers(channel.Id, 0, 100) + require.Nil(t, err) + + require.GreaterOrEqual(t, len(*members), 4) // sanity check for channel membership + + for _, member := range *members { + if includes(tt.inUserIDs, member.UserId) { + require.True(t, member.SchemeAdmin) + } else { + require.False(t, member.SchemeAdmin) + } + + // Ensure guest account never changes. + if member.UserId == user4.Id { + require.False(t, member.SchemeUser) + require.False(t, member.SchemeAdmin) + require.True(t, member.SchemeGuest) + } + } + }) + } +} diff --git a/store/storetest/mocks/ChannelStore.go b/store/storetest/mocks/ChannelStore.go index f7106cec6d..d28537fc71 100644 --- a/store/storetest/mocks/ChannelStore.go +++ b/store/storetest/mocks/ChannelStore.go @@ -1707,6 +1707,22 @@ func (_m *ChannelStore) UpdateMember(member *model.ChannelMember) (*model.Channe return r0, r1 } +// UpdateMembersRole provides a mock function with given fields: channelID, userIDs +func (_m *ChannelStore) UpdateMembersRole(channelID string, userIDs []string) *model.AppError { + ret := _m.Called(channelID, userIDs) + + var r0 *model.AppError + if rf, ok := ret.Get(0).(func(string, []string) *model.AppError); ok { + r0 = rf(channelID, userIDs) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*model.AppError) + } + } + + return r0 +} + // UserBelongsToChannels provides a mock function with given fields: userId, channelIds func (_m *ChannelStore) UserBelongsToChannels(userId string, channelIds []string) (bool, *model.AppError) { ret := _m.Called(userId, channelIds) diff --git a/store/storetest/mocks/GroupStore.go b/store/storetest/mocks/GroupStore.go index fbb30bb811..30055d39da 100644 --- a/store/storetest/mocks/GroupStore.go +++ b/store/storetest/mocks/GroupStore.go @@ -14,6 +14,31 @@ type GroupStore struct { mock.Mock } +// AdminRoleGroupsForSyncableMember provides a mock function with given fields: userID, syncableID, syncableType +func (_m *GroupStore) AdminRoleGroupsForSyncableMember(userID string, syncableID string, syncableType model.GroupSyncableType) ([]string, *model.AppError) { + ret := _m.Called(userID, syncableID, syncableType) + + var r0 []string + if rf, ok := ret.Get(0).(func(string, string, model.GroupSyncableType) []string); ok { + r0 = rf(userID, syncableID, syncableType) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]string) + } + } + + var r1 *model.AppError + if rf, ok := ret.Get(1).(func(string, string, model.GroupSyncableType) *model.AppError); ok { + r1 = rf(userID, syncableID, syncableType) + } else { + if ret.Get(1) != nil { + r1 = ret.Get(1).(*model.AppError) + } + } + + return r0, r1 +} + // ChannelMembersMinusGroupMembers provides a mock function with given fields: channelID, groupIDs, page, perPage func (_m *GroupStore) ChannelMembersMinusGroupMembers(channelID string, groupIDs []string, page int, perPage int) ([]*model.UserWithGroups, *model.AppError) { ret := _m.Called(channelID, groupIDs, page, perPage) @@ -532,15 +557,15 @@ func (_m *GroupStore) GetGroups(page int, perPage int, opts model.GroupSearchOpt } // GetGroupsByChannel provides a mock function with given fields: channelId, opts -func (_m *GroupStore) GetGroupsByChannel(channelId string, opts model.GroupSearchOpts) ([]*model.Group, *model.AppError) { +func (_m *GroupStore) GetGroupsByChannel(channelId string, opts model.GroupSearchOpts) ([]*model.GroupWithSchemeAdmin, *model.AppError) { ret := _m.Called(channelId, opts) - var r0 []*model.Group - if rf, ok := ret.Get(0).(func(string, model.GroupSearchOpts) []*model.Group); ok { + var r0 []*model.GroupWithSchemeAdmin + if rf, ok := ret.Get(0).(func(string, model.GroupSearchOpts) []*model.GroupWithSchemeAdmin); ok { r0 = rf(channelId, opts) } else { if ret.Get(0) != nil { - r0 = ret.Get(0).([]*model.Group) + r0 = ret.Get(0).([]*model.GroupWithSchemeAdmin) } } @@ -557,15 +582,15 @@ func (_m *GroupStore) GetGroupsByChannel(channelId string, opts model.GroupSearc } // GetGroupsByTeam provides a mock function with given fields: teamId, opts -func (_m *GroupStore) GetGroupsByTeam(teamId string, opts model.GroupSearchOpts) ([]*model.Group, *model.AppError) { +func (_m *GroupStore) GetGroupsByTeam(teamId string, opts model.GroupSearchOpts) ([]*model.GroupWithSchemeAdmin, *model.AppError) { ret := _m.Called(teamId, opts) - var r0 []*model.Group - if rf, ok := ret.Get(0).(func(string, model.GroupSearchOpts) []*model.Group); ok { + var r0 []*model.GroupWithSchemeAdmin + if rf, ok := ret.Get(0).(func(string, model.GroupSearchOpts) []*model.GroupWithSchemeAdmin); ok { r0 = rf(teamId, opts) } else { if ret.Get(0) != nil { - r0 = ret.Get(0).([]*model.Group) + r0 = ret.Get(0).([]*model.GroupWithSchemeAdmin) } } @@ -670,6 +695,31 @@ func (_m *GroupStore) PermanentDeleteMembersByUser(userId string) *model.AppErro return r0 } +// PermittedSyncableAdmins provides a mock function with given fields: syncableID, syncableType +func (_m *GroupStore) PermittedSyncableAdmins(syncableID string, syncableType model.GroupSyncableType) ([]string, *model.AppError) { + ret := _m.Called(syncableID, syncableType) + + var r0 []string + if rf, ok := ret.Get(0).(func(string, model.GroupSyncableType) []string); ok { + r0 = rf(syncableID, syncableType) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]string) + } + } + + var r1 *model.AppError + if rf, ok := ret.Get(1).(func(string, model.GroupSyncableType) *model.AppError); ok { + r1 = rf(syncableID, syncableType) + } else { + if ret.Get(1) != nil { + r1 = ret.Get(1).(*model.AppError) + } + } + + return r0, r1 +} + // TeamMembersMinusGroupMembers provides a mock function with given fields: teamID, groupIDs, page, perPage func (_m *GroupStore) TeamMembersMinusGroupMembers(teamID string, groupIDs []string, page int, perPage int) ([]*model.UserWithGroups, *model.AppError) { ret := _m.Called(teamID, groupIDs, page, perPage) diff --git a/store/storetest/mocks/TeamStore.go b/store/storetest/mocks/TeamStore.go index 0b2a7b2877..ea0e128f7d 100644 --- a/store/storetest/mocks/TeamStore.go +++ b/store/storetest/mocks/TeamStore.go @@ -1056,6 +1056,22 @@ func (_m *TeamStore) UpdateMember(member *model.TeamMember) (*model.TeamMember, return r0, r1 } +// UpdateMembersRole provides a mock function with given fields: teamID, userIDs +func (_m *TeamStore) UpdateMembersRole(teamID string, userIDs []string) *model.AppError { + ret := _m.Called(teamID, userIDs) + + var r0 *model.AppError + if rf, ok := ret.Get(0).(func(string, []string) *model.AppError); ok { + r0 = rf(teamID, userIDs) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*model.AppError) + } + } + + return r0 +} + // UserBelongsToTeams provides a mock function with given fields: userId, teamIds func (_m *TeamStore) UserBelongsToTeams(userId string, teamIds []string) (bool, *model.AppError) { ret := _m.Called(userId, teamIds) diff --git a/store/timer_layer.go b/store/timer_layer.go index 0c3cdeba89..4626d4a8e9 100644 --- a/store/timer_layer.go +++ b/store/timer_layer.go @@ -1736,6 +1736,22 @@ func (s *TimerLayerChannelStore) UpdateMember(member *model.ChannelMember) (*mod return resultVar0, resultVar1 } +func (s *TimerLayerChannelStore) UpdateMembersRole(channelID string, userIDs []string) *model.AppError { + start := timemodule.Now() + + resultVar0 := s.ChannelStore.UpdateMembersRole(channelID, userIDs) + + elapsed := float64(timemodule.Since(start)) / float64(timemodule.Second) + if s.Root.Metrics != nil { + success := "false" + if resultVar0 == nil { + success = "true" + } + s.Root.Metrics.ObserveStoreMethodDuration("ChannelStore.UpdateMembersRole", success, elapsed) + } + return resultVar0 +} + func (s *TimerLayerChannelStore) UserBelongsToChannels(userId string, channelIds []string) (bool, *model.AppError) { start := timemodule.Now() @@ -2520,6 +2536,22 @@ func (s *TimerLayerFileInfoStore) Save(info *model.FileInfo) (*model.FileInfo, * return resultVar0, resultVar1 } +func (s *TimerLayerGroupStore) AdminRoleGroupsForSyncableMember(userID string, syncableID string, syncableType model.GroupSyncableType) ([]string, *model.AppError) { + start := timemodule.Now() + + resultVar0, resultVar1 := s.GroupStore.AdminRoleGroupsForSyncableMember(userID, syncableID, syncableType) + + elapsed := float64(timemodule.Since(start)) / float64(timemodule.Second) + if s.Root.Metrics != nil { + success := "false" + if resultVar1 == nil { + success = "true" + } + s.Root.Metrics.ObserveStoreMethodDuration("GroupStore.AdminRoleGroupsForSyncableMember", success, elapsed) + } + return resultVar0, resultVar1 +} + func (s *TimerLayerGroupStore) ChannelMembersMinusGroupMembers(channelID string, groupIDs []string, page int, perPage int) ([]*model.UserWithGroups, *model.AppError) { start := timemodule.Now() @@ -2856,7 +2888,7 @@ func (s *TimerLayerGroupStore) GetGroups(page int, perPage int, opts model.Group return resultVar0, resultVar1 } -func (s *TimerLayerGroupStore) GetGroupsByChannel(channelId string, opts model.GroupSearchOpts) ([]*model.Group, *model.AppError) { +func (s *TimerLayerGroupStore) GetGroupsByChannel(channelId string, opts model.GroupSearchOpts) ([]*model.GroupWithSchemeAdmin, *model.AppError) { start := timemodule.Now() resultVar0, resultVar1 := s.GroupStore.GetGroupsByChannel(channelId, opts) @@ -2872,7 +2904,7 @@ func (s *TimerLayerGroupStore) GetGroupsByChannel(channelId string, opts model.G return resultVar0, resultVar1 } -func (s *TimerLayerGroupStore) GetGroupsByTeam(teamId string, opts model.GroupSearchOpts) ([]*model.Group, *model.AppError) { +func (s *TimerLayerGroupStore) GetGroupsByTeam(teamId string, opts model.GroupSearchOpts) ([]*model.GroupWithSchemeAdmin, *model.AppError) { start := timemodule.Now() resultVar0, resultVar1 := s.GroupStore.GetGroupsByTeam(teamId, opts) @@ -2952,6 +2984,22 @@ func (s *TimerLayerGroupStore) PermanentDeleteMembersByUser(userId string) *mode return resultVar0 } +func (s *TimerLayerGroupStore) PermittedSyncableAdmins(syncableID string, syncableType model.GroupSyncableType) ([]string, *model.AppError) { + start := timemodule.Now() + + resultVar0, resultVar1 := s.GroupStore.PermittedSyncableAdmins(syncableID, syncableType) + + elapsed := float64(timemodule.Since(start)) / float64(timemodule.Second) + if s.Root.Metrics != nil { + success := "false" + if resultVar1 == nil { + success = "true" + } + s.Root.Metrics.ObserveStoreMethodDuration("GroupStore.PermittedSyncableAdmins", success, elapsed) + } + return resultVar0, resultVar1 +} + func (s *TimerLayerGroupStore) TeamMembersMinusGroupMembers(teamID string, groupIDs []string, page int, perPage int) ([]*model.UserWithGroups, *model.AppError) { start := timemodule.Now() @@ -5880,6 +5928,22 @@ func (s *TimerLayerTeamStore) UpdateMember(member *model.TeamMember) (*model.Tea return resultVar0, resultVar1 } +func (s *TimerLayerTeamStore) UpdateMembersRole(teamID string, userIDs []string) *model.AppError { + start := timemodule.Now() + + resultVar0 := s.TeamStore.UpdateMembersRole(teamID, userIDs) + + elapsed := float64(timemodule.Since(start)) / float64(timemodule.Second) + if s.Root.Metrics != nil { + success := "false" + if resultVar0 == nil { + success = "true" + } + s.Root.Metrics.ObserveStoreMethodDuration("TeamStore.UpdateMembersRole", success, elapsed) + } + return resultVar0 +} + func (s *TimerLayerTeamStore) UserBelongsToTeams(userId string, teamIds []string) (bool, *model.AppError) { start := timemodule.Now()