Files
mattermost/mattermost.go
Corey Hulen 2e5617c29b PLT-2057 User as a first class object (#2648)
* Adding TeamMember to system

* Fixing all unit tests on the backend

* Fixing merge conflicts

* Fixing merge conflict

* Adding javascript unit tests

* Adding TeamMember to system

* Fixing all unit tests on the backend

* Fixing merge conflicts

* Fixing merge conflict

* Adding javascript unit tests

* Adding client side unit test

* Cleaning up the clint side tests

* Fixing msg

* Adding more client side unit tests

* Adding more using tests

* Adding last bit of client side unit tests and adding make cmd

* Fixing bad merge

* Fixing libraries

* Updating to new client side API

* Fixing borken unit test

* Fixing unit tests

* ugg...trying to beat gofmt

* ugg...trying to beat gofmt

* Cleaning up remainder of the server side routes

* Adding inital load api

* Increased coverage of webhook unit tests (#2660)

* Adding loading ... to root html

* Fixing bad merge

* Removing explicit content type so superagent will guess corectly (#2685)

* Fixing merge and unit tests

* Adding create team UI

* Fixing signup flows

* Adding LDAP unit tests and enterprise unit test helper (#2702)

* Add the ability to reset MFA from the commandline (#2706)

* Fixing compliance unit tests

* Fixing client side tests

* Adding open server to system console

* Moving websocket connection

* Fixing unit test

* Fixing unit tests

* Fixing unit tests

* Adding nickname and more LDAP unit tests (#2717)

* Adding join open teams

* Cleaning up all TODOs in the code

* Fixing web sockets

* Removing unused webockets file

* PLT-2533 Add the ability to reset a user's MFA from the system console (#2715)

* Add the ability to reset a user's MFA from the system console

* Add client side unit test for adminResetMfa

* Reorganizing authentication to fix LDAP error message (#2723)

* Fixing failing unit test

* Initial upgrade db code

* Adding upgrade script

* Fixing upgrade script after running on core

* Update OAuth and Claim routes to work with user model changes (#2739)

* Fixing perminant deletion. Adding ability to delete all user and the entire database (#2740)

* Fixing team invite ldap login call (#2741)

* Fixing bluebar and some img stuff

* Fix all the different file upload web utils (#2743)

* Fixing invalid session redirect (#2744)

* Redirect on bad channel name (#2746)

* Fixing a bunch of issue and removing dead code

* Patch to fix error message on leave channel (#2747)

* Setting EnableOpenServer to false by default

* Fixing config

* Fixing upgrade

* Fixing reported bugs

* Bug fixes for PLT-2057

* PLT-2563 Redo password recovery to use a database table (#2745)

* Redo password recovery to use a database table

* Update reset password audits

* Split out admin and user reset password APIs to be separate

* Delete password recovery when user is permanently deleted

* Consolidate password resetting into a single function

* Removed private channels as an option for outgoing webhooks (#2752)

* PLT-2577/PLT-2552 Fixes for backstage (#2753)

* Added URL to incoming webhook list

* Fixed client functions for adding/removing integrations

* Disallowed slash commands without trigger words

* Fixed clientside handling of errors on AddCommand page

* Minor auth cleanup (#2758)

* Changed EditPostModal to just close if you save without making any changes (#2759)

* Renamed client -> Client in async_client.jsx and fixed eslint warnings (#2756)

* Fixed url in channel info modal (#2755)

* Fixing reported issues

* Moving to version 3 of the apis

* Fixing command unit tests (#2760)

* Adding team admins

* Fixing DM issue

* Fixing eslint error

* Properly set EditPostModal's originalText state in all cases (#2762)

* Update client config check to assume features is defined if server is licensed (#2772)

* Fixing url link

* Fixing issue with websocket crashing when sending messages to different teams
2016-04-21 22:37:01 -07:00

1025 lines
29 KiB
Go

// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package main
import (
"flag"
"fmt"
"io/ioutil"
"net/http"
"net/url"
"os"
"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 flagCmdAssignRole bool
var flagCmdVersion bool
var flagCmdResetPassword bool
var flagCmdResetMfa bool
var flagCmdPermanentDeleteUser bool
var flagCmdPermanentDeleteTeam bool
var flagCmdPermanentDeleteAllUsers bool
var flagCmdResetDatabase bool
var flagUsername string
var flagCmdUploadLicense bool
var flagConfigFile string
var flagLicenseFile string
var flagEmail string
var flagPassword string
var flagTeamName string
var flagRole string
var flagRunCmds bool
func main() {
parseCmds()
utils.InitTranslations()
utils.LoadConfig(flagConfigFile)
if flagRunCmds {
utils.ConfigureCmdLineLog()
}
pwd, _ := os.Getwd()
l4g.Info(utils.T("mattermost.current_version"), model.CurrentVersion, model.BuildNumber, model.BuildDate, model.BuildHash)
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))
// Speical 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"))
time.Sleep(time.Second)
panic(fmt.Sprintf(utils.T("store.sql.read_replicas_not_licensed.critical")))
}
if flagRunCmds {
runCmds()
} else {
api.StartServer()
// If we allow testing then listen for manual testing URL hits
if utils.Cfg.ServiceSettings.EnableTesting {
manualtesting.InitManualTesting()
}
setDiagnosticId()
runSecurityAndDiagnosticsJobAndForget()
if einterfaces.GetComplianceInterface() != nil {
einterfaces.GetComplianceInterface().StartComplianceDailyJob()
}
// 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
api.StopServer()
}
}
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 runSecurityAndDiagnosticsJobAndForget() {
go func() {
for {
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.User().GetTotalActiveUsersCount(); ucr.Err == nil {
v.Set(utils.PROP_DIAGNOSTIC_ACTIVE_USER_COUNT, strconv.FormatInt(ucr.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)
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)
}
}
}
}
}
}
time.Sleep(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(&flagRole, "role", "", "")
flag.BoolVar(&flagCmdUpdateDb30, "upgrade_db_30", false, "")
flag.BoolVar(&flagCmdCreateTeam, "create_team", false, "")
flag.BoolVar(&flagCmdCreateUser, "create_user", false, "")
flag.BoolVar(&flagCmdAssignRole, "assign_role", false, "")
flag.BoolVar(&flagCmdVersion, "version", 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(&flagCmdUploadLicense, "upload_license", false, "")
flag.Parse()
flagRunCmds = (flagCmdCreateTeam ||
flagCmdCreateUser ||
flagCmdAssignRole ||
flagCmdResetPassword ||
flagCmdResetMfa ||
flagCmdVersion ||
flagCmdPermanentDeleteUser ||
flagCmdPermanentDeleteTeam ||
flagCmdPermanentDeleteAllUsers ||
flagCmdResetDatabase ||
flagCmdUploadLicense)
}
func runCmds() {
cmdVersion()
cmdCreateTeam()
cmdCreateUser()
cmdAssignRole()
cmdResetPassword()
cmdResetMfa()
cmdPermDeleteUser()
cmdPermDeleteTeam()
cmdPermDeleteAllUsers()
cmdResetDatabase()
cmdUploadLicense()
}
type TeamForUpgrade struct {
Id string
Name string
}
// 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)
l4g.Info("Attempting to run speical 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)
}
var confirmBackup string
fmt.Println("\nPlease see http://www.mattermost.org/upgrade-to-3-0/")
fmt.Println("**WARNING** This upgrade process will be irreversible.")
fmt.Print("Have you performed a database backup? (YES/NO): ")
fmt.Scanln(&confirmBackup)
if confirmBackup != "YES" {
fmt.Fprintln(os.Stderr, "ABORTED: You did not answer YES exactly, in all capitals.")
flushLogAndExit(1)
}
var flagTeamName string
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)
}
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 speical 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)
primaryUsers := convertTeamTo30(team, uniqueEmails, uniqueUsernames)
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(otherTeam, uniqueEmails, uniqueUsernames)
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)
}
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.RemoveColumnIfExists("Teams", "AllowTeamListing")
store.RemoveColumnIfExists("Users", "TeamId")
}
l4g.Info("Finished running speical 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
}
func convertTeamTo30(team *TeamForUpgrade, uniqueEmails map[string]bool, uniqueUsernames 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 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
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 = ""
shouldUpdateUser = 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)
}
}
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 uniqueUsernames[user.Username] {
shouldUpdateUser = true
user.Username = user.Username + "." + team.Name
}
if shouldUpdateUser {
if _, err := store.GetMaster().Exec(`
UPDATE Users
SET
Email = :Email,
Username = :Username,
Roles = :Roles
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)
}
uniqueEmails[user.Email] = true
uniqueUsernames[user.Username] = 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(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(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
splits := strings.Split(strings.Replace(flagEmail, "@", " ", -1), " ")
user.Username = splits[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)
}
}
err = api.JoinUserToTeam(team, ruser)
if err != nil {
l4g.Error("%v", err)
flushLogAndExit(1)
}
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)
os.Exit(0)
}
}
func cmdAssignRole() {
if flagCmdAssignRole {
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 !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.UpdateRoles(c, user, flagRole)
}
os.Exit(0)
}
}
func cmdResetPassword() {
if flagCmdResetPassword {
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(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)
}
var confirmBackup string
fmt.Print("Have you performed a database backup? (YES/NO): ")
fmt.Scanln(&confirmBackup)
if confirmBackup != "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)
}
var confirmBackup string
fmt.Print("Have you performed a database backup? (YES/NO): ")
fmt.Scanln(&confirmBackup)
if confirmBackup != "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()
var confirmBackup string
fmt.Print("Have you performed a database backup? (YES/NO): ")
fmt.Scanln(&confirmBackup)
if confirmBackup != "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 {
var confirmBackup string
fmt.Print("Have you performed a database backup? (YES/NO): ")
fmt.Scanln(&confirmBackup)
if confirmBackup != "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 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
-role="admin" The role used in other commands
valid values are
"" - The empty role is basic user
permissions
"admin" - Represents a team admin and
is used to help administer one team.
"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 -team_name,
-email and -password flag to create a user.
Example:
platform -create_user -team_name="name" -email="user@example.com" -password="mypassword"
-assign_role Assigns role to a user. It requires the -role,
-email and -team_name flag. You may need to log out
of your current sessions for the new role to be
applied.
Example:
platform -assign_role -team_name="name" -email="user@example.com" -role="admin"
-reset_password Resets the password for a user. It requires the
-team_name, -email and -password flag.
Example:
platform -reset_password -team_name="name" -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_mfa -username="someuser"
-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"
-version Display the current of the Mattermost platform
-help Displays this help page`