Merge remote-tracking branch 'origin/master' into advanced-permissions-phase-2

Этот коммит содержится в:
Martin Kraft
2018-05-18 08:23:02 -04:00
родитель 8a0702e0c3 e591fcf3d8
Коммит 78d95a25f7
41 изменённых файлов: 288 добавлений и 264 удалений

495
cmd/mattermost/commands/channel.go Обычный файл
Просмотреть файл

@@ -0,0 +1,495 @@
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package commands
import (
"errors"
"fmt"
"github.com/mattermost/mattermost-server/app"
"github.com/mattermost/mattermost-server/model"
"github.com/spf13/cobra"
)
var ChannelCmd = &cobra.Command{
Use: "channel",
Short: "Management of channels",
}
var ChannelCreateCmd = &cobra.Command{
Use: "create",
Short: "Create a channel",
Long: `Create a channel.`,
Example: ` channel create --team myteam --name mynewchannel --display_name "My New Channel"
channel create --team myteam --name mynewprivatechannel --display_name "My New Private Channel" --private`,
RunE: createChannelCmdF,
}
var RemoveChannelUsersCmd = &cobra.Command{
Use: "remove [channel] [users]",
Short: "Remove users from channel",
Long: "Remove some users from channel",
Example: " channel remove myteam:mychannel user@example.com username",
RunE: removeChannelUsersCmdF,
}
var AddChannelUsersCmd = &cobra.Command{
Use: "add [channel] [users]",
Short: "Add users to channel",
Long: "Add some users to channel",
Example: " channel add myteam:mychannel user@example.com username",
RunE: addChannelUsersCmdF,
}
var ArchiveChannelsCmd = &cobra.Command{
Use: "archive [channels]",
Short: "Archive channels",
Long: `Archive some channels.
Archive a channel along with all related information including posts from the database.
Channels can be specified by [team]:[channel]. ie. myteam:mychannel or by channel ID.`,
Example: " channel archive myteam:mychannel",
RunE: archiveChannelsCmdF,
}
var DeleteChannelsCmd = &cobra.Command{
Use: "delete [channels]",
Short: "Delete channels",
Long: `Permanently delete some channels.
Permanently deletes a channel along with all related information including posts from the database.
Channels can be specified by [team]:[channel]. ie. myteam:mychannel or by channel ID.`,
Example: " channel delete myteam:mychannel",
RunE: deleteChannelsCmdF,
}
var ListChannelsCmd = &cobra.Command{
Use: "list [teams]",
Short: "List all channels on specified teams.",
Long: `List all channels on specified teams.
Archived channels are appended with ' (archived)'.`,
Example: " channel list myteam",
RunE: listChannelsCmdF,
}
var MoveChannelsCmd = &cobra.Command{
Use: "move [team] [channels]",
Short: "Moves channels to the specified team",
Long: `Moves the provided channels to the specified team.
Validates that all users in the channel belong to the target team. Incoming/Outgoing webhooks are moved along with the channel.
Channels can be specified by [team]:[channel]. ie. myteam:mychannel or by channel ID.`,
Example: " channel move newteam oldteam:mychannel",
RunE: moveChannelsCmdF,
}
var RestoreChannelsCmd = &cobra.Command{
Use: "restore [channels]",
Short: "Restore some channels",
Long: `Restore a previously deleted channel
Channels can be specified by [team]:[channel]. ie. myteam:mychannel or by channel ID.`,
Example: " channel restore myteam:mychannel",
RunE: restoreChannelsCmdF,
}
var ModifyChannelCmd = &cobra.Command{
Use: "modify [channel]",
Short: "Modify a channel's public/private type",
Long: `Change the public/private type of a channel.
Channel can be specified by [team]:[channel]. ie. myteam:mychannel or by channel ID.`,
Example: " channel modify myteam:mychannel --private",
RunE: modifyChannelCmdF,
}
func init() {
ChannelCreateCmd.Flags().String("name", "", "Channel Name")
ChannelCreateCmd.Flags().String("display_name", "", "Channel Display Name")
ChannelCreateCmd.Flags().String("team", "", "Team name or ID")
ChannelCreateCmd.Flags().String("header", "", "Channel header")
ChannelCreateCmd.Flags().String("purpose", "", "Channel purpose")
ChannelCreateCmd.Flags().Bool("private", false, "Create a private channel.")
MoveChannelsCmd.Flags().String("username", "", "Required. Username who is moving the channel.")
DeleteChannelsCmd.Flags().Bool("confirm", false, "Confirm you really want to delete the channels.")
ModifyChannelCmd.Flags().Bool("private", false, "Convert the channel to a private channel")
ModifyChannelCmd.Flags().Bool("public", false, "Convert the channel to a public channel")
ModifyChannelCmd.Flags().String("username", "", "Required. Username who changes the channel privacy.")
ChannelCmd.AddCommand(
ChannelCreateCmd,
RemoveChannelUsersCmd,
AddChannelUsersCmd,
ArchiveChannelsCmd,
DeleteChannelsCmd,
ListChannelsCmd,
MoveChannelsCmd,
RestoreChannelsCmd,
ModifyChannelCmd,
)
RootCmd.AddCommand(ChannelCmd)
}
func createChannelCmdF(command *cobra.Command, args []string) error {
a, err := InitDBCommandContextCobra(command)
if err != nil {
return err
}
defer a.Shutdown()
name, errn := command.Flags().GetString("name")
if errn != nil || name == "" {
return errors.New("Name is required")
}
displayname, errdn := command.Flags().GetString("display_name")
if errdn != nil || displayname == "" {
return errors.New("Display Name is required")
}
teamArg, errteam := command.Flags().GetString("team")
if errteam != nil || teamArg == "" {
return errors.New("Team is required")
}
header, _ := command.Flags().GetString("header")
purpose, _ := command.Flags().GetString("purpose")
useprivate, _ := command.Flags().GetBool("private")
channelType := model.CHANNEL_OPEN
if useprivate {
channelType = model.CHANNEL_PRIVATE
}
team := getTeamFromTeamArg(a, teamArg)
if team == nil {
return errors.New("Unable to find team: " + teamArg)
}
channel := &model.Channel{
TeamId: team.Id,
Name: name,
DisplayName: displayname,
Header: header,
Purpose: purpose,
Type: channelType,
CreatorId: "",
}
if _, err := a.CreateChannel(channel, false); err != nil {
return err
}
return nil
}
func removeChannelUsersCmdF(command *cobra.Command, args []string) error {
a, err := InitDBCommandContextCobra(command)
if err != nil {
return err
}
defer a.Shutdown()
if len(args) < 2 {
return errors.New("Not enough arguments.")
}
channel := getChannelFromChannelArg(a, args[0])
if channel == nil {
return errors.New("Unable to find channel '" + args[0] + "'")
}
users := getUsersFromUserArgs(a, args[1:])
for i, user := range users {
removeUserFromChannel(a, channel, user, args[i+1])
}
return nil
}
func removeUserFromChannel(a *app.App, channel *model.Channel, user *model.User, userArg string) {
if user == nil {
CommandPrintErrorln("Can't find user '" + userArg + "'")
return
}
if err := a.RemoveUserFromChannel(user.Id, "", channel); err != nil {
CommandPrintErrorln("Unable to remove '" + userArg + "' from " + channel.Name + ". Error: " + err.Error())
}
}
func addChannelUsersCmdF(command *cobra.Command, args []string) error {
a, err := InitDBCommandContextCobra(command)
if err != nil {
return err
}
defer a.Shutdown()
if len(args) < 2 {
return errors.New("Not enough arguments.")
}
channel := getChannelFromChannelArg(a, args[0])
if channel == nil {
return errors.New("Unable to find channel '" + args[0] + "'")
}
users := getUsersFromUserArgs(a, args[1:])
for i, user := range users {
addUserToChannel(a, channel, user, args[i+1])
}
return nil
}
func addUserToChannel(a *app.App, channel *model.Channel, user *model.User, userArg string) {
if user == nil {
CommandPrintErrorln("Can't find user '" + userArg + "'")
return
}
if _, err := a.AddUserToChannel(user, channel); err != nil {
CommandPrintErrorln("Unable to add '" + userArg + "' from " + channel.Name + ". Error: " + err.Error())
}
}
func archiveChannelsCmdF(command *cobra.Command, args []string) error {
a, err := InitDBCommandContextCobra(command)
if err != nil {
return err
}
defer a.Shutdown()
if len(args) < 1 {
return errors.New("Enter at least one channel to archive.")
}
channels := getChannelsFromChannelArgs(a, args)
for i, channel := range channels {
if channel == nil {
CommandPrintErrorln("Unable to find channel '" + args[i] + "'")
continue
}
if result := <-a.Srv.Store.Channel().Delete(channel.Id, model.GetMillis()); result.Err != nil {
CommandPrintErrorln("Unable to archive channel '" + channel.Name + "' error: " + result.Err.Error())
}
}
return nil
}
func deleteChannelsCmdF(command *cobra.Command, args []string) error {
a, err := InitDBCommandContextCobra(command)
if err != nil {
return err
}
defer a.Shutdown()
if len(args) < 1 {
return errors.New("Enter at least one channel to delete.")
}
confirmFlag, _ := command.Flags().GetBool("confirm")
if !confirmFlag {
var confirm string
CommandPrettyPrintln("Are you sure you want to delete the channels specified? All data will be permanently deleted? (YES/NO): ")
fmt.Scanln(&confirm)
if confirm != "YES" {
return errors.New("ABORTED: You did not answer YES exactly, in all capitals.")
}
}
channels := getChannelsFromChannelArgs(a, args)
for i, channel := range channels {
if channel == nil {
CommandPrintErrorln("Unable to find channel '" + args[i] + "'")
continue
}
if err := deleteChannel(a, channel); err != nil {
CommandPrintErrorln("Unable to delete channel '" + channel.Name + "' error: " + err.Error())
} else {
CommandPrettyPrintln("Deleted channel '" + channel.Name + "'")
}
}
return nil
}
func deleteChannel(a *app.App, channel *model.Channel) *model.AppError {
return a.PermanentDeleteChannel(channel)
}
func moveChannelsCmdF(command *cobra.Command, args []string) error {
a, err := InitDBCommandContextCobra(command)
if err != nil {
return err
}
defer a.Shutdown()
if len(args) < 2 {
return errors.New("Enter the destination team and at least one channel to move.")
}
team := getTeamFromTeamArg(a, args[0])
if team == nil {
return errors.New("Unable to find destination team '" + args[0] + "'")
}
username, erru := command.Flags().GetString("username")
if erru != nil || username == "" {
return errors.New("Username is required")
}
user := getUserFromUserArg(a, username)
channels := getChannelsFromChannelArgs(a, args[1:])
for i, channel := range channels {
if channel == nil {
CommandPrintErrorln("Unable to find channel '" + args[i] + "'")
continue
}
originTeamID := channel.TeamId
if err := moveChannel(a, team, channel, user); err != nil {
CommandPrintErrorln("Unable to move channel '" + channel.Name + "' error: " + err.Error())
} else {
CommandPrettyPrintln("Moved channel '" + channel.Name + "' to " + team.Name + "(" + team.Id + ") from " + originTeamID + ".")
}
}
return nil
}
func moveChannel(a *app.App, team *model.Team, channel *model.Channel, user *model.User) *model.AppError {
oldTeamId := channel.TeamId
if err := a.MoveChannel(team, channel, user); err != nil {
return err
}
if incomingWebhooks, err := a.GetIncomingWebhooksForTeamPage(oldTeamId, 0, 10000000); err != nil {
return err
} else {
for _, webhook := range incomingWebhooks {
if webhook.ChannelId == channel.Id {
webhook.TeamId = team.Id
if result := <-a.Srv.Store.Webhook().UpdateIncoming(webhook); result.Err != nil {
CommandPrintErrorln("Failed to move incoming webhook '" + webhook.Id + "' to new team.")
}
}
}
}
if outgoingWebhooks, err := a.GetOutgoingWebhooksForTeamPage(oldTeamId, 0, 10000000); err != nil {
return err
} else {
for _, webhook := range outgoingWebhooks {
if webhook.ChannelId == channel.Id {
webhook.TeamId = team.Id
if result := <-a.Srv.Store.Webhook().UpdateOutgoing(webhook); result.Err != nil {
CommandPrintErrorln("Failed to move outgoing webhook '" + webhook.Id + "' to new team.")
}
}
}
}
return nil
}
func listChannelsCmdF(command *cobra.Command, args []string) error {
a, err := InitDBCommandContextCobra(command)
if err != nil {
return err
}
defer a.Shutdown()
if len(args) < 1 {
return errors.New("Enter at least one team.")
}
teams := getTeamsFromTeamArgs(a, args)
for i, team := range teams {
if team == nil {
CommandPrintErrorln("Unable to find team '" + args[i] + "'")
continue
}
if result := <-a.Srv.Store.Channel().GetAll(team.Id); result.Err != nil {
CommandPrintErrorln("Unable to list channels for '" + args[i] + "'")
} else {
channels := result.Data.([]*model.Channel)
for _, channel := range channels {
if channel.DeleteAt > 0 {
CommandPrettyPrintln(channel.Name + " (archived)")
} else {
CommandPrettyPrintln(channel.Name)
}
}
}
}
return nil
}
func restoreChannelsCmdF(command *cobra.Command, args []string) error {
a, err := InitDBCommandContextCobra(command)
if err != nil {
return err
}
defer a.Shutdown()
if len(args) < 1 {
return errors.New("Enter at least one channel.")
}
channels := getChannelsFromChannelArgs(a, args)
for i, channel := range channels {
if channel == nil {
CommandPrintErrorln("Unable to find channel '" + args[i] + "'")
continue
}
if result := <-a.Srv.Store.Channel().SetDeleteAt(channel.Id, 0, model.GetMillis()); result.Err != nil {
CommandPrintErrorln("Unable to restore channel '" + args[i] + "'")
}
}
return nil
}
func modifyChannelCmdF(command *cobra.Command, args []string) error {
a, err := InitDBCommandContextCobra(command)
if err != nil {
return err
}
defer a.Shutdown()
if len(args) != 1 {
return errors.New("Enter at one channel to modify.")
}
username, erru := command.Flags().GetString("username")
if erru != nil || username == "" {
return errors.New("Username is required")
}
public, _ := command.Flags().GetBool("public")
private, _ := command.Flags().GetBool("private")
if public == private {
return errors.New("You must specify only one of --public or --private")
}
channel := getChannelFromChannelArg(a, args[0])
if channel == nil {
return errors.New("Unable to find channel '" + args[0] + "'")
}
if !(channel.Type == model.CHANNEL_OPEN || channel.Type == model.CHANNEL_PRIVATE) {
return errors.New("You can only change the type of public/private channels.")
}
channel.Type = model.CHANNEL_OPEN
if private {
channel.Type = model.CHANNEL_PRIVATE
}
user := getUserFromUserArg(a, username)
if _, err := a.UpdateChannelPrivacy(channel, user); err != nil {
return errors.New("Failed to update channel ('" + args[0] + "') privacy - " + err.Error())
}
return nil
}

115
cmd/mattermost/commands/channel_test.go Обычный файл
Просмотреть файл

@@ -0,0 +1,115 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package commands
import (
"strings"
"testing"
"github.com/mattermost/mattermost-server/api4"
"github.com/mattermost/mattermost-server/model"
"github.com/stretchr/testify/require"
)
func TestJoinChannel(t *testing.T) {
th := api4.Setup().InitBasic()
defer th.TearDown()
channel := th.CreatePublicChannel()
CheckCommand(t, "channel", "add", th.BasicTeam.Name+":"+channel.Name, th.BasicUser2.Email)
// Joining twice should succeed
CheckCommand(t, "channel", "add", th.BasicTeam.Name+":"+channel.Name, th.BasicUser2.Email)
// should fail because channel does not exist
require.Error(t, RunCommand(t, "channel", "add", th.BasicTeam.Name+":"+channel.Name+"asdf", th.BasicUser2.Email))
}
func TestRemoveChannel(t *testing.T) {
th := api4.Setup().InitBasic()
defer th.TearDown()
channel := th.CreatePublicChannel()
CheckCommand(t, "channel", "add", th.BasicTeam.Name+":"+channel.Name, th.BasicUser2.Email)
// should fail because channel does not exist
require.Error(t, RunCommand(t, "channel", "remove", th.BasicTeam.Name+":doesnotexist", th.BasicUser2.Email))
CheckCommand(t, "channel", "remove", th.BasicTeam.Name+":"+channel.Name, th.BasicUser2.Email)
// Leaving twice should succeed
CheckCommand(t, "channel", "remove", th.BasicTeam.Name+":"+channel.Name, th.BasicUser2.Email)
}
func TestMoveChannel(t *testing.T) {
th := api4.Setup().InitBasic()
defer th.TearDown()
team1 := th.BasicTeam
team2 := th.CreateTeam()
user1 := th.BasicUser
th.LinkUserToTeam(user1, team2)
channel := th.BasicChannel
th.LinkUserToTeam(user1, team1)
th.LinkUserToTeam(user1, team2)
adminEmail := user1.Email
adminUsername := user1.Username
origin := team1.Name + ":" + channel.Name
dest := team2.Name
CheckCommand(t, "channel", "add", origin, adminEmail)
// should fail with nill because errors are logged instead of returned when a channel does not exist
require.Nil(t, RunCommand(t, "channel", "move", dest, team1.Name+":doesnotexist", "--username", adminUsername))
CheckCommand(t, "channel", "move", dest, origin, "--username", adminUsername)
}
func TestListChannels(t *testing.T) {
th := api4.Setup().InitBasic()
defer th.TearDown()
channel := th.CreatePublicChannel()
th.Client.Must(th.Client.DeleteChannel(channel.Id))
output := CheckCommand(t, "channel", "list", th.BasicTeam.Name)
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")
}
}
func TestRestoreChannel(t *testing.T) {
th := api4.Setup().InitBasic()
defer th.TearDown()
channel := th.CreatePublicChannel()
th.Client.Must(th.Client.DeleteChannel(channel.Id))
CheckCommand(t, "channel", "restore", th.BasicTeam.Name+":"+channel.Name)
// restoring twice should succeed
CheckCommand(t, "channel", "restore", th.BasicTeam.Name+":"+channel.Name)
}
func TestCreateChannel(t *testing.T) {
th := api4.Setup().InitBasic()
defer th.TearDown()
id := model.NewId()
name := "name" + id
CheckCommand(t, "channel", "create", "--display_name", name, "--team", th.BasicTeam.Name, "--name", name)
name = name + "-private"
CheckCommand(t, "channel", "create", "--display_name", name, "--team", th.BasicTeam.Name, "--private", "--name", name)
}

60
cmd/mattermost/commands/channelargs.go Обычный файл
Просмотреть файл

@@ -0,0 +1,60 @@
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package commands
import (
"fmt"
"strings"
"github.com/mattermost/mattermost-server/app"
"github.com/mattermost/mattermost-server/model"
)
const CHANNEL_ARG_SEPARATOR = ":"
func getChannelsFromChannelArgs(a *app.App, channelArgs []string) []*model.Channel {
channels := make([]*model.Channel, 0, len(channelArgs))
for _, channelArg := range channelArgs {
channel := getChannelFromChannelArg(a, channelArg)
channels = append(channels, channel)
}
return channels
}
func parseChannelArg(channelArg string) (string, string) {
result := strings.SplitN(channelArg, CHANNEL_ARG_SEPARATOR, 2)
if len(result) == 1 {
return "", channelArg
}
return result[0], result[1]
}
func getChannelFromChannelArg(a *app.App, channelArg string) *model.Channel {
teamArg, channelPart := parseChannelArg(channelArg)
if teamArg == "" && channelPart == "" {
return nil
}
var channel *model.Channel
if teamArg != "" {
team := getTeamFromTeamArg(a, teamArg)
if team == nil {
return nil
}
if result := <-a.Srv.Store.Channel().GetByNameIncludeDeleted(team.Id, channelPart, true); result.Err == nil {
channel = result.Data.(*model.Channel)
} else {
fmt.Println(result.Err.Error())
}
}
if channel == nil {
if result := <-a.Srv.Store.Channel().Get(channelPart, true); result.Err == nil {
channel = result.Data.(*model.Channel)
}
}
return channel
}

45
cmd/mattermost/commands/cmdtestlib.go Обычный файл
Просмотреть файл

@@ -0,0 +1,45 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package commands
import (
"flag"
"fmt"
"os"
"os/exec"
"path/filepath"
"strings"
"testing"
"github.com/stretchr/testify/require"
)
var coverprofileCounters map[string]int = make(map[string]int)
func execArgs(t *testing.T, args []string) []string {
ret := []string{"-test.run", "ExecCommand"}
if coverprofile := flag.Lookup("test.coverprofile").Value.String(); coverprofile != "" {
dir := filepath.Dir(coverprofile)
base := filepath.Base(coverprofile)
baseParts := strings.SplitN(base, ".", 2)
coverprofileCounters[t.Name()] = coverprofileCounters[t.Name()] + 1
baseParts[0] = fmt.Sprintf("%v-%v-%v", baseParts[0], t.Name(), coverprofileCounters[t.Name()])
ret = append(ret, "-test.coverprofile", filepath.Join(dir, strings.Join(baseParts, ".")))
}
return append(append(ret, "--", "--disableconfigwatch"), args...)
}
func CheckCommand(t *testing.T, args ...string) string {
path, err := os.Executable()
require.NoError(t, err)
output, err := exec.Command(path, execArgs(t, args)...).CombinedOutput()
require.NoError(t, err, string(output))
return strings.TrimSpace(strings.TrimSuffix(strings.TrimSpace(string(output)), "PASS"))
}
func RunCommand(t *testing.T, args ...string) error {
path, err := os.Executable()
require.NoError(t, err)
return exec.Command(path, execArgs(t, args)...).Run()
}

69
cmd/mattermost/commands/command.go Обычный файл
Просмотреть файл

@@ -0,0 +1,69 @@
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package commands
import (
"errors"
"github.com/mattermost/mattermost-server/app"
"github.com/mattermost/mattermost-server/model"
"github.com/spf13/cobra"
)
var CommandCmd = &cobra.Command{
Use: "command",
Short: "Management of slash commands",
}
var CommandMoveCmd = &cobra.Command{
Use: "move",
Short: "Move a slash command to a different team",
Long: `Move a slash command to a different team. Commands can be specified by [team]:[command-trigger-word]. ie. myteam:trigger or by command ID.`,
Example: ` command move newteam oldteam:command`,
RunE: moveCommandCmdF,
}
func init() {
CommandCmd.AddCommand(
CommandMoveCmd,
)
RootCmd.AddCommand(CommandCmd)
}
func moveCommandCmdF(command *cobra.Command, args []string) error {
a, err := InitDBCommandContextCobra(command)
if err != nil {
return err
}
defer a.Shutdown()
if len(args) < 2 {
return errors.New("Enter the destination team and at least one comamnd to move.")
}
team := getTeamFromTeamArg(a, args[0])
if team == nil {
return errors.New("Unable to find destination team '" + args[0] + "'")
}
commands := getCommandsFromCommandArgs(a, args[1:])
CommandPrintErrorln(commands)
for i, command := range commands {
if command == nil {
CommandPrintErrorln("Unable to find command '" + args[i+1] + "'")
continue
}
if err := moveCommand(a, team, command); err != nil {
CommandPrintErrorln("Unable to move command '" + command.Trigger + "' error: " + err.Error())
} else {
CommandPrettyPrintln("Moved command '" + command.Trigger + "'")
}
}
return nil
}
func moveCommand(a *app.App, team *model.Team, command *model.Command) *model.AppError {
return a.MoveCommand(team, command)
}

64
cmd/mattermost/commands/commandargs.go Обычный файл
Просмотреть файл

@@ -0,0 +1,64 @@
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package commands
import (
"fmt"
"strings"
"github.com/mattermost/mattermost-server/app"
"github.com/mattermost/mattermost-server/model"
)
const COMMAND_ARGS_SEPARATOR = ":"
func getCommandsFromCommandArgs(a *app.App, commandArgs []string) []*model.Command {
commands := make([]*model.Command, 0, len(commandArgs))
for _, commandArg := range commandArgs {
command := getCommandFromCommandArg(a, commandArg)
commands = append(commands, command)
}
return commands
}
func parseCommandArg(commandArg string) (string, string) {
result := strings.SplitN(commandArg, COMMAND_ARGS_SEPARATOR, 2)
if len(result) == 1 {
return "", commandArg
}
return result[0], result[1]
}
func getCommandFromCommandArg(a *app.App, commandArg string) *model.Command {
teamArg, commandPart := parseCommandArg(commandArg)
if teamArg == "" && commandPart == "" {
return nil
}
var command *model.Command
if teamArg != "" {
team := getTeamFromTeamArg(a, teamArg)
if team == nil {
return nil
}
if result := <-a.Srv.Store.Command().GetByTrigger(team.Id, commandPart); result.Err == nil {
command = result.Data.(*model.Command)
} else {
fmt.Println(result.Err.Error())
}
}
if command == nil {
if result := <-a.Srv.Store.Command().Get(commandPart); result.Err == nil {
command = result.Data.(*model.Command)
}
}
return command
}

67
cmd/mattermost/commands/config.go Обычный файл
Просмотреть файл

@@ -0,0 +1,67 @@
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package commands
import (
"encoding/json"
"errors"
"os"
"github.com/mattermost/mattermost-server/model"
"github.com/mattermost/mattermost-server/utils"
"github.com/spf13/cobra"
)
var ConfigCmd = &cobra.Command{
Use: "config",
Short: "Configuration",
}
var ValidateConfigCmd = &cobra.Command{
Use: "validate",
Short: "Validate config file",
Long: "If the config file is valid, this command will output a success message and have a zero exit code. If it is invalid, this command will output an error and have a non-zero exit code.",
RunE: configValidateCmdF,
}
func init() {
ConfigCmd.AddCommand(
ValidateConfigCmd,
)
RootCmd.AddCommand(ConfigCmd)
}
func configValidateCmdF(command *cobra.Command, args []string) error {
utils.TranslationsPreInit()
model.AppErrorInit(utils.T)
filePath, err := command.Flags().GetString("config")
if err != nil {
return err
}
filePath = utils.FindConfigFile(filePath)
file, err := os.Open(filePath)
if err != nil {
return err
}
decoder := json.NewDecoder(file)
config := model.Config{}
err = decoder.Decode(&config)
if err != nil {
return err
}
if _, err := file.Stat(); err != nil {
return err
}
if err := config.IsValid(); err != nil {
return errors.New(utils.T(err.Id))
}
CommandPrettyPrintln("The document is valid")
return nil
}

48
cmd/mattermost/commands/config_flag_test.go Обычный файл
Просмотреть файл

@@ -0,0 +1,48 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package commands
import (
"io/ioutil"
"os"
"path/filepath"
"testing"
"github.com/stretchr/testify/require"
"encoding/json"
"github.com/mattermost/mattermost-server/utils"
)
func TestConfigFlag(t *testing.T) {
dir, err := ioutil.TempDir("", "")
require.NoError(t, err)
defer os.RemoveAll(dir)
utils.TranslationsPreInit()
config, _, _, err := utils.LoadConfig("config.json")
require.Nil(t, err)
configPath := filepath.Join(dir, "foo.json")
require.NoError(t, ioutil.WriteFile(configPath, []byte(config.ToJson()), 0600))
timezones := utils.LoadTimezones("timezones.json")
tzConfigPath := filepath.Join(dir, "timezones.json")
timezoneData, _ := json.Marshal(timezones)
require.NoError(t, ioutil.WriteFile(tzConfigPath, timezoneData, 0600))
i18n, ok := utils.FindDir("i18n")
require.True(t, ok)
require.NoError(t, utils.CopyDir(i18n, filepath.Join(dir, "i18n")))
prevDir, err := os.Getwd()
require.NoError(t, err)
defer os.Chdir(prevDir)
os.Chdir(dir)
require.Error(t, RunCommand(t, "version"))
CheckCommand(t, "--config", "foo.json", "version")
CheckCommand(t, "--config", "./foo.json", "version")
CheckCommand(t, "--config", configPath, "version")
}

30
cmd/mattermost/commands/config_test.go Обычный файл
Просмотреть файл

@@ -0,0 +1,30 @@
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package commands
import (
"io/ioutil"
"os"
"path/filepath"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/mattermost/mattermost-server/model"
)
func TestConfigValidate(t *testing.T) {
dir, err := ioutil.TempDir("", "")
require.NoError(t, err)
defer os.RemoveAll(dir)
path := filepath.Join(dir, "config.json")
config := &model.Config{}
config.SetDefaults()
require.NoError(t, ioutil.WriteFile(path, []byte(config.ToJson()), 0600))
assert.Error(t, RunCommand(t, "--config", "foo.json", "config", "validate"))
assert.NoError(t, RunCommand(t, "--config", path, "config", "validate"))
}

Просмотреть файл

@@ -0,0 +1,19 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package commands
import (
"flag"
"testing"
"github.com/stretchr/testify/require"
)
func TestExecCommand(t *testing.T) {
if filter := flag.Lookup("test.run").Value.String(); filter != "ExecCommand" {
t.Skip("use -run ExecCommand to execute a command via the test executable")
}
RootCmd.SetArgs(flag.Args())
require.NoError(t, RootCmd.Execute())
}

144
cmd/mattermost/commands/import.go Обычный файл
Просмотреть файл

@@ -0,0 +1,144 @@
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package commands
import (
"errors"
"os"
"fmt"
"github.com/spf13/cobra"
)
var ImportCmd = &cobra.Command{
Use: "import",
Short: "Import data.",
}
var SlackImportCmd = &cobra.Command{
Use: "slack [team] [file]",
Short: "Import a team from Slack.",
Long: "Import a team from a Slack export zip file.",
Example: " import slack myteam slack_export.zip",
RunE: slackImportCmdF,
}
var BulkImportCmd = &cobra.Command{
Use: "bulk [file]",
Short: "Import bulk data.",
Long: "Import data from a Mattermost Bulk Import File.",
Example: " import bulk bulk_data.json",
RunE: bulkImportCmdF,
}
func init() {
BulkImportCmd.Flags().Bool("apply", false, "Save the import data to the database. Use with caution - this cannot be reverted.")
BulkImportCmd.Flags().Bool("validate", false, "Validate the import data without making any changes to the system.")
BulkImportCmd.Flags().Int("workers", 2, "How many workers to run whilst doing the import.")
ImportCmd.AddCommand(
BulkImportCmd,
SlackImportCmd,
)
RootCmd.AddCommand(ImportCmd)
}
func slackImportCmdF(command *cobra.Command, args []string) error {
a, err := InitDBCommandContextCobra(command)
if err != nil {
return err
}
defer a.Shutdown()
if len(args) != 2 {
return errors.New("Incorrect number of arguments.")
}
team := getTeamFromTeamArg(a, args[0])
if team == nil {
return errors.New("Unable to find team '" + args[0] + "'")
}
fileReader, err := os.Open(args[1])
if err != nil {
return err
}
defer fileReader.Close()
fileInfo, err := fileReader.Stat()
if err != nil {
return err
}
CommandPrettyPrintln("Running Slack Import. This may take a long time for large teams or teams with many messages.")
a.SlackImport(fileReader, fileInfo.Size(), team.Id)
CommandPrettyPrintln("Finished Slack Import.")
return nil
}
func bulkImportCmdF(command *cobra.Command, args []string) error {
a, err := InitDBCommandContextCobra(command)
if err != nil {
return err
}
defer a.Shutdown()
apply, err := command.Flags().GetBool("apply")
if err != nil {
return errors.New("Apply flag error")
}
validate, err := command.Flags().GetBool("validate")
if err != nil {
return errors.New("Validate flag error")
}
workers, err := command.Flags().GetInt("workers")
if err != nil {
return errors.New("Workers flag error")
}
if len(args) != 1 {
return errors.New("Incorrect number of arguments.")
}
fileReader, err := os.Open(args[0])
if err != nil {
return err
}
defer fileReader.Close()
if apply && validate {
CommandPrettyPrintln("Use only one of --apply or --validate.")
return nil
} else if apply && !validate {
CommandPrettyPrintln("Running Bulk Import. This may take a long time.")
} else {
CommandPrettyPrintln("Running Bulk Import Data Validation.")
CommandPrettyPrintln("** This checks the validity of the entities in the data file, but does not persist any changes **")
CommandPrettyPrintln("Use the --apply flag to perform the actual data import.")
}
CommandPrettyPrintln("")
if err, lineNumber := a.BulkImport(fileReader, !apply, workers); err != nil {
CommandPrettyPrintln(err.Error())
if lineNumber != 0 {
CommandPrettyPrintln(fmt.Sprintf("Error occurred on data file line %v", lineNumber))
}
return err
} else {
if apply {
CommandPrettyPrintln("Finished Bulk Import.")
} else {
CommandPrettyPrintln("Validation complete. You can now perform the import by rerunning this command with the --apply flag.")
}
}
return nil
}

46
cmd/mattermost/commands/init.go Обычный файл
Просмотреть файл

@@ -0,0 +1,46 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package commands
import (
"github.com/mattermost/mattermost-server/app"
"github.com/mattermost/mattermost-server/model"
"github.com/mattermost/mattermost-server/utils"
"github.com/spf13/cobra"
)
func InitDBCommandContextCobra(command *cobra.Command) (*app.App, error) {
config, err := command.Flags().GetString("config")
if err != nil {
return nil, err
}
a, err := InitDBCommandContext(config)
if err != nil {
// Returning an error just prints the usage message, so actually panic
panic(err)
}
a.DoAdvancedPermissionsMigration()
return a, nil
}
func InitDBCommandContext(configFileLocation string) (*app.App, error) {
if err := utils.TranslationsPreInit(); err != nil {
return nil, err
}
model.AppErrorInit(utils.T)
a, err := app.New(app.ConfigFile(configFileLocation))
if err != nil {
return nil, err
}
if model.BuildEnterpriseReady == "true" {
a.LoadLicense()
}
return a, nil
}

62
cmd/mattermost/commands/jobserver.go Обычный файл
Просмотреть файл

@@ -0,0 +1,62 @@
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package commands
import (
"os"
"os/signal"
"syscall"
"github.com/mattermost/mattermost-server/mlog"
"github.com/spf13/cobra"
)
var JobserverCmd = &cobra.Command{
Use: "jobserver",
Short: "Start the Mattermost job server",
Run: jobserverCmdF,
}
func init() {
JobserverCmd.Flags().Bool("nojobs", false, "Do not run jobs on this jobserver.")
JobserverCmd.Flags().Bool("noschedule", false, "Do not schedule jobs from this jobserver.")
RootCmd.AddCommand(JobserverCmd)
}
func jobserverCmdF(command *cobra.Command, args []string) {
// Options
noJobs, _ := command.Flags().GetBool("nojobs")
noSchedule, _ := command.Flags().GetBool("noschedule")
// Initialize
a, err := InitDBCommandContext("config.json")
if err != nil {
panic(err.Error())
}
defer a.Shutdown()
a.LoadLicense()
// Run jobs
mlog.Info("Starting Mattermost job server")
if !noJobs {
a.Jobs.StartWorkers()
}
if !noSchedule {
a.Jobs.StartSchedulers()
}
signalChan := make(chan os.Signal, 1)
signal.Notify(signalChan, os.Interrupt, syscall.SIGINT, syscall.SIGTERM)
<-signalChan
// Cleanup anything that isn't handled by a defer statement
mlog.Info("Stopping Mattermost job server")
a.Jobs.StopSchedulers()
a.Jobs.StopWorkers()
mlog.Info("Stopped Mattermost job server")
}

77
cmd/mattermost/commands/ldap.go Обычный файл
Просмотреть файл

@@ -0,0 +1,77 @@
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package commands
import (
"github.com/mattermost/mattermost-server/model"
"github.com/spf13/cobra"
)
var LdapCmd = &cobra.Command{
Use: "ldap",
Short: "LDAP related utilities",
}
var LdapSyncCmd = &cobra.Command{
Use: "sync",
Short: "Synchronize now",
Long: "Synchronize all LDAP users now.",
Example: " ldap sync",
RunE: ldapSyncCmdF,
}
var LdapIdMigrate = &cobra.Command{
Use: "idmigrate",
Short: "Migrate LDAP IdAttribute to new value",
Long: "Migrate LDAP IdAttribute to new value. Run this utility then change the IdAttribute to the new value.",
Example: " ldap idmigrate objectGUID",
Args: cobra.ExactArgs(1),
RunE: ldapIdMigrateCmdF,
}
func init() {
LdapCmd.AddCommand(
LdapSyncCmd,
LdapIdMigrate,
)
RootCmd.AddCommand(LdapCmd)
}
func ldapSyncCmdF(command *cobra.Command, args []string) error {
a, err := InitDBCommandContextCobra(command)
if err != nil {
return err
}
defer a.Shutdown()
if ldapI := a.Ldap; ldapI != nil {
job, err := ldapI.StartSynchronizeJob(true)
if err != nil || job.Status == model.JOB_STATUS_ERROR || job.Status == model.JOB_STATUS_CANCELED {
CommandPrintErrorln("ERROR: AD/LDAP Synchronization please check the server logs")
} else {
CommandPrettyPrintln("SUCCESS: AD/LDAP Synchronization Complete")
}
}
return nil
}
func ldapIdMigrateCmdF(command *cobra.Command, args []string) error {
a, err := InitDBCommandContextCobra(command)
if err != nil {
return err
}
defer a.Shutdown()
toAttribute := args[0]
if ldapI := a.Ldap; ldapI != nil {
if err := ldapI.MigrateIDAttribute(toAttribute); err != nil {
CommandPrintErrorln("ERROR: AD/LDAP IdAttribute migration failed! Error: " + err.Error())
} else {
CommandPrettyPrintln("SUCCESS: AD/LDAP IdAttribute migration complete. You can now change your IdAttribute to: " + toAttribute)
}
}
return nil
}

54
cmd/mattermost/commands/license.go Обычный файл
Просмотреть файл

@@ -0,0 +1,54 @@
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package commands
import (
"errors"
"io/ioutil"
"github.com/spf13/cobra"
)
var LicenseCmd = &cobra.Command{
Use: "license",
Short: "Licensing commands",
}
var UploadLicenseCmd = &cobra.Command{
Use: "upload [license]",
Short: "Upload a license.",
Long: "Upload a license. Replaces current license.",
Example: " license upload /path/to/license/mylicensefile.mattermost-license",
RunE: uploadLicenseCmdF,
}
func init() {
LicenseCmd.AddCommand(UploadLicenseCmd)
RootCmd.AddCommand(LicenseCmd)
}
func uploadLicenseCmdF(command *cobra.Command, args []string) error {
a, err := InitDBCommandContextCobra(command)
if err != nil {
return err
}
defer a.Shutdown()
if len(args) != 1 {
return errors.New("Enter one license file to upload")
}
var fileBytes []byte
if fileBytes, err = ioutil.ReadFile(args[0]); err != nil {
return err
}
if _, err := a.SaveLicense(fileBytes); err != nil {
return err
}
CommandPrettyPrintln("Uploaded license file")
return nil
}

81
cmd/mattermost/commands/message_export.go Обычный файл
Просмотреть файл

@@ -0,0 +1,81 @@
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package commands
import (
"errors"
"context"
"time"
"github.com/mattermost/mattermost-server/model"
"github.com/spf13/cobra"
)
var MessageExportCmd = &cobra.Command{
Use: "export",
Short: "Export data from Mattermost",
Long: "Export data from Mattermost in a format suitable for import into a third-party application",
Example: "export --format=actiance --exportFrom=12345",
RunE: messageExportCmdF,
}
func init() {
MessageExportCmd.Flags().String("format", "actiance", "The format to export data in")
MessageExportCmd.Flags().Int64("exportFrom", -1, "The timestamp of the earliest post to export, expressed in seconds since the unix epoch.")
MessageExportCmd.Flags().Int("timeoutSeconds", -1, "The maximum number of seconds to wait for the job to complete before timing out.")
RootCmd.AddCommand(MessageExportCmd)
}
func messageExportCmdF(command *cobra.Command, args []string) error {
a, err := InitDBCommandContextCobra(command)
if err != nil {
return err
}
defer a.Shutdown()
if !*a.Config().MessageExportSettings.EnableExport {
return errors.New("ERROR: The message export feature is not enabled")
}
// for now, format is hard-coded to actiance. In time, we'll have to support other formats and inject them into job data
if format, err := command.Flags().GetString("format"); err != nil {
return errors.New("format flag error")
} else if format != "actiance" {
return errors.New("unsupported export format")
}
startTime, err := command.Flags().GetInt64("exportFrom")
if err != nil {
return errors.New("exportFrom flag error")
} else if startTime < 0 {
return errors.New("exportFrom must be a positive integer")
}
timeoutSeconds, err := command.Flags().GetInt("timeoutSeconds")
if err != nil {
return errors.New("timeoutSeconds error")
} else if timeoutSeconds < 0 {
return errors.New("timeoutSeconds must be a positive integer")
}
if messageExportI := a.MessageExport; messageExportI != nil {
ctx := context.Background()
if timeoutSeconds > 0 {
var cancel context.CancelFunc
ctx, cancel = context.WithTimeout(ctx, time.Second*time.Duration(timeoutSeconds))
defer cancel()
}
job, err := messageExportI.StartSynchronizeJob(ctx, startTime)
if err != nil || job.Status == model.JOB_STATUS_ERROR || job.Status == model.JOB_STATUS_CANCELED {
CommandPrintErrorln("ERROR: Message export job failed. Please check the server logs")
} else {
CommandPrettyPrintln("SUCCESS: Message export job complete")
}
}
return nil
}

Просмотреть файл

@@ -0,0 +1,66 @@
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package commands
import (
"io/ioutil"
"os"
"path/filepath"
"testing"
"github.com/stretchr/testify/require"
"github.com/mattermost/mattermost-server/model"
"github.com/mattermost/mattermost-server/utils"
)
// There are no tests that actually run the Message Export job, because it can take a long time to complete depending
// on the size of the database that the config is pointing to. As such, these tests just ensure that the CLI command
// fails fast if invalid flags are supplied
func TestMessageExportNotEnabled(t *testing.T) {
configPath := writeTempConfig(t, false)
defer os.RemoveAll(filepath.Dir(configPath))
// should fail fast because the feature isn't enabled
require.Error(t, RunCommand(t, "--config", configPath, "export"))
}
func TestMessageExportInvalidFormat(t *testing.T) {
configPath := writeTempConfig(t, true)
defer os.RemoveAll(filepath.Dir(configPath))
// should fail fast because format isn't supported
require.Error(t, RunCommand(t, "--config", configPath, "--format", "not_actiance", "export"))
}
func TestMessageExportNegativeExportFrom(t *testing.T) {
configPath := writeTempConfig(t, true)
defer os.RemoveAll(filepath.Dir(configPath))
// should fail fast because export from must be a valid timestamp
require.Error(t, RunCommand(t, "--config", configPath, "--format", "actiance", "--exportFrom", "-1", "export"))
}
func TestMessageExportNegativeTimeoutSeconds(t *testing.T) {
configPath := writeTempConfig(t, true)
defer os.RemoveAll(filepath.Dir(configPath))
// should fail fast because timeout seconds must be a positive int
require.Error(t, RunCommand(t, "--config", configPath, "--format", "actiance", "--exportFrom", "0", "--timeoutSeconds", "-1", "export"))
}
func writeTempConfig(t *testing.T, isMessageExportEnabled bool) string {
dir, err := ioutil.TempDir("", "")
require.NoError(t, err)
utils.TranslationsPreInit()
config, _, _, appErr := utils.LoadConfig("config.json")
require.Nil(t, appErr)
config.MessageExportSettings.EnableExport = model.NewBool(isMessageExportEnabled)
configPath := filepath.Join(dir, "foo.json")
require.NoError(t, ioutil.WriteFile(configPath, []byte(config.ToJson()), 0600))
return configPath
}

21
cmd/mattermost/commands/output.go Обычный файл
Просмотреть файл

@@ -0,0 +1,21 @@
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package commands
import (
"fmt"
"os"
)
func CommandPrintln(a ...interface{}) (int, error) {
return fmt.Println(a...)
}
func CommandPrintErrorln(a ...interface{}) (int, error) {
return fmt.Fprintln(os.Stderr, a...)
}
func CommandPrettyPrintln(a ...interface{}) (int, error) {
return fmt.Fprintln(os.Stderr, a...)
}

130
cmd/mattermost/commands/permissions.go Обычный файл
Просмотреть файл

@@ -0,0 +1,130 @@
// Copyright (c) 2018-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package commands
import (
"errors"
"fmt"
"os"
"github.com/spf13/cobra"
"github.com/mattermost/mattermost-server/utils"
)
var PermissionsCmd = &cobra.Command{
Use: "permissions",
Short: "Management of the Permissions system",
}
var ResetPermissionsCmd = &cobra.Command{
Use: "reset",
Short: "Reset the permissions system to its default state",
Long: "Reset the permissions system to its default state",
Example: " permissions reset",
RunE: resetPermissionsCmdF,
}
var ExportPermissionsCmd = &cobra.Command{
Use: "export",
Short: "Export permissions data",
Long: "Export Roles and Schemes to JSONL for use by Mattermost permissions import.",
Example: " permissions export > export.jsonl",
RunE: exportPermissionsCmdF,
PreRun: func(cmd *cobra.Command, args []string) {
os.Setenv("MM_LOGSETTINGS_CONSOLELEVEL", "error")
},
}
var ImportPermissionsCmd = &cobra.Command{
Use: "import [file]",
Short: "Import permissions data",
Long: "Import Roles and Schemes JSONL data as created by the Mattermost permissions export.",
Example: " permissions import export.jsonl",
RunE: importPermissionsCmdF,
}
func init() {
ResetPermissionsCmd.Flags().Bool("confirm", false, "Confirm you really want to reset the permissions system and a database backup has been performed.")
PermissionsCmd.AddCommand(
ResetPermissionsCmd,
ExportPermissionsCmd,
ImportPermissionsCmd,
)
RootCmd.AddCommand(PermissionsCmd)
}
func resetPermissionsCmdF(command *cobra.Command, args []string) error {
a, err := InitDBCommandContextCobra(command)
if err != nil {
return err
}
confirmFlag, _ := command.Flags().GetBool("confirm")
if !confirmFlag {
var confirm string
CommandPrettyPrintln("Have you performed a database backup? (YES/NO): ")
fmt.Scanln(&confirm)
if confirm != "YES" {
return errors.New("ABORTED: You did not answer YES exactly, in all capitals.")
}
CommandPrettyPrintln("Are you sure you want to reset the permissions system? All data related to the permissions system will be permanently deleted and all users will revert to having the default permissions. (YES/NO): ")
fmt.Scanln(&confirm)
if confirm != "YES" {
return errors.New("ABORTED: You did not answer YES exactly, in all capitals.")
}
}
if err := a.ResetPermissionsSystem(); err != nil {
return errors.New(err.Error())
}
CommandPrettyPrintln("Permissions system successfully reset")
return nil
}
func exportPermissionsCmdF(command *cobra.Command, args []string) error {
a, err := InitDBCommandContextCobra(command)
if err != nil {
return err
}
defer a.Shutdown()
if license := a.License(); license == nil {
return errors.New(utils.T("cli.license.critical"))
}
if err = a.ExportPermissions(os.Stdout); err != nil {
return errors.New(err.Error())
}
return nil
}
func importPermissionsCmdF(command *cobra.Command, args []string) error {
a, err := InitDBCommandContextCobra(command)
if err != nil {
return err
}
defer a.Shutdown()
if license := a.License(); license == nil {
return errors.New(utils.T("cli.license.critical"))
}
file, err := os.Open(args[0])
if err != nil {
return err
}
defer file.Close()
if err := a.ImportPermissions(file); err != nil {
return err
}
return nil
}

53
cmd/mattermost/commands/reset.go Обычный файл
Просмотреть файл

@@ -0,0 +1,53 @@
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package commands
import (
"errors"
"fmt"
"github.com/spf13/cobra"
)
var ResetCmd = &cobra.Command{
Use: "reset",
Short: "Reset the database to initial state",
Long: "Completely erases the database causing the loss of all data. This will reset Mattermost to its initial state.",
RunE: resetCmdF,
}
func init() {
ResetCmd.Flags().Bool("confirm", false, "Confirm you really want to delete everything and a DB backup has been performed.")
RootCmd.AddCommand(ResetCmd)
}
func resetCmdF(command *cobra.Command, args []string) error {
a, err := InitDBCommandContextCobra(command)
if err != nil {
return err
}
defer a.Shutdown()
confirmFlag, _ := command.Flags().GetBool("confirm")
if !confirmFlag {
var confirm string
CommandPrettyPrintln("Have you performed a database backup? (YES/NO): ")
fmt.Scanln(&confirm)
if confirm != "YES" {
return errors.New("ABORTED: You did not answer YES exactly, in all capitals.")
}
CommandPrettyPrintln("Are you sure you want to delete everything? All data will be permanently deleted? (YES/NO): ")
fmt.Scanln(&confirm)
if confirm != "YES" {
return errors.New("ABORTED: You did not answer YES exactly, in all capitals.")
}
}
a.Srv.Store.DropAllTables()
CommandPrettyPrintln("Database successfully reset")
return nil
}

131
cmd/mattermost/commands/roles.go Обычный файл
Просмотреть файл

@@ -0,0 +1,131 @@
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package commands
import (
"errors"
"strings"
"github.com/spf13/cobra"
"github.com/mattermost/mattermost-server/model"
)
var RolesCmd = &cobra.Command{
Use: "roles",
Short: "Management of user roles",
}
var MakeSystemAdminCmd = &cobra.Command{
Use: "system_admin [users]",
Short: "Set a user as system admin",
Long: "Make some users system admins",
Example: " roles system_admin user1",
RunE: makeSystemAdminCmdF,
}
var MakeMemberCmd = &cobra.Command{
Use: "member [users]",
Short: "Remove system admin privileges",
Long: "Remove system admin privileges from some users.",
Example: " roles member user1",
RunE: makeMemberCmdF,
}
func init() {
RolesCmd.AddCommand(
MakeSystemAdminCmd,
MakeMemberCmd,
)
RootCmd.AddCommand(RolesCmd)
}
func makeSystemAdminCmdF(command *cobra.Command, args []string) error {
a, err := InitDBCommandContextCobra(command)
if err != nil {
return err
}
defer a.Shutdown()
if len(args) < 1 {
return errors.New("Enter at least one user.")
}
users := getUsersFromUserArgs(a, args)
for i, user := range users {
if user == nil {
return errors.New("Unable to find user '" + args[i] + "'")
}
systemAdmin := false
systemUser := false
roles := strings.Fields(user.Roles)
for _, role := range roles {
switch role {
case model.SYSTEM_ADMIN_ROLE_ID:
systemAdmin = true
case model.SYSTEM_USER_ROLE_ID:
systemUser = true
}
}
if !systemUser {
roles = append(roles, model.SYSTEM_USER_ROLE_ID)
}
if !systemAdmin {
roles = append(roles, model.SYSTEM_ADMIN_ROLE_ID)
}
if _, err := a.UpdateUserRoles(user.Id, strings.Join(roles, " "), true); err != nil {
return err
}
}
return nil
}
func makeMemberCmdF(command *cobra.Command, args []string) error {
a, err := InitDBCommandContextCobra(command)
if err != nil {
return err
}
defer a.Shutdown()
if len(args) < 1 {
return errors.New("Enter at least one user.")
}
users := getUsersFromUserArgs(a, args)
for i, user := range users {
if user == nil {
return errors.New("Unable to find user '" + args[i] + "'")
}
systemUser := false
var newRoles []string
roles := strings.Fields(user.Roles)
for _, role := range roles {
switch role {
case model.SYSTEM_ADMIN_ROLE_ID:
default:
if role == model.SYSTEM_USER_ROLE_ID {
systemUser = true
}
newRoles = append(newRoles, role)
}
}
if !systemUser {
newRoles = append(roles, model.SYSTEM_USER_ROLE_ID)
}
if _, err := a.UpdateUserRoles(user.Id, strings.Join(newRoles, " "), true); err != nil {
return err
}
}
return nil
}

38
cmd/mattermost/commands/roles_test.go Обычный файл
Просмотреть файл

@@ -0,0 +1,38 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package commands
import (
"testing"
"github.com/mattermost/mattermost-server/api4"
"github.com/mattermost/mattermost-server/model"
)
func TestAssignRole(t *testing.T) {
th := api4.Setup().InitBasic()
defer th.TearDown()
CheckCommand(t, "roles", "system_admin", th.BasicUser.Email)
if result := <-th.App.Srv.Store.User().GetByEmail(th.BasicUser.Email); result.Err != nil {
t.Fatal()
} else {
user := result.Data.(*model.User)
if user.Roles != "system_user system_admin" {
t.Fatal("Got wrong roles:", user.Roles)
}
}
CheckCommand(t, "roles", "member", th.BasicUser.Email)
if result := <-th.App.Srv.Store.User().GetByEmail(th.BasicUser.Email); result.Err != nil {
t.Fatal()
} else {
user := result.Data.(*model.User)
if user.Roles != "system_user" {
t.Fatal("Got wrong roles:", user.Roles, user.Id)
}
}
}

28
cmd/mattermost/commands/root.go Обычный файл
Просмотреть файл

@@ -0,0 +1,28 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package commands
import (
"github.com/spf13/cobra"
)
type Command = cobra.Command
func Run(args []string) error {
RootCmd.SetArgs(args)
return RootCmd.Execute()
}
var RootCmd = &cobra.Command{
Use: "platform",
Short: "Open source, self-hosted Slack-alternative",
Long: `Mattermost offers workplace messaging across web, PC and phones with archiving, search and integration with your existing systems. Documentation available at https://docs.mattermost.com`,
}
func init() {
RootCmd.PersistentFlags().StringP("config", "c", "config.json", "Configuration file to use.")
RootCmd.PersistentFlags().Bool("disableconfigwatch", false, "When set config.json will not be loaded from disk when the file is changed.")
RootCmd.PersistentFlags().Bool("platform", false, "This flag signifies that the user tried to start the command from the platform binary, so we can log a mssage")
RootCmd.PersistentFlags().MarkHidden("platform")
}

636
cmd/mattermost/commands/sampledata.go Обычный файл
Просмотреть файл

@@ -0,0 +1,636 @@
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package commands
import (
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"math/rand"
"os"
"path"
"sort"
"strings"
"time"
"github.com/icrowley/fake"
"github.com/mattermost/mattermost-server/app"
"github.com/spf13/cobra"
)
var SampleDataCmd = &cobra.Command{
Use: "sampledata",
Short: "Generate sample data",
RunE: sampleDataCmdF,
}
func init() {
SampleDataCmd.Flags().Int64P("seed", "s", 1, "Seed used for generating the random data (Different seeds generate different data).")
SampleDataCmd.Flags().IntP("teams", "t", 2, "The number of sample teams.")
SampleDataCmd.Flags().Int("channels-per-team", 10, "The number of sample channels per team.")
SampleDataCmd.Flags().IntP("users", "u", 15, "The number of sample users.")
SampleDataCmd.Flags().Int("team-memberships", 2, "The number of sample team memberships per user.")
SampleDataCmd.Flags().Int("channel-memberships", 5, "The number of sample channel memberships per user in a team.")
SampleDataCmd.Flags().Int("posts-per-channel", 100, "The number of sample post per channel.")
SampleDataCmd.Flags().Int("direct-channels", 30, "The number of sample direct message channels.")
SampleDataCmd.Flags().Int("posts-per-direct-channel", 15, "The number of sample posts per direct message channel.")
SampleDataCmd.Flags().Int("group-channels", 15, "The number of sample group message channels.")
SampleDataCmd.Flags().Int("posts-per-group-channel", 30, "The number of sample posts per group message channel.")
SampleDataCmd.Flags().IntP("workers", "w", 2, "How many workers to run during the import.")
SampleDataCmd.Flags().String("profile-images", "", "Optional. Path to folder with images to randomly pick as user profile image.")
SampleDataCmd.Flags().StringP("bulk", "b", "", "Optional. Path to write a JSONL bulk file instead of loading into the database.")
RootCmd.AddCommand(SampleDataCmd)
}
func sliceIncludes(vs []string, t string) bool {
for _, v := range vs {
if v == t {
return true
}
}
return false
}
func randomPastTime(seconds int) int64 {
now := time.Now()
today := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, time.FixedZone("UTC", 0))
return today.Unix() - int64(rand.Intn(seconds*1000))
}
func randomEmoji() string {
emojis := []string{"+1", "-1", "heart", "blush"}
return emojis[rand.Intn(len(emojis))]
}
func randomReaction(users []string, parentCreateAt int64) app.ReactionImportData {
user := users[rand.Intn(len(users))]
emoji := randomEmoji()
date := parentCreateAt + int64(rand.Intn(100000))
return app.ReactionImportData{
User: &user,
EmojiName: &emoji,
CreateAt: &date,
}
}
func randomReply(users []string, parentCreateAt int64) app.ReplyImportData {
user := users[rand.Intn(len(users))]
message := randomMessage(users)
date := parentCreateAt + int64(rand.Intn(100000))
return app.ReplyImportData{
User: &user,
Message: &message,
CreateAt: &date,
}
}
func randomMessage(users []string) string {
var message string
switch rand.Intn(30) {
case 0:
mention := users[rand.Intn(len(users))]
message = "@" + mention + " " + fake.Sentence()
case 1:
switch rand.Intn(2) {
case 0:
mattermostVideos := []string{"Q4MgnxbpZas", "BFo7E9-Kc_E", "LsMLR-BHsKg", "MRmGDhlMhNA", "mUOPxT7VgWc"}
message = "https://www.youtube.com/watch?v=" + mattermostVideos[rand.Intn(len(mattermostVideos))]
case 1:
mattermostTweets := []string{"943119062334353408", "949370809528832005", "948539688171819009", "939122439115681792", "938061722027425797"}
message = "https://twitter.com/mattermosthq/status/" + mattermostTweets[rand.Intn(len(mattermostTweets))]
}
case 2:
message = ""
if rand.Intn(2) == 0 {
message += fake.Sentence()
}
for i := 0; i < rand.Intn(4)+1; i++ {
message += "\n * " + fake.Word()
}
default:
if rand.Intn(2) == 0 {
message = fake.Sentence()
} else {
message = fake.Paragraph()
}
if rand.Intn(3) == 0 {
message += "\n" + fake.Sentence()
}
if rand.Intn(3) == 0 {
message += "\n" + fake.Sentence()
}
if rand.Intn(3) == 0 {
message += "\n" + fake.Sentence()
}
}
return message
}
func sampleDataCmdF(command *cobra.Command, args []string) error {
a, err := InitDBCommandContextCobra(command)
if err != nil {
return err
}
defer a.Shutdown()
seed, err := command.Flags().GetInt64("seed")
if err != nil {
return errors.New("Invalid seed parameter")
}
bulk, err := command.Flags().GetString("bulk")
if err != nil {
return errors.New("Invalid bulk parameter")
}
teams, err := command.Flags().GetInt("teams")
if err != nil || teams < 0 {
return errors.New("Invalid teams parameter")
}
channelsPerTeam, err := command.Flags().GetInt("channels-per-team")
if err != nil || channelsPerTeam < 0 {
return errors.New("Invalid channels-per-team parameter")
}
users, err := command.Flags().GetInt("users")
if err != nil || users < 0 {
return errors.New("Invalid users parameter")
}
teamMemberships, err := command.Flags().GetInt("team-memberships")
if err != nil || teamMemberships < 0 {
return errors.New("Invalid team-memberships parameter")
}
channelMemberships, err := command.Flags().GetInt("channel-memberships")
if err != nil || channelMemberships < 0 {
return errors.New("Invalid channel-memberships parameter")
}
postsPerChannel, err := command.Flags().GetInt("posts-per-channel")
if err != nil || postsPerChannel < 0 {
return errors.New("Invalid posts-per-channel parameter")
}
directChannels, err := command.Flags().GetInt("direct-channels")
if err != nil || directChannels < 0 {
return errors.New("Invalid direct-channels parameter")
}
postsPerDirectChannel, err := command.Flags().GetInt("posts-per-direct-channel")
if err != nil || postsPerDirectChannel < 0 {
return errors.New("Invalid posts-per-direct-channel parameter")
}
groupChannels, err := command.Flags().GetInt("group-channels")
if err != nil || groupChannels < 0 {
return errors.New("Invalid group-channels parameter")
}
postsPerGroupChannel, err := command.Flags().GetInt("posts-per-group-channel")
if err != nil || postsPerGroupChannel < 0 {
return errors.New("Invalid posts-per-group-channel parameter")
}
workers, err := command.Flags().GetInt("workers")
if err != nil {
return errors.New("Invalid workers parameter")
}
profileImagesPath, err := command.Flags().GetString("profile-images")
if err != nil {
return errors.New("Invalid profile-images parameter")
}
profileImages := []string{}
if profileImagesPath != "" {
profileImagesStat, err := os.Stat(profileImagesPath)
if os.IsNotExist(err) {
return errors.New("Profile images folder doesn't exists.")
}
if !profileImagesStat.IsDir() {
return errors.New("profile-images parameters must be a folder path.")
}
profileImagesFiles, err := ioutil.ReadDir(profileImagesPath)
if err != nil {
return errors.New("Invalid profile-images parameter")
}
for _, profileImage := range profileImagesFiles {
profileImages = append(profileImages, path.Join(profileImagesPath, profileImage.Name()))
}
sort.Strings(profileImages)
}
if workers < 1 {
return errors.New("You must have at least one worker.")
}
if teamMemberships > teams {
return errors.New("You can't have more team memberships than teams.")
}
if channelMemberships > channelsPerTeam {
return errors.New("You can't have more channel memberships than channels per team.")
}
var bulkFile *os.File
switch bulk {
case "":
bulkFile, err = ioutil.TempFile("", ".mattermost-sample-data-")
defer os.Remove(bulkFile.Name())
if err != nil {
return errors.New("Unable to open temporary file.")
}
case "-":
bulkFile = os.Stdout
default:
bulkFile, err = os.OpenFile(bulk, os.O_RDWR|os.O_CREATE, 0755)
if err != nil {
return errors.New("Unable to write into the \"" + bulk + "\" file.")
}
}
encoder := json.NewEncoder(bulkFile)
version := 1
encoder.Encode(app.LineImportData{Type: "version", Version: &version})
fake.Seed(seed)
rand.Seed(seed)
teamsAndChannels := make(map[string][]string)
for i := 0; i < teams; i++ {
teamLine := createTeam(i)
teamsAndChannels[*teamLine.Team.Name] = []string{}
encoder.Encode(teamLine)
}
teamsList := []string{}
for teamName := range teamsAndChannels {
teamsList = append(teamsList, teamName)
}
sort.Strings(teamsList)
for _, teamName := range teamsList {
for i := 0; i < channelsPerTeam; i++ {
channelLine := createChannel(i, teamName)
teamsAndChannels[teamName] = append(teamsAndChannels[teamName], *channelLine.Channel.Name)
encoder.Encode(channelLine)
}
}
allUsers := []string{}
for i := 0; i < users; i++ {
userLine := createUser(i, teamMemberships, channelMemberships, teamsAndChannels, profileImages)
encoder.Encode(userLine)
allUsers = append(allUsers, *userLine.User.Username)
}
for team, channels := range teamsAndChannels {
for _, channel := range channels {
for i := 0; i < postsPerChannel; i++ {
postLine := createPost(team, channel, allUsers)
encoder.Encode(postLine)
}
}
}
for i := 0; i < directChannels; i++ {
user1 := allUsers[rand.Intn(len(allUsers))]
user2 := allUsers[rand.Intn(len(allUsers))]
channelLine := createDirectChannel([]string{user1, user2})
encoder.Encode(channelLine)
for j := 0; j < postsPerDirectChannel; j++ {
postLine := createDirectPost([]string{user1, user2})
encoder.Encode(postLine)
}
}
for i := 0; i < groupChannels; i++ {
users := []string{}
totalUsers := 3 + rand.Intn(3)
for len(users) < totalUsers {
user := allUsers[rand.Intn(len(allUsers))]
if !sliceIncludes(users, user) {
users = append(users, user)
}
}
channelLine := createDirectChannel(users)
encoder.Encode(channelLine)
for j := 0; j < postsPerGroupChannel; j++ {
postLine := createDirectPost(users)
encoder.Encode(postLine)
}
}
if bulk == "" {
_, err := bulkFile.Seek(0, 0)
if err != nil {
return errors.New("Unable to read correctly the temporary file.")
}
importErr, lineNumber := a.BulkImport(bulkFile, false, workers)
if importErr != nil {
return fmt.Errorf("%s: %s, %s (line: %d)", importErr.Where, importErr.Message, importErr.DetailedError, lineNumber)
}
} else if bulk != "-" {
err := bulkFile.Close()
if err != nil {
return errors.New("Unable to close correctly the output file")
}
}
return nil
}
func createUser(idx int, teamMemberships int, channelMemberships int, teamsAndChannels map[string][]string, profileImages []string) app.LineImportData {
password := fmt.Sprintf("user-%d", idx)
email := fmt.Sprintf("user-%d@sample.mattermost.com", idx)
firstName := fake.FirstName()
lastName := fake.LastName()
username := fmt.Sprintf("%s.%s", strings.ToLower(firstName), strings.ToLower(lastName))
if idx == 0 {
username = "sysadmin"
password = "sysadmin"
email = "sysadmin@sample.mattermost.com"
} else if idx == 1 {
username = "user-1"
}
position := fake.JobTitle()
roles := "system_user"
if idx%5 == 0 {
roles = "system_admin system_user"
}
// The 75% of the users have custom profile image
var profileImage *string = nil
if rand.Intn(4) != 0 {
profileImageSelector := rand.Int()
if len(profileImages) > 0 {
profileImage = &profileImages[profileImageSelector%len(profileImages)]
}
}
useMilitaryTime := "false"
if idx != 0 && rand.Intn(2) == 0 {
useMilitaryTime = "true"
}
collapsePreviews := "false"
if idx != 0 && rand.Intn(2) == 0 {
collapsePreviews = "true"
}
messageDisplay := "clean"
if idx != 0 && rand.Intn(2) == 0 {
messageDisplay = "compact"
}
channelDisplayMode := "full"
if idx != 0 && rand.Intn(2) == 0 {
channelDisplayMode = "centered"
}
// Some users has nickname
nickname := ""
if rand.Intn(5) == 0 {
nickname = fake.Company()
}
// Half of users skip tutorial
tutorialStep := "999"
switch rand.Intn(6) {
case 1:
tutorialStep = "1"
case 2:
tutorialStep = "2"
case 3:
tutorialStep = "3"
}
teams := []app.UserTeamImportData{}
possibleTeams := []string{}
for teamName := range teamsAndChannels {
possibleTeams = append(possibleTeams, teamName)
}
sort.Strings(possibleTeams)
for x := 0; x < teamMemberships; x++ {
if len(possibleTeams) == 0 {
break
}
position := rand.Intn(len(possibleTeams))
team := possibleTeams[position]
possibleTeams = append(possibleTeams[:position], possibleTeams[position+1:]...)
if teamChannels, err := teamsAndChannels[team]; err {
teams = append(teams, createTeamMembership(channelMemberships, teamChannels, &team))
}
}
user := app.UserImportData{
ProfileImage: profileImage,
Username: &username,
Email: &email,
Password: &password,
Nickname: &nickname,
FirstName: &firstName,
LastName: &lastName,
Position: &position,
Roles: &roles,
Teams: &teams,
UseMilitaryTime: &useMilitaryTime,
CollapsePreviews: &collapsePreviews,
MessageDisplay: &messageDisplay,
ChannelDisplayMode: &channelDisplayMode,
TutorialStep: &tutorialStep,
}
return app.LineImportData{
Type: "user",
User: &user,
}
}
func createTeamMembership(numOfchannels int, teamChannels []string, teamName *string) app.UserTeamImportData {
roles := "team_user"
if rand.Intn(5) == 0 {
roles = "team_user team_admin"
}
channels := []app.UserChannelImportData{}
teamChannelsCopy := append([]string(nil), teamChannels...)
for x := 0; x < numOfchannels; x++ {
if len(teamChannelsCopy) == 0 {
break
}
position := rand.Intn(len(teamChannelsCopy))
channelName := teamChannelsCopy[position]
teamChannelsCopy = append(teamChannelsCopy[:position], teamChannelsCopy[position+1:]...)
channels = append(channels, createChannelMembership(channelName))
}
return app.UserTeamImportData{
Name: teamName,
Roles: &roles,
Channels: &channels,
}
}
func createChannelMembership(channelName string) app.UserChannelImportData {
roles := "channel_user"
if rand.Intn(5) == 0 {
roles = "channel_user channel_admin"
}
favorite := rand.Intn(5) == 0
return app.UserChannelImportData{
Name: &channelName,
Roles: &roles,
Favorite: &favorite,
}
}
func createTeam(idx int) app.LineImportData {
displayName := fake.Word()
name := fmt.Sprintf("%s-%d", fake.Word(), idx)
allowOpenInvite := rand.Intn(2) == 0
description := fake.Paragraph()
if len(description) > 255 {
description = description[0:255]
}
teamType := "O"
if rand.Intn(2) == 0 {
teamType = "I"
}
team := app.TeamImportData{
DisplayName: &displayName,
Name: &name,
AllowOpenInvite: &allowOpenInvite,
Description: &description,
Type: &teamType,
}
return app.LineImportData{
Type: "team",
Team: &team,
}
}
func createChannel(idx int, teamName string) app.LineImportData {
displayName := fake.Word()
name := fmt.Sprintf("%s-%d", fake.Word(), idx)
header := fake.Paragraph()
purpose := fake.Paragraph()
if len(purpose) > 250 {
purpose = purpose[0:250]
}
channelType := "P"
if rand.Intn(2) == 0 {
channelType = "O"
}
channel := app.ChannelImportData{
Team: &teamName,
Name: &name,
DisplayName: &displayName,
Type: &channelType,
Header: &header,
Purpose: &purpose,
}
return app.LineImportData{
Type: "channel",
Channel: &channel,
}
}
func createPost(team string, channel string, allUsers []string) app.LineImportData {
message := randomMessage(allUsers)
create_at := randomPastTime(50000)
user := allUsers[rand.Intn(len(allUsers))]
// Some messages are flagged by an user
flagged_by := []string{}
if rand.Intn(10) == 0 {
flagged_by = append(flagged_by, allUsers[rand.Intn(len(allUsers))])
}
reactions := []app.ReactionImportData{}
if rand.Intn(10) == 0 {
for {
reactions = append(reactions, randomReaction(allUsers, create_at))
if rand.Intn(3) == 0 {
break
}
}
}
replies := []app.ReplyImportData{}
if rand.Intn(10) == 0 {
for {
replies = append(replies, randomReply(allUsers, create_at))
if rand.Intn(4) == 0 {
break
}
}
}
post := app.PostImportData{
Team: &team,
Channel: &channel,
User: &user,
Message: &message,
CreateAt: &create_at,
FlaggedBy: &flagged_by,
Reactions: &reactions,
Replies: &replies,
}
return app.LineImportData{
Type: "post",
Post: &post,
}
}
func createDirectChannel(members []string) app.LineImportData {
header := fake.Sentence()
channel := app.DirectChannelImportData{
Members: &members,
Header: &header,
}
return app.LineImportData{
Type: "direct_channel",
DirectChannel: &channel,
}
}
func createDirectPost(members []string) app.LineImportData {
message := randomMessage(members)
create_at := randomPastTime(50000)
user := members[rand.Intn(len(members))]
// Some messages are flagged by an user
flagged_by := []string{}
if rand.Intn(10) == 0 {
flagged_by = append(flagged_by, members[rand.Intn(len(members))])
}
reactions := []app.ReactionImportData{}
if rand.Intn(10) == 0 {
for {
reactions = append(reactions, randomReaction(members, create_at))
if rand.Intn(3) == 0 {
break
}
}
}
replies := []app.ReplyImportData{}
if rand.Intn(10) == 0 {
for {
replies = append(replies, randomReply(members, create_at))
if rand.Intn(4) == 0 {
break
}
}
}
post := app.DirectPostImportData{
ChannelMembers: &members,
User: &user,
Message: &message,
CreateAt: &create_at,
FlaggedBy: &flagged_by,
Reactions: &reactions,
Replies: &replies,
}
return app.LineImportData{
Type: "direct_post",
DirectPost: &post,
}
}

25
cmd/mattermost/commands/sampledata_test.go Обычный файл
Просмотреть файл

@@ -0,0 +1,25 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package commands
import (
"testing"
"github.com/mattermost/mattermost-server/api4"
"github.com/stretchr/testify/require"
)
func TestSampledataBadParameters(t *testing.T) {
th := api4.Setup().InitBasic()
defer th.TearDown()
// should fail because you need at least 1 worker
require.Error(t, RunCommand(t, "sampledata", "--workers", "0"))
// should fail because you have more team memberships than teams
require.Error(t, RunCommand(t, "sampledata", "--teams", "10", "--teams-memberships", "11"))
// should fail because you have more channel memberships than channels per team
require.Error(t, RunCommand(t, "sampledata", "--channels-per-team", "10", "--channel-memberships", "11"))
}

299
cmd/mattermost/commands/server.go Обычный файл
Просмотреть файл

@@ -0,0 +1,299 @@
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package commands
import (
"fmt"
"net"
"os"
"os/signal"
"syscall"
"time"
"github.com/mattermost/mattermost-server/api4"
"github.com/mattermost/mattermost-server/app"
"github.com/mattermost/mattermost-server/manualtesting"
"github.com/mattermost/mattermost-server/mlog"
"github.com/mattermost/mattermost-server/model"
"github.com/mattermost/mattermost-server/utils"
"github.com/mattermost/mattermost-server/web"
"github.com/mattermost/mattermost-server/wsapi"
"github.com/spf13/cobra"
)
const (
SESSIONS_CLEANUP_BATCH_SIZE = 1000
)
var MaxNotificationsPerChannelDefault int64 = 1000000
var serverCmd = &cobra.Command{
Use: "server",
Short: "Run the Mattermost server",
RunE: serverCmdF,
SilenceUsage: true,
}
func init() {
RootCmd.AddCommand(serverCmd)
RootCmd.RunE = serverCmdF
}
func serverCmdF(command *cobra.Command, args []string) error {
config, err := command.Flags().GetString("config")
if err != nil {
return err
}
disableConfigWatch, _ := command.Flags().GetBool("disableconfigwatch")
usedPlatform, _ := command.Flags().GetBool("platform")
interruptChan := make(chan os.Signal, 1)
return runServer(config, disableConfigWatch, usedPlatform, interruptChan)
}
func runServer(configFileLocation string, disableConfigWatch bool, usedPlatform bool, interruptChan chan os.Signal) error {
options := []app.Option{app.ConfigFile(configFileLocation)}
if disableConfigWatch {
options = append(options, app.DisableConfigWatch)
}
a, err := app.New(options...)
if err != nil {
mlog.Critical(err.Error())
return err
}
defer a.Shutdown()
utils.TestConnection(a.Config())
pwd, _ := os.Getwd()
if usedPlatform {
mlog.Error("The platform binary has been deprecated, please switch to using the mattermost binary.")
}
mlog.Info(fmt.Sprintf("Current version is %v (%v/%v/%v/%v)", model.CurrentVersion, model.BuildNumber, model.BuildDate, model.BuildHash, model.BuildHashEnterprise))
mlog.Info(fmt.Sprintf("Enterprise Enabled: %v", model.BuildEnterpriseReady))
mlog.Info(fmt.Sprintf("Current working directory is %v", pwd))
mlog.Info(fmt.Sprintf("Loaded config file from %v", utils.FindConfigFile(configFileLocation)))
backend, appErr := a.FileBackend()
if appErr == nil {
appErr = backend.TestConnection()
}
if appErr != nil {
mlog.Error("Problem with file storage settings: " + appErr.Error())
}
if model.BuildEnterpriseReady == "true" {
a.LoadLicense()
}
a.DoAdvancedPermissionsMigration()
a.InitPlugins(*a.Config().PluginSettings.Directory, *a.Config().PluginSettings.ClientDirectory, nil)
a.AddConfigListener(func(prevCfg, cfg *model.Config) {
if *cfg.PluginSettings.Enable {
a.InitPlugins(*cfg.PluginSettings.Directory, *a.Config().PluginSettings.ClientDirectory, nil)
} else {
a.ShutDownPlugins()
}
})
serverErr := a.StartServer()
if serverErr != nil {
mlog.Critical(serverErr.Error())
return serverErr
}
api := api4.Init(a, a.Srv.Router)
wsapi.Init(a, a.Srv.WebSocketRouter)
web.NewWeb(a, a.Srv.Router)
license := a.License()
if license == nil && len(a.Config().SqlSettings.DataSourceReplicas) > 1 {
mlog.Warn("More than 1 read replica functionality disabled by current license. Please contact your system administrator about upgrading your enterprise license.")
a.UpdateConfig(func(cfg *model.Config) {
cfg.SqlSettings.DataSourceReplicas = cfg.SqlSettings.DataSourceReplicas[:1]
})
}
if license == nil {
a.UpdateConfig(func(cfg *model.Config) {
cfg.TeamSettings.MaxNotificationsPerChannel = &MaxNotificationsPerChannelDefault
})
}
a.ReloadConfig()
// Enable developer settings if this is a "dev" build
if model.BuildNumber == "dev" {
a.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableDeveloper = true })
}
resetStatuses(a)
// If we allow testing then listen for manual testing URL hits
if a.Config().ServiceSettings.EnableTesting {
manualtesting.Init(api)
}
a.EnsureDiagnosticId()
a.Go(func() {
runSecurityJob(a)
})
a.Go(func() {
runDiagnosticsJob(a)
})
a.Go(func() {
runSessionCleanupJob(a)
})
a.Go(func() {
runTokenCleanupJob(a)
})
a.Go(func() {
runCommandWebhookCleanupJob(a)
})
if complianceI := a.Compliance; complianceI != nil {
complianceI.StartComplianceDailyJob()
}
if a.Cluster != nil {
a.RegisterAllClusterMessageHandlers()
a.Cluster.StartInterNodeCommunication()
}
if a.Metrics != nil {
a.Metrics.StartServer()
}
if a.Elasticsearch != nil {
a.Go(func() {
if err := a.Elasticsearch.Start(); err != nil {
mlog.Error(err.Error())
}
})
}
if *a.Config().JobSettings.RunJobs {
a.Jobs.StartWorkers()
}
if *a.Config().JobSettings.RunScheduler {
a.Jobs.StartSchedulers()
}
notifyReady()
// wait for kill signal before attempting to gracefully shutdown
// the running service
signal.Notify(interruptChan, os.Interrupt, syscall.SIGINT, syscall.SIGTERM)
<-interruptChan
if a.Cluster != nil {
a.Cluster.StopInterNodeCommunication()
}
if a.Metrics != nil {
a.Metrics.StopServer()
}
a.Jobs.StopSchedulers()
a.Jobs.StopWorkers()
return nil
}
func runSecurityJob(a *app.App) {
doSecurity(a)
model.CreateRecurringTask("Security", func() {
doSecurity(a)
}, time.Hour*4)
}
func runDiagnosticsJob(a *app.App) {
doDiagnostics(a)
model.CreateRecurringTask("Diagnostics", func() {
doDiagnostics(a)
}, time.Hour*24)
}
func runTokenCleanupJob(a *app.App) {
doTokenCleanup(a)
model.CreateRecurringTask("Token Cleanup", func() {
doTokenCleanup(a)
}, time.Hour*1)
}
func runCommandWebhookCleanupJob(a *app.App) {
doCommandWebhookCleanup(a)
model.CreateRecurringTask("Command Hook Cleanup", func() {
doCommandWebhookCleanup(a)
}, time.Hour*1)
}
func runSessionCleanupJob(a *app.App) {
doSessionCleanup(a)
model.CreateRecurringTask("Session Cleanup", func() {
doSessionCleanup(a)
}, time.Hour*24)
}
func resetStatuses(a *app.App) {
if result := <-a.Srv.Store.Status().ResetAll(); result.Err != nil {
mlog.Error(fmt.Sprint("mattermost.reset_status.error FIXME: NOT FOUND IN TRANSLATIONS FILE", result.Err.Error()))
}
}
func doSecurity(a *app.App) {
a.DoSecurityUpdateCheck()
}
func doDiagnostics(a *app.App) {
if *a.Config().LogSettings.EnableDiagnostics {
a.SendDailyDiagnostics()
}
}
func notifyReady() {
// If the environment vars provide a systemd notification socket,
// notify systemd that the server is ready.
systemdSocket := os.Getenv("NOTIFY_SOCKET")
if systemdSocket != "" {
mlog.Info("Sending systemd READY notification.")
err := sendSystemdReadyNotification(systemdSocket)
if err != nil {
mlog.Error(err.Error())
}
}
}
func sendSystemdReadyNotification(socketPath string) error {
msg := "READY=1"
addr := &net.UnixAddr{
Name: socketPath,
Net: "unixgram",
}
conn, err := net.DialUnix(addr.Net, nil, addr)
if err != nil {
return err
}
defer conn.Close()
_, err = conn.Write([]byte(msg))
return err
}
func doTokenCleanup(a *app.App) {
a.Srv.Store.Token().Cleanup()
}
func doCommandWebhookCleanup(a *app.App) {
a.Srv.Store.CommandWebhook().Cleanup()
}
func doSessionCleanup(a *app.App) {
a.Srv.Store.Session().Cleanup(model.GetMillis(), SESSIONS_CLEANUP_BATCH_SIZE)
}

136
cmd/mattermost/commands/server_test.go Обычный файл
Просмотреть файл

@@ -0,0 +1,136 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package commands
import (
"io/ioutil"
"net"
"os"
"syscall"
"testing"
"github.com/mattermost/mattermost-server/jobs"
"github.com/mattermost/mattermost-server/utils"
"github.com/stretchr/testify/require"
)
type ServerTestHelper struct {
configPath string
disableConfigWatch bool
interruptChan chan os.Signal
originalInterval int
}
func SetupServerTest() *ServerTestHelper {
// Build a channel that will be used by the server to receive system signals…
interruptChan := make(chan os.Signal, 1)
// …and sent it immediately a SIGINT value.
// This will make the server loop stop as soon as it started successfully.
interruptChan <- syscall.SIGINT
// Let jobs poll for termination every 0.2s (instead of every 15s by default)
// Otherwise we would have to wait the whole polling duration before the test
// terminates.
originalInterval := jobs.DEFAULT_WATCHER_POLLING_INTERVAL
jobs.DEFAULT_WATCHER_POLLING_INTERVAL = 200
th := &ServerTestHelper{
configPath: utils.FindConfigFile("config.json"),
disableConfigWatch: true,
interruptChan: interruptChan,
originalInterval: originalInterval,
}
return th
}
func (th *ServerTestHelper) TearDownServerTest() {
jobs.DEFAULT_WATCHER_POLLING_INTERVAL = th.originalInterval
}
func TestRunServerSuccess(t *testing.T) {
th := SetupServerTest()
defer th.TearDownServerTest()
err := runServer(th.configPath, th.disableConfigWatch, false, th.interruptChan)
require.NoError(t, err)
}
func TestRunServerInvalidConfigFile(t *testing.T) {
th := SetupServerTest()
defer th.TearDownServerTest()
// Start the server with an unreadable config file
unreadableConfigFile, err := ioutil.TempFile("", "mattermost-unreadable-config-file-")
if err != nil {
panic(err)
}
os.Chmod(unreadableConfigFile.Name(), 0200)
defer os.Remove(unreadableConfigFile.Name())
err = runServer(unreadableConfigFile.Name(), th.disableConfigWatch, false, th.interruptChan)
require.Error(t, err)
}
func TestRunServerSystemdNotification(t *testing.T) {
th := SetupServerTest()
defer th.TearDownServerTest()
// Get a random temporary filename for using as a mock systemd socket
socketFile, err := ioutil.TempFile("", "mattermost-systemd-mock-socket-")
if err != nil {
panic(err)
}
socketPath := socketFile.Name()
os.Remove(socketPath)
// Set the socket path in the process environment
originalSocket := os.Getenv("NOTIFY_SOCKET")
os.Setenv("NOTIFY_SOCKET", socketPath)
defer os.Setenv("NOTIFY_SOCKET", originalSocket)
// Open the socket connection
addr := &net.UnixAddr{
Name: socketPath,
Net: "unixgram",
}
connection, err := net.ListenUnixgram("unixgram", addr)
if err != nil {
panic(err)
}
defer connection.Close()
defer os.Remove(socketPath)
// Listen for socket data
socketReader := make(chan string)
go func(ch chan string) {
buffer := make([]byte, 512)
count, err := connection.Read(buffer)
if err != nil {
panic(err)
}
data := buffer[0:count]
ch <- string(data)
}(socketReader)
// Start and stop the server
err = runServer(th.configPath, th.disableConfigWatch, false, th.interruptChan)
require.NoError(t, err)
// Ensure the notification has been sent on the socket and is correct
notification := <-socketReader
require.Equal(t, notification, "READY=1")
}
func TestRunServerNoSystemd(t *testing.T) {
th := SetupServerTest()
defer th.TearDownServerTest()
// Temporarily remove any Systemd socket defined in the environment
originalSocket := os.Getenv("NOTIFY_SOCKET")
os.Unsetenv("NOTIFY_SOCKET")
defer os.Setenv("NOTIFY_SOCKET", originalSocket)
err := runServer(th.configPath, th.disableConfigWatch, false, th.interruptChan)
require.NoError(t, err)
}

249
cmd/mattermost/commands/team.go Обычный файл
Просмотреть файл

@@ -0,0 +1,249 @@
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package commands
import (
"errors"
"fmt"
"github.com/mattermost/mattermost-server/app"
"github.com/mattermost/mattermost-server/model"
"github.com/spf13/cobra"
)
var TeamCmd = &cobra.Command{
Use: "team",
Short: "Management of teams",
}
var TeamCreateCmd = &cobra.Command{
Use: "create",
Short: "Create a team",
Long: `Create a team.`,
Example: ` team create --name mynewteam --display_name "My New Team"
team create --name private --display_name "My New Private Team" --private`,
RunE: createTeamCmdF,
}
var RemoveUsersCmd = &cobra.Command{
Use: "remove [team] [users]",
Short: "Remove users from team",
Long: "Remove some users from team",
Example: " team remove myteam user@example.com username",
RunE: removeUsersCmdF,
}
var AddUsersCmd = &cobra.Command{
Use: "add [team] [users]",
Short: "Add users to team",
Long: "Add some users to team",
Example: " team add myteam user@example.com username",
RunE: addUsersCmdF,
}
var DeleteTeamsCmd = &cobra.Command{
Use: "delete [teams]",
Short: "Delete teams",
Long: `Permanently delete some teams.
Permanently deletes a team along with all related information including posts from the database.`,
Example: " team delete myteam",
RunE: deleteTeamsCmdF,
}
var ListTeamsCmd = &cobra.Command{
Use: "list",
Short: "List all teams.",
Long: `List all teams on the server.`,
Example: " team list",
RunE: listTeamsCmdF,
}
func init() {
TeamCreateCmd.Flags().String("name", "", "Team Name")
TeamCreateCmd.Flags().String("display_name", "", "Team Display Name")
TeamCreateCmd.Flags().Bool("private", false, "Create a private team.")
TeamCreateCmd.Flags().String("email", "", "Administrator Email (anyone with this email is automatically a team admin)")
DeleteTeamsCmd.Flags().Bool("confirm", false, "Confirm you really want to delete the team and a DB backup has been performed.")
TeamCmd.AddCommand(
TeamCreateCmd,
RemoveUsersCmd,
AddUsersCmd,
DeleteTeamsCmd,
ListTeamsCmd,
)
RootCmd.AddCommand(TeamCmd)
}
func createTeamCmdF(command *cobra.Command, args []string) error {
a, err := InitDBCommandContextCobra(command)
if err != nil {
return err
}
defer a.Shutdown()
name, errn := command.Flags().GetString("name")
if errn != nil || name == "" {
return errors.New("Name is required")
}
displayname, errdn := command.Flags().GetString("display_name")
if errdn != nil || displayname == "" {
return errors.New("Display Name is required")
}
email, _ := command.Flags().GetString("email")
useprivate, _ := command.Flags().GetBool("private")
teamType := model.TEAM_OPEN
if useprivate {
teamType = model.TEAM_INVITE
}
team := &model.Team{
Name: name,
DisplayName: displayname,
Email: email,
Type: teamType,
}
if _, err := a.CreateTeam(team); err != nil {
return errors.New("Team creation failed: " + err.Error())
}
return nil
}
func removeUsersCmdF(command *cobra.Command, args []string) error {
a, err := InitDBCommandContextCobra(command)
if err != nil {
return err
}
defer a.Shutdown()
if len(args) < 2 {
return errors.New("Not enough arguments.")
}
team := getTeamFromTeamArg(a, args[0])
if team == nil {
return errors.New("Unable to find team '" + args[0] + "'")
}
users := getUsersFromUserArgs(a, args[1:])
for i, user := range users {
removeUserFromTeam(a, team, user, args[i+1])
}
return nil
}
func removeUserFromTeam(a *app.App, team *model.Team, user *model.User, userArg string) {
if user == nil {
CommandPrintErrorln("Can't find user '" + userArg + "'")
return
}
if err := a.LeaveTeam(team, user, ""); err != nil {
CommandPrintErrorln("Unable to remove '" + userArg + "' from " + team.Name + ". Error: " + err.Error())
}
}
func addUsersCmdF(command *cobra.Command, args []string) error {
a, err := InitDBCommandContextCobra(command)
if err != nil {
return err
}
defer a.Shutdown()
if len(args) < 2 {
return errors.New("Not enough arguments.")
}
team := getTeamFromTeamArg(a, args[0])
if team == nil {
return errors.New("Unable to find team '" + args[0] + "'")
}
users := getUsersFromUserArgs(a, args[1:])
for i, user := range users {
addUserToTeam(a, team, user, args[i+1])
}
return nil
}
func addUserToTeam(a *app.App, team *model.Team, user *model.User, userArg string) {
if user == nil {
CommandPrintErrorln("Can't find user '" + userArg + "'")
return
}
if err := a.JoinUserToTeam(team, user, ""); err != nil {
CommandPrintErrorln("Unable to add '" + userArg + "' to " + team.Name)
}
}
func deleteTeamsCmdF(command *cobra.Command, args []string) error {
a, err := InitDBCommandContextCobra(command)
if err != nil {
return err
}
defer a.Shutdown()
if len(args) < 1 {
return errors.New("Not enough arguments.")
}
confirmFlag, _ := command.Flags().GetBool("confirm")
if !confirmFlag {
var confirm string
CommandPrettyPrintln("Have you performed a database backup? (YES/NO): ")
fmt.Scanln(&confirm)
if confirm != "YES" {
return errors.New("ABORTED: You did not answer YES exactly, in all capitals.")
}
CommandPrettyPrintln("Are you sure you want to delete the teams specified? All data will be permanently deleted? (YES/NO): ")
fmt.Scanln(&confirm)
if confirm != "YES" {
return errors.New("ABORTED: You did not answer YES exactly, in all capitals.")
}
}
teams := getTeamsFromTeamArgs(a, args)
for i, team := range teams {
if team == nil {
CommandPrintErrorln("Unable to find team '" + args[i] + "'")
continue
}
if err := deleteTeam(a, team); err != nil {
CommandPrintErrorln("Unable to delete team '" + team.Name + "' error: " + err.Error())
} else {
CommandPrettyPrintln("Deleted team '" + team.Name + "'")
}
}
return nil
}
func deleteTeam(a *app.App, team *model.Team) *model.AppError {
return a.PermanentDeleteTeam(team)
}
func listTeamsCmdF(command *cobra.Command, args []string) error {
a, err := InitDBCommandContextCobra(command)
if err != nil {
return err
}
defer a.Shutdown()
teams, err2 := a.GetAllTeams()
if err2 != nil {
return err2
}
for _, team := range teams {
CommandPrettyPrintln(team.Name)
}
return nil
}

97
cmd/mattermost/commands/team_test.go Обычный файл
Просмотреть файл

@@ -0,0 +1,97 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package commands
import (
"strings"
"testing"
"github.com/mattermost/mattermost-server/api4"
"github.com/mattermost/mattermost-server/model"
)
func TestCreateTeam(t *testing.T) {
th := api4.Setup().InitSystemAdmin()
defer th.TearDown()
id := model.NewId()
name := "name" + id
displayName := "Name " + id
CheckCommand(t, "team", "create", "--name", name, "--display_name", displayName)
found := th.SystemAdminClient.Must(th.SystemAdminClient.TeamExists(name, "")).(bool)
if !found {
t.Fatal("Failed to create Team")
}
}
func TestJoinTeam(t *testing.T) {
th := api4.Setup().InitSystemAdmin().InitBasic()
defer th.TearDown()
CheckCommand(t, "team", "add", th.BasicTeam.Name, th.BasicUser.Email)
profiles := th.SystemAdminClient.Must(th.SystemAdminClient.GetUsersInTeam(th.BasicTeam.Id, 0, 1000, "")).([]*model.User)
found := false
for _, user := range profiles {
if user.Email == th.BasicUser.Email {
found = true
}
}
if !found {
t.Fatal("Failed to create User")
}
}
func TestLeaveTeam(t *testing.T) {
th := api4.Setup().InitBasic()
defer th.TearDown()
CheckCommand(t, "team", "remove", th.BasicTeam.Name, th.BasicUser.Email)
profiles := th.Client.Must(th.Client.GetUsersInTeam(th.BasicTeam.Id, 0, 1000, "")).([]*model.User)
found := false
for _, user := range profiles {
if user.Email == th.BasicUser.Email {
found = true
}
}
if found {
t.Fatal("profile should not be on team")
}
if result := <-th.App.Srv.Store.Team().GetTeamsByUserId(th.BasicUser.Id); result.Err != nil {
teamMembers := result.Data.([]*model.TeamMember)
if len(teamMembers) > 0 {
t.Fatal("Shouldn't be in team")
}
}
}
func TestListTeams(t *testing.T) {
th := api4.Setup().InitBasic()
defer th.TearDown()
id := model.NewId()
name := "name" + id
displayName := "Name " + id
CheckCommand(t, "team", "create", "--name", name, "--display_name", displayName)
output := CheckCommand(t, "team", "list", th.BasicTeam.Name, th.BasicUser.Email)
if !strings.Contains(string(output), name) {
t.Fatal("should have the created team")
}
}

33
cmd/mattermost/commands/teamargs.go Обычный файл
Просмотреть файл

@@ -0,0 +1,33 @@
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package commands
import (
"github.com/mattermost/mattermost-server/app"
"github.com/mattermost/mattermost-server/model"
)
func getTeamsFromTeamArgs(a *app.App, teamArgs []string) []*model.Team {
teams := make([]*model.Team, 0, len(teamArgs))
for _, teamArg := range teamArgs {
team := getTeamFromTeamArg(a, teamArg)
teams = append(teams, team)
}
return teams
}
func getTeamFromTeamArg(a *app.App, teamArg string) *model.Team {
var team *model.Team
if result := <-a.Srv.Store.Team().GetByName(teamArg); result.Err == nil {
team = result.Data.(*model.Team)
}
if team == nil {
if result := <-a.Srv.Store.Team().Get(teamArg); result.Err == nil {
team = result.Data.(*model.Team)
}
}
return team
}

147
cmd/mattermost/commands/test.go Обычный файл
Просмотреть файл

@@ -0,0 +1,147 @@
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package commands
import (
"bufio"
"fmt"
"os"
"os/exec"
"os/signal"
"syscall"
"github.com/mattermost/mattermost-server/api4"
"github.com/mattermost/mattermost-server/model"
"github.com/mattermost/mattermost-server/utils"
"github.com/mattermost/mattermost-server/wsapi"
"github.com/spf13/cobra"
)
var TestCmd = &cobra.Command{
Use: "test",
Short: "Testing Commands",
Hidden: true,
}
var RunWebClientTestsCmd = &cobra.Command{
Use: "web_client_tests",
Short: "Run the web client tests",
RunE: webClientTestsCmdF,
}
var RunServerForWebClientTestsCmd = &cobra.Command{
Use: "web_client_tests_server",
Short: "Run the server configured for running the web client tests against it",
RunE: serverForWebClientTestsCmdF,
}
func init() {
TestCmd.AddCommand(
RunWebClientTestsCmd,
RunServerForWebClientTestsCmd,
)
RootCmd.AddCommand(TestCmd)
}
func webClientTestsCmdF(command *cobra.Command, args []string) error {
a, err := InitDBCommandContextCobra(command)
if err != nil {
return err
}
defer a.Shutdown()
utils.InitTranslations(a.Config().LocalizationSettings)
serverErr := a.StartServer()
if serverErr != nil {
return serverErr
}
api4.Init(a, a.Srv.Router)
wsapi.Init(a, a.Srv.WebSocketRouter)
a.UpdateConfig(setupClientTests)
runWebClientTests()
return nil
}
func serverForWebClientTestsCmdF(command *cobra.Command, args []string) error {
a, err := InitDBCommandContextCobra(command)
if err != nil {
return err
}
defer a.Shutdown()
utils.InitTranslations(a.Config().LocalizationSettings)
serverErr := a.StartServer()
if serverErr != nil {
return serverErr
}
api4.Init(a, a.Srv.Router)
wsapi.Init(a, a.Srv.WebSocketRouter)
a.UpdateConfig(setupClientTests)
c := make(chan os.Signal, 1)
signal.Notify(c, os.Interrupt, syscall.SIGINT, syscall.SIGTERM)
<-c
return nil
}
func setupClientTests(cfg *model.Config) {
*cfg.TeamSettings.EnableOpenServer = true
*cfg.ServiceSettings.EnableCommands = false
*cfg.ServiceSettings.EnableOnlyAdminIntegrations = false
*cfg.ServiceSettings.EnableCustomEmoji = true
cfg.ServiceSettings.EnableIncomingWebhooks = false
cfg.ServiceSettings.EnableOutgoingWebhooks = false
}
func executeTestCommand(command *exec.Cmd) {
cmdOutPipe, err := command.StdoutPipe()
if err != nil {
CommandPrintErrorln("Failed to run tests")
os.Exit(1)
return
}
cmdErrOutPipe, err := command.StderrPipe()
if err != nil {
CommandPrintErrorln("Failed to run tests")
os.Exit(1)
return
}
cmdOutReader := bufio.NewScanner(cmdOutPipe)
cmdErrOutReader := bufio.NewScanner(cmdErrOutPipe)
go func() {
for cmdOutReader.Scan() {
fmt.Println(cmdOutReader.Text())
}
}()
go func() {
for cmdErrOutReader.Scan() {
fmt.Println(cmdErrOutReader.Text())
}
}()
if err := command.Run(); err != nil {
CommandPrintErrorln("Client Tests failed")
os.Exit(1)
return
}
}
func runWebClientTests() {
if webappDir := os.Getenv("WEBAPP_DIR"); webappDir != "" {
os.Chdir(webappDir)
} else {
os.Chdir("../mattermost-webapp")
}
cmd := exec.Command("npm", "test")
executeTestCommand(cmd)
}

715
cmd/mattermost/commands/user.go Обычный файл
Просмотреть файл

@@ -0,0 +1,715 @@
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package commands
import (
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"github.com/mattermost/mattermost-server/app"
"github.com/mattermost/mattermost-server/model"
"github.com/spf13/cobra"
)
var UserCmd = &cobra.Command{
Use: "user",
Short: "Management of users",
}
var UserActivateCmd = &cobra.Command{
Use: "activate [emails, usernames, userIds]",
Short: "Activate users",
Long: "Activate users that have been deactivated.",
Example: ` user activate user@example.com
user activate username`,
RunE: userActivateCmdF,
}
var UserDeactivateCmd = &cobra.Command{
Use: "deactivate [emails, usernames, userIds]",
Short: "Deactivate users",
Long: "Deactivate users. Deactivated users are immediately logged out of all sessions and are unable to log back in.",
Example: ` user deactivate user@example.com
user deactivate username`,
RunE: userDeactivateCmdF,
}
var UserCreateCmd = &cobra.Command{
Use: "create",
Short: "Create a user",
Long: "Create a user",
Example: ` user create --email user@example.com --username userexample --password Password1`,
RunE: userCreateCmdF,
}
var UserInviteCmd = &cobra.Command{
Use: "invite [email] [teams]",
Short: "Send user an email invite to a team.",
Long: `Send user an email invite to a team.
You can invite a user to multiple teams by listing them.
You can specify teams by name or ID.`,
Example: ` user invite user@example.com myteam
user invite user@example.com myteam1 myteam2`,
RunE: userInviteCmdF,
}
var ResetUserPasswordCmd = &cobra.Command{
Use: "password [user] [password]",
Short: "Set a user's password",
Long: "Set a user's password",
Example: " user password user@example.com Password1",
RunE: resetUserPasswordCmdF,
}
var updateUserEmailCmd = &cobra.Command{
Use: "email [user] [new email]",
Short: "Change email of the user",
Long: "Change email of the user.",
Example: ` user email test user@example.com
user activate username`,
RunE: updateUserEmailCmdF,
}
var ResetUserMfaCmd = &cobra.Command{
Use: "resetmfa [users]",
Short: "Turn off MFA",
Long: `Turn off multi-factor authentication for a user.
If MFA enforcement is enabled, the user will be forced to re-enable MFA as soon as they login.`,
Example: " user resetmfa user@example.com",
RunE: resetUserMfaCmdF,
}
var DeleteUserCmd = &cobra.Command{
Use: "delete [users]",
Short: "Delete users and all posts",
Long: "Permanently delete user and all related information including posts.",
Example: " user delete user@example.com",
RunE: deleteUserCmdF,
}
var DeleteAllUsersCmd = &cobra.Command{
Use: "deleteall",
Short: "Delete all users and all posts",
Long: "Permanently delete all users and all related information including posts.",
Example: " user deleteall",
RunE: deleteAllUsersCommandF,
}
var MigrateAuthCmd = &cobra.Command{
Use: "migrate_auth [from_auth] [to_auth] [migration-options]",
Short: "Mass migrate user accounts authentication type",
Long: `Migrates accounts from one authentication provider to another. For example, you can upgrade your authentication provider from email to ldap.`,
Example: " user migrate_auth email saml users.json",
Args: func(command *cobra.Command, args []string) error {
if len(args) < 2 {
return errors.New("Auth migration requires at least 2 arguments.")
}
toAuth := args[1]
if toAuth != "ldap" && toAuth != "saml" {
return errors.New("Invalid to_auth parameter, must be saml or ldap.")
}
if toAuth == "ldap" && len(args) != 3 {
return errors.New("Ldap migration requires 3 arguments.")
}
autoFlag, _ := command.Flags().GetBool("auto")
if toAuth == "saml" && autoFlag {
if len(args) != 2 {
return errors.New("Saml migration requires two arguments when using the --auto flag. See help text for details.")
}
}
if toAuth == "saml" && !autoFlag {
if len(args) != 3 {
return errors.New("Saml migration requires three arguments when not using the --auto flag. See help text for details.")
}
}
return nil
},
RunE: migrateAuthCmdF,
}
var VerifyUserCmd = &cobra.Command{
Use: "verify [users]",
Short: "Verify email of users",
Long: "Verify the emails of some users.",
Example: " user verify user1",
RunE: verifyUserCmdF,
}
var SearchUserCmd = &cobra.Command{
Use: "search [users]",
Short: "Search for users",
Long: "Search for users based on username, email, or user ID.",
Example: " user search user1@mail.com user2@mail.com",
RunE: searchUserCmdF,
}
func init() {
UserCreateCmd.Flags().String("username", "", "Required. Username for the new user account.")
UserCreateCmd.Flags().String("email", "", "Required. The email address for the new user account.")
UserCreateCmd.Flags().String("password", "", "Required. The password for the new user account.")
UserCreateCmd.Flags().String("nickname", "", "Optional. The nickname for the new user account.")
UserCreateCmd.Flags().String("firstname", "", "Optional. The first name for the new user account.")
UserCreateCmd.Flags().String("lastname", "", "Optional. The last name for the new user account.")
UserCreateCmd.Flags().String("locale", "", "Optional. The locale (ex: en, fr) for the new user account.")
UserCreateCmd.Flags().Bool("system_admin", false, "Optional. If supplied, the new user will be a system administrator. Defaults to false.")
DeleteUserCmd.Flags().Bool("confirm", false, "Confirm you really want to delete the user and a DB backup has been performed.")
DeleteAllUsersCmd.Flags().Bool("confirm", false, "Confirm you really want to delete the user and a DB backup has been performed.")
MigrateAuthCmd.Flags().Bool("force", false, "Force the migration to occur even if there are duplicates on the LDAP server. Duplicates will not be migrated. (ldap only)")
MigrateAuthCmd.Flags().Bool("auto", false, "Automatically migrate all users. Assumes the usernames and emails are identical between Mattermost and SAML services. (saml only)")
MigrateAuthCmd.Flags().Bool("dryRun", false, "Run a simulation of the migration process without changing the database.")
MigrateAuthCmd.SetUsageTemplate(`Usage:
platform user migrate_auth [from_auth] [to_auth] [migration-options] [flags]
Examples:
{{.Example}}
Arguments:
from_auth:
The authentication service to migrate users accounts from.
Supported options: email, gitlab, ldap, saml.
to_auth:
The authentication service to migrate users to.
Supported options: ldap, saml.
migration-options:
Migration specific options, full command help for more information.
Flags:
{{.LocalFlags.FlagUsages | trimTrailingWhitespaces}}
Global Flags:
{{.InheritedFlags.FlagUsages | trimTrailingWhitespaces}}
`)
MigrateAuthCmd.SetHelpTemplate(`Usage:
platform user migrate_auth [from_auth] [to_auth] [migration-options] [flags]
Examples:
{{.Example}}
Arguments:
from_auth:
The authentication service to migrate users accounts from.
Supported options: email, gitlab, ldap, saml.
to_auth:
The authentication service to migrate users to.
Supported options: ldap, saml.
migration-options (ldap):
match_field:
The field that is guaranteed to be the same in both authentication services. For example, if the users emails are consistent set to email.
Supported options: email, username.
migration-options (saml):
users_file:
The path of a json file with the usernames and emails of all users to migrate to SAML. The username and email must be the same that the SAML service provider store. And the email must match with the email in mattermost database.
Example json content:
{
"usr1@email.com": "usr.one",
"usr2@email.com": "usr.two"
}
Flags:
{{.LocalFlags.FlagUsages | trimTrailingWhitespaces}}
Global Flags:
{{.InheritedFlags.FlagUsages | trimTrailingWhitespaces}}
`)
UserCmd.AddCommand(
UserActivateCmd,
UserDeactivateCmd,
UserCreateCmd,
UserInviteCmd,
ResetUserPasswordCmd,
updateUserEmailCmd,
ResetUserMfaCmd,
DeleteUserCmd,
DeleteAllUsersCmd,
MigrateAuthCmd,
VerifyUserCmd,
SearchUserCmd,
)
RootCmd.AddCommand(UserCmd)
}
func userActivateCmdF(command *cobra.Command, args []string) error {
a, err := InitDBCommandContextCobra(command)
if err != nil {
return err
}
defer a.Shutdown()
if len(args) < 1 {
return errors.New("Expected at least one argument. See help text for details.")
}
changeUsersActiveStatus(a, args, true)
return nil
}
func changeUsersActiveStatus(a *app.App, userArgs []string, active bool) {
users := getUsersFromUserArgs(a, userArgs)
for i, user := range users {
err := changeUserActiveStatus(a, user, userArgs[i], active)
if err != nil {
CommandPrintErrorln(err.Error())
}
}
}
func changeUserActiveStatus(a *app.App, user *model.User, userArg string, activate bool) error {
if user == nil {
return fmt.Errorf("Can't find user '%v'", userArg)
}
if user.IsSSOUser() {
fmt.Println("You must also deactivate this user in the SSO provider or they will be reactivated on next login or sync.")
}
if _, err := a.UpdateActive(user, activate); err != nil {
return fmt.Errorf("Unable to change activation status of user: %v", userArg)
}
return nil
}
func userDeactivateCmdF(command *cobra.Command, args []string) error {
a, err := InitDBCommandContextCobra(command)
if err != nil {
return err
}
defer a.Shutdown()
if len(args) < 1 {
return errors.New("Expected at least one argument. See help text for details.")
}
changeUsersActiveStatus(a, args, false)
return nil
}
func userCreateCmdF(command *cobra.Command, args []string) error {
a, err := InitDBCommandContextCobra(command)
if err != nil {
return err
}
defer a.Shutdown()
username, erru := command.Flags().GetString("username")
if erru != nil || username == "" {
return errors.New("Username is required")
}
email, erre := command.Flags().GetString("email")
if erre != nil || email == "" {
return errors.New("Email is required")
}
password, errp := command.Flags().GetString("password")
if errp != nil || password == "" {
return errors.New("Password is required")
}
nickname, _ := command.Flags().GetString("nickname")
firstname, _ := command.Flags().GetString("firstname")
lastname, _ := command.Flags().GetString("lastname")
locale, _ := command.Flags().GetString("locale")
systemAdmin, _ := command.Flags().GetBool("system_admin")
user := &model.User{
Username: username,
Email: email,
Password: password,
Nickname: nickname,
FirstName: firstname,
LastName: lastname,
Locale: locale,
}
if ruser, err := a.CreateUser(user); err != nil {
return errors.New("Unable to create user. Error: " + err.Error())
} else if systemAdmin {
a.UpdateUserRoles(ruser.Id, "system_user system_admin", false)
}
CommandPrettyPrintln("Created User")
return nil
}
func userInviteCmdF(command *cobra.Command, args []string) error {
a, err := InitDBCommandContextCobra(command)
if err != nil {
return err
}
defer a.Shutdown()
if len(args) < 2 {
return errors.New("Expected at least two arguments. See help text for details.")
}
email := args[0]
if !model.IsValidEmail(email) {
return errors.New("Invalid email")
}
teams := getTeamsFromTeamArgs(a, args[1:])
for i, team := range teams {
err := inviteUser(a, email, team, args[i+1])
if err != nil {
CommandPrintErrorln(err.Error())
}
}
return nil
}
func inviteUser(a *app.App, email string, team *model.Team, teamArg string) error {
invites := []string{email}
if team == nil {
return fmt.Errorf("Can't find team '%v'", teamArg)
}
a.SendInviteEmails(team, "Administrator", invites, *a.Config().ServiceSettings.SiteURL)
CommandPrettyPrintln("Invites may or may not have been sent.")
return nil
}
func resetUserPasswordCmdF(command *cobra.Command, args []string) error {
a, err := InitDBCommandContextCobra(command)
if err != nil {
return err
}
defer a.Shutdown()
if len(args) != 2 {
return errors.New("Expected two arguments. See help text for details.")
}
user := getUserFromUserArg(a, args[0])
if user == nil {
return errors.New("Unable to find user '" + args[0] + "'")
}
password := args[1]
if result := <-a.Srv.Store.User().UpdatePassword(user.Id, model.HashPassword(password)); result.Err != nil {
return result.Err
}
return nil
}
func updateUserEmailCmdF(command *cobra.Command, args []string) error {
a, err := InitDBCommandContextCobra(command)
if err != nil {
return err
}
defer a.Shutdown()
if len(args) != 2 {
return errors.New("Expected two arguments. See help text for details.")
}
newEmail := args[1]
if !model.IsValidEmail(newEmail) {
return errors.New("Invalid email: '" + newEmail + "'")
}
if len(args) != 2 {
return errors.New("Expected two arguments. See help text for details.")
}
user := getUserFromUserArg(a, args[0])
if user == nil {
return errors.New("Unable to find user '" + args[0] + "'")
}
user.Email = newEmail
_, errUpdate := a.UpdateUser(user, true)
if errUpdate != nil {
return errors.New(errUpdate.Message)
}
return nil
}
func resetUserMfaCmdF(command *cobra.Command, args []string) error {
a, err := InitDBCommandContextCobra(command)
if err != nil {
return err
}
defer a.Shutdown()
if len(args) < 1 {
return errors.New("Expected at least one argument. See help text for details.")
}
users := getUsersFromUserArgs(a, args)
for i, user := range users {
if user == nil {
return errors.New("Unable to find user '" + args[i] + "'")
}
if err := a.DeactivateMfa(user.Id); err != nil {
return err
}
}
return nil
}
func deleteUserCmdF(command *cobra.Command, args []string) error {
a, err := InitDBCommandContextCobra(command)
if err != nil {
return err
}
defer a.Shutdown()
if len(args) < 1 {
return errors.New("Expected at least one argument. See help text for details.")
}
confirmFlag, _ := command.Flags().GetBool("confirm")
if !confirmFlag {
var confirm string
CommandPrettyPrintln("Have you performed a database backup? (YES/NO): ")
fmt.Scanln(&confirm)
if confirm != "YES" {
return errors.New("ABORTED: You did not answer YES exactly, in all capitals.")
}
CommandPrettyPrintln("Are you sure you want to permanently delete the specified users? (YES/NO): ")
fmt.Scanln(&confirm)
if confirm != "YES" {
return errors.New("ABORTED: You did not answer YES exactly, in all capitals.")
}
}
users := getUsersFromUserArgs(a, args)
for i, user := range users {
if user == nil {
return errors.New("Unable to find user '" + args[i] + "'")
}
if err := a.PermanentDeleteUser(user); err != nil {
return err
}
}
return nil
}
func deleteAllUsersCommandF(command *cobra.Command, args []string) error {
a, err := InitDBCommandContextCobra(command)
if err != nil {
return err
}
defer a.Shutdown()
if len(args) > 0 {
return errors.New("Expected zero arguments.")
}
confirmFlag, _ := command.Flags().GetBool("confirm")
if !confirmFlag {
var confirm string
CommandPrettyPrintln("Have you performed a database backup? (YES/NO): ")
fmt.Scanln(&confirm)
if confirm != "YES" {
return errors.New("ABORTED: You did not answer YES exactly, in all capitals.")
}
CommandPrettyPrintln("Are you sure you want to permanently delete all user accounts? (YES/NO): ")
fmt.Scanln(&confirm)
if confirm != "YES" {
return errors.New("ABORTED: You did not answer YES exactly, in all capitals.")
}
}
if err := a.PermanentDeleteAllUsers(); err != nil {
return err
}
CommandPrettyPrintln("All user accounts successfully deleted.")
return nil
}
func migrateAuthCmdF(command *cobra.Command, args []string) error {
if args[1] == "saml" {
return migrateAuthToSamlCmdF(command, args)
}
return migrateAuthToLdapCmdF(command, args)
}
func migrateAuthToLdapCmdF(command *cobra.Command, args []string) error {
a, err := InitDBCommandContextCobra(command)
if err != nil {
return err
}
defer a.Shutdown()
fromAuth := args[0]
matchField := args[2]
if len(fromAuth) == 0 || (fromAuth != "email" && fromAuth != "gitlab" && fromAuth != "saml") {
return errors.New("Invalid from_auth argument")
}
// Email auth in Mattermost system is represented by ""
if fromAuth == "email" {
fromAuth = ""
}
if len(matchField) == 0 || (matchField != "email" && matchField != "username") {
return errors.New("Invalid match_field argument")
}
forceFlag, _ := command.Flags().GetBool("force")
dryRunFlag, _ := command.Flags().GetBool("dryRun")
if migrate := a.AccountMigration; migrate != nil {
if err := migrate.MigrateToLdap(fromAuth, matchField, forceFlag, dryRunFlag); err != nil {
return errors.New("Error while migrating users: " + err.Error())
}
CommandPrettyPrintln("Successfully migrated accounts.")
}
return nil
}
func migrateAuthToSamlCmdF(command *cobra.Command, args []string) error {
a, err := InitDBCommandContextCobra(command)
if err != nil {
return err
}
defer a.Shutdown()
dryRunFlag, _ := command.Flags().GetBool("dryRun")
autoFlag, _ := command.Flags().GetBool("auto")
matchesFile := ""
matches := map[string]string{}
if !autoFlag {
matchesFile = args[2]
file, e := ioutil.ReadFile(matchesFile)
if e != nil {
return errors.New("Invalid users file.")
}
if json.Unmarshal(file, &matches) != nil {
return errors.New("Invalid users file.")
}
}
fromAuth := args[0]
if len(fromAuth) == 0 || (fromAuth != "email" && fromAuth != "gitlab" && fromAuth != "ldap") {
return errors.New("Invalid from_auth argument")
}
if autoFlag && !dryRunFlag {
var confirm string
CommandPrettyPrintln("You are about to perform an automatic \"" + fromAuth + " to saml\" migration. This must only be done if your current Mattermost users with " + fromAuth + " auth have the same username and email in your SAML service. Otherwise, provide the usernames and emails from your SAML Service using the \"users file\" without the \"--auto\" option.\n\nDo you want to proceed with automatic migration anyway? (YES/NO):")
fmt.Scanln(&confirm)
if confirm != "YES" {
return errors.New("ABORTED: You did not answer YES exactly, in all capitals.")
}
}
// Email auth in Mattermost system is represented by ""
if fromAuth == "email" {
fromAuth = ""
}
if migrate := a.AccountMigration; migrate != nil {
if err := migrate.MigrateToSaml(fromAuth, matches, autoFlag, dryRunFlag); err != nil {
return errors.New("Error while migrating users: " + err.Error())
}
CommandPrettyPrintln("Successfully migrated accounts.")
}
return nil
}
func verifyUserCmdF(command *cobra.Command, args []string) error {
a, err := InitDBCommandContextCobra(command)
if err != nil {
return err
}
defer a.Shutdown()
if len(args) < 1 {
return errors.New("Expected at least one argument. See help text for details.")
}
users := getUsersFromUserArgs(a, args)
for i, user := range users {
if user == nil {
CommandPrintErrorln("Unable to find user '" + args[i] + "'")
continue
}
if cresult := <-a.Srv.Store.User().VerifyEmail(user.Id); cresult.Err != nil {
CommandPrintErrorln("Unable to verify '" + args[i] + "' email. Error: " + cresult.Err.Error())
}
}
return nil
}
func searchUserCmdF(command *cobra.Command, args []string) error {
a, err := InitDBCommandContextCobra(command)
if err != nil {
return err
}
defer a.Shutdown()
if len(args) < 1 {
return errors.New("Expected at least one argument. See help text for details.")
}
users := getUsersFromUserArgs(a, args)
for i, user := range users {
if i > 0 {
CommandPrettyPrintln("------------------------------")
}
if user == nil {
CommandPrintErrorln("Unable to find user '" + args[i] + "'")
continue
}
CommandPrettyPrintln("id: " + user.Id)
CommandPrettyPrintln("username: " + user.Username)
CommandPrettyPrintln("nickname: " + user.Nickname)
CommandPrettyPrintln("position: " + user.Position)
CommandPrettyPrintln("first_name: " + user.FirstName)
CommandPrettyPrintln("last_name: " + user.LastName)
CommandPrettyPrintln("email: " + user.Email)
CommandPrettyPrintln("auth_service: " + user.AuthService)
}
return nil
}

121
cmd/mattermost/commands/user_test.go Обычный файл
Просмотреть файл

@@ -0,0 +1,121 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package commands
import (
"testing"
"github.com/mattermost/mattermost-server/api4"
"github.com/mattermost/mattermost-server/model"
"github.com/stretchr/testify/require"
)
func TestCreateUserWithTeam(t *testing.T) {
th := api4.Setup().InitBasic().InitSystemAdmin()
defer th.TearDown()
id := model.NewId()
email := "success+" + id + "@simulator.amazonses.com"
username := "name" + id
CheckCommand(t, "user", "create", "--email", email, "--password", "mypassword1", "--username", username)
CheckCommand(t, "team", "add", th.BasicTeam.Id, email)
profiles := th.SystemAdminClient.Must(th.SystemAdminClient.GetUsersInTeam(th.BasicTeam.Id, 0, 1000, "")).([]*model.User)
found := false
for _, user := range profiles {
if user.Email == email {
found = true
}
}
if !found {
t.Fatal("Failed to create User")
}
}
func TestCreateUserWithoutTeam(t *testing.T) {
th := api4.Setup()
defer th.TearDown()
id := model.NewId()
email := "success+" + id + "@simulator.amazonses.com"
username := "name" + id
CheckCommand(t, "user", "create", "--email", email, "--password", "mypassword1", "--username", username)
if result := <-th.App.Srv.Store.User().GetByEmail(email); result.Err != nil {
t.Fatal()
} else {
user := result.Data.(*model.User)
if user.Email != email {
t.Fatal()
}
}
}
func TestResetPassword(t *testing.T) {
th := api4.Setup().InitBasic()
defer th.TearDown()
CheckCommand(t, "user", "password", th.BasicUser.Email, "password2")
th.Client.Logout()
th.BasicUser.Password = "password2"
th.LoginBasic()
}
func TestMakeUserActiveAndInactive(t *testing.T) {
th := api4.Setup().InitBasic()
defer th.TearDown()
// first inactivate the user
CheckCommand(t, "user", "deactivate", th.BasicUser.Email)
// activate the inactive user
CheckCommand(t, "user", "activate", th.BasicUser.Email)
}
func TestChangeUserEmail(t *testing.T) {
th := api4.Setup().InitBasic()
defer th.TearDown()
newEmail := model.NewId() + "@mattermost-test.com"
CheckCommand(t, "user", "email", th.BasicUser.Username, newEmail)
if result := <-th.App.Srv.Store.User().GetByEmail(th.BasicUser.Email); result.Err == nil {
t.Fatal("should've updated to the new email")
}
if result := <-th.App.Srv.Store.User().GetByEmail(newEmail); result.Err != nil {
t.Fatal()
} else {
user := result.Data.(*model.User)
if user.Email != newEmail {
t.Fatal("should've updated to the new email")
}
}
// should fail because using an invalid email
require.Error(t, RunCommand(t, "user", "email", th.BasicUser.Username, "wrong$email.com"))
// should fail because missing one parameter
require.Error(t, RunCommand(t, "user", "email", th.BasicUser.Username))
// should fail because missing both parameters
require.Error(t, RunCommand(t, "user", "email"))
// should fail because have more than 2 parameters
require.Error(t, RunCommand(t, "user", "email", th.BasicUser.Username, "new@email.com", "extra!"))
// should fail because user not found
require.Error(t, RunCommand(t, "user", "email", "invalidUser", newEmail))
// should fail because email already in use
require.Error(t, RunCommand(t, "user", "email", th.BasicUser.Username, th.BasicUser2.Email))
}

39
cmd/mattermost/commands/userargs.go Обычный файл
Просмотреть файл

@@ -0,0 +1,39 @@
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package commands
import (
"github.com/mattermost/mattermost-server/app"
"github.com/mattermost/mattermost-server/model"
)
func getUsersFromUserArgs(a *app.App, userArgs []string) []*model.User {
users := make([]*model.User, 0, len(userArgs))
for _, userArg := range userArgs {
user := getUserFromUserArg(a, userArg)
users = append(users, user)
}
return users
}
func getUserFromUserArg(a *app.App, userArg string) *model.User {
var user *model.User
if result := <-a.Srv.Store.User().GetByEmail(userArg); result.Err == nil {
user = result.Data.(*model.User)
}
if user == nil {
if result := <-a.Srv.Store.User().GetByUsername(userArg); result.Err == nil {
user = result.Data.(*model.User)
}
}
if user == nil {
if result := <-a.Srv.Store.User().Get(userArg); result.Err == nil {
user = result.Data.(*model.User)
}
}
return user
}

44
cmd/mattermost/commands/version.go Обычный файл
Просмотреть файл

@@ -0,0 +1,44 @@
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package commands
import (
"github.com/mattermost/mattermost-server/app"
"github.com/mattermost/mattermost-server/model"
"github.com/mattermost/mattermost-server/store"
"github.com/mattermost/mattermost-server/store/sqlstore"
"github.com/spf13/cobra"
)
var VersionCmd = &cobra.Command{
Use: "version",
Short: "Display version information",
RunE: versionCmdF,
}
func init() {
RootCmd.AddCommand(VersionCmd)
}
func versionCmdF(command *cobra.Command, args []string) error {
a, err := InitDBCommandContextCobra(command)
if err != nil {
return err
}
printVersion(a)
return nil
}
func printVersion(a *app.App) {
CommandPrintln("Version: " + model.CurrentVersion)
CommandPrintln("Build Number: " + model.BuildNumber)
CommandPrintln("Build Date: " + model.BuildDate)
CommandPrintln("Build Hash: " + model.BuildHash)
CommandPrintln("Build Enterprise Ready: " + model.BuildEnterpriseReady)
if supplier, ok := a.Srv.Store.(*store.LayeredStore).DatabaseLayer.(*sqlstore.SqlSupplier); ok {
CommandPrintln("DB Version: " + supplier.GetCurrentSchemaVersion())
}
}

12
cmd/mattermost/commands/version_test.go Обычный файл
Просмотреть файл

@@ -0,0 +1,12 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package commands
import (
"testing"
)
func TestVersion(t *testing.T) {
CheckCommand(t, "version")
}

33
cmd/mattermost/main.go Обычный файл
Просмотреть файл

@@ -0,0 +1,33 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package main
import (
"os"
"github.com/mattermost/mattermost-server/cmd/mattermost/commands"
// Plugins
_ "github.com/mattermost/mattermost-server/model/gitlab"
// Enterprise Imports
_ "github.com/mattermost/mattermost-server/imports"
// Enterprise Deps
_ "github.com/dgryski/dgoogauth"
_ "github.com/go-ldap/ldap"
_ "github.com/hako/durafmt"
_ "github.com/hashicorp/memberlist"
_ "github.com/mattermost/rsc/qr"
_ "github.com/prometheus/client_golang/prometheus"
_ "github.com/prometheus/client_golang/prometheus/promhttp"
_ "github.com/tylerb/graceful"
_ "gopkg.in/olivere/elastic.v5"
)
func main() {
if err := commands.Run(os.Args[1:]); err != nil {
os.Exit(1)
}
}