mirror of
https://github.com/mattermost/mattermost.git
synced 2025-02-25 18:55:24 -06:00
1809 lines
50 KiB
Go
1809 lines
50 KiB
Go
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
|
|
// See License.txt for license information.
|
|
|
|
package main
|
|
|
|
import (
|
|
"bufio"
|
|
"flag"
|
|
"fmt"
|
|
"html/template"
|
|
"io/ioutil"
|
|
"net/http"
|
|
"net/url"
|
|
"os"
|
|
"os/exec"
|
|
"os/signal"
|
|
"runtime"
|
|
"strconv"
|
|
"strings"
|
|
"syscall"
|
|
"time"
|
|
|
|
l4g "github.com/alecthomas/log4go"
|
|
"github.com/mattermost/platform/api"
|
|
"github.com/mattermost/platform/einterfaces"
|
|
"github.com/mattermost/platform/manualtesting"
|
|
"github.com/mattermost/platform/model"
|
|
"github.com/mattermost/platform/store"
|
|
"github.com/mattermost/platform/utils"
|
|
"github.com/mattermost/platform/web"
|
|
|
|
// Plugins
|
|
_ "github.com/mattermost/platform/model/gitlab"
|
|
|
|
// Enterprise Deps
|
|
_ "github.com/dgryski/dgoogauth"
|
|
_ "github.com/go-ldap/ldap"
|
|
_ "github.com/mattermost/rsc/qr"
|
|
)
|
|
|
|
//ENTERPRISE_IMPORTS
|
|
|
|
var flagCmdUpdateDb30 bool
|
|
var flagCmdCreateTeam bool
|
|
var flagCmdCreateUser bool
|
|
var flagCmdInviteUser bool
|
|
var flagCmdAssignRole bool
|
|
var flagCmdCreateChannel bool
|
|
var flagCmdJoinChannel bool
|
|
var flagCmdLeaveChannel bool
|
|
var flagCmdListChannels bool
|
|
var flagCmdRestoreChannel bool
|
|
var flagCmdJoinTeam bool
|
|
var flagCmdLeaveTeam bool
|
|
var flagCmdVersion bool
|
|
var flagCmdRunWebClientTests bool
|
|
var flagCmdRunJavascriptClientTests bool
|
|
var flagCmdResetPassword bool
|
|
var flagCmdResetMfa bool
|
|
var flagCmdPermanentDeleteUser bool
|
|
var flagCmdPermanentDeleteTeam bool
|
|
var flagCmdPermanentDeleteAllUsers bool
|
|
var flagCmdResetDatabase bool
|
|
var flagCmdRunLdapSync bool
|
|
var flagCmdMigrateAccounts bool
|
|
var flagUsername string
|
|
var flagCmdUploadLicense bool
|
|
var flagConfigFile string
|
|
var flagLicenseFile string
|
|
var flagEmail string
|
|
var flagPassword string
|
|
var flagTeamName string
|
|
var flagChannelName string
|
|
var flagConfirmBackup string
|
|
var flagRole string
|
|
var flagRunCmds bool
|
|
var flagFromAuth string
|
|
var flagToAuth string
|
|
var flagMatchField string
|
|
var flagChannelType string
|
|
var flagChannelHeader string
|
|
var flagChannelPurpose string
|
|
|
|
func doLoadConfig(filename string) (err string) {
|
|
defer func() {
|
|
if r := recover(); r != nil {
|
|
err = fmt.Sprintf("%v", r)
|
|
}
|
|
}()
|
|
utils.TranslationsPreInit()
|
|
utils.LoadConfig(filename)
|
|
return ""
|
|
}
|
|
|
|
func main() {
|
|
parseCmds()
|
|
|
|
if errstr := doLoadConfig(flagConfigFile); errstr != "" {
|
|
l4g.Exit("Unable to load mattermost configuration file: ", errstr)
|
|
return
|
|
}
|
|
|
|
if flagRunCmds {
|
|
utils.ConfigureCmdLineLog()
|
|
}
|
|
utils.InitTranslations(utils.Cfg.LocalizationSettings)
|
|
utils.TestConnection(utils.Cfg)
|
|
|
|
pwd, _ := os.Getwd()
|
|
l4g.Info(utils.T("mattermost.current_version"), model.CurrentVersion, model.BuildNumber, model.BuildDate, model.BuildHash, model.BuildHashEnterprise)
|
|
l4g.Info(utils.T("mattermost.entreprise_enabled"), model.BuildEnterpriseReady)
|
|
l4g.Info(utils.T("mattermost.working_dir"), pwd)
|
|
l4g.Info(utils.T("mattermost.config_file"), utils.FindConfigFile(flagConfigFile))
|
|
|
|
// Enable developer settings if this is a "dev" build
|
|
if model.BuildNumber == "dev" {
|
|
*utils.Cfg.ServiceSettings.EnableDeveloper = true
|
|
}
|
|
|
|
// Special case for upgrading the db to 3.0
|
|
// ADDED for 3.0 REMOVE for 3.4
|
|
cmdUpdateDb30()
|
|
|
|
api.NewServer()
|
|
api.InitApi()
|
|
web.InitWeb()
|
|
|
|
if model.BuildEnterpriseReady == "true" {
|
|
api.LoadLicense()
|
|
}
|
|
|
|
if !utils.IsLicensed && len(utils.Cfg.SqlSettings.DataSourceReplicas) > 1 {
|
|
l4g.Critical(utils.T("store.sql.read_replicas_not_licensed.critical"))
|
|
return
|
|
}
|
|
|
|
if flagRunCmds {
|
|
runCmds()
|
|
} else {
|
|
resetStatuses()
|
|
|
|
api.StartServer()
|
|
|
|
// If we allow testing then listen for manual testing URL hits
|
|
if utils.Cfg.ServiceSettings.EnableTesting {
|
|
manualtesting.InitManualTesting()
|
|
}
|
|
|
|
setDiagnosticId()
|
|
go runSecurityAndDiagnosticsJob()
|
|
|
|
if complianceI := einterfaces.GetComplianceInterface(); complianceI != nil {
|
|
complianceI.StartComplianceDailyJob()
|
|
}
|
|
|
|
if einterfaces.GetClusterInterface() != nil {
|
|
einterfaces.GetClusterInterface().StartInterNodeCommunication()
|
|
}
|
|
|
|
// wait for kill signal before attempting to gracefully shutdown
|
|
// the running service
|
|
c := make(chan os.Signal)
|
|
signal.Notify(c, os.Interrupt, syscall.SIGINT, syscall.SIGTERM)
|
|
<-c
|
|
|
|
if einterfaces.GetClusterInterface() != nil {
|
|
einterfaces.GetClusterInterface().StopInterNodeCommunication()
|
|
}
|
|
|
|
api.StopServer()
|
|
}
|
|
}
|
|
|
|
func resetStatuses() {
|
|
if result := <-api.Srv.Store.Status().ResetAll(); result.Err != nil {
|
|
l4g.Error(utils.T("mattermost.reset_status.error"), result.Err.Error())
|
|
}
|
|
}
|
|
|
|
func setDiagnosticId() {
|
|
if result := <-api.Srv.Store.System().Get(); result.Err == nil {
|
|
props := result.Data.(model.StringMap)
|
|
|
|
id := props[model.SYSTEM_DIAGNOSTIC_ID]
|
|
if len(id) == 0 {
|
|
id = model.NewId()
|
|
systemId := &model.System{Name: model.SYSTEM_DIAGNOSTIC_ID, Value: id}
|
|
<-api.Srv.Store.System().Save(systemId)
|
|
}
|
|
|
|
utils.CfgDiagnosticId = id
|
|
}
|
|
}
|
|
|
|
func doSecurityAndDiagnostics() {
|
|
if *utils.Cfg.ServiceSettings.EnableSecurityFixAlert {
|
|
if result := <-api.Srv.Store.System().Get(); result.Err == nil {
|
|
props := result.Data.(model.StringMap)
|
|
lastSecurityTime, _ := strconv.ParseInt(props[model.SYSTEM_LAST_SECURITY_TIME], 10, 0)
|
|
currentTime := model.GetMillis()
|
|
|
|
if (currentTime - lastSecurityTime) > 1000*60*60*24*1 {
|
|
l4g.Debug(utils.T("mattermost.security_checks.debug"))
|
|
|
|
v := url.Values{}
|
|
|
|
v.Set(utils.PROP_DIAGNOSTIC_ID, utils.CfgDiagnosticId)
|
|
v.Set(utils.PROP_DIAGNOSTIC_BUILD, model.CurrentVersion+"."+model.BuildNumber)
|
|
v.Set(utils.PROP_DIAGNOSTIC_ENTERPRISE_READY, model.BuildEnterpriseReady)
|
|
v.Set(utils.PROP_DIAGNOSTIC_DATABASE, utils.Cfg.SqlSettings.DriverName)
|
|
v.Set(utils.PROP_DIAGNOSTIC_OS, runtime.GOOS)
|
|
v.Set(utils.PROP_DIAGNOSTIC_CATEGORY, utils.VAL_DIAGNOSTIC_CATEGORY_DEFAULT)
|
|
|
|
if len(props[model.SYSTEM_RAN_UNIT_TESTS]) > 0 {
|
|
v.Set(utils.PROP_DIAGNOSTIC_UNIT_TESTS, "1")
|
|
} else {
|
|
v.Set(utils.PROP_DIAGNOSTIC_UNIT_TESTS, "0")
|
|
}
|
|
|
|
systemSecurityLastTime := &model.System{Name: model.SYSTEM_LAST_SECURITY_TIME, Value: strconv.FormatInt(currentTime, 10)}
|
|
if lastSecurityTime == 0 {
|
|
<-api.Srv.Store.System().Save(systemSecurityLastTime)
|
|
} else {
|
|
<-api.Srv.Store.System().Update(systemSecurityLastTime)
|
|
}
|
|
|
|
if ucr := <-api.Srv.Store.User().GetTotalUsersCount(); ucr.Err == nil {
|
|
v.Set(utils.PROP_DIAGNOSTIC_USER_COUNT, strconv.FormatInt(ucr.Data.(int64), 10))
|
|
}
|
|
|
|
if ucr := <-api.Srv.Store.Status().GetTotalActiveUsersCount(); ucr.Err == nil {
|
|
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"))
|
|
return
|
|
}
|
|
|
|
bulletins := model.SecurityBulletinsFromJson(res.Body)
|
|
ioutil.ReadAll(res.Body)
|
|
res.Body.Close()
|
|
|
|
for _, bulletin := range bulletins {
|
|
if bulletin.AppliesToVersion == model.CurrentVersion {
|
|
if props["SecurityBulletin_"+bulletin.Id] == "" {
|
|
if results := <-api.Srv.Store.User().GetSystemAdminProfiles(); results.Err != nil {
|
|
l4g.Error(utils.T("mattermost.system_admins.error"))
|
|
return
|
|
} else {
|
|
users := results.Data.(map[string]*model.User)
|
|
|
|
resBody, err := http.Get(utils.DIAGNOSTIC_URL + "/bulletins/" + bulletin.Id)
|
|
if err != nil {
|
|
l4g.Error(utils.T("mattermost.security_bulletin.error"))
|
|
return
|
|
}
|
|
|
|
body, err := ioutil.ReadAll(resBody.Body)
|
|
res.Body.Close()
|
|
if err != nil || resBody.StatusCode != 200 {
|
|
l4g.Error(utils.T("mattermost.security_bulletin_read.error"))
|
|
return
|
|
}
|
|
|
|
for _, user := range users {
|
|
l4g.Info(utils.T("mattermost.send_bulletin.info"), bulletin.Id, user.Email)
|
|
utils.SendMail(user.Email, utils.T("mattermost.bulletin.subject"), string(body))
|
|
}
|
|
}
|
|
|
|
bulletinSeen := &model.System{Name: "SecurityBulletin_" + bulletin.Id, Value: bulletin.Id}
|
|
<-api.Srv.Store.System().Save(bulletinSeen)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func runSecurityAndDiagnosticsJob() {
|
|
doSecurityAndDiagnostics()
|
|
model.CreateRecurringTask("Security and Diagnostics", doSecurityAndDiagnostics, time.Hour*4)
|
|
}
|
|
|
|
func parseCmds() {
|
|
flag.Usage = func() {
|
|
fmt.Fprintln(os.Stderr, usage)
|
|
}
|
|
|
|
flag.StringVar(&flagConfigFile, "config", "config.json", "")
|
|
flag.StringVar(&flagUsername, "username", "", "")
|
|
flag.StringVar(&flagLicenseFile, "license", "", "")
|
|
flag.StringVar(&flagEmail, "email", "", "")
|
|
flag.StringVar(&flagPassword, "password", "", "")
|
|
flag.StringVar(&flagTeamName, "team_name", "", "")
|
|
flag.StringVar(&flagChannelName, "channel_name", "", "")
|
|
flag.StringVar(&flagConfirmBackup, "confirm_backup", "", "")
|
|
flag.StringVar(&flagFromAuth, "from_auth", "", "")
|
|
flag.StringVar(&flagToAuth, "to_auth", "", "")
|
|
flag.StringVar(&flagMatchField, "match_field", "email", "")
|
|
flag.StringVar(&flagRole, "role", "", "")
|
|
flag.StringVar(&flagChannelType, "channel_type", "O", "")
|
|
flag.StringVar(&flagChannelHeader, "channel_header", "", "")
|
|
flag.StringVar(&flagChannelPurpose, "channel_purpose", "", "")
|
|
|
|
flag.BoolVar(&flagCmdUpdateDb30, "upgrade_db_30", false, "")
|
|
flag.BoolVar(&flagCmdCreateTeam, "create_team", false, "")
|
|
flag.BoolVar(&flagCmdCreateUser, "create_user", false, "")
|
|
flag.BoolVar(&flagCmdInviteUser, "invite_user", false, "")
|
|
flag.BoolVar(&flagCmdAssignRole, "assign_role", false, "")
|
|
flag.BoolVar(&flagCmdCreateChannel, "create_channel", 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(&flagCmdLeaveTeam, "leave_team", false, "")
|
|
flag.BoolVar(&flagCmdVersion, "version", false, "")
|
|
flag.BoolVar(&flagCmdRunWebClientTests, "run_web_client_tests", false, "")
|
|
flag.BoolVar(&flagCmdRunJavascriptClientTests, "run_javascript_client_tests", false, "")
|
|
flag.BoolVar(&flagCmdResetPassword, "reset_password", false, "")
|
|
flag.BoolVar(&flagCmdResetMfa, "reset_mfa", false, "")
|
|
flag.BoolVar(&flagCmdPermanentDeleteUser, "permanent_delete_user", false, "")
|
|
flag.BoolVar(&flagCmdPermanentDeleteTeam, "permanent_delete_team", false, "")
|
|
flag.BoolVar(&flagCmdPermanentDeleteAllUsers, "permanent_delete_all_users", false, "")
|
|
flag.BoolVar(&flagCmdResetDatabase, "reset_database", false, "")
|
|
flag.BoolVar(&flagCmdRunLdapSync, "ldap_sync", false, "")
|
|
flag.BoolVar(&flagCmdMigrateAccounts, "migrate_accounts", false, "")
|
|
flag.BoolVar(&flagCmdUploadLicense, "upload_license", false, "")
|
|
|
|
flag.Parse()
|
|
|
|
flagRunCmds = (flagCmdCreateTeam ||
|
|
flagCmdCreateUser ||
|
|
flagCmdInviteUser ||
|
|
flagCmdLeaveTeam ||
|
|
flagCmdAssignRole ||
|
|
flagCmdCreateChannel ||
|
|
flagCmdJoinChannel ||
|
|
flagCmdLeaveChannel ||
|
|
flagCmdListChannels ||
|
|
flagCmdRestoreChannel ||
|
|
flagCmdJoinTeam ||
|
|
flagCmdResetPassword ||
|
|
flagCmdResetMfa ||
|
|
flagCmdVersion ||
|
|
flagCmdRunWebClientTests ||
|
|
flagCmdRunJavascriptClientTests ||
|
|
flagCmdPermanentDeleteUser ||
|
|
flagCmdPermanentDeleteTeam ||
|
|
flagCmdPermanentDeleteAllUsers ||
|
|
flagCmdResetDatabase ||
|
|
flagCmdRunLdapSync ||
|
|
flagCmdMigrateAccounts ||
|
|
flagCmdUploadLicense)
|
|
}
|
|
|
|
func runCmds() {
|
|
cmdVersion()
|
|
cmdRunClientTests()
|
|
cmdCreateTeam()
|
|
cmdCreateUser()
|
|
cmdInviteUser()
|
|
cmdLeaveTeam()
|
|
cmdAssignRole()
|
|
cmdCreateChannel()
|
|
cmdJoinChannel()
|
|
cmdLeaveChannel()
|
|
cmdListChannels()
|
|
cmdRestoreChannel()
|
|
cmdJoinTeam()
|
|
cmdResetPassword()
|
|
cmdResetMfa()
|
|
cmdPermDeleteUser()
|
|
cmdPermDeleteTeam()
|
|
cmdPermDeleteAllUsers()
|
|
cmdResetDatabase()
|
|
cmdUploadLicense()
|
|
cmdRunLdapSync()
|
|
cmdRunMigrateAccounts()
|
|
}
|
|
|
|
type TeamForUpgrade struct {
|
|
Id string
|
|
Name string
|
|
}
|
|
|
|
func setupClientTests() {
|
|
*utils.Cfg.TeamSettings.EnableOpenServer = true
|
|
*utils.Cfg.ServiceSettings.EnableCommands = false
|
|
*utils.Cfg.ServiceSettings.EnableOnlyAdminIntegrations = false
|
|
*utils.Cfg.ServiceSettings.EnableCustomEmoji = true
|
|
utils.Cfg.ServiceSettings.EnableIncomingWebhooks = false
|
|
utils.Cfg.ServiceSettings.EnableOutgoingWebhooks = false
|
|
}
|
|
|
|
func executeTestCommand(cmd *exec.Cmd) {
|
|
cmdOutPipe, err := cmd.StdoutPipe()
|
|
if err != nil {
|
|
l4g.Error("Failed to run tests")
|
|
os.Exit(1)
|
|
}
|
|
|
|
cmdOutReader := bufio.NewScanner(cmdOutPipe)
|
|
go func() {
|
|
for cmdOutReader.Scan() {
|
|
fmt.Println(cmdOutReader.Text())
|
|
}
|
|
}()
|
|
|
|
if err := cmd.Run(); err != nil {
|
|
l4g.Error("Client Tests failed")
|
|
os.Exit(1)
|
|
}
|
|
}
|
|
|
|
func runWebClientTests() {
|
|
os.Chdir("webapp")
|
|
cmd := exec.Command("npm", "test")
|
|
executeTestCommand(cmd)
|
|
}
|
|
|
|
func cmdRunClientTests() {
|
|
if flagCmdRunWebClientTests {
|
|
setupClientTests()
|
|
api.StartServer()
|
|
runWebClientTests()
|
|
api.StopServer()
|
|
}
|
|
}
|
|
|
|
// ADDED for 3.0 REMOVE for 3.4
|
|
func cmdUpdateDb30() {
|
|
if flagCmdUpdateDb30 {
|
|
api.Srv = &api.Server{}
|
|
api.Srv.Store = store.NewSqlStoreForUpgrade30()
|
|
store := api.Srv.Store.(*store.SqlStore)
|
|
utils.InitHTML()
|
|
|
|
l4g.Info("Attempting to run special upgrade of the database schema to version 3.0 for user model changes")
|
|
time.Sleep(time.Second)
|
|
|
|
if !store.DoesColumnExist("Users", "TeamId") {
|
|
fmt.Println("**WARNING** the database schema appears to be upgraded to 3.0")
|
|
flushLogAndExit(1)
|
|
}
|
|
|
|
if !(store.SchemaVersion == "2.2.0" ||
|
|
store.SchemaVersion == "2.1.0" ||
|
|
store.SchemaVersion == "2.0.0") {
|
|
fmt.Println("**WARNING** the database schema needs to be version 2.2.0, 2.1.0 or 2.0.0 to upgrade")
|
|
flushLogAndExit(1)
|
|
}
|
|
|
|
fmt.Println("\nPlease see http://www.mattermost.org/upgrade-to-3-0/")
|
|
fmt.Println("**WARNING** This upgrade process will be irreversible.")
|
|
|
|
if len(flagConfirmBackup) == 0 {
|
|
fmt.Print("Have you performed a database backup? (YES/NO): ")
|
|
fmt.Scanln(&flagConfirmBackup)
|
|
}
|
|
|
|
if flagConfirmBackup != "YES" {
|
|
fmt.Fprintln(os.Stderr, "ABORTED: You did not answer YES exactly, in all capitals.")
|
|
flushLogAndExit(1)
|
|
}
|
|
|
|
var teams []*TeamForUpgrade
|
|
|
|
if _, err := store.GetMaster().Select(&teams, "SELECT Id, Name FROM Teams"); err != nil {
|
|
l4g.Error("Failed to load all teams details=%v", err)
|
|
flushLogAndExit(1)
|
|
}
|
|
|
|
if len(flagTeamName) == 0 {
|
|
fmt.Println(fmt.Sprintf("We found %v teams.", len(teams)))
|
|
|
|
for _, team := range teams {
|
|
fmt.Println(team.Name)
|
|
}
|
|
|
|
fmt.Print("Please pick a primary team from the list above: ")
|
|
fmt.Scanln(&flagTeamName)
|
|
}
|
|
|
|
var team *TeamForUpgrade
|
|
for _, t := range teams {
|
|
if t.Name == flagTeamName {
|
|
team = t
|
|
break
|
|
}
|
|
}
|
|
|
|
if team == nil {
|
|
l4g.Error("Failed to find primary team details")
|
|
flushLogAndExit(1)
|
|
}
|
|
|
|
l4g.Info("Starting special 3.0 database upgrade with performed_backup=YES team_name=%v", team.Name)
|
|
l4g.Info("Primary team %v will be left unchanged", team.Name)
|
|
l4g.Info("Upgrading primary team %v", team.Name)
|
|
|
|
uniqueEmails := make(map[string]bool)
|
|
uniqueUsernames := make(map[string]bool)
|
|
uniqueAuths := make(map[string]bool)
|
|
primaryUsers := convertTeamTo30(team.Name, team, uniqueEmails, uniqueUsernames, uniqueAuths)
|
|
|
|
l4g.Info("Upgraded %v users", len(primaryUsers))
|
|
|
|
for _, otherTeam := range teams {
|
|
if otherTeam.Id != team.Id {
|
|
l4g.Info("Upgrading team %v", otherTeam.Name)
|
|
users := convertTeamTo30(team.Name, otherTeam, uniqueEmails, uniqueUsernames, uniqueAuths)
|
|
l4g.Info("Upgraded %v users", len(users))
|
|
|
|
}
|
|
}
|
|
|
|
l4g.Info("Altering other scheme changes needed 3.0 for user model changes")
|
|
|
|
if _, err := store.GetMaster().Exec(`
|
|
UPDATE Channels
|
|
SET
|
|
TeamId = ''
|
|
WHERE
|
|
Type = 'D'
|
|
`,
|
|
); err != nil {
|
|
l4g.Error("Failed to update direct channel types details=%v", err)
|
|
flushLogAndExit(1)
|
|
}
|
|
|
|
if _, err := store.GetMaster().Exec(`
|
|
UPDATE Users
|
|
SET
|
|
AuthData = NULL
|
|
WHERE
|
|
AuthData = ''
|
|
`,
|
|
); err != nil {
|
|
l4g.Error("Failed to update AuthData types details=%v", err)
|
|
flushLogAndExit(1)
|
|
}
|
|
|
|
extraLength := store.GetMaxLengthOfColumnIfExists("Audits", "ExtraInfo")
|
|
if len(extraLength) > 0 && extraLength != "1024" {
|
|
store.AlterColumnTypeIfExists("Audits", "ExtraInfo", "VARCHAR(1024)", "VARCHAR(1024)")
|
|
}
|
|
|
|
actionLength := store.GetMaxLengthOfColumnIfExists("Audits", "Action")
|
|
if len(actionLength) > 0 && actionLength != "512" {
|
|
store.AlterColumnTypeIfExists("Audits", "Action", "VARCHAR(512)", "VARCHAR(512)")
|
|
}
|
|
|
|
if store.DoesColumnExist("Sessions", "TeamId") {
|
|
store.RemoveColumnIfExists("Sessions", "TeamId")
|
|
store.GetMaster().Exec(`TRUNCATE Sessions`)
|
|
}
|
|
|
|
// ADDED for 2.2 REMOVE for 2.6
|
|
store.CreateColumnIfNotExists("Users", "MfaActive", "tinyint(1)", "boolean", "0")
|
|
store.CreateColumnIfNotExists("Users", "MfaSecret", "varchar(128)", "character varying(128)", "")
|
|
|
|
// ADDED for 2.2 REMOVE for 2.6
|
|
if store.DoesColumnExist("Users", "TeamId") {
|
|
store.RemoveIndexIfExists("idx_users_team_id", "Users")
|
|
store.CreateUniqueIndexIfNotExists("idx_users_email_unique", "Users", "Email")
|
|
store.CreateUniqueIndexIfNotExists("idx_users_username_unique", "Users", "Username")
|
|
store.CreateUniqueIndexIfNotExists("idx_users_authdata_unique", "Users", "AuthData")
|
|
store.RemoveColumnIfExists("Teams", "AllowTeamListing")
|
|
store.RemoveColumnIfExists("Users", "TeamId")
|
|
}
|
|
|
|
l4g.Info("Finished running special upgrade of the database schema to version 3.0 for user model changes")
|
|
|
|
if result := <-store.System().Update(&model.System{Name: "Version", Value: model.CurrentVersion}); result.Err != nil {
|
|
l4g.Error("Failed to update system schema version details=%v", result.Err)
|
|
flushLogAndExit(1)
|
|
}
|
|
|
|
l4g.Info(utils.T("store.sql.upgraded.warn"), model.CurrentVersion)
|
|
fmt.Println("**SUCCESS** with upgrade")
|
|
|
|
flushLogAndExit(0)
|
|
}
|
|
}
|
|
|
|
type UserForUpgrade struct {
|
|
Id string
|
|
Username string
|
|
Email string
|
|
Roles string
|
|
TeamId string
|
|
AuthData *string
|
|
}
|
|
|
|
func convertTeamTo30(primaryTeamName string, team *TeamForUpgrade, uniqueEmails map[string]bool, uniqueUsernames map[string]bool, uniqueAuths map[string]bool) []*UserForUpgrade {
|
|
store := api.Srv.Store.(*store.SqlStore)
|
|
var users []*UserForUpgrade
|
|
if _, err := store.GetMaster().Select(&users, "SELECT Users.Id, Users.Username, Users.Email, Users.Roles, Users.TeamId, Users.AuthData FROM Users WHERE Users.TeamId = :TeamId", map[string]interface{}{"TeamId": team.Id}); err != nil {
|
|
l4g.Error("Failed to load profiles for team details=%v", err)
|
|
flushLogAndExit(1)
|
|
}
|
|
|
|
var members []*model.TeamMember
|
|
if result := <-api.Srv.Store.Team().GetMembers(team.Id); result.Err != nil {
|
|
l4g.Error("Failed to load team membership details=%v", result.Err)
|
|
flushLogAndExit(1)
|
|
} else {
|
|
members = result.Data.([]*model.TeamMember)
|
|
}
|
|
|
|
for _, user := range users {
|
|
shouldUpdateUser := false
|
|
shouldUpdateRole := false
|
|
previousRole := user.Roles
|
|
previousEmail := user.Email
|
|
previousUsername := user.Username
|
|
|
|
member := &model.TeamMember{
|
|
TeamId: team.Id,
|
|
UserId: user.Id,
|
|
}
|
|
|
|
if model.IsInRole(user.Roles, model.ROLE_TEAM_ADMIN) {
|
|
member.Roles = model.ROLE_TEAM_ADMIN
|
|
user.Roles = ""
|
|
shouldUpdateRole = true
|
|
}
|
|
|
|
exists := false
|
|
for _, member := range members {
|
|
if member.UserId == user.Id {
|
|
exists = true
|
|
break
|
|
}
|
|
}
|
|
|
|
if !exists {
|
|
if result := <-api.Srv.Store.Team().SaveMember(member); result.Err != nil {
|
|
l4g.Error("Failed to save membership for %v details=%v", user.Email, result.Err)
|
|
flushLogAndExit(1)
|
|
}
|
|
}
|
|
|
|
err := api.MoveFile(
|
|
"teams/"+team.Id+"/users/"+user.Id+"/profile.png",
|
|
"users/"+user.Id+"/profile.png",
|
|
)
|
|
|
|
if err != nil {
|
|
l4g.Warn("No profile image to move for %v", user.Email)
|
|
}
|
|
|
|
if uniqueEmails[user.Email] {
|
|
shouldUpdateUser = true
|
|
emailParts := strings.Split(user.Email, "@")
|
|
if len(emailParts) == 2 {
|
|
user.Email = emailParts[0] + "+" + team.Name + "@" + emailParts[1]
|
|
} else {
|
|
user.Email = user.Email + "." + team.Name
|
|
}
|
|
|
|
if len(user.Email) > 127 {
|
|
user.Email = user.Email[:127]
|
|
}
|
|
}
|
|
|
|
if uniqueUsernames[user.Username] {
|
|
shouldUpdateUser = true
|
|
user.Username = team.Name + "." + user.Username
|
|
|
|
if len(user.Username) > 63 {
|
|
user.Username = user.Username[:63]
|
|
}
|
|
}
|
|
|
|
if user.AuthData != nil && *user.AuthData != "" && uniqueAuths[*user.AuthData] {
|
|
shouldUpdateUser = true
|
|
}
|
|
|
|
if shouldUpdateUser {
|
|
if _, err := store.GetMaster().Exec(`
|
|
UPDATE Users
|
|
SET
|
|
Email = :Email,
|
|
Username = :Username,
|
|
Roles = :Roles,
|
|
AuthService = '',
|
|
AuthData = NULL
|
|
WHERE
|
|
Id = :Id
|
|
`,
|
|
map[string]interface{}{
|
|
"Email": user.Email,
|
|
"Username": user.Username,
|
|
"Roles": user.Roles,
|
|
"Id": user.Id,
|
|
},
|
|
); err != nil {
|
|
l4g.Error("Failed to update user %v details=%v", user.Email, err)
|
|
flushLogAndExit(1)
|
|
}
|
|
|
|
l4g.Info("modified user_id=%v, changed email from=%v to=%v, changed username from=%v to %v changed roles from=%v to=%v", user.Id, previousEmail, user.Email, previousUsername, user.Username, previousRole, user.Roles)
|
|
|
|
emailChanged := previousEmail != user.Email
|
|
usernameChanged := previousUsername != user.Username
|
|
|
|
if emailChanged || usernameChanged {
|
|
bodyPage := utils.NewHTMLTemplate("upgrade_30_body", "")
|
|
|
|
EmailChanged := ""
|
|
UsernameChanged := ""
|
|
|
|
if emailChanged {
|
|
EmailChanged = "true"
|
|
}
|
|
|
|
if usernameChanged {
|
|
UsernameChanged = "true"
|
|
}
|
|
|
|
bodyPage.Html["Info"] = template.HTML(utils.T("api.templates.upgrade_30_body.info",
|
|
map[string]interface{}{
|
|
"SiteName": utils.ClientCfg["SiteName"],
|
|
"TeamName": team.Name,
|
|
"Email": user.Email,
|
|
"Username": user.Username,
|
|
"EmailChanged": EmailChanged,
|
|
"UsernameChanged": UsernameChanged,
|
|
}))
|
|
|
|
utils.SendMail(
|
|
previousEmail,
|
|
utils.T("api.templates.upgrade_30_subject.info"),
|
|
bodyPage.Render(),
|
|
)
|
|
}
|
|
}
|
|
|
|
if shouldUpdateRole {
|
|
if _, err := store.GetMaster().Exec(`
|
|
UPDATE Users
|
|
SET
|
|
Roles = ''
|
|
WHERE
|
|
Id = :Id
|
|
`,
|
|
map[string]interface{}{
|
|
"Id": user.Id,
|
|
},
|
|
); err != nil {
|
|
l4g.Error("Failed to update user role %v details=%v", user.Email, err)
|
|
flushLogAndExit(1)
|
|
}
|
|
|
|
l4g.Info("modified user_id=%v, changed roles from=%v to=%v", user.Id, previousRole, user.Roles)
|
|
}
|
|
|
|
uniqueEmails[user.Email] = true
|
|
uniqueUsernames[user.Username] = true
|
|
|
|
if user.AuthData != nil && *user.AuthData != "" {
|
|
uniqueAuths[*user.AuthData] = true
|
|
}
|
|
}
|
|
|
|
return users
|
|
}
|
|
|
|
func cmdCreateTeam() {
|
|
if flagCmdCreateTeam {
|
|
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)
|
|
}
|
|
|
|
c := getMockContext()
|
|
|
|
team := &model.Team{}
|
|
team.DisplayName = flagTeamName
|
|
team.Name = flagTeamName
|
|
team.Email = flagEmail
|
|
team.Type = model.TEAM_OPEN
|
|
|
|
api.CreateTeam(c, team)
|
|
if c.Err != nil {
|
|
if c.Err.Id != "store.sql_team.save.domain_exists.app_error" {
|
|
l4g.Error("%v", c.Err)
|
|
flushLogAndExit(1)
|
|
}
|
|
}
|
|
|
|
os.Exit(0)
|
|
}
|
|
}
|
|
|
|
func cmdCreateUser() {
|
|
if flagCmdCreateUser {
|
|
if len(flagEmail) == 0 {
|
|
fmt.Fprintln(os.Stderr, "flag needs an argument: -email")
|
|
flag.Usage()
|
|
os.Exit(1)
|
|
}
|
|
|
|
if len(flagPassword) == 0 {
|
|
fmt.Fprintln(os.Stderr, "flag needs an argument: -password")
|
|
flag.Usage()
|
|
os.Exit(1)
|
|
}
|
|
|
|
var team *model.Team
|
|
user := &model.User{}
|
|
user.Email = flagEmail
|
|
user.Password = flagPassword
|
|
|
|
if len(flagUsername) == 0 {
|
|
splits := strings.Split(strings.Replace(flagEmail, "@", " ", -1), " ")
|
|
user.Username = splits[0]
|
|
} else {
|
|
user.Username = flagUsername
|
|
}
|
|
|
|
if len(flagTeamName) > 0 {
|
|
if result := <-api.Srv.Store.Team().GetByName(flagTeamName); result.Err != nil {
|
|
l4g.Error("%v", result.Err)
|
|
flushLogAndExit(1)
|
|
} else {
|
|
team = result.Data.(*model.Team)
|
|
}
|
|
}
|
|
|
|
ruser, err := api.CreateUser(user)
|
|
if err != nil {
|
|
if err.Id != "store.sql_user.save.email_exists.app_error" {
|
|
l4g.Error("%v", err)
|
|
flushLogAndExit(1)
|
|
}
|
|
}
|
|
|
|
if team != nil {
|
|
err = api.JoinUserToTeam(team, ruser)
|
|
if err != nil {
|
|
l4g.Error("%v", err)
|
|
flushLogAndExit(1)
|
|
}
|
|
}
|
|
|
|
os.Exit(0)
|
|
}
|
|
}
|
|
|
|
func cmdInviteUser() {
|
|
if flagCmdInviteUser {
|
|
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(*utils.Cfg.ServiceSettings.SiteURL) == 0 {
|
|
fmt.Fprintln(os.Stderr, "SiteURL must be specified in config.json")
|
|
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(team.Email); result.Err != nil {
|
|
l4g.Error("%v", result.Err)
|
|
flushLogAndExit(1)
|
|
} else {
|
|
user = result.Data.(*model.User)
|
|
}
|
|
|
|
invites := []string{flagEmail}
|
|
c := getMockContext()
|
|
api.InviteMembers(c, team, user, invites)
|
|
|
|
os.Exit(0)
|
|
}
|
|
}
|
|
|
|
func cmdVersion() {
|
|
if flagCmdVersion {
|
|
fmt.Fprintln(os.Stderr, "Version: "+model.CurrentVersion)
|
|
fmt.Fprintln(os.Stderr, "Build Number: "+model.BuildNumber)
|
|
fmt.Fprintln(os.Stderr, "Build Date: "+model.BuildDate)
|
|
fmt.Fprintln(os.Stderr, "Build Hash: "+model.BuildHash)
|
|
fmt.Fprintln(os.Stderr, "Build Enterprise Ready: "+model.BuildEnterpriseReady)
|
|
fmt.Fprintln(os.Stderr, "DB Version: "+api.Srv.Store.(*store.SqlStore).SchemaVersion)
|
|
|
|
os.Exit(0)
|
|
}
|
|
}
|
|
|
|
func cmdAssignRole() {
|
|
if flagCmdAssignRole {
|
|
if len(flagEmail) == 0 {
|
|
fmt.Fprintln(os.Stderr, "flag needs an argument: -email")
|
|
flag.Usage()
|
|
os.Exit(1)
|
|
}
|
|
|
|
if !model.IsValidUserRoles(flagRole) {
|
|
fmt.Fprintln(os.Stderr, "flag invalid argument: -role")
|
|
flag.Usage()
|
|
os.Exit(1)
|
|
}
|
|
|
|
c := getMockContext()
|
|
|
|
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)
|
|
}
|
|
|
|
if !user.IsInRole(flagRole) {
|
|
api.UpdateUserRoles(c, user, flagRole)
|
|
}
|
|
|
|
os.Exit(0)
|
|
}
|
|
}
|
|
|
|
func cmdCreateChannel() {
|
|
if flagCmdCreateChannel {
|
|
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 len(flagEmail) == 0 {
|
|
fmt.Fprintln(os.Stderr, "flag needs an argument: -email")
|
|
flag.Usage()
|
|
os.Exit(1)
|
|
}
|
|
|
|
if flagChannelType != "O" && flagChannelType != "P" {
|
|
fmt.Fprintln(os.Stderr, "flag channel_type must have on of the following values: O or P")
|
|
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 %v", utils.T(result.Err.Message), result.Err.DetailedError)
|
|
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 %v", utils.T(result.Err.Message), result.Err.DetailedError)
|
|
flushLogAndExit(1)
|
|
} else {
|
|
user = result.Data.(*model.User)
|
|
}
|
|
|
|
c := getMockContext()
|
|
c.Session.UserId = user.Id
|
|
|
|
channel := &model.Channel{}
|
|
channel.DisplayName = flagChannelName
|
|
channel.CreatorId = user.Id
|
|
channel.Name = flagChannelName
|
|
channel.TeamId = team.Id
|
|
channel.Type = flagChannelType
|
|
channel.Header = flagChannelHeader
|
|
channel.Purpose = flagChannelPurpose
|
|
|
|
if _, err := api.CreateChannel(c, channel, true); err != nil {
|
|
l4g.Error("%v %v", utils.T(err.Message), err.DetailedError)
|
|
flushLogAndExit(1)
|
|
}
|
|
|
|
os.Exit(0)
|
|
}
|
|
}
|
|
|
|
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: -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)
|
|
}
|
|
|
|
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)
|
|
}
|
|
|
|
err := api.JoinUserToTeam(team, user)
|
|
if err != nil {
|
|
l4g.Error("%v", err)
|
|
flushLogAndExit(1)
|
|
}
|
|
|
|
os.Exit(0)
|
|
}
|
|
}
|
|
|
|
func cmdLeaveTeam() {
|
|
if flagCmdLeaveTeam {
|
|
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)
|
|
}
|
|
|
|
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)
|
|
}
|
|
|
|
err := api.LeaveTeam(team, user)
|
|
|
|
if err != nil {
|
|
l4g.Error("%v", err)
|
|
flushLogAndExit(1)
|
|
}
|
|
|
|
os.Exit(0)
|
|
}
|
|
}
|
|
|
|
func cmdResetPassword() {
|
|
if flagCmdResetPassword {
|
|
if len(flagEmail) == 0 {
|
|
fmt.Fprintln(os.Stderr, "flag needs an argument: -email")
|
|
flag.Usage()
|
|
os.Exit(1)
|
|
}
|
|
|
|
if len(flagPassword) == 0 {
|
|
fmt.Fprintln(os.Stderr, "flag needs an argument: -password")
|
|
flag.Usage()
|
|
os.Exit(1)
|
|
}
|
|
|
|
if len(flagPassword) < 5 {
|
|
fmt.Fprintln(os.Stderr, "flag invalid argument needs to be more than 4 characters: -password")
|
|
flag.Usage()
|
|
os.Exit(1)
|
|
}
|
|
|
|
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)
|
|
}
|
|
|
|
if result := <-api.Srv.Store.User().UpdatePassword(user.Id, model.HashPassword(flagPassword)); result.Err != nil {
|
|
l4g.Error("%v", result.Err)
|
|
flushLogAndExit(1)
|
|
}
|
|
|
|
os.Exit(0)
|
|
}
|
|
}
|
|
|
|
func cmdResetMfa() {
|
|
if flagCmdResetMfa {
|
|
if len(flagEmail) == 0 && len(flagUsername) == 0 {
|
|
fmt.Fprintln(os.Stderr, "flag needs an argument: -email OR -username")
|
|
flag.Usage()
|
|
os.Exit(1)
|
|
}
|
|
|
|
var user *model.User
|
|
if len(flagEmail) > 0 {
|
|
if result := <-api.Srv.Store.User().GetByEmail(flagEmail); result.Err != nil {
|
|
l4g.Error("%v", result.Err)
|
|
flushLogAndExit(1)
|
|
} else {
|
|
user = result.Data.(*model.User)
|
|
}
|
|
} else {
|
|
if result := <-api.Srv.Store.User().GetByUsername(flagUsername); result.Err != nil {
|
|
l4g.Error("%v", result.Err)
|
|
flushLogAndExit(1)
|
|
} else {
|
|
user = result.Data.(*model.User)
|
|
}
|
|
}
|
|
|
|
if err := api.DeactivateMfa(user.Id); err != nil {
|
|
l4g.Error("%v", err)
|
|
flushLogAndExit(1)
|
|
}
|
|
|
|
os.Exit(0)
|
|
}
|
|
}
|
|
|
|
func cmdPermDeleteUser() {
|
|
if flagCmdPermanentDeleteUser {
|
|
if len(flagEmail) == 0 {
|
|
fmt.Fprintln(os.Stderr, "flag needs an argument: -email")
|
|
flag.Usage()
|
|
os.Exit(1)
|
|
}
|
|
|
|
c := getMockContext()
|
|
|
|
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)
|
|
}
|
|
|
|
if len(flagConfirmBackup) == 0 {
|
|
fmt.Print("Have you performed a database backup? (YES/NO): ")
|
|
fmt.Scanln(&flagConfirmBackup)
|
|
}
|
|
|
|
if flagConfirmBackup != "YES" {
|
|
fmt.Print("ABORTED: You did not answer YES exactly, in all capitals.")
|
|
flushLogAndExit(1)
|
|
}
|
|
|
|
var confirm string
|
|
fmt.Printf("Are you sure you want to delete the user %v? All data will be permanently deleted? (YES/NO): ", user.Email)
|
|
fmt.Scanln(&confirm)
|
|
if confirm != "YES" {
|
|
fmt.Print("ABORTED: You did not answer YES exactly, in all capitals.")
|
|
flushLogAndExit(1)
|
|
}
|
|
|
|
if err := api.PermanentDeleteUser(c, user); err != nil {
|
|
l4g.Error("%v", err)
|
|
flushLogAndExit(1)
|
|
} else {
|
|
fmt.Print("SUCCESS: User deleted.")
|
|
flushLogAndExit(0)
|
|
}
|
|
}
|
|
}
|
|
|
|
func cmdPermDeleteTeam() {
|
|
if flagCmdPermanentDeleteTeam {
|
|
if len(flagTeamName) == 0 {
|
|
fmt.Fprintln(os.Stderr, "flag needs an argument: -team_name")
|
|
flag.Usage()
|
|
os.Exit(1)
|
|
}
|
|
|
|
c := getMockContext()
|
|
|
|
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 len(flagConfirmBackup) == 0 {
|
|
fmt.Print("Have you performed a database backup? (YES/NO): ")
|
|
fmt.Scanln(&flagConfirmBackup)
|
|
}
|
|
|
|
if flagConfirmBackup != "YES" {
|
|
fmt.Print("ABORTED: You did not answer YES exactly, in all capitals.")
|
|
flushLogAndExit(1)
|
|
}
|
|
|
|
var confirm string
|
|
fmt.Printf("Are you sure you want to delete the team %v? All data will be permanently deleted? (YES/NO): ", team.Name)
|
|
fmt.Scanln(&confirm)
|
|
if confirm != "YES" {
|
|
fmt.Print("ABORTED: You did not answer YES exactly, in all capitals.")
|
|
flushLogAndExit(1)
|
|
}
|
|
|
|
if err := api.PermanentDeleteTeam(c, team); err != nil {
|
|
l4g.Error("%v", err)
|
|
flushLogAndExit(1)
|
|
} else {
|
|
fmt.Print("SUCCESS: Team deleted.")
|
|
flushLogAndExit(0)
|
|
}
|
|
}
|
|
}
|
|
|
|
func cmdPermDeleteAllUsers() {
|
|
if flagCmdPermanentDeleteAllUsers {
|
|
c := getMockContext()
|
|
|
|
if len(flagConfirmBackup) == 0 {
|
|
fmt.Print("Have you performed a database backup? (YES/NO): ")
|
|
fmt.Scanln(&flagConfirmBackup)
|
|
}
|
|
|
|
if flagConfirmBackup != "YES" {
|
|
fmt.Print("ABORTED: You did not answer YES exactly, in all capitals.")
|
|
flushLogAndExit(1)
|
|
}
|
|
|
|
var confirm string
|
|
fmt.Printf("Are you sure you want to delete all the users? All data will be permanently deleted? (YES/NO): ")
|
|
fmt.Scanln(&confirm)
|
|
if confirm != "YES" {
|
|
fmt.Print("ABORTED: You did not answer YES exactly, in all capitals.")
|
|
flushLogAndExit(1)
|
|
}
|
|
|
|
if err := api.PermanentDeleteAllUsers(c); err != nil {
|
|
l4g.Error("%v", err)
|
|
flushLogAndExit(1)
|
|
} else {
|
|
fmt.Print("SUCCESS: All users deleted.")
|
|
flushLogAndExit(0)
|
|
}
|
|
}
|
|
}
|
|
|
|
func cmdResetDatabase() {
|
|
if flagCmdResetDatabase {
|
|
|
|
if len(flagConfirmBackup) == 0 {
|
|
fmt.Print("Have you performed a database backup? (YES/NO): ")
|
|
fmt.Scanln(&flagConfirmBackup)
|
|
}
|
|
|
|
if flagConfirmBackup != "YES" {
|
|
fmt.Print("ABORTED: You did not answer YES exactly, in all capitals.")
|
|
flushLogAndExit(1)
|
|
}
|
|
|
|
var confirm string
|
|
fmt.Printf("Are you sure you want to delete everything? ALL data will be permanently deleted? (YES/NO): ")
|
|
fmt.Scanln(&confirm)
|
|
if confirm != "YES" {
|
|
fmt.Print("ABORTED: You did not answer YES exactly, in all capitals.")
|
|
flushLogAndExit(1)
|
|
}
|
|
|
|
api.Srv.Store.DropAllTables()
|
|
fmt.Print("SUCCESS: Database reset.")
|
|
flushLogAndExit(0)
|
|
}
|
|
|
|
}
|
|
|
|
func cmdRunLdapSync() {
|
|
if flagCmdRunLdapSync {
|
|
if ldapI := einterfaces.GetLdapInterface(); ldapI != nil {
|
|
if err := ldapI.Syncronize(); err != nil {
|
|
fmt.Println("ERROR: Ldap Syncronization Failed")
|
|
l4g.Error("%v", err.Error())
|
|
flushLogAndExit(1)
|
|
} else {
|
|
fmt.Println("SUCCESS: Ldap Syncronization Complete")
|
|
flushLogAndExit(0)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func cmdRunMigrateAccounts() {
|
|
if flagCmdMigrateAccounts {
|
|
if len(flagFromAuth) == 0 || (flagFromAuth != "email" && flagFromAuth != "gitlab" && flagFromAuth != "saml") {
|
|
fmt.Fprintln(os.Stderr, "flag needs an argument: -from_auth")
|
|
flag.Usage()
|
|
os.Exit(1)
|
|
}
|
|
|
|
if len(flagToAuth) == 0 || flagToAuth != "ldap" {
|
|
fmt.Fprintln(os.Stderr, "flag needs an argument: -from_auth")
|
|
flag.Usage()
|
|
os.Exit(1)
|
|
}
|
|
|
|
// Email auth in Mattermost system is represented by ""
|
|
if flagFromAuth == "email" {
|
|
flagFromAuth = ""
|
|
}
|
|
|
|
if len(flagMatchField) == 0 || (flagMatchField != "email" && flagMatchField != "username") {
|
|
fmt.Fprintln(os.Stderr, "flag needs an argument: -match_field")
|
|
flag.Usage()
|
|
os.Exit(1)
|
|
}
|
|
|
|
if migrate := einterfaces.GetAccountMigrationInterface(); migrate != nil {
|
|
if err := migrate.MigrateToLdap(flagFromAuth, flagMatchField); err != nil {
|
|
fmt.Println("ERROR: Account migration failed.")
|
|
l4g.Error("%v", err.Error())
|
|
flushLogAndExit(1)
|
|
} else {
|
|
fmt.Println("SUCCESS: Account migration complete.")
|
|
flushLogAndExit(0)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func cmdUploadLicense() {
|
|
if flagCmdUploadLicense {
|
|
if model.BuildEnterpriseReady != "true" {
|
|
fmt.Fprintln(os.Stderr, "build must be enterprise ready")
|
|
os.Exit(1)
|
|
}
|
|
|
|
if len(flagLicenseFile) == 0 {
|
|
fmt.Fprintln(os.Stderr, "flag needs an argument: -team_name")
|
|
flag.Usage()
|
|
os.Exit(1)
|
|
}
|
|
|
|
var fileBytes []byte
|
|
var err error
|
|
if fileBytes, err = ioutil.ReadFile(flagLicenseFile); err != nil {
|
|
l4g.Error("%v", err)
|
|
flushLogAndExit(1)
|
|
}
|
|
|
|
if _, err := api.SaveLicense(fileBytes); err != nil {
|
|
l4g.Error("%v", err)
|
|
flushLogAndExit(1)
|
|
} else {
|
|
flushLogAndExit(0)
|
|
}
|
|
|
|
flushLogAndExit(0)
|
|
}
|
|
}
|
|
|
|
func flushLogAndExit(code int) {
|
|
l4g.Close()
|
|
time.Sleep(time.Second)
|
|
os.Exit(code)
|
|
}
|
|
|
|
func getMockContext() *api.Context {
|
|
c := &api.Context{}
|
|
c.RequestId = model.NewId()
|
|
c.IpAddress = "cmd_line"
|
|
c.T = utils.TfuncWithFallback(model.DEFAULT_LOCALE)
|
|
c.Locale = model.DEFAULT_LOCALE
|
|
return c
|
|
}
|
|
|
|
var usage = `Mattermost commands to help configure the system
|
|
|
|
NAME:
|
|
platform -- platform configuation tool
|
|
|
|
USAGE:
|
|
platform [options]
|
|
|
|
FLAGS:
|
|
-config="config.json" Path to the config file
|
|
|
|
-username="someuser" Username used in other commands
|
|
|
|
-license="ex.mattermost-license" Path to your license file
|
|
|
|
-email="user@example.com" Email address used in other commands
|
|
|
|
-password="mypassword" Password used in other commands
|
|
|
|
-team_name="name" The team name used in other commands
|
|
|
|
-channel_name="name" The channel name used in other commands
|
|
|
|
-channel_header="string" The channel header used in other commands
|
|
|
|
-channel_purpose="string" The channel purpose used in other commands
|
|
|
|
-channel_type="type" The channel type used in other commands
|
|
valid values are
|
|
"O" - public channel
|
|
"P" - private group
|
|
|
|
-role="system_admin" The role used in other commands
|
|
valid values are
|
|
"" - The empty role is basic user
|
|
permissions
|
|
"system_admin" - Represents a system
|
|
admin who has access to all teams
|
|
and configuration settings.
|
|
COMMANDS:
|
|
-create_team Creates a team. It requires the -team_name
|
|
and -email flag to create a team.
|
|
Example:
|
|
platform -create_team -team_name="name" -email="user@example.com"
|
|
|
|
-create_user Creates a user. It requires the -email and -password flag
|
|
and -team_name and -username are optional to create a user.
|
|
Example:
|
|
platform -create_user -team_name="name" -email="user@example.com" -password="mypassword" -username="user"
|
|
|
|
-invite_user Invites a user to a team by email. It requires the -team_name
|
|
and -email flags.
|
|
Example:
|
|
platform -invite_user -team_name="name" -email="user@example.com"
|
|
|
|
-leave_team Removes a user from a team. It requires the -team_name
|
|
and -email.
|
|
Example:
|
|
platform -remove_user_from_team -team_name="name" -email="user@example.com"
|
|
|
|
-join_team Joins a user to the team. It required the -email and
|
|
-team_name. You may need to logout of your current session
|
|
for the new team to be applied.
|
|
Example:
|
|
platform -join_team -email="user@example.com" -team_name="name"
|
|
|
|
-assign_role Assigns role to a user. It requires the -role and
|
|
-email flag. You may need to log out
|
|
of your current sessions for the new role to be
|
|
applied.
|
|
Example:
|
|
platform -assign_role -email="user@example.com" -role="system_admin"
|
|
|
|
-create_channel Create a new channel in the specified team. It requires the -email,
|
|
-team_name, -channel_name, -channel_type flags. Optional you can set
|
|
the -channel_header and -channel_purpose.
|
|
|
|
Example:
|
|
platform -create_channel -email="user@example.com" -team_name="name" -channel_name="channel_name" -channel_type="O"
|
|
|
|
-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:
|
|
platform -reset_password -email="user@example.com" -password="newpassword"
|
|
|
|
-reset_mfa Turns off multi-factor authentication for a user. It requires the
|
|
-email or -username flag.
|
|
Example:
|
|
platform -reset_mfa -username="someuser"
|
|
|
|
-reset_database Completely erases the database causing the loss of all data. This
|
|
will reset Mattermost to it's initial state. (note this will not
|
|
erase your configuration.)
|
|
|
|
Example:
|
|
platform -reset_database
|
|
|
|
-permanent_delete_user Permanently deletes a user and all related information
|
|
including posts from the database. It requires the
|
|
-email flag. You may need to restart the
|
|
server to invalidate the cache
|
|
Example:
|
|
platform -permanent_delete_user -email="user@example.com"
|
|
|
|
-permanent_delete_all_users Permanently deletes all users and all related information
|
|
including posts from the database. It requires the
|
|
-team_name, and -email flag. You may need to restart the
|
|
server to invalidate the cache
|
|
Example:
|
|
platform -permanent_delete_all_users -team_name="name" -email="user@example.com"
|
|
|
|
-permanent_delete_team Permanently deletes a team allong with
|
|
all related information including posts from the database.
|
|
It requires the -team_name flag. You may need to restart
|
|
the server to invalidate the cache.
|
|
Example:
|
|
platform -permanent_delete_team -team_name="name"
|
|
|
|
-upload_license Uploads a license to the server. Requires the -license flag.
|
|
|
|
Example:
|
|
platform -upload_license -license="/path/to/license/example.mattermost-license"
|
|
|
|
-migrate_accounts Migrates accounts from one authentication provider to anouther. Requires -from_auth -to_auth and -match_field flags. Supported options for -from_auth: email, gitlab, saml. Supported options for -to_auth ldap. Supported options for -match_field email, username. Will display any accounts that are not migrated succesfully.
|
|
|
|
Example:
|
|
platform -migrate_accounts -from_auth email -to_auth ldap -match_field username
|
|
|
|
-upgrade_db_30 Upgrades the database from a version 2.x schema to version 3 see
|
|
http://www.mattermost.org/upgrading-to-mattermost-3-0/
|
|
|
|
Example:
|
|
platform -upgrade_db_30
|
|
|
|
-version Display the current of the Mattermost platform
|
|
|
|
-help Displays this help page`
|