diff options
-rw-r--r-- | Makefile | 6 | ||||
-rw-r--r-- | api/cli_test.go | 169 | ||||
-rw-r--r-- | i18n/en.json | 8 | ||||
-rw-r--r-- | mattermost.go | 270 | ||||
-rw-r--r-- | store/sql_channel_store.go | 28 | ||||
-rw-r--r-- | store/sql_channel_store_test.go | 9 | ||||
-rw-r--r-- | store/store.go | 2 | ||||
-rw-r--r-- | utils/diagnostic.go | 1 |
8 files changed, 490 insertions, 3 deletions
@@ -309,6 +309,12 @@ run-server: prepare-enterprise start-docker mkdir -p $(BUILD_WEBAPP_DIR)/dist/files $(GO) run $(GOFLAGS) $(GO_LINKER_FLAGS) *.go & +run-cli: prepare-enterprise start-docker + @echo Running mattermost for development + @echo Example should be like >'make ARGS="-version" run-cli' + + $(GO) run $(GOFLAGS) $(GO_LINKER_FLAGS) *.go ${ARGS} + run-client: @echo Running mattermost client for development diff --git a/api/cli_test.go b/api/cli_test.go index 12f38b54a..8184c2e06 100644 --- a/api/cli_test.go +++ b/api/cli_test.go @@ -135,6 +135,175 @@ func TestCliAssignRole(t *testing.T) { } } +func TestCliJoinChannel(t *testing.T) { + if disableCliTests { + return + } + + th := Setup().InitBasic() + channel := th.CreateChannel(th.BasicClient, th.BasicTeam) + + // These test cannot run since this feature requires an enteprise license + + // cmd := exec.Command("bash", "-c", `go run ../mattermost.go -join_channel -team_name="`+th.BasicTeam.Name+`" -channel_name="`+channel.Name+`" -email="`+th.BasicUser2.Email+`"`) + // output, err := cmd.CombinedOutput() + // if err != nil { + // t.Log(string(output)) + // t.Fatal(err) + // } + + // // Joining twice should succeed + // cmd1 := exec.Command("bash", "-c", `go run ../mattermost.go -join_channel -team_name="`+th.BasicTeam.Name+`" -channel_name="`+channel.Name+`" -email="`+th.BasicUser2.Email+`"`) + // output1, err1 := cmd1.CombinedOutput() + // if err1 != nil { + // t.Log(string(output1)) + // t.Fatal(err1) + // } + + // should fail because channel does not exist + cmd2 := exec.Command("bash", "-c", `go run ../mattermost.go -join_channel -team_name="`+th.BasicTeam.Name+`" -channel_name="`+channel.Name+`asdf" -email="`+th.BasicUser2.Email+`"`) + output2, err2 := cmd2.CombinedOutput() + if err2 == nil { + t.Log(string(output2)) + t.Fatal() + } + + // should fail because channel does not have license + cmd3 := exec.Command("bash", "-c", `go run ../mattermost.go -join_channel -team_name="`+th.BasicTeam.Name+`" -channel_name="`+channel.Name+`" -email="`+th.BasicUser2.Email+`"`) + output3, err3 := cmd3.CombinedOutput() + if err3 == nil { + t.Log(string(output3)) + t.Fatal() + } +} + +func TestCliRemoveChannel(t *testing.T) { + if disableCliTests { + return + } + + th := Setup().InitBasic() + channel := th.CreateChannel(th.BasicClient, th.BasicTeam) + + // These test cannot run since this feature requires an enteprise license + + // cmd := exec.Command("bash", "-c", `go run ../mattermost.go -join_channel -team_name="`+th.BasicTeam.Name+`" -channel_name="`+channel.Name+`" -email="`+th.BasicUser2.Email+`"`) + // output, err := cmd.CombinedOutput() + // if err != nil { + // t.Log(string(output)) + // t.Fatal(err) + // } + + // cmd0 := exec.Command("bash", "-c", `go run ../mattermost.go -leave_channel -team_name="`+th.BasicTeam.Name+`" -channel_name="`+channel.Name+`" -email="`+th.BasicUser2.Email+`"`) + // output0, err0 := cmd0.CombinedOutput() + // if err0 != nil { + // t.Log(string(output0)) + // t.Fatal(err0) + // } + + // // Leaving twice should succeed + // cmd1 := exec.Command("bash", "-c", `go run ../mattermost.go -leave_channel -team_name="`+th.BasicTeam.Name+`" -channel_name="`+channel.Name+`" -email="`+th.BasicUser2.Email+`"`) + // output1, err1 := cmd1.CombinedOutput() + // if err1 != nil { + // t.Log(string(output1)) + // t.Fatal(err1) + // } + + // cannot leave town-square + cmd1a := exec.Command("bash", "-c", `go run ../mattermost.go -leave_channel -team_name="`+th.BasicTeam.Name+`" -channel_name="town-square" -email="`+th.BasicUser2.Email+`"`) + output1a, err1a := cmd1a.CombinedOutput() + if err1a == nil { + t.Log(string(output1a)) + t.Fatal() + } + + // should fail because channel does not exist + cmd2 := exec.Command("bash", "-c", `go run ../mattermost.go -leave_channel -team_name="`+th.BasicTeam.Name+`" -channel_name="`+channel.Name+`asdf" -email="`+th.BasicUser2.Email+`"`) + output2, err2 := cmd2.CombinedOutput() + if err2 == nil { + t.Log(string(output2)) + t.Fatal() + } + + // should fail because channel does not have license + cmd3 := exec.Command("bash", "-c", `go run ../mattermost.go -leave_channel -team_name="`+th.BasicTeam.Name+`" -channel_name="`+channel.Name+`" -email="`+th.BasicUser2.Email+`"`) + output3, err3 := cmd3.CombinedOutput() + if err3 == nil { + t.Log(string(output3)) + t.Fatal() + } +} + +func TestCliListChannels(t *testing.T) { + if disableCliTests { + return + } + + th := Setup().InitBasic() + channel := th.CreateChannel(th.BasicClient, th.BasicTeam) + th.BasicClient.Must(th.BasicClient.DeleteChannel(channel.Id)) + + // These test cannot run since this feature requires an enteprise license + + // cmd := exec.Command("bash", "-c", `go run ../mattermost.go -list_channels -team_name="`+th.BasicTeam.Name+`"`) + // output, err := cmd.CombinedOutput() + // if err != nil { + // t.Log(string(output)) + // t.Fatal(err) + // } + + // if !strings.Contains(string(output), "town-square") { + // t.Fatal("should have channels") + // } + + // if !strings.Contains(string(output), channel.Name+" (archived)") { + // t.Fatal("should have archived channel") + // } + + // should fail because channel does not have license + cmd3 := exec.Command("bash", "-c", `go run ../mattermost.go -list_channels -team_name="`+th.BasicTeam.Name+``) + output3, err3 := cmd3.CombinedOutput() + if err3 == nil { + t.Log(string(output3)) + t.Fatal() + } +} + +func TestCliRestoreChannel(t *testing.T) { + if disableCliTests { + return + } + + th := Setup().InitBasic() + channel := th.CreateChannel(th.BasicClient, th.BasicTeam) + th.BasicClient.Must(th.BasicClient.DeleteChannel(channel.Id)) + + // These test cannot run since this feature requires an enteprise license + + // cmd := exec.Command("bash", "-c", `go run ../mattermost.go -restore_channel -team_name="`+th.BasicTeam.Name+`" -channel_name="`+channel.Name+`"`) + // output, err := cmd.CombinedOutput() + // if err != nil { + // t.Log(string(output)) + // t.Fatal(err) + // } + + // // restoring twice should succeed + // cmd1 := exec.Command("bash", "-c", `go run ../mattermost.go -restore_channel -team_name="`+th.BasicTeam.Name+`" -channel_name="`+channel.Name+`"`) + // output1, err1 := cmd1.CombinedOutput() + // if err1 != nil { + // t.Log(string(output1)) + // t.Fatal(err1) + // } + + // should fail because channel does not have license + cmd3 := exec.Command("bash", "-c", `go run ../mattermost.go -restore_channel -team_name="`+th.BasicTeam.Name+`" -channel_name="`+channel.Name+`"`) + output3, err3 := cmd3.CombinedOutput() + if err3 == nil { + t.Log(string(output3)) + t.Fatal() + } +} + func TestCliJoinTeam(t *testing.T) { if disableCliTests { return diff --git a/i18n/en.json b/i18n/en.json index 8436d48d9..8bd66522d 100644 --- a/i18n/en.json +++ b/i18n/en.json @@ -3172,6 +3172,10 @@ "translation": "More than 1 read replica functionality disabled by current license. Please contact your system administrator about upgrading your enterprise license." }, { + "id": "cli.license.critical", + "translation": "Feature requires an enterprise license. Please contact your system administrator about upgrading your enterprise license." + }, + { "id": "store.sql.remove_index.critical", "translation": "Failed to remove index %v" }, @@ -3292,6 +3296,10 @@ "translation": "We couldn't get all the channels" }, { + "id": "store.sql_channel.get_all.app_error", + "translation": "We couldn't get all the channels" + }, + { "id": "store.sql_channel.get_member.app_error", "translation": "We couldn't get the channel member" }, diff --git a/mattermost.go b/mattermost.go index fc9baa56d..423e00536 100644 --- a/mattermost.go +++ b/mattermost.go @@ -45,6 +45,10 @@ var flagCmdCreateTeam bool var flagCmdCreateUser bool var flagCmdInviteUser bool var flagCmdAssignRole bool +var flagCmdJoinChannel bool +var flagCmdLeaveChannel bool +var flagCmdListChannels bool +var flagCmdRestoreChannel bool var flagCmdJoinTeam bool var flagCmdVersion bool var flagCmdRunWebClientTests bool @@ -63,6 +67,7 @@ var flagLicenseFile string var flagEmail string var flagPassword string var flagTeamName string +var flagChannelName string var flagSiteURL string var flagConfirmBackup string var flagRole string @@ -198,6 +203,10 @@ func doSecurityAndDiagnostics() { v.Set(utils.PROP_DIAGNOSTIC_ACTIVE_USER_COUNT, strconv.FormatInt(ucr.Data.(int64), 10)) } + if tcr := <-api.Srv.Store.Team().AnalyticsTeamCount(); tcr.Err == nil { + v.Set(utils.PROP_DIAGNOSTIC_TEAM_COUNT, strconv.FormatInt(tcr.Data.(int64), 10)) + } + res, err := http.Get(utils.DIAGNOSTIC_URL + "/security?" + v.Encode()) if err != nil { l4g.Error(utils.T("mattermost.security_info.error")) @@ -262,6 +271,7 @@ func parseCmds() { flag.StringVar(&flagEmail, "email", "", "") flag.StringVar(&flagPassword, "password", "", "") flag.StringVar(&flagTeamName, "team_name", "", "") + flag.StringVar(&flagChannelName, "channel_name", "", "") flag.StringVar(&flagSiteURL, "site_url", "", "") flag.StringVar(&flagConfirmBackup, "confirm_backup", "", "") flag.StringVar(&flagRole, "role", "", "") @@ -271,6 +281,10 @@ func parseCmds() { flag.BoolVar(&flagCmdCreateUser, "create_user", false, "") flag.BoolVar(&flagCmdInviteUser, "invite_user", false, "") flag.BoolVar(&flagCmdAssignRole, "assign_role", false, "") + flag.BoolVar(&flagCmdJoinChannel, "join_channel", false, "") + flag.BoolVar(&flagCmdLeaveChannel, "leave_channel", false, "") + flag.BoolVar(&flagCmdListChannels, "list_channels", false, "") + flag.BoolVar(&flagCmdRestoreChannel, "restore_channel", false, "") flag.BoolVar(&flagCmdJoinTeam, "join_team", false, "") flag.BoolVar(&flagCmdVersion, "version", false, "") flag.BoolVar(&flagCmdRunWebClientTests, "run_web_client_tests", false, "") @@ -290,6 +304,10 @@ func parseCmds() { flagCmdCreateUser || flagCmdInviteUser || flagCmdAssignRole || + flagCmdJoinChannel || + flagCmdLeaveChannel || + flagCmdListChannels || + flagCmdRestoreChannel || flagCmdJoinTeam || flagCmdResetPassword || flagCmdResetMfa || @@ -311,6 +329,10 @@ func runCmds() { cmdCreateUser() cmdInviteUser() cmdAssignRole() + cmdJoinChannel() + cmdLeaveChannel() + cmdListChannels() + cmdRestoreChannel() cmdJoinTeam() cmdResetPassword() cmdResetMfa() @@ -905,10 +927,230 @@ func cmdAssignRole() { } } +func cmdJoinChannel() { + if flagCmdJoinChannel { + if len(flagTeamName) == 0 { + fmt.Fprintln(os.Stderr, "flag needs an argument: -team_name") + flag.Usage() + os.Exit(1) + } + + if len(flagEmail) == 0 { + fmt.Fprintln(os.Stderr, "flag needs an argument: -email") + flag.Usage() + os.Exit(1) + } + + if len(flagChannelName) == 0 { + fmt.Fprintln(os.Stderr, "flag needs an argument: -channel_name") + flag.Usage() + os.Exit(1) + } + + if !utils.IsLicensed { + fmt.Fprintln(os.Stderr, utils.T("cli.license.critical")) + flag.Usage() + os.Exit(1) + } + + var team *model.Team + if result := <-api.Srv.Store.Team().GetByName(flagTeamName); result.Err != nil { + l4g.Error("%v", result.Err) + flushLogAndExit(1) + } else { + team = result.Data.(*model.Team) + } + + var user *model.User + if result := <-api.Srv.Store.User().GetByEmail(flagEmail); result.Err != nil { + l4g.Error("%v", result.Err) + flushLogAndExit(1) + } else { + user = result.Data.(*model.User) + } + + var channel *model.Channel + if result := <-api.Srv.Store.Channel().GetByName(team.Id, flagChannelName); result.Err != nil { + l4g.Error("%v", result.Err) + flushLogAndExit(1) + } else { + channel = result.Data.(*model.Channel) + } + + _, err := api.AddUserToChannel(user, channel) + if err != nil { + l4g.Error("%v", err) + flushLogAndExit(1) + } + + os.Exit(0) + } +} + +func cmdLeaveChannel() { + if flagCmdLeaveChannel { + if len(flagTeamName) == 0 { + fmt.Fprintln(os.Stderr, "flag needs an argument: -team_name") + flag.Usage() + os.Exit(1) + } + + if len(flagEmail) == 0 { + fmt.Fprintln(os.Stderr, "flag needs an argument: -email") + flag.Usage() + os.Exit(1) + } + + if len(flagChannelName) == 0 { + fmt.Fprintln(os.Stderr, "flag needs an argument: -channel_name") + flag.Usage() + os.Exit(1) + } + + if flagChannelName == model.DEFAULT_CHANNEL { + fmt.Fprintln(os.Stderr, "flag has invalid argument: -channel_name (cannot leave town-square)") + flag.Usage() + os.Exit(1) + } + + if !utils.IsLicensed { + fmt.Fprintln(os.Stderr, utils.T("cli.license.critical")) + flag.Usage() + os.Exit(1) + } + + var team *model.Team + if result := <-api.Srv.Store.Team().GetByName(flagTeamName); result.Err != nil { + l4g.Error("%v", result.Err) + flushLogAndExit(1) + } else { + team = result.Data.(*model.Team) + } + + var user *model.User + if result := <-api.Srv.Store.User().GetByEmail(flagEmail); result.Err != nil { + l4g.Error("%v", result.Err) + flushLogAndExit(1) + } else { + user = result.Data.(*model.User) + } + + var channel *model.Channel + if result := <-api.Srv.Store.Channel().GetByName(team.Id, flagChannelName); result.Err != nil { + l4g.Error("%v", result.Err) + flushLogAndExit(1) + } else { + channel = result.Data.(*model.Channel) + } + + err := api.RemoveUserFromChannel(user.Id, user.Id, channel) + if err != nil { + l4g.Error("%v", err) + flushLogAndExit(1) + } + + os.Exit(0) + } +} + +func cmdListChannels() { + if flagCmdListChannels { + if len(flagTeamName) == 0 { + fmt.Fprintln(os.Stderr, "flag needs an argument: -team_name") + flag.Usage() + os.Exit(1) + } + + if !utils.IsLicensed { + fmt.Fprintln(os.Stderr, utils.T("cli.license.critical")) + flag.Usage() + os.Exit(1) + } + + var team *model.Team + if result := <-api.Srv.Store.Team().GetByName(flagTeamName); result.Err != nil { + l4g.Error("%v", result.Err) + flushLogAndExit(1) + } else { + team = result.Data.(*model.Team) + } + + if result := <-api.Srv.Store.Channel().GetAll(team.Id); result.Err != nil { + l4g.Error("%v", result.Err) + flushLogAndExit(1) + } else { + channels := result.Data.([]*model.Channel) + + for _, channel := range channels { + + if channel.DeleteAt > 0 { + fmt.Fprintln(os.Stdout, channel.Name+" (archived)") + } else { + fmt.Fprintln(os.Stdout, channel.Name) + } + } + } + + os.Exit(0) + } +} + +func cmdRestoreChannel() { + if flagCmdRestoreChannel { + if len(flagTeamName) == 0 { + fmt.Fprintln(os.Stderr, "flag needs an argument: -team_name") + flag.Usage() + os.Exit(1) + } + + if len(flagChannelName) == 0 { + fmt.Fprintln(os.Stderr, "flag needs an argument: -channel_name") + flag.Usage() + os.Exit(1) + } + + if !utils.IsLicensed { + fmt.Fprintln(os.Stderr, utils.T("cli.license.critical")) + flag.Usage() + os.Exit(1) + } + + var team *model.Team + if result := <-api.Srv.Store.Team().GetByName(flagTeamName); result.Err != nil { + l4g.Error("%v", result.Err) + flushLogAndExit(1) + } else { + team = result.Data.(*model.Team) + } + + var channel *model.Channel + if result := <-api.Srv.Store.Channel().GetAll(team.Id); result.Err != nil { + l4g.Error("%v", result.Err) + flushLogAndExit(1) + } else { + channels := result.Data.([]*model.Channel) + + for _, ctemp := range channels { + if ctemp.Name == flagChannelName { + channel = ctemp + break + } + } + } + + if result := <-api.Srv.Store.Channel().SetDeleteAt(channel.Id, 0, model.GetMillis()); result.Err != nil { + l4g.Error("%v", result.Err) + flushLogAndExit(1) + } + + os.Exit(0) + } +} + func cmdJoinTeam() { if flagCmdJoinTeam { if len(flagTeamName) == 0 { - fmt.Fprintln(os.Stderr, "flag needs an argument: -email") + fmt.Fprintln(os.Stderr, "flag needs an argument: -team_name") flag.Usage() os.Exit(1) } @@ -1275,7 +1517,7 @@ COMMANDS: Example: platform -invite_user -team_name="name" -email="user@example.com" -site_url="https://mattermost.example.com" - -join_team Joins a user to the team. It required the -email and + -join_team Joins a user to the team. It requires the -email and -team_name. You may need to logout of your current session for the new team to be applied. Example: @@ -1288,6 +1530,30 @@ COMMANDS: Example: platform -assign_role -email="user@example.com" -role="system_admin" + -join_channel Joins a user to the channel. It requires the -email, channel_name and + -team_name flags. You may need to logout of your current session + for the new channel to be applied. Requires an enterprise license. + Example: + platform -join_channel -email="user@example.com" -team_name="name" -channel_name="channel_name" + + -leave_channel Removes a user from the channel. It requires the -email, channel_name and + -team_name flags. You may need to logout of your current session + for the channel to be removed. Requires an enterprise license. + Example: + platform -leave_channel -email="user@example.com" -team_name="name" -channel_name="channel_name" + + -list_channels Lists all public channels and private groups for a given team. + It will append ' (archived)' to the channel name if archived. It requires the + -team_name flag. Requires an enterprise license. + Example: + platform -list_channels -team_name="name" + + -restore_channel Restores a previously deleted channel. + It requires the -channel_name and + -team_name flags. Requires an enterprise license. + Example: + platform -restore_channel -team_name="name" -channel_name="channel_name" + -reset_password Resets the password for a user. It requires the -email and -password flag. Example: diff --git a/store/sql_channel_store.go b/store/sql_channel_store.go index 3636fb62f..e5e0aa8ba 100644 --- a/store/sql_channel_store.go +++ b/store/sql_channel_store.go @@ -289,12 +289,16 @@ func (s SqlChannelStore) get(id string, master bool) StoreChannel { } func (s SqlChannelStore) Delete(channelId string, time int64) StoreChannel { + return s.SetDeleteAt(channelId, time, time) +} + +func (s SqlChannelStore) SetDeleteAt(channelId string, deleteAt int64, updateAt int64) StoreChannel { storeChannel := make(StoreChannel) go func() { result := StoreResult{} - _, err := s.GetMaster().Exec("Update Channels SET DeleteAt = :Time, UpdateAt = :Time WHERE Id = :ChannelId", map[string]interface{}{"Time": time, "ChannelId": channelId}) + _, err := s.GetMaster().Exec("Update Channels SET DeleteAt = :DeleteAt, UpdateAt = :UpdateAt WHERE Id = :ChannelId", map[string]interface{}{"DeleteAt": deleteAt, "UpdateAt": updateAt, "ChannelId": channelId}) if err != nil { result.Err = model.NewLocAppError("SqlChannelStore.Delete", "store.sql_channel.delete.channel.app_error", nil, "id="+channelId+", err="+err.Error()) } @@ -948,6 +952,28 @@ func (s SqlChannelStore) GetForExport(teamId string) StoreChannel { return storeChannel } +func (s SqlChannelStore) GetAll(teamId string) StoreChannel { + storeChannel := make(StoreChannel) + + go func() { + result := StoreResult{} + + var data []*model.Channel + _, err := s.GetReplica().Select(&data, "SELECT * FROM Channels WHERE TeamId = :TeamId AND Type != 'D' ORDER BY Name", map[string]interface{}{"TeamId": teamId}) + + if err != nil { + result.Err = model.NewLocAppError("SqlChannelStore.GetAll", "store.sql_channel.get_all.app_error", nil, "teamId="+teamId+", err="+err.Error()) + } else { + result.Data = data + } + + storeChannel <- result + close(storeChannel) + }() + + return storeChannel +} + func (s SqlChannelStore) AnalyticsTypeCount(teamId string, channelType string) StoreChannel { storeChannel := make(StoreChannel) diff --git a/store/sql_channel_store_test.go b/store/sql_channel_store_test.go index 2b284d06e..0c293972c 100644 --- a/store/sql_channel_store_test.go +++ b/store/sql_channel_store_test.go @@ -200,6 +200,15 @@ func TestChannelStoreGet(t *testing.T) { t.Fatal("invalid returned channel") } } + + if r3 := <-store.Channel().GetAll(o1.TeamId); r3.Err != nil { + t.Fatal(r3.Err) + } else { + channels := r3.Data.([]*model.Channel) + if len(channels) == 0 { + t.Fatal("too little") + } + } } func TestChannelStoreDelete(t *testing.T) { diff --git a/store/store.go b/store/store.go index 29a7e8d82..f576cc2ab 100644 --- a/store/store.go +++ b/store/store.go @@ -77,12 +77,14 @@ type ChannelStore interface { Get(id string) StoreChannel GetFromMaster(id string) StoreChannel Delete(channelId string, time int64) StoreChannel + SetDeleteAt(channelId string, deleteAt int64, updateAt int64) StoreChannel PermanentDeleteByTeam(teamId string) StoreChannel GetByName(team_id string, domain string) StoreChannel GetChannels(teamId string, userId string) StoreChannel GetMoreChannels(teamId string, userId string) StoreChannel GetChannelCounts(teamId string, userId string) StoreChannel GetForExport(teamId string) StoreChannel + GetAll(teamId string) StoreChannel SaveMember(member *model.ChannelMember) StoreChannel UpdateMember(member *model.ChannelMember) StoreChannel diff --git a/utils/diagnostic.go b/utils/diagnostic.go index ee199cb35..4c73d18f3 100644 --- a/utils/diagnostic.go +++ b/utils/diagnostic.go @@ -19,6 +19,7 @@ const ( PROP_DIAGNOSTIC_DATABASE = "db" PROP_DIAGNOSTIC_OS = "os" PROP_DIAGNOSTIC_USER_COUNT = "uc" + PROP_DIAGNOSTIC_TEAM_COUNT = "tc" PROP_DIAGNOSTIC_ACTIVE_USER_COUNT = "auc" PROP_DIAGNOSTIC_UNIT_TESTS = "ut" ) |