From 2c0068a288248c26337cbc21eae48275ab23ce58 Mon Sep 17 00:00:00 2001 From: Miguel de la Cruz Date: Fri, 10 May 2019 15:53:01 +0100 Subject: [PATCH] [MM-15164] Add group commands (#10812) --- cmd/mattermost/commands/group.go | 305 ++++++++++++++++++++ cmd/mattermost/commands/group_test.go | 391 ++++++++++++++++++++++++++ store/sqlstore/group_supplier.go | 4 + 3 files changed, 700 insertions(+) create mode 100644 cmd/mattermost/commands/group.go create mode 100644 cmd/mattermost/commands/group_test.go diff --git a/cmd/mattermost/commands/group.go b/cmd/mattermost/commands/group.go new file mode 100644 index 0000000000..f220f0d66c --- /dev/null +++ b/cmd/mattermost/commands/group.go @@ -0,0 +1,305 @@ +// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package commands + +import ( + "fmt" + + "github.com/mattermost/mattermost-server/model" + + "github.com/pkg/errors" + "github.com/spf13/cobra" +) + +var GroupCmd = &cobra.Command{ + Use: "group", + Short: "Management of groups", +} + +var ChannelGroupCmd = &cobra.Command{ + Use: "channel", + Short: "Management of channel groups", +} + +var ChannelGroupEnableCmd = &cobra.Command{ + Use: "enable [team]:[channel]", + Short: "Enables group constraint on the specified channel", + Example: " group channel enable myteam:mychannel", + Args: cobra.ExactArgs(1), + RunE: channelGroupEnableCmdF, +} + +var ChannelGroupDisableCmd = &cobra.Command{ + Use: "disable [team]:[channel]", + Short: "Disables group constraint on the specified channel", + Example: " group channel disable myteam:mychannel", + Args: cobra.ExactArgs(1), + RunE: channelGroupDisableCmdF, +} + +var ChannelGroupStatusCmd = &cobra.Command{ + Use: "status [team]:[channel]", + Short: "Shows the group constraint status of the specified channel", + Example: " group channel status myteam:mychannel", + Args: cobra.ExactArgs(1), + RunE: channelGroupStatusCmdF, +} + +var ChannelGroupListCmd = &cobra.Command{ + Use: "list [team]:[channel]", + Short: "List channel groups", + Long: "Lists the groups associated with a channel", + Example: " group channel list myteam:mychannel", + Args: cobra.ExactArgs(1), + RunE: channelGroupListCmdF, +} + +var TeamGroupCmd = &cobra.Command{ + Use: "team", + Short: "Management of team groups", +} + +var TeamGroupEnableCmd = &cobra.Command{ + Use: "enable [team]", + Short: "Enables group constraint on the specified team", + Example: " group team enable myteam", + Args: cobra.ExactArgs(1), + RunE: teamGroupEnableCmdF, +} + +var TeamGroupDisableCmd = &cobra.Command{ + Use: "disable [team]", + Short: "Disables group constraint on the specified team", + Example: " group team disable myteam", + Args: cobra.ExactArgs(1), + RunE: teamGroupDisableCmdF, +} + +var TeamGroupStatusCmd = &cobra.Command{ + Use: "status [team]", + Short: "Shows the group constraint status of the specified team", + Example: " group team status myteam", + Args: cobra.ExactArgs(1), + RunE: teamGroupStatusCmdF, +} + +var TeamGroupListCmd = &cobra.Command{ + Use: "list [team]", + Short: "List team groups", + Long: "Lists the groups associated with a team", + Example: " group team list myteam", + Args: cobra.ExactArgs(1), + RunE: teamGroupListCmdF, +} + +func init() { + ChannelGroupCmd.AddCommand( + ChannelGroupEnableCmd, + ChannelGroupDisableCmd, + ChannelGroupStatusCmd, + ChannelGroupListCmd, + ) + + TeamGroupCmd.AddCommand( + TeamGroupEnableCmd, + TeamGroupDisableCmd, + TeamGroupStatusCmd, + TeamGroupListCmd, + ) + + GroupCmd.AddCommand( + ChannelGroupCmd, + TeamGroupCmd, + ) + + RootCmd.AddCommand(GroupCmd) +} + +func channelGroupEnableCmdF(command *cobra.Command, args []string) error { + a, err := InitDBCommandContextCobra(command) + if err != nil { + return err + } + defer a.Shutdown() + + channel := getChannelFromChannelArg(a, args[0]) + if channel == nil { + return errors.New("Unable to find channel '" + args[0] + "'") + } + + groups, appErr := a.GetGroupsByChannel(channel.Id, 0, 9999) + if appErr != nil { + return appErr + } + + if len(groups) == 0 { + return errors.New("Channel '" + args[0] + "' has no groups associated. It cannot be group-constrained") + } + + channel.GroupConstrained = model.NewBool(true) + if _, appErr = a.UpdateChannel(channel); appErr != nil { + return appErr + } + + return nil +} + +func channelGroupDisableCmdF(command *cobra.Command, args []string) error { + a, err := InitDBCommandContextCobra(command) + if err != nil { + return err + } + defer a.Shutdown() + + channel := getChannelFromChannelArg(a, args[0]) + if channel == nil { + return errors.New("Unable to find channel '" + args[0] + "'") + } + + channel.GroupConstrained = model.NewBool(false) + if _, appErr := a.UpdateChannel(channel); appErr != nil { + return appErr + } + + return nil +} + +func channelGroupStatusCmdF(command *cobra.Command, args []string) error { + a, err := InitDBCommandContextCobra(command) + if err != nil { + return err + } + defer a.Shutdown() + + channel := getChannelFromChannelArg(a, args[0]) + if channel == nil { + return errors.New("Unable to find channel '" + args[0] + "'") + } + + if channel.GroupConstrained != nil && *channel.GroupConstrained { + fmt.Println("Enabled") + } else { + fmt.Println("Disabled") + } + + return nil +} + +func channelGroupListCmdF(command *cobra.Command, args []string) error { + a, err := InitDBCommandContextCobra(command) + if err != nil { + return err + } + defer a.Shutdown() + + channel := getChannelFromChannelArg(a, args[0]) + if channel == nil { + return errors.New("Unable to find channel '" + args[0] + "'") + } + + groups, appErr := a.GetGroupsByChannel(channel.Id, 0, 9999) + if appErr != nil { + return appErr + } + + for _, group := range groups { + fmt.Println(group.DisplayName) + } + + return nil +} + +func teamGroupEnableCmdF(command *cobra.Command, args []string) error { + a, err := InitDBCommandContextCobra(command) + if err != nil { + return err + } + defer a.Shutdown() + + team := getTeamFromTeamArg(a, args[0]) + if team == nil { + return errors.New("Unable to find team '" + args[0] + "'") + } + + groups, appErr := a.GetGroupsByTeam(team.Id, 0, 9999) + if appErr != nil { + return appErr + } + + if len(groups) == 0 { + return errors.New("Team '" + args[0] + "' has no groups associated. It cannot be group-constrained") + } + + team.GroupConstrained = model.NewBool(true) + if _, appErr = a.UpdateTeam(team); appErr != nil { + return appErr + } + + return nil +} + +func teamGroupDisableCmdF(command *cobra.Command, args []string) error { + a, err := InitDBCommandContextCobra(command) + if err != nil { + return err + } + defer a.Shutdown() + + team := getTeamFromTeamArg(a, args[0]) + if team == nil { + return errors.New("Unable to find team '" + args[0] + "'") + } + + team.GroupConstrained = model.NewBool(false) + if _, appErr := a.UpdateTeam(team); appErr != nil { + return appErr + } + + return nil +} + +func teamGroupStatusCmdF(command *cobra.Command, args []string) error { + a, err := InitDBCommandContextCobra(command) + if err != nil { + return err + } + defer a.Shutdown() + + team := getTeamFromTeamArg(a, args[0]) + if team == nil { + return errors.New("Unable to find team '" + args[0] + "'") + } + + if team.GroupConstrained != nil && *team.GroupConstrained { + fmt.Println("Enabled") + } else { + fmt.Println("Disabled") + } + + return nil +} + +func teamGroupListCmdF(command *cobra.Command, args []string) error { + a, err := InitDBCommandContextCobra(command) + if err != nil { + return err + } + defer a.Shutdown() + + team := getTeamFromTeamArg(a, args[0]) + if team == nil { + return errors.New("Unable to find team '" + args[0] + "'") + } + + groups, appErr := a.GetGroupsByTeam(team.Id, 0, 9999) + if appErr != nil { + return appErr + } + + for _, group := range groups { + fmt.Println(group.DisplayName) + } + + return nil +} diff --git a/cmd/mattermost/commands/group_test.go b/cmd/mattermost/commands/group_test.go new file mode 100644 index 0000000000..a4dcb4b4e8 --- /dev/null +++ b/cmd/mattermost/commands/group_test.go @@ -0,0 +1,391 @@ +// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package commands + +import ( + "testing" + + "github.com/mattermost/mattermost-server/model" + "github.com/stretchr/testify/require" +) + +func TestChannelGroupEnable(t *testing.T) { + th := Setup().InitBasic() + defer th.TearDown() + + // create public channel + channel := th.CreatePublicChannel() + + // try to enable, should fail because channel has no groups + require.Error(t, th.RunCommand(t, "group", "channel", "enable", th.BasicTeam.Name+":"+channel.Name)) + + // add group + id := model.NewId() + group, err := th.App.CreateGroup(&model.Group{ + DisplayName: "dn_" + id, + Name: "name" + id, + Source: model.GroupSourceLdap, + Description: "description_" + id, + RemoteId: model.NewId(), + }) + require.Nil(t, err) + + _, err = th.App.CreateGroupSyncable(&model.GroupSyncable{ + AutoAdd: true, + SyncableId: channel.Id, + Type: model.GroupSyncableTypeChannel, + GroupId: group.Id, + }) + require.Nil(t, err) + + // enabling should succeed now + th.CheckCommand(t, "group", "channel", "enable", th.BasicTeam.Name+":"+channel.Name) + channel, appErr := th.App.GetChannelByName(channel.Name, th.BasicTeam.Id, false) + require.Nil(t, appErr) + require.NotNil(t, channel.GroupConstrained) + require.True(t, *channel.GroupConstrained) + + // try to enable nonexistent channel, should fail + require.Error(t, th.RunCommand(t, "group", "channel", "enable", th.BasicTeam.Name+":"+channel.Name+"asdf")) +} + +func TestChannelGroupDisable(t *testing.T) { + th := Setup().InitBasic() + defer th.TearDown() + + // create public channel + channel := th.CreatePublicChannel() + + // try to disable, should work + th.CheckCommand(t, "group", "channel", "disable", th.BasicTeam.Name+":"+channel.Name) + channel, appErr := th.App.GetChannelByName(channel.Name, th.BasicTeam.Id, false) + require.Nil(t, appErr) + require.NotNil(t, channel.GroupConstrained) + require.False(t, *channel.GroupConstrained) + + // add group and enable + id := model.NewId() + group, err := th.App.CreateGroup(&model.Group{ + DisplayName: "dn_" + id, + Name: "name" + id, + Source: model.GroupSourceLdap, + Description: "description_" + id, + RemoteId: model.NewId(), + }) + require.Nil(t, err) + + _, err = th.App.CreateGroupSyncable(&model.GroupSyncable{ + AutoAdd: true, + SyncableId: channel.Id, + Type: model.GroupSyncableTypeChannel, + GroupId: group.Id, + }) + require.Nil(t, err) + + th.CheckCommand(t, "group", "channel", "enable", th.BasicTeam.Name+":"+channel.Name) + channel, appErr = th.App.GetChannelByName(channel.Name, th.BasicTeam.Id, false) + require.Nil(t, appErr) + require.NotNil(t, channel.GroupConstrained) + require.True(t, *channel.GroupConstrained) + + // try to disable, should work + th.CheckCommand(t, "group", "channel", "disable", th.BasicTeam.Name+":"+channel.Name) + channel, appErr = th.App.GetChannelByName(channel.Name, th.BasicTeam.Id, false) + require.Nil(t, appErr) + require.NotNil(t, channel.GroupConstrained) + require.False(t, *channel.GroupConstrained) + + // try to disable nonexistent channel, should fail + require.Error(t, th.RunCommand(t, "group", "channel", "disable", th.BasicTeam.Name+":"+channel.Name+"asdf")) +} + +func TestChannelGroupStatus(t *testing.T) { + th := Setup().InitBasic() + defer th.TearDown() + + // create public channel + channel := th.CreatePublicChannel() + + // get status, should be Disabled + output := th.CheckCommand(t, "group", "channel", "status", th.BasicTeam.Name+":"+channel.Name) + require.Contains(t, string(output), "Disabled") + + // add group and enable + id := model.NewId() + group, err := th.App.CreateGroup(&model.Group{ + DisplayName: "dn_" + id, + Name: "name" + id, + Source: model.GroupSourceLdap, + Description: "description_" + id, + RemoteId: model.NewId(), + }) + require.Nil(t, err) + + _, err = th.App.CreateGroupSyncable(&model.GroupSyncable{ + AutoAdd: true, + SyncableId: channel.Id, + Type: model.GroupSyncableTypeChannel, + GroupId: group.Id, + }) + require.Nil(t, err) + + th.CheckCommand(t, "group", "channel", "enable", th.BasicTeam.Name+":"+channel.Name) + channel, appErr := th.App.GetChannelByName(channel.Name, th.BasicTeam.Id, false) + require.Nil(t, appErr) + require.NotNil(t, channel.GroupConstrained) + require.True(t, *channel.GroupConstrained) + + // get status, should be enabled + output = th.CheckCommand(t, "group", "channel", "status", th.BasicTeam.Name+":"+channel.Name) + require.Contains(t, string(output), "Enabled") + + // try to get status of nonexistent channel, should fail + require.Error(t, th.RunCommand(t, "group", "channel", "status", th.BasicTeam.Name+":"+channel.Name+"asdf")) +} + +func TestChannelGroupList(t *testing.T) { + th := Setup().InitBasic() + defer th.TearDown() + + // create public channel + channel := th.CreatePublicChannel() + + // list groups for a channel with none, should work + th.CheckCommand(t, "group", "channel", "list", th.BasicTeam.Name+":"+channel.Name) + + // add groups and enable + id1 := model.NewId() + g1, err := th.App.CreateGroup(&model.Group{ + DisplayName: "dn_" + id1, + Name: "name" + id1, + Source: model.GroupSourceLdap, + Description: "description_" + id1, + RemoteId: model.NewId(), + }) + require.Nil(t, err) + + _, err = th.App.CreateGroupSyncable(&model.GroupSyncable{ + AutoAdd: true, + SyncableId: channel.Id, + Type: model.GroupSyncableTypeChannel, + GroupId: g1.Id, + }) + require.Nil(t, err) + + id2 := model.NewId() + g2, err := th.App.CreateGroup(&model.Group{ + DisplayName: "dn_" + id2, + Name: "name" + id2, + Source: model.GroupSourceLdap, + Description: "description_" + id2, + RemoteId: model.NewId(), + }) + require.Nil(t, err) + + _, err = th.App.CreateGroupSyncable(&model.GroupSyncable{ + AutoAdd: true, + SyncableId: channel.Id, + Type: model.GroupSyncableTypeChannel, + GroupId: g2.Id, + }) + require.Nil(t, err) + + th.CheckCommand(t, "group", "channel", "enable", th.BasicTeam.Name+":"+channel.Name) + channel, appErr := th.App.GetChannelByName(channel.Name, th.BasicTeam.Id, false) + require.Nil(t, appErr) + require.NotNil(t, channel.GroupConstrained) + require.True(t, *channel.GroupConstrained) + + // list groups + output := th.CheckCommand(t, "group", "channel", "list", th.BasicTeam.Name+":"+channel.Name) + require.Contains(t, string(output), g1.DisplayName) + require.Contains(t, string(output), g2.DisplayName) + + // try to get list of nonexistent channel, should fail + require.Error(t, th.RunCommand(t, "group", "channel", "list", th.BasicTeam.Name+":"+channel.Name+"asdf")) +} + +func TestTeamGroupEnable(t *testing.T) { + th := Setup().InitBasic() + defer th.TearDown() + + // try to enable, should fail because team has no groups + require.Error(t, th.RunCommand(t, "group", "team", "enable", th.BasicTeam.Name)) + + // add group + id := model.NewId() + group, err := th.App.CreateGroup(&model.Group{ + DisplayName: "dn_" + id, + Name: "name" + id, + Source: model.GroupSourceLdap, + Description: "description_" + id, + RemoteId: model.NewId(), + }) + require.Nil(t, err) + + _, err = th.App.CreateGroupSyncable(&model.GroupSyncable{ + AutoAdd: true, + SyncableId: th.BasicTeam.Id, + Type: model.GroupSyncableTypeTeam, + GroupId: group.Id, + }) + require.Nil(t, err) + + // enabling should succeed now + th.CheckCommand(t, "group", "team", "enable", th.BasicTeam.Name) + team, appErr := th.App.GetTeamByName(th.BasicTeam.Name) + require.Nil(t, appErr) + require.NotNil(t, team.GroupConstrained) + require.True(t, *team.GroupConstrained) + + // try to enable nonexistent team, should fail + require.Error(t, th.RunCommand(t, "group", "team", "enable", th.BasicTeam.Name+"asdf")) +} + +func TestTeamGroupDisable(t *testing.T) { + th := Setup().InitBasic() + defer th.TearDown() + + // try to disable, should work + th.CheckCommand(t, "group", "team", "disable", th.BasicTeam.Name) + team, appErr := th.App.GetTeamByName(th.BasicTeam.Name) + require.Nil(t, appErr) + require.NotNil(t, team.GroupConstrained) + require.False(t, *team.GroupConstrained) + + // add group and enable + id := model.NewId() + group, err := th.App.CreateGroup(&model.Group{ + DisplayName: "dn_" + id, + Name: "name" + id, + Source: model.GroupSourceLdap, + Description: "description_" + id, + RemoteId: model.NewId(), + }) + require.Nil(t, err) + + _, err = th.App.CreateGroupSyncable(&model.GroupSyncable{ + AutoAdd: true, + SyncableId: team.Id, + Type: model.GroupSyncableTypeTeam, + GroupId: group.Id, + }) + require.Nil(t, err) + + th.CheckCommand(t, "group", "team", "enable", th.BasicTeam.Name) + team, appErr = th.App.GetTeamByName(th.BasicTeam.Name) + require.Nil(t, appErr) + require.NotNil(t, team.GroupConstrained) + require.True(t, *team.GroupConstrained) + + // try to disable, should work + th.CheckCommand(t, "group", "team", "disable", th.BasicTeam.Name) + team, appErr = th.App.GetTeamByName(th.BasicTeam.Name) + require.Nil(t, appErr) + require.NotNil(t, team.GroupConstrained) + require.False(t, *team.GroupConstrained) + + // try to disable nonexistent team, should fail + require.Error(t, th.RunCommand(t, "group", "team", "disable", th.BasicTeam.Name+"asdf")) +} + +func TestTeamGroupStatus(t *testing.T) { + th := Setup().InitBasic() + defer th.TearDown() + + // get status, should be Disabled + output := th.CheckCommand(t, "group", "team", "status", th.BasicTeam.Name) + require.Contains(t, string(output), "Disabled") + + // add group and enable + id := model.NewId() + group, err := th.App.CreateGroup(&model.Group{ + DisplayName: "dn_" + id, + Name: "name" + id, + Source: model.GroupSourceLdap, + Description: "description_" + id, + RemoteId: model.NewId(), + }) + require.Nil(t, err) + + _, err = th.App.CreateGroupSyncable(&model.GroupSyncable{ + AutoAdd: true, + SyncableId: th.BasicTeam.Id, + Type: model.GroupSyncableTypeTeam, + GroupId: group.Id, + }) + require.Nil(t, err) + + th.CheckCommand(t, "group", "team", "enable", th.BasicTeam.Name) + team, appErr := th.App.GetTeamByName(th.BasicTeam.Name) + require.Nil(t, appErr) + require.NotNil(t, team.GroupConstrained) + require.True(t, *team.GroupConstrained) + + // get status, should be enabled + output = th.CheckCommand(t, "group", "team", "status", th.BasicTeam.Name) + require.Contains(t, string(output), "Enabled") + + // try to get status of nonexistent channel, should fail + require.Error(t, th.RunCommand(t, "group", "team", "status", th.BasicTeam.Name+"asdf")) +} + +func TestTeamGroupList(t *testing.T) { + th := Setup().InitBasic() + defer th.TearDown() + + // list groups for a team with none, should work + th.CheckCommand(t, "group", "team", "list", th.BasicTeam.Name) + + // add groups and enable + id1 := model.NewId() + g1, err := th.App.CreateGroup(&model.Group{ + DisplayName: "dn_" + id1, + Name: "name" + id1, + Source: model.GroupSourceLdap, + Description: "description_" + id1, + RemoteId: model.NewId(), + }) + require.Nil(t, err) + + _, err = th.App.CreateGroupSyncable(&model.GroupSyncable{ + AutoAdd: true, + SyncableId: th.BasicTeam.Id, + Type: model.GroupSyncableTypeTeam, + GroupId: g1.Id, + }) + require.Nil(t, err) + + id2 := model.NewId() + g2, err := th.App.CreateGroup(&model.Group{ + DisplayName: "dn_" + id2, + Name: "name" + id2, + Source: model.GroupSourceLdap, + Description: "description_" + id2, + RemoteId: model.NewId(), + }) + require.Nil(t, err) + + _, err = th.App.CreateGroupSyncable(&model.GroupSyncable{ + AutoAdd: true, + SyncableId: th.BasicTeam.Id, + Type: model.GroupSyncableTypeTeam, + GroupId: g2.Id, + }) + require.Nil(t, err) + + th.CheckCommand(t, "group", "team", "enable", th.BasicTeam.Name) + team, appErr := th.App.GetTeamByName(th.BasicTeam.Name) + require.Nil(t, appErr) + require.NotNil(t, team.GroupConstrained) + require.True(t, *team.GroupConstrained) + + // list groups + output := th.CheckCommand(t, "group", "team", "list", th.BasicTeam.Name) + require.Contains(t, string(output), g1.DisplayName) + require.Contains(t, string(output), g2.DisplayName) + + // try to get list of nonexistent team, should fail + require.Error(t, th.RunCommand(t, "group", "team", "list", th.BasicTeam.Name+"asdf")) +} diff --git a/store/sqlstore/group_supplier.go b/store/sqlstore/group_supplier.go index 6f4ba9fcb5..233bc8a640 100644 --- a/store/sqlstore/group_supplier.go +++ b/store/sqlstore/group_supplier.go @@ -827,6 +827,8 @@ func (s *SqlSupplier) GetGroupsByChannel(ctx context.Context, channelId string, ON gc.GroupId = ug.Id WHERE + gc.DeleteAt = 0 + AND ug.DeleteAt = 0 AND gc.ChannelId = :ChannelId @@ -907,6 +909,8 @@ func (s *SqlSupplier) GetGroupsByTeam(ctx context.Context, teamId string, page, ON gt.GroupId = ug.Id WHERE + gt.DeleteAt = 0 + AND ug.DeleteAt = 0 AND gt.TeamId = :TeamId