diff options
Diffstat (limited to 'api')
-rw-r--r-- | api/command.go | 728 | ||||
-rw-r--r-- | api/command_echo.go | 81 | ||||
-rw-r--r-- | api/command_echo_test.go | 42 | ||||
-rw-r--r-- | api/command_join.go | 54 | ||||
-rw-r--r-- | api/command_join_test.go | 71 | ||||
-rw-r--r-- | api/command_loadtest.go | 357 | ||||
-rw-r--r-- | api/command_loadtest_test.go | 220 | ||||
-rw-r--r-- | api/command_logout.go | 29 | ||||
-rw-r--r-- | api/command_logout_test.go | 32 | ||||
-rw-r--r-- | api/command_me.go | 29 | ||||
-rw-r--r-- | api/command_me_test.go | 47 | ||||
-rw-r--r-- | api/command_shrug.go | 34 | ||||
-rw-r--r-- | api/command_shrug_test.go | 47 | ||||
-rw-r--r-- | api/command_test.go | 274 | ||||
-rw-r--r-- | api/user.go | 4 | ||||
-rw-r--r-- | api/webhook.go | 62 | ||||
-rw-r--r-- | api/webhook_test.go | 84 |
17 files changed, 1491 insertions, 704 deletions
diff --git a/api/command.go b/api/command.go index 00293cf16..6e2133f34 100644 --- a/api/command.go +++ b/api/command.go @@ -4,12 +4,8 @@ package api import ( - "io" "net/http" - "path" - "strconv" "strings" - "time" l4g "github.com/alecthomas/log4go" "github.com/gorilla/mux" @@ -17,630 +13,268 @@ import ( "github.com/mattermost/platform/utils" ) -type commandHandler func(c *Context, command *model.Command) bool - -var ( - cmds = map[string]string{ - "logoutCommand": "/logout", - "joinCommand": "/join", - "loadTestCommand": "/loadtest", - "echoCommand": "/echo", - "shrugCommand": "/shrug", - "meCommand": "/me", - } - commands = []commandHandler{ - logoutCommand, - joinCommand, - loadTestCommand, - echoCommand, - shrugCommand, - meCommand, - } - commandNotImplementedErr = model.NewAppError("checkCommand", "Command not implemented", "") -) -var echoSem chan bool - -func InitCommand(r *mux.Router) { - l4g.Debug("Initializing command api routes") - r.Handle("/command", ApiUserRequired(command)).Methods("POST") +type CommandProvider interface { + GetCommand() *model.Command + DoCommand(c *Context, channelId string, message string) *model.CommandResponse } -func command(c *Context, w http.ResponseWriter, r *http.Request) { +var commandProviders = make(map[string]CommandProvider) - props := model.MapFromJson(r.Body) +func RegisterCommandProvider(newProvider CommandProvider) { + commandProviders[newProvider.GetCommand().Trigger] = newProvider +} - command := &model.Command{ - Command: strings.TrimSpace(props["command"]), - ChannelId: strings.TrimSpace(props["channelId"]), - Suggest: props["suggest"] == "true", - Suggestions: make([]*model.SuggestCommand, 0, 128), +func GetCommandProvidersProvider(name string) CommandProvider { + provider, ok := commandProviders[name] + if ok { + return provider } - checkCommand(c, command) - if c.Err != nil { - if c.Err != commandNotImplementedErr { - return - } else { - c.Err = nil - command.Response = model.RESP_NOT_IMPLEMENTED - w.Write([]byte(command.ToJson())) - return - } - } else { - w.Write([]byte(command.ToJson())) - } + return nil } -func checkCommand(c *Context, command *model.Command) bool { +func InitCommand(r *mux.Router) { + l4g.Debug("Initializing command api routes") - if len(command.Command) == 0 || strings.Index(command.Command, "/") != 0 { - c.Err = model.NewAppError("checkCommand", "Command must start with /", "") - return false - } + sr := r.PathPrefix("/commands").Subrouter() - if len(command.ChannelId) > 0 { - cchan := Srv.Store.Channel().CheckPermissionsTo(c.Session.TeamId, command.ChannelId, c.Session.UserId) + sr.Handle("/execute", ApiUserRequired(executeCommand)).Methods("POST") + sr.Handle("/list", ApiUserRequired(listCommands)).Methods("GET") - if !c.HasPermissionsToChannel(cchan, "checkCommand") { - return true - } - } + sr.Handle("/create", ApiUserRequired(createCommand)).Methods("POST") + sr.Handle("/list_team_commands", ApiUserRequired(listTeamCommands)).Methods("GET") + sr.Handle("/regen_token", ApiUserRequired(regenCommandToken)).Methods("POST") + sr.Handle("/delete", ApiUserRequired(deleteCommand)).Methods("POST") +} - if !command.Suggest { - implemented := false - for _, cmd := range cmds { - bounds := len(cmd) - if len(command.Command) < bounds { - continue - } - if command.Command[:bounds] == cmd { - implemented = true - } - } - if !implemented { - c.Err = commandNotImplementedErr - return false +func listCommands(c *Context, w http.ResponseWriter, r *http.Request) { + commands := make([]*model.Command, 0, 32) + seen := make(map[string]bool) + for _, value := range commandProviders { + cpy := *value.GetCommand() + if cpy.AutoComplete && !seen[cpy.Id] { + cpy.Sanatize() + seen[cpy.Trigger] = true + commands = append(commands, &cpy) } } - for _, v := range commands { - - if v(c, command) || c.Err != nil { - return true + if result := <-Srv.Store.Command().GetByTeam(c.Session.TeamId); result.Err != nil { + c.Err = result.Err + return + } else { + teamCmds := result.Data.([]*model.Command) + for _, cmd := range teamCmds { + if cmd.AutoComplete && !seen[cmd.Id] { + cmd.Sanatize() + seen[cmd.Trigger] = true + commands = append(commands, cmd) + } } } - return false + w.Write([]byte(model.CommandListToJson(commands))) } -func logoutCommand(c *Context, command *model.Command) bool { +func executeCommand(c *Context, w http.ResponseWriter, r *http.Request) { + props := model.MapFromJson(r.Body) + command := strings.TrimSpace(props["command"]) + channelId := strings.TrimSpace(props["channelId"]) - cmd := cmds["logoutCommand"] + if len(command) <= 1 || strings.Index(command, "/") != 0 { + c.Err = model.NewAppError("command", "Command must start with /", "") + return + } - if strings.Index(command.Command, cmd) == 0 { - command.AddSuggestion(&model.SuggestCommand{Suggestion: cmd, Description: "Logout"}) + if len(channelId) > 0 { + cchan := Srv.Store.Channel().CheckPermissionsTo(c.Session.TeamId, channelId, c.Session.UserId) - if !command.Suggest { - command.GotoLocation = "/logout" - command.Response = model.RESP_EXECUTED - return true + if !c.HasPermissionsToChannel(cchan, "checkCommand") { + return } - - } else if strings.Index(cmd, command.Command) == 0 { - command.AddSuggestion(&model.SuggestCommand{Suggestion: cmd, Description: "Logout"}) } - return false -} + parts := strings.Split(command, " ") + trigger := parts[0][1:] + provider := GetCommandProvidersProvider(trigger) -func echoCommand(c *Context, command *model.Command) bool { - cmd := cmds["echoCommand"] - maxThreads := 100 + if provider != nil { + message := strings.Join(parts[1:], " ") + response := provider.DoCommand(c, channelId, message) - if !command.Suggest && strings.Index(command.Command, cmd) == 0 { - parameters := strings.SplitN(command.Command, " ", 2) - if len(parameters) != 2 || len(parameters[1]) == 0 { - return false - } - message := strings.Trim(parameters[1], " ") - delay := 0 - if endMsg := strings.LastIndex(message, "\""); string(message[0]) == "\"" && endMsg > 1 { - if checkDelay, err := strconv.Atoi(strings.Trim(message[endMsg:], " \"")); err == nil { - delay = checkDelay - } - message = message[1:endMsg] - } else if strings.Index(message, " ") > -1 { - delayIdx := strings.LastIndex(message, " ") - delayStr := strings.Trim(message[delayIdx:], " ") - - if checkDelay, err := strconv.Atoi(delayStr); err == nil { - delay = checkDelay - message = message[:delayIdx] + if response.ResponseType == model.COMMAND_RESPONSE_TYPE_IN_CHANNEL { + post := &model.Post{} + post.ChannelId = channelId + post.Message = response.Text + if _, err := CreatePost(c, post, true); err != nil { + c.Err = model.NewAppError("command", "An error while saving the command response to the channel", "") } - } - - if delay > 10000 { - c.Err = model.NewAppError("echoCommand", "Delays must be under 10000 seconds", "") - return false - } - - if echoSem == nil { - // We want one additional thread allowed so we never reach channel lockup - echoSem = make(chan bool, maxThreads+1) - } - - if len(echoSem) >= maxThreads { - c.Err = model.NewAppError("echoCommand", "High volume of echo request, cannot process request", "") - return false - } - - echoSem <- true - go func() { - defer func() { <-echoSem }() + } else if response.ResponseType == model.COMMAND_RESPONSE_TYPE_EPHEMERAL { post := &model.Post{} - post.ChannelId = command.ChannelId - post.Message = message - - time.Sleep(time.Duration(delay) * time.Second) - + post.ChannelId = channelId + post.Message = "TODO_EPHEMERAL: " + response.Text if _, err := CreatePost(c, post, true); err != nil { - l4g.Error("Unable to create /echo post, err=%v", err) + c.Err = model.NewAppError("command", "An error while saving the command response to the channel", "") } - }() - - command.Response = model.RESP_EXECUTED - return true - - } else if strings.Index(cmd, command.Command) == 0 { - command.AddSuggestion(&model.SuggestCommand{Suggestion: cmd, Description: "Echo back text from your account, /echo \"message\" [delay in seconds]"}) - } - - return false -} - -func meCommand(c *Context, command *model.Command) bool { - cmd := cmds["meCommand"] - - if !command.Suggest && strings.Index(command.Command, cmd) == 0 { - message := "" - - parameters := strings.SplitN(command.Command, " ", 2) - if len(parameters) > 1 { - message += "*" + parameters[1] + "*" - } - - post := &model.Post{} - post.Message = message - post.ChannelId = command.ChannelId - if _, err := CreatePost(c, post, false); err != nil { - l4g.Error("Unable to create /me post post, err=%v", err) - return false } - command.Response = model.RESP_EXECUTED - return true - } else if strings.Index(cmd, command.Command) == 0 { - command.AddSuggestion(&model.SuggestCommand{Suggestion: cmd, Description: "Do an action, /me [message]"}) + w.Write([]byte(response.ToJson())) + } else { + c.Err = model.NewAppError("command", "Command with a trigger of '"+trigger+"' not found", "") } - - return false } -func shrugCommand(c *Context, command *model.Command) bool { - cmd := cmds["shrugCommand"] - - if !command.Suggest && strings.Index(command.Command, cmd) == 0 { - message := `¯\\\_(ツ)_/¯` - - parameters := strings.SplitN(command.Command, " ", 2) - if len(parameters) > 1 { - message += " " + parameters[1] - } - - post := &model.Post{} - post.Message = message - post.ChannelId = command.ChannelId - if _, err := CreatePost(c, post, false); err != nil { - l4g.Error("Unable to create /shrug post post, err=%v", err) - return false - } - command.Response = model.RESP_EXECUTED - return true - - } else if strings.Index(cmd, command.Command) == 0 { - command.AddSuggestion(&model.SuggestCommand{Suggestion: cmd, Description: "Adds ¯\\_(ツ)_/¯ to your message, /shrug [message]"}) +func createCommand(c *Context, w http.ResponseWriter, r *http.Request) { + if !*utils.Cfg.ServiceSettings.EnableCommands { + c.Err = model.NewAppError("createCommand", "Commands have been disabled by the system admin.", "") + c.Err.StatusCode = http.StatusNotImplemented + return } - return false -} - -func joinCommand(c *Context, command *model.Command) bool { - - // looks for "/join channel-name" - cmd := cmds["joinCommand"] - - if strings.Index(command.Command, cmd) == 0 { - - parts := strings.Split(command.Command, " ") - - startsWith := "" - - if len(parts) == 2 { - startsWith = parts[1] + if *utils.Cfg.ServiceSettings.EnableOnlyAdminIntegrations { + if !(c.IsSystemAdmin() || c.IsTeamAdmin()) { + c.Err = model.NewAppError("createCommand", "Integrations have been limited to admins only.", "") + c.Err.StatusCode = http.StatusForbidden + return } + } - if result := <-Srv.Store.Channel().GetMoreChannels(c.Session.TeamId, c.Session.UserId); result.Err != nil { - c.Err = result.Err - return false - } else { - channels := result.Data.(*model.ChannelList) - - for _, v := range channels.Channels { - - if v.Name == startsWith && !command.Suggest { - - if v.Type == model.CHANNEL_DIRECT { - return false - } + c.LogAudit("attempt") - JoinChannel(c, v.Id, "") + cmd := model.CommandFromJson(r.Body) - if c.Err != nil { - return false - } + if cmd == nil { + c.SetInvalidParam("createCommand", "command") + return + } - command.GotoLocation = c.GetTeamURL() + "/channels/" + v.Name - command.Response = model.RESP_EXECUTED - return true - } + cmd.CreatorId = c.Session.UserId + cmd.TeamId = c.Session.TeamId - if len(startsWith) == 0 || strings.Index(v.Name, startsWith) == 0 { - command.AddSuggestion(&model.SuggestCommand{Suggestion: cmd + " " + v.Name, Description: "Join the open channel"}) - } - } - } - } else if strings.Index(cmd, command.Command) == 0 { - command.AddSuggestion(&model.SuggestCommand{Suggestion: cmd, Description: "Join an open channel"}) + if result := <-Srv.Store.Command().Save(cmd); result.Err != nil { + c.Err = result.Err + return + } else { + c.LogAudit("success") + rcmd := result.Data.(*model.Command) + w.Write([]byte(rcmd.ToJson())) } - - return false } -func loadTestCommand(c *Context, command *model.Command) bool { - cmd := cmds["loadTestCommand"] - - // This command is only available when EnableTesting is true - if !utils.Cfg.ServiceSettings.EnableTesting { - return false +func listTeamCommands(c *Context, w http.ResponseWriter, r *http.Request) { + if !*utils.Cfg.ServiceSettings.EnableCommands { + c.Err = model.NewAppError("createCommand", "Commands have been disabled by the system admin.", "") + c.Err.StatusCode = http.StatusNotImplemented + return } - if strings.Index(command.Command, cmd) == 0 { - if loadTestSetupCommand(c, command) { - return true - } - if loadTestUsersCommand(c, command) { - return true - } - if loadTestChannelsCommand(c, command) { - return true - } - if loadTestPostsCommand(c, command) { - return true - } - if loadTestUrlCommand(c, command) { - return true + if *utils.Cfg.ServiceSettings.EnableOnlyAdminIntegrations { + if !(c.IsSystemAdmin() || c.IsTeamAdmin()) { + c.Err = model.NewAppError("createCommand", "Integrations have been limited to admins only.", "") + c.Err.StatusCode = http.StatusForbidden + return } - } else if strings.Index(cmd, command.Command) == 0 { - command.AddSuggestion(&model.SuggestCommand{Suggestion: cmd, Description: "Debug Load Testing"}) } - return false -} - -func parseRange(command string, cmd string) (utils.Range, bool) { - tokens := strings.Fields(strings.TrimPrefix(command, cmd)) - var begin int - var end int - var err1 error - var err2 error - switch { - case len(tokens) == 1: - begin, err1 = strconv.Atoi(tokens[0]) - end = begin - if err1 != nil { - return utils.Range{0, 0}, false - } - case len(tokens) >= 2: - begin, err1 = strconv.Atoi(tokens[0]) - end, err2 = strconv.Atoi(tokens[1]) - if err1 != nil || err2 != nil { - return utils.Range{0, 0}, false - } - default: - return utils.Range{0, 0}, false + if result := <-Srv.Store.Command().GetByTeam(c.Session.TeamId); result.Err != nil { + c.Err = result.Err + return + } else { + cmds := result.Data.([]*model.Command) + w.Write([]byte(model.CommandListToJson(cmds))) } - return utils.Range{begin, end}, true } -func contains(items []string, token string) bool { - for _, elem := range items { - if elem == token { - return true - } +func regenCommandToken(c *Context, w http.ResponseWriter, r *http.Request) { + if !*utils.Cfg.ServiceSettings.EnableCommands { + c.Err = model.NewAppError("createCommand", "Commands have been disabled by the system admin.", "") + c.Err.StatusCode = http.StatusNotImplemented + return } - return false -} -func loadTestSetupCommand(c *Context, command *model.Command) bool { - cmd := cmds["loadTestCommand"] + " setup" - - if strings.Index(command.Command, cmd) == 0 && !command.Suggest { - tokens := strings.Fields(strings.TrimPrefix(command.Command, cmd)) - doTeams := contains(tokens, "teams") - doFuzz := contains(tokens, "fuzz") - - numArgs := 0 - if doTeams { - numArgs++ - } - if doFuzz { - numArgs++ - } - - var numTeams int - var numChannels int - var numUsers int - var numPosts int - - // Defaults - numTeams = 10 - numChannels = 10 - numUsers = 10 - numPosts = 10 - - if doTeams { - if (len(tokens) - numArgs) >= 4 { - numTeams, _ = strconv.Atoi(tokens[numArgs+0]) - numChannels, _ = strconv.Atoi(tokens[numArgs+1]) - numUsers, _ = strconv.Atoi(tokens[numArgs+2]) - numPosts, _ = strconv.Atoi(tokens[numArgs+3]) - } - } else { - if (len(tokens) - numArgs) >= 3 { - numChannels, _ = strconv.Atoi(tokens[numArgs+0]) - numUsers, _ = strconv.Atoi(tokens[numArgs+1]) - numPosts, _ = strconv.Atoi(tokens[numArgs+2]) - } - } - client := model.NewClient(c.GetSiteURL()) - - if doTeams { - if err := CreateBasicUser(client); err != nil { - l4g.Error("Failed to create testing environment") - return true - } - client.LoginByEmail(BTEST_TEAM_NAME, BTEST_USER_EMAIL, BTEST_USER_PASSWORD) - environment, err := CreateTestEnvironmentWithTeams( - client, - utils.Range{numTeams, numTeams}, - utils.Range{numChannels, numChannels}, - utils.Range{numUsers, numUsers}, - utils.Range{numPosts, numPosts}, - doFuzz) - if err != true { - l4g.Error("Failed to create testing environment") - return true - } else { - l4g.Info("Testing environment created") - for i := 0; i < len(environment.Teams); i++ { - l4g.Info("Team Created: " + environment.Teams[i].Name) - l4g.Info("\t User to login: " + environment.Environments[i].Users[0].Email + ", " + USER_PASSWORD) - } - } - } else { - client.MockSession(c.Session.Token) - CreateTestEnvironmentInTeam( - client, - c.Session.TeamId, - utils.Range{numChannels, numChannels}, - utils.Range{numUsers, numUsers}, - utils.Range{numPosts, numPosts}, - doFuzz) + if *utils.Cfg.ServiceSettings.EnableOnlyAdminIntegrations { + if !(c.IsSystemAdmin() || c.IsTeamAdmin()) { + c.Err = model.NewAppError("createCommand", "Integrations have been limited to admins only.", "") + c.Err.StatusCode = http.StatusForbidden + return } - return true - } else if strings.Index(cmd, command.Command) == 0 { - command.AddSuggestion(&model.SuggestCommand{ - Suggestion: cmd, - Description: "Creates a testing environment in current team. [teams] [fuzz] <Num Channels> <Num Users> <NumPosts>"}) } - return false -} + c.LogAudit("attempt") -func loadTestUsersCommand(c *Context, command *model.Command) bool { - cmd1 := cmds["loadTestCommand"] + " users" - cmd2 := cmds["loadTestCommand"] + " users fuzz" + props := model.MapFromJson(r.Body) - if strings.Index(command.Command, cmd1) == 0 && !command.Suggest { - cmd := cmd1 - doFuzz := false - if strings.Index(command.Command, cmd2) == 0 { - doFuzz = true - cmd = cmd2 - } - usersr, err := parseRange(command.Command, cmd) - if err == false { - usersr = utils.Range{10, 15} - } - client := model.NewClient(c.GetSiteURL()) - userCreator := NewAutoUserCreator(client, c.Session.TeamId) - userCreator.Fuzzy = doFuzz - userCreator.CreateTestUsers(usersr) - return true - } else if strings.Index(cmd1, command.Command) == 0 { - command.AddSuggestion(&model.SuggestCommand{Suggestion: cmd1, Description: "Add a specified number of random users to current team <Min Users> <Max Users>"}) - command.AddSuggestion(&model.SuggestCommand{Suggestion: cmd2, Description: "Add a specified number of random users with fuzz text to current team <Min Users> <Max Users>"}) - } else if strings.Index(cmd2, command.Command) == 0 { - command.AddSuggestion(&model.SuggestCommand{Suggestion: cmd2, Description: "Add a specified number of random users with fuzz text to current team <Min Users> <Max Users>"}) + id := props["id"] + if len(id) == 0 { + c.SetInvalidParam("regenCommandToken", "id") + return } - return false -} - -func loadTestChannelsCommand(c *Context, command *model.Command) bool { - cmd1 := cmds["loadTestCommand"] + " channels" - cmd2 := cmds["loadTestCommand"] + " channels fuzz" + var cmd *model.Command + if result := <-Srv.Store.Command().Get(id); result.Err != nil { + c.Err = result.Err + return + } else { + cmd = result.Data.(*model.Command) - if strings.Index(command.Command, cmd1) == 0 && !command.Suggest { - cmd := cmd1 - doFuzz := false - if strings.Index(command.Command, cmd2) == 0 { - doFuzz = true - cmd = cmd2 - } - channelsr, err := parseRange(command.Command, cmd) - if err == false { - channelsr = utils.Range{20, 30} + if c.Session.TeamId != cmd.TeamId && c.Session.UserId != cmd.CreatorId && !c.IsTeamAdmin() { + c.LogAudit("fail - inappropriate permissions") + c.Err = model.NewAppError("regenToken", "Inappropriate permissions to regenerate command token", "user_id="+c.Session.UserId) + return } - client := model.NewClient(c.GetSiteURL()) - client.MockSession(c.Session.Token) - channelCreator := NewAutoChannelCreator(client, c.Session.TeamId) - channelCreator.Fuzzy = doFuzz - channelCreator.CreateTestChannels(channelsr) - return true - } else if strings.Index(cmd1, command.Command) == 0 { - command.AddSuggestion(&model.SuggestCommand{Suggestion: cmd1, Description: "Add a specified number of random channels to current team <MinChannels> <MaxChannels>"}) - command.AddSuggestion(&model.SuggestCommand{Suggestion: cmd2, Description: "Add a specified number of random channels with fuzz text to current team <Min Channels> <Max Channels>"}) - } else if strings.Index(cmd2, command.Command) == 0 { - command.AddSuggestion(&model.SuggestCommand{Suggestion: cmd2, Description: "Add a specified number of random channels with fuzz text to current team <Min Channels> <Max Channels>"}) } - return false -} - -func loadTestPostsCommand(c *Context, command *model.Command) bool { - cmd1 := cmds["loadTestCommand"] + " posts" - cmd2 := cmds["loadTestCommand"] + " posts fuzz" - - if strings.Index(command.Command, cmd1) == 0 && !command.Suggest { - cmd := cmd1 - doFuzz := false - if strings.Index(command.Command, cmd2) == 0 { - cmd = cmd2 - doFuzz = true - } - - postsr, err := parseRange(command.Command, cmd) - if err == false { - postsr = utils.Range{20, 30} - } + cmd.Token = model.NewId() - tokens := strings.Fields(strings.TrimPrefix(command.Command, cmd)) - rimages := utils.Range{0, 0} - if len(tokens) >= 3 { - if numImages, err := strconv.Atoi(tokens[2]); err == nil { - rimages = utils.Range{numImages, numImages} - } - } - - var usernames []string - if result := <-Srv.Store.User().GetProfiles(c.Session.TeamId); result.Err == nil { - profileUsers := result.Data.(map[string]*model.User) - usernames = make([]string, len(profileUsers)) - i := 0 - for _, userprof := range profileUsers { - usernames[i] = userprof.Username - i++ - } - } - - client := model.NewClient(c.GetSiteURL()) - client.MockSession(c.Session.Token) - testPoster := NewAutoPostCreator(client, command.ChannelId) - testPoster.Fuzzy = doFuzz - testPoster.Users = usernames - - numImages := utils.RandIntFromRange(rimages) - numPosts := utils.RandIntFromRange(postsr) - for i := 0; i < numPosts; i++ { - testPoster.HasImage = (i < numImages) - testPoster.CreateRandomPost() - } - return true - } else if strings.Index(cmd1, command.Command) == 0 { - command.AddSuggestion(&model.SuggestCommand{Suggestion: cmd1, Description: "Add some random posts to current channel <Min Posts> <Max Posts> <Min Images> <Max Images>"}) - command.AddSuggestion(&model.SuggestCommand{Suggestion: cmd2, Description: "Add some random posts with fuzz text to current channel <Min Posts> <Max Posts> <Min Images> <Max Images>"}) - } else if strings.Index(cmd2, command.Command) == 0 { - command.AddSuggestion(&model.SuggestCommand{Suggestion: cmd2, Description: "Add some random posts with fuzz text to current channel <Min Posts> <Max Posts> <Min Images> <Max Images>"}) + if result := <-Srv.Store.Command().Update(cmd); result.Err != nil { + c.Err = result.Err + return + } else { + w.Write([]byte(result.Data.(*model.Command).ToJson())) } - - return false } -func loadTestUrlCommand(c *Context, command *model.Command) bool { - cmd := cmds["loadTestCommand"] + " url" - - if strings.Index(command.Command, cmd) == 0 && !command.Suggest { - url := "" - - parameters := strings.SplitN(command.Command, " ", 3) - if len(parameters) != 3 { - c.Err = model.NewAppError("loadTestUrlCommand", "Command must contain a url", "") - return true - } else { - url = parameters[2] - } - - // provide a shortcut to easily access tests stored in doc/developer/tests - if !strings.HasPrefix(url, "http") { - url = "https://raw.githubusercontent.com/mattermost/platform/master/doc/developer/tests/" + url - - if path.Ext(url) == "" { - url += ".md" - } - } +func deleteCommand(c *Context, w http.ResponseWriter, r *http.Request) { + if !*utils.Cfg.ServiceSettings.EnableCommands { + c.Err = model.NewAppError("createCommand", "Commands have been disabled by the system admin.", "") + c.Err.StatusCode = http.StatusNotImplemented + return + } - var contents io.ReadCloser - if r, err := http.Get(url); err != nil { - c.Err = model.NewAppError("loadTestUrlCommand", "Unable to get file", err.Error()) - return false - } else if r.StatusCode > 400 { - c.Err = model.NewAppError("loadTestUrlCommand", "Unable to get file", r.Status) - return false - } else { - contents = r.Body + if *utils.Cfg.ServiceSettings.EnableOnlyAdminIntegrations { + if !(c.IsSystemAdmin() || c.IsTeamAdmin()) { + c.Err = model.NewAppError("createCommand", "Integrations have been limited to admins only.", "") + c.Err.StatusCode = http.StatusForbidden + return } + } - bytes := make([]byte, 4000) - - // break contents into 4000 byte posts - for { - length, err := contents.Read(bytes) - if err != nil && err != io.EOF { - c.Err = model.NewAppError("loadTestUrlCommand", "Encountered error reading file", err.Error()) - return false - } + c.LogAudit("attempt") - if length == 0 { - break - } + props := model.MapFromJson(r.Body) - post := &model.Post{} - post.Message = string(bytes[:length]) - post.ChannelId = command.ChannelId + id := props["id"] + if len(id) == 0 { + c.SetInvalidParam("deleteCommand", "id") + return + } - if _, err := CreatePost(c, post, false); err != nil { - l4g.Error("Unable to create post, err=%v", err) - return false - } + if result := <-Srv.Store.Command().Get(id); result.Err != nil { + c.Err = result.Err + return + } else { + if c.Session.TeamId != result.Data.(*model.Command).TeamId && c.Session.UserId != result.Data.(*model.Command).CreatorId && !c.IsTeamAdmin() { + c.LogAudit("fail - inappropriate permissions") + c.Err = model.NewAppError("deleteCommand", "Inappropriate permissions to delete command", "user_id="+c.Session.UserId) + return } + } - command.Response = model.RESP_EXECUTED - - return true - } else if strings.Index(cmd, command.Command) == 0 && strings.Index(command.Command, "/loadtest posts") != 0 { - command.AddSuggestion(&model.SuggestCommand{Suggestion: cmd, Description: "Add a post containing the text from a given url to current channel <Url>"}) + if err := (<-Srv.Store.Command().Delete(id, model.GetMillis())).Err; err != nil { + c.Err = err + return } - return false + c.LogAudit("success") + w.Write([]byte(model.MapToJson(props))) } diff --git a/api/command_echo.go b/api/command_echo.go new file mode 100644 index 000000000..5d34578c8 --- /dev/null +++ b/api/command_echo.go @@ -0,0 +1,81 @@ +// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package api + +import ( + "strconv" + "strings" + "time" + + l4g "code.google.com/p/log4go" + "github.com/mattermost/platform/model" +) + +var echoSem chan bool + +type EchoProvider struct { +} + +func init() { + RegisterCommandProvider(&EchoProvider{}) +} + +func (me *EchoProvider) GetCommand() *model.Command { + return &model.Command{ + Trigger: "echo", + AutoComplete: true, + AutoCompleteDesc: "Echo back text from your account", + AutoCompleteHint: "\"message\" [delay in seconds]", + DisplayName: "echo", + } +} + +func (me *EchoProvider) DoCommand(c *Context, channelId string, message string) *model.CommandResponse { + maxThreads := 100 + + delay := 0 + if endMsg := strings.LastIndex(message, "\""); string(message[0]) == "\"" && endMsg > 1 { + if checkDelay, err := strconv.Atoi(strings.Trim(message[endMsg:], " \"")); err == nil { + delay = checkDelay + } + message = message[1:endMsg] + } else if strings.Index(message, " ") > -1 { + delayIdx := strings.LastIndex(message, " ") + delayStr := strings.Trim(message[delayIdx:], " ") + + if checkDelay, err := strconv.Atoi(delayStr); err == nil { + delay = checkDelay + message = message[:delayIdx] + } + } + + if delay > 10000 { + return &model.CommandResponse{Text: "Delays must be under 10000 seconds", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL} + } + + if echoSem == nil { + // We want one additional thread allowed so we never reach channel lockup + echoSem = make(chan bool, maxThreads+1) + } + + if len(echoSem) >= maxThreads { + return &model.CommandResponse{Text: "High volume of echo request, cannot process request", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL} + } + + echoSem <- true + go func() { + defer func() { <-echoSem }() + post := &model.Post{} + post.ChannelId = channelId + post.Message = message + + time.Sleep(time.Duration(delay) * time.Second) + + if _, err := CreatePost(c, post, true); err != nil { + l4g.Error("Unable to create /echo post, err=%v", err) + } + }() + + return &model.CommandResponse{} +} diff --git a/api/command_echo_test.go b/api/command_echo_test.go new file mode 100644 index 000000000..3bfaa0279 --- /dev/null +++ b/api/command_echo_test.go @@ -0,0 +1,42 @@ +// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package api + +import ( + "testing" + "time" + + "github.com/mattermost/platform/model" + "github.com/mattermost/platform/store" +) + +func TestEchoCommand(t *testing.T) { + Setup() + + team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} + team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team) + + user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"} + user1 = Client.Must(Client.CreateUser(user1, "")).Data.(*model.User) + store.Must(Srv.Store.User().VerifyEmail(user1.Id)) + + Client.LoginByEmail(team.Name, user1.Email, "pwd") + + channel1 := &model.Channel{DisplayName: "AA", Name: "aa" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id} + channel1 = Client.Must(Client.CreateChannel(channel1)).Data.(*model.Channel) + + echoTestString := "/echo test" + + r1 := Client.Must(Client.Command(channel1.Id, echoTestString, false)).Data.(*model.CommandResponse) + if r1 == nil { + t.Fatal("Echo command failed to execute") + } + + time.Sleep(100 * time.Millisecond) + + p1 := Client.Must(Client.GetPosts(channel1.Id, 0, 2, "")).Data.(*model.PostList) + if len(p1.Order) != 1 { + t.Fatal("Echo command failed to send") + } +} diff --git a/api/command_join.go b/api/command_join.go new file mode 100644 index 000000000..67c1c1ad1 --- /dev/null +++ b/api/command_join.go @@ -0,0 +1,54 @@ +// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package api + +import ( + "github.com/mattermost/platform/model" +) + +type JoinProvider struct { +} + +func init() { + RegisterCommandProvider(&JoinProvider{}) +} + +func (me *JoinProvider) GetCommand() *model.Command { + return &model.Command{ + Trigger: "join", + AutoComplete: true, + AutoCompleteDesc: "Join the open channel", + AutoCompleteHint: "[channel-name]", + DisplayName: "join", + } +} + +func (me *JoinProvider) DoCommand(c *Context, channelId string, message string) *model.CommandResponse { + if result := <-Srv.Store.Channel().GetMoreChannels(c.Session.TeamId, c.Session.UserId); result.Err != nil { + return &model.CommandResponse{Text: "An error occured while listing channels.", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL} + } else { + channels := result.Data.(*model.ChannelList) + + for _, v := range channels.Channels { + + if v.Name == message { + + if v.Type == model.CHANNEL_DIRECT { + return &model.CommandResponse{Text: "An error occured while joining the channel.", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL} + } + + JoinChannel(c, v.Id, "") + + if c.Err != nil { + c.Err = nil + return &model.CommandResponse{Text: "An error occured while joining the channel.", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL} + } + + return &model.CommandResponse{GotoLocation: c.GetTeamURL() + "/channels/" + v.Name, Text: "Joined channel.", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL} + } + } + } + + return &model.CommandResponse{ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL, Text: "We couldn't find the channel"} +} diff --git a/api/command_join_test.go b/api/command_join_test.go new file mode 100644 index 000000000..7260915a6 --- /dev/null +++ b/api/command_join_test.go @@ -0,0 +1,71 @@ +// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package api + +import ( + "strings" + "testing" + + "github.com/mattermost/platform/model" + "github.com/mattermost/platform/store" +) + +func TestJoinCommands(t *testing.T) { + Setup() + + team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} + team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team) + + user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"} + user1 = Client.Must(Client.CreateUser(user1, "")).Data.(*model.User) + store.Must(Srv.Store.User().VerifyEmail(user1.Id)) + + Client.LoginByEmail(team.Name, user1.Email, "pwd") + + channel0 := &model.Channel{DisplayName: "00", Name: "00" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id} + channel0 = Client.Must(Client.CreateChannel(channel0)).Data.(*model.Channel) + + channel1 := &model.Channel{DisplayName: "AA", Name: "aa" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id} + channel1 = Client.Must(Client.CreateChannel(channel1)).Data.(*model.Channel) + Client.Must(Client.LeaveChannel(channel1.Id)) + + channel2 := &model.Channel{DisplayName: "BB", Name: "bb" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id} + channel2 = Client.Must(Client.CreateChannel(channel2)).Data.(*model.Channel) + Client.Must(Client.LeaveChannel(channel2.Id)) + + user2 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"} + user2 = Client.Must(Client.CreateUser(user2, "")).Data.(*model.User) + store.Must(Srv.Store.User().VerifyEmail(user1.Id)) + + data := make(map[string]string) + data["user_id"] = user2.Id + channel3 := Client.Must(Client.CreateDirectChannel(data)).Data.(*model.Channel) + + rs5 := Client.Must(Client.Command(channel0.Id, "/join "+channel2.Name, false)).Data.(*model.CommandResponse) + if !strings.HasSuffix(rs5.GotoLocation, "/"+team.Name+"/channels/"+channel2.Name) { + t.Fatal("failed to join channel") + } + + rs6 := Client.Must(Client.Command(channel0.Id, "/join "+channel3.Name, false)).Data.(*model.CommandResponse) + if strings.HasSuffix(rs6.GotoLocation, "/"+team.Name+"/channels/"+channel3.Name) { + t.Fatal("should not have joined direct message channel") + } + + c1 := Client.Must(Client.GetChannels("")).Data.(*model.ChannelList) + + if len(c1.Channels) != 5 { // 4 because of town-square, off-topic and direct + t.Fatal("didn't join channel") + } + + found := false + for _, c := range c1.Channels { + if c.Name == channel2.Name { + found = true + break + } + } + if !found { + t.Fatal("didn't join channel") + } +} diff --git a/api/command_loadtest.go b/api/command_loadtest.go new file mode 100644 index 000000000..eaf0b91b1 --- /dev/null +++ b/api/command_loadtest.go @@ -0,0 +1,357 @@ +// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package api + +import ( + "io" + "net/http" + "path" + "strconv" + "strings" + + l4g "code.google.com/p/log4go" + "github.com/mattermost/platform/model" + "github.com/mattermost/platform/utils" +) + +var usage = `Mattermost load testing commands to help configure the system + + COMMANDS: + + Setup - Creates a testing environment in current team. + /loadtest setup [teams] [fuzz] <Num Channels> <Num Users> <NumPosts> + + Example: + /loadtest setup teams fuzz 10 20 50 + + Users - Add a specified number of random users with fuzz text to current team. + /loadtest users [fuzz] <Min Users> <Max Users> + + Example: + /loadtest users fuzz 5 10 + + Channels - Add a specified number of random channels with fuzz text to current team. + /loadtest channels [fuzz] <Min Channels> <Max Channels> + + Example: + /loadtest channels fuzz 5 10 + + Posts - Add some random posts with fuzz text to current channel. + /loadtest posts [fuzz] <Min Posts> <Max Posts> <Max Images> + + Example: + /loadtest posts fuzz 5 10 3 + + Url - Add a post containing the text from a given url to current channel. + /loadtest url + + Example: + /loadtest http://www.example.com/sample_file.md + + +` + +type LoadTestProvider struct { +} + +func init() { + if !utils.Cfg.ServiceSettings.EnableTesting { + RegisterCommandProvider(&LoadTestProvider{}) + } +} + +func (me *LoadTestProvider) GetCommand() *model.Command { + return &model.Command{ + Trigger: "loadtest", + AutoComplete: false, + AutoCompleteDesc: "Debug Load Testing", + AutoCompleteHint: "help", + DisplayName: "loadtest", + } +} + +func (me *LoadTestProvider) DoCommand(c *Context, channelId string, message string) *model.CommandResponse { + + // This command is only available when EnableTesting is true + // if !utils.Cfg.ServiceSettings.EnableTesting { + // return &model.CommandResponse{} + // } + + if strings.HasPrefix(message, "setup") { + return me.SetupCommand(c, channelId, message) + } + + if strings.HasPrefix(message, "users") { + return me.UsersCommand(c, channelId, message) + } + + if strings.HasPrefix(message, "channels") { + return me.ChannelsCommand(c, channelId, message) + } + + if strings.HasPrefix(message, "posts") { + return me.PostsCommand(c, channelId, message) + } + + if strings.HasPrefix(message, "url") { + return me.UrlCommand(c, channelId, message) + } + + return me.HelpCommand(c, channelId, message) +} + +func (me *LoadTestProvider) HelpCommand(c *Context, channelId string, message string) *model.CommandResponse { + return &model.CommandResponse{Text: usage, ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL} +} + +func (me *LoadTestProvider) SetupCommand(c *Context, channelId string, message string) *model.CommandResponse { + tokens := strings.Fields(strings.TrimPrefix(message, "setup")) + doTeams := contains(tokens, "teams") + doFuzz := contains(tokens, "fuzz") + + numArgs := 0 + if doTeams { + numArgs++ + } + if doFuzz { + numArgs++ + } + + var numTeams int + var numChannels int + var numUsers int + var numPosts int + + // Defaults + numTeams = 10 + numChannels = 10 + numUsers = 10 + numPosts = 10 + + if doTeams { + if (len(tokens) - numArgs) >= 4 { + numTeams, _ = strconv.Atoi(tokens[numArgs+0]) + numChannels, _ = strconv.Atoi(tokens[numArgs+1]) + numUsers, _ = strconv.Atoi(tokens[numArgs+2]) + numPosts, _ = strconv.Atoi(tokens[numArgs+3]) + } + } else { + if (len(tokens) - numArgs) >= 3 { + numChannels, _ = strconv.Atoi(tokens[numArgs+0]) + numUsers, _ = strconv.Atoi(tokens[numArgs+1]) + numPosts, _ = strconv.Atoi(tokens[numArgs+2]) + } + } + client := model.NewClient(c.GetSiteURL()) + + if doTeams { + if err := CreateBasicUser(client); err != nil { + return &model.CommandResponse{Text: "Failed to create testing environment", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL} + } + client.LoginByEmail(BTEST_TEAM_NAME, BTEST_USER_EMAIL, BTEST_USER_PASSWORD) + environment, err := CreateTestEnvironmentWithTeams( + client, + utils.Range{numTeams, numTeams}, + utils.Range{numChannels, numChannels}, + utils.Range{numUsers, numUsers}, + utils.Range{numPosts, numPosts}, + doFuzz) + if err != true { + return &model.CommandResponse{Text: "Failed to create testing environment", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL} + } else { + l4g.Info("Testing environment created") + for i := 0; i < len(environment.Teams); i++ { + l4g.Info("Team Created: " + environment.Teams[i].Name) + l4g.Info("\t User to login: " + environment.Environments[i].Users[0].Email + ", " + USER_PASSWORD) + } + } + } else { + client.MockSession(c.Session.Token) + CreateTestEnvironmentInTeam( + client, + c.Session.TeamId, + utils.Range{numChannels, numChannels}, + utils.Range{numUsers, numUsers}, + utils.Range{numPosts, numPosts}, + doFuzz) + } + + return &model.CommandResponse{Text: "Creating enviroment...", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL} +} + +func (me *LoadTestProvider) UsersCommand(c *Context, channelId string, message string) *model.CommandResponse { + cmd := strings.TrimSpace(strings.TrimPrefix(message, "users")) + + doFuzz := false + if strings.Index(cmd, "fuzz") == 0 { + doFuzz = true + cmd = strings.TrimSpace(strings.TrimPrefix(cmd, "fuzz")) + } + + usersr, err := parseRange(cmd, "") + if err == false { + usersr = utils.Range{2, 5} + } + + client := model.NewClient(c.GetSiteURL()) + userCreator := NewAutoUserCreator(client, c.Session.TeamId) + userCreator.Fuzzy = doFuzz + userCreator.CreateTestUsers(usersr) + + return &model.CommandResponse{Text: "Adding users...", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL} +} + +func (me *LoadTestProvider) ChannelsCommand(c *Context, channelId string, message string) *model.CommandResponse { + cmd := strings.TrimSpace(strings.TrimPrefix(message, "channels")) + + doFuzz := false + if strings.Index(cmd, "fuzz") == 0 { + doFuzz = true + cmd = strings.TrimSpace(strings.TrimPrefix(cmd, "fuzz")) + } + + channelsr, err := parseRange(cmd, "") + if err == false { + channelsr = utils.Range{2, 5} + } + client := model.NewClient(c.GetSiteURL()) + client.MockSession(c.Session.Token) + channelCreator := NewAutoChannelCreator(client, c.Session.TeamId) + channelCreator.Fuzzy = doFuzz + channelCreator.CreateTestChannels(channelsr) + + return &model.CommandResponse{Text: "Adding channels...", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL} +} + +func (me *LoadTestProvider) PostsCommand(c *Context, channelId string, message string) *model.CommandResponse { + cmd := strings.TrimSpace(strings.TrimPrefix(message, "posts")) + + doFuzz := false + if strings.Index(cmd, "fuzz") == 0 { + doFuzz = true + cmd = strings.TrimSpace(strings.TrimPrefix(cmd, "fuzz")) + } + + postsr, err := parseRange(cmd, "") + if err == false { + postsr = utils.Range{20, 30} + } + + tokens := strings.Fields(cmd) + rimages := utils.Range{0, 0} + if len(tokens) >= 3 { + if numImages, err := strconv.Atoi(tokens[2]); err == nil { + rimages = utils.Range{numImages, numImages} + } + } + + var usernames []string + if result := <-Srv.Store.User().GetProfiles(c.Session.TeamId); result.Err == nil { + profileUsers := result.Data.(map[string]*model.User) + usernames = make([]string, len(profileUsers)) + i := 0 + for _, userprof := range profileUsers { + usernames[i] = userprof.Username + i++ + } + } + + client := model.NewClient(c.GetSiteURL()) + client.MockSession(c.Session.Token) + testPoster := NewAutoPostCreator(client, channelId) + testPoster.Fuzzy = doFuzz + testPoster.Users = usernames + + numImages := utils.RandIntFromRange(rimages) + numPosts := utils.RandIntFromRange(postsr) + for i := 0; i < numPosts; i++ { + testPoster.HasImage = (i < numImages) + testPoster.CreateRandomPost() + } + + return &model.CommandResponse{Text: "Adding posts...", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL} +} + +func (me *LoadTestProvider) UrlCommand(c *Context, channelId string, message string) *model.CommandResponse { + url := strings.TrimSpace(strings.TrimPrefix(message, "url")) + if len(url) == 0 { + return &model.CommandResponse{Text: "Command must contain a url", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL} + } + + // provide a shortcut to easily access tests stored in doc/developer/tests + if !strings.HasPrefix(url, "http") { + url = "https://raw.githubusercontent.com/mattermost/platform/master/doc/developer/tests/" + url + + if path.Ext(url) == "" { + url += ".md" + } + } + + var contents io.ReadCloser + if r, err := http.Get(url); err != nil { + return &model.CommandResponse{Text: "Unable to get file", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL} + } else if r.StatusCode > 400 { + return &model.CommandResponse{Text: "Unable to get file", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL} + } else { + contents = r.Body + } + + bytes := make([]byte, 4000) + + // break contents into 4000 byte posts + for { + length, err := contents.Read(bytes) + if err != nil && err != io.EOF { + return &model.CommandResponse{Text: "Encountered error reading file", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL} + } + + if length == 0 { + break + } + + post := &model.Post{} + post.Message = string(bytes[:length]) + post.ChannelId = channelId + + if _, err := CreatePost(c, post, false); err != nil { + return &model.CommandResponse{Text: "Unable to create post", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL} + } + } + + return &model.CommandResponse{Text: "Loading url...", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL} +} + +func parseRange(command string, cmd string) (utils.Range, bool) { + tokens := strings.Fields(strings.TrimPrefix(command, cmd)) + var begin int + var end int + var err1 error + var err2 error + switch { + case len(tokens) == 1: + begin, err1 = strconv.Atoi(tokens[0]) + end = begin + if err1 != nil { + return utils.Range{0, 0}, false + } + case len(tokens) >= 2: + begin, err1 = strconv.Atoi(tokens[0]) + end, err2 = strconv.Atoi(tokens[1]) + if err1 != nil || err2 != nil { + return utils.Range{0, 0}, false + } + default: + return utils.Range{0, 0}, false + } + return utils.Range{begin, end}, true +} + +func contains(items []string, token string) bool { + for _, elem := range items { + if elem == token { + return true + } + } + return false +} diff --git a/api/command_loadtest_test.go b/api/command_loadtest_test.go new file mode 100644 index 000000000..4af2a636a --- /dev/null +++ b/api/command_loadtest_test.go @@ -0,0 +1,220 @@ +// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package api + +import ( + "strings" + "testing" + "time" + + "github.com/mattermost/platform/model" + "github.com/mattermost/platform/store" + "github.com/mattermost/platform/utils" +) + +func TestLoadTestHelpCommands(t *testing.T) { + Setup() + // enable testing to use /loadtest but don't save it since we don't want to overwrite config.json + enableTesting := utils.Cfg.ServiceSettings.EnableTesting + defer func() { + utils.Cfg.ServiceSettings.EnableTesting = enableTesting + }() + + utils.Cfg.ServiceSettings.EnableTesting = true + + team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} + team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team) + + user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"} + user1 = Client.Must(Client.CreateUser(user1, "")).Data.(*model.User) + store.Must(Srv.Store.User().VerifyEmail(user1.Id)) + + Client.LoginByEmail(team.Name, user1.Email, "pwd") + + channel := &model.Channel{DisplayName: "00", Name: "00" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id} + channel = Client.Must(Client.CreateChannel(channel)).Data.(*model.Channel) + + rs := Client.Must(Client.Command(channel.Id, "/loadtest help", false)).Data.(*model.CommandResponse) + if !strings.Contains(rs.Text, "Mattermost load testing commands to help") { + t.Fatal(rs.Text) + } + + time.Sleep(2 * time.Second) +} + +func TestLoadTestSetupCommands(t *testing.T) { + Setup() + // enable testing to use /loadtest but don't save it since we don't want to overwrite config.json + enableTesting := utils.Cfg.ServiceSettings.EnableTesting + defer func() { + utils.Cfg.ServiceSettings.EnableTesting = enableTesting + }() + + utils.Cfg.ServiceSettings.EnableTesting = true + + team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} + team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team) + + user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"} + user1 = Client.Must(Client.CreateUser(user1, "")).Data.(*model.User) + store.Must(Srv.Store.User().VerifyEmail(user1.Id)) + + Client.LoginByEmail(team.Name, user1.Email, "pwd") + + channel := &model.Channel{DisplayName: "00", Name: "00" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id} + channel = Client.Must(Client.CreateChannel(channel)).Data.(*model.Channel) + + rs := Client.Must(Client.Command(channel.Id, "/loadtest setup fuzz 1 1 1", false)).Data.(*model.CommandResponse) + if rs.Text != "Creating enviroment..." { + t.Fatal(rs.Text) + } + + time.Sleep(2 * time.Second) +} + +func TestLoadTestUsersCommands(t *testing.T) { + Setup() + // enable testing to use /loadtest but don't save it since we don't want to overwrite config.json + enableTesting := utils.Cfg.ServiceSettings.EnableTesting + defer func() { + utils.Cfg.ServiceSettings.EnableTesting = enableTesting + }() + + utils.Cfg.ServiceSettings.EnableTesting = true + + team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} + team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team) + + user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"} + user1 = Client.Must(Client.CreateUser(user1, "")).Data.(*model.User) + store.Must(Srv.Store.User().VerifyEmail(user1.Id)) + + Client.LoginByEmail(team.Name, user1.Email, "pwd") + + channel := &model.Channel{DisplayName: "00", Name: "00" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id} + channel = Client.Must(Client.CreateChannel(channel)).Data.(*model.Channel) + + rs := Client.Must(Client.Command(channel.Id, "/loadtest users fuzz 1 2", false)).Data.(*model.CommandResponse) + if rs.Text != "Adding users..." { + t.Fatal(rs.Text) + } + + time.Sleep(2 * time.Second) +} + +func TestLoadTestChannelsCommands(t *testing.T) { + Setup() + // enable testing to use /loadtest but don't save it since we don't want to overwrite config.json + enableTesting := utils.Cfg.ServiceSettings.EnableTesting + defer func() { + utils.Cfg.ServiceSettings.EnableTesting = enableTesting + }() + + utils.Cfg.ServiceSettings.EnableTesting = true + + team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} + team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team) + + user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"} + user1 = Client.Must(Client.CreateUser(user1, "")).Data.(*model.User) + store.Must(Srv.Store.User().VerifyEmail(user1.Id)) + + Client.LoginByEmail(team.Name, user1.Email, "pwd") + + channel := &model.Channel{DisplayName: "00", Name: "00" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id} + channel = Client.Must(Client.CreateChannel(channel)).Data.(*model.Channel) + + rs := Client.Must(Client.Command(channel.Id, "/loadtest channels fuzz 1 2", false)).Data.(*model.CommandResponse) + if rs.Text != "Adding channels..." { + t.Fatal(rs.Text) + } + + time.Sleep(2 * time.Second) +} + +func TestLoadTestPostsCommands(t *testing.T) { + Setup() + // enable testing to use /loadtest but don't save it since we don't want to overwrite config.json + enableTesting := utils.Cfg.ServiceSettings.EnableTesting + defer func() { + utils.Cfg.ServiceSettings.EnableTesting = enableTesting + }() + + utils.Cfg.ServiceSettings.EnableTesting = true + + team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} + team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team) + + user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"} + user1 = Client.Must(Client.CreateUser(user1, "")).Data.(*model.User) + store.Must(Srv.Store.User().VerifyEmail(user1.Id)) + + Client.LoginByEmail(team.Name, user1.Email, "pwd") + + channel := &model.Channel{DisplayName: "00", Name: "00" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id} + channel = Client.Must(Client.CreateChannel(channel)).Data.(*model.Channel) + + rs := Client.Must(Client.Command(channel.Id, "/loadtest posts fuzz 2 3 2", false)).Data.(*model.CommandResponse) + if rs.Text != "Adding posts..." { + t.Fatal(rs.Text) + } + + time.Sleep(2 * time.Second) +} + +func TestLoadTestUrlCommands(t *testing.T) { + Setup() + // enable testing to use /loadtest but don't save it since we don't want to overwrite config.json + enableTesting := utils.Cfg.ServiceSettings.EnableTesting + defer func() { + utils.Cfg.ServiceSettings.EnableTesting = enableTesting + }() + + utils.Cfg.ServiceSettings.EnableTesting = true + + team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} + team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team) + + user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"} + user1 = Client.Must(Client.CreateUser(user1, "")).Data.(*model.User) + store.Must(Srv.Store.User().VerifyEmail(user1.Id)) + + Client.LoginByEmail(team.Name, user1.Email, "pwd") + + channel := &model.Channel{DisplayName: "00", Name: "00" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id} + channel = Client.Must(Client.CreateChannel(channel)).Data.(*model.Channel) + + command := "/loadtest url " + if r := Client.Must(Client.Command(channel.Id, command, false)).Data.(*model.CommandResponse); r.Text != "Command must contain a url" { + t.Fatal("/loadtest url with no url should've failed") + } + + command = "/loadtest url http://www.hopefullynonexistent.file/path/asdf/qwerty" + if r := Client.Must(Client.Command(channel.Id, command, false)).Data.(*model.CommandResponse); r.Text != "Unable to get file" { + t.Fatal("/loadtest url with invalid url should've failed") + } + + command = "/loadtest url https://raw.githubusercontent.com/mattermost/platform/master/README.md" + if r := Client.Must(Client.Command(channel.Id, command, false)).Data.(*model.CommandResponse); r.Text != "Loading url..." { + t.Fatal("/loadtest url for README.md should've executed") + } + + command = "/loadtest url test-emoticons.md" + if r := Client.Must(Client.Command(channel.Id, command, false)).Data.(*model.CommandResponse); r.Text != "Loading url..." { + t.Fatal("/loadtest url for test-emoticons.md should've executed") + } + + command = "/loadtest url test-emoticons" + if r := Client.Must(Client.Command(channel.Id, command, false)).Data.(*model.CommandResponse); r.Text != "Loading url..." { + t.Fatal("/loadtest url for test-emoticons should've executed") + } + + posts := Client.Must(Client.GetPosts(channel.Id, 0, 5, "")).Data.(*model.PostList) + // note that this may make more than 3 posts if files are too long to fit in an individual post + if len(posts.Order) < 3 { + t.Fatal("/loadtest url made too few posts, perhaps there needs to be a delay before GetPosts in the test?") + } + + time.Sleep(2 * time.Second) +} diff --git a/api/command_logout.go b/api/command_logout.go new file mode 100644 index 000000000..01e81aaf0 --- /dev/null +++ b/api/command_logout.go @@ -0,0 +1,29 @@ +// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package api + +import ( + "github.com/mattermost/platform/model" +) + +type LogoutProvider struct { +} + +func init() { + RegisterCommandProvider(&LogoutProvider{}) +} + +func (me *LogoutProvider) GetCommand() *model.Command { + return &model.Command{ + Trigger: "logout", + AutoComplete: true, + AutoCompleteDesc: "Logout", + AutoCompleteHint: "", + DisplayName: "logout", + } +} + +func (me *LogoutProvider) DoCommand(c *Context, channelId string, message string) *model.CommandResponse { + return &model.CommandResponse{GotoLocation: "/logout", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL, Text: "Logging out..."} +} diff --git a/api/command_logout_test.go b/api/command_logout_test.go new file mode 100644 index 000000000..86979316b --- /dev/null +++ b/api/command_logout_test.go @@ -0,0 +1,32 @@ +// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package api + +import ( + "testing" + + "github.com/mattermost/platform/model" + "github.com/mattermost/platform/store" +) + +func TestLogoutTestCommand(t *testing.T) { + Setup() + + team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} + team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team) + + user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"} + user1 = Client.Must(Client.CreateUser(user1, "")).Data.(*model.User) + store.Must(Srv.Store.User().VerifyEmail(user1.Id)) + + Client.LoginByEmail(team.Name, user1.Email, "pwd") + + channel1 := &model.Channel{DisplayName: "AA", Name: "aa" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id} + channel1 = Client.Must(Client.CreateChannel(channel1)).Data.(*model.Channel) + + rs1 := Client.Must(Client.Command(channel1.Id, "/logout", false)).Data.(*model.CommandResponse) + if rs1.GotoLocation != "/logout" { + t.Fatal("failed to logout") + } +} diff --git a/api/command_me.go b/api/command_me.go new file mode 100644 index 000000000..f0154fe53 --- /dev/null +++ b/api/command_me.go @@ -0,0 +1,29 @@ +// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package api + +import ( + "github.com/mattermost/platform/model" +) + +type MeProvider struct { +} + +func init() { + RegisterCommandProvider(&MeProvider{}) +} + +func (me *MeProvider) GetCommand() *model.Command { + return &model.Command{ + Trigger: "me", + AutoComplete: true, + AutoCompleteDesc: "Do an action", + AutoCompleteHint: "[message]", + DisplayName: "me", + } +} + +func (me *MeProvider) DoCommand(c *Context, channelId string, message string) *model.CommandResponse { + return &model.CommandResponse{ResponseType: model.COMMAND_RESPONSE_TYPE_IN_CHANNEL, Text: "*" + message + "*"} +} diff --git a/api/command_me_test.go b/api/command_me_test.go new file mode 100644 index 000000000..d55a15b2c --- /dev/null +++ b/api/command_me_test.go @@ -0,0 +1,47 @@ +// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package api + +import ( + "testing" + "time" + + "github.com/mattermost/platform/model" + "github.com/mattermost/platform/store" +) + +func TestMeCommand(t *testing.T) { + Setup() + + team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} + team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team) + + user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"} + user1 = Client.Must(Client.CreateUser(user1, "")).Data.(*model.User) + store.Must(Srv.Store.User().VerifyEmail(user1.Id)) + + Client.LoginByEmail(team.Name, user1.Email, "pwd") + + channel1 := &model.Channel{DisplayName: "AA", Name: "aa" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id} + channel1 = Client.Must(Client.CreateChannel(channel1)).Data.(*model.Channel) + + testString := "/me hello" + + r1 := Client.Must(Client.Command(channel1.Id, testString, false)).Data.(*model.CommandResponse) + if r1 == nil { + t.Fatal("Command failed to execute") + } + + time.Sleep(100 * time.Millisecond) + + p1 := Client.Must(Client.GetPosts(channel1.Id, 0, 2, "")).Data.(*model.PostList) + if len(p1.Order) != 1 { + t.Fatal("Command failed to send") + } else { + if p1.Posts[p1.Order[0]].Message != `*hello*` { + t.Log(p1.Posts[p1.Order[0]].Message) + t.Fatal("invalid shrug reponse") + } + } +} diff --git a/api/command_shrug.go b/api/command_shrug.go new file mode 100644 index 000000000..c49bd46ae --- /dev/null +++ b/api/command_shrug.go @@ -0,0 +1,34 @@ +// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package api + +import ( + "github.com/mattermost/platform/model" +) + +type ShrugProvider struct { +} + +func init() { + RegisterCommandProvider(&ShrugProvider{}) +} + +func (me *ShrugProvider) GetCommand() *model.Command { + return &model.Command{ + Trigger: "shrug", + AutoComplete: true, + AutoCompleteDesc: `Adds ¯\_(ツ)_/¯ to your message`, + AutoCompleteHint: "[message]", + DisplayName: "shrug", + } +} + +func (me *ShrugProvider) DoCommand(c *Context, channelId string, message string) *model.CommandResponse { + rmsg := `¯\\\_(ツ)\_/¯` + if len(message) > 0 { + rmsg = message + " " + rmsg + } + + return &model.CommandResponse{ResponseType: model.COMMAND_RESPONSE_TYPE_IN_CHANNEL, Text: rmsg} +} diff --git a/api/command_shrug_test.go b/api/command_shrug_test.go new file mode 100644 index 000000000..92cecf664 --- /dev/null +++ b/api/command_shrug_test.go @@ -0,0 +1,47 @@ +// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package api + +import ( + "testing" + "time" + + "github.com/mattermost/platform/model" + "github.com/mattermost/platform/store" +) + +func TestShrugCommand(t *testing.T) { + Setup() + + team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} + team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team) + + user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"} + user1 = Client.Must(Client.CreateUser(user1, "")).Data.(*model.User) + store.Must(Srv.Store.User().VerifyEmail(user1.Id)) + + Client.LoginByEmail(team.Name, user1.Email, "pwd") + + channel1 := &model.Channel{DisplayName: "AA", Name: "aa" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id} + channel1 = Client.Must(Client.CreateChannel(channel1)).Data.(*model.Channel) + + testString := "/shrug" + + r1 := Client.Must(Client.Command(channel1.Id, testString, false)).Data.(*model.CommandResponse) + if r1 == nil { + t.Fatal("Command failed to execute") + } + + time.Sleep(100 * time.Millisecond) + + p1 := Client.Must(Client.GetPosts(channel1.Id, 0, 2, "")).Data.(*model.PostList) + if len(p1.Order) != 1 { + t.Fatal("Command failed to send") + } else { + if p1.Posts[p1.Order[0]].Message != `¯\\\_(ツ)\_/¯` { + t.Log(p1.Posts[p1.Order[0]].Message) + t.Fatal("invalid shrug reponse") + } + } +} diff --git a/api/command_test.go b/api/command_test.go index f38cf1397..e5e954170 100644 --- a/api/command_test.go +++ b/api/command_test.go @@ -4,16 +4,14 @@ package api import ( - "strings" "testing" - "time" "github.com/mattermost/platform/model" "github.com/mattermost/platform/store" "github.com/mattermost/platform/utils" ) -func TestSuggestRootCommands(t *testing.T) { +func TestListCommands(t *testing.T) { Setup() team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} @@ -25,177 +23,160 @@ func TestSuggestRootCommands(t *testing.T) { Client.LoginByEmail(team.Name, user1.Email, "pwd") - if _, err := Client.Command("", "", true); err == nil { - t.Fatal("Should fail") - } - - rs1 := Client.Must(Client.Command("", "/", true)).Data.(*model.Command) + if results, err := Client.ListCommands(); err != nil { + t.Fatal(err) + } else { + commands := results.Data.([]*model.Command) + foundEcho := false - hasLogout := false - for _, v := range rs1.Suggestions { - if v.Suggestion == "/logout" { - hasLogout = true + for _, command := range commands { + if command.Trigger == "echo" { + foundEcho = true + } } - } - - if !hasLogout { - t.Log(rs1.Suggestions) - t.Fatal("should have logout cmd") - } - - rs2 := Client.Must(Client.Command("", "/log", true)).Data.(*model.Command) - - if rs2.Suggestions[0].Suggestion != "/logout" { - t.Fatal("should have logout cmd") - } - - rs3 := Client.Must(Client.Command("", "/joi", true)).Data.(*model.Command) - - if rs3.Suggestions[0].Suggestion != "/join" { - t.Fatal("should have join cmd") - } - rs4 := Client.Must(Client.Command("", "/ech", true)).Data.(*model.Command) - - if rs4.Suggestions[0].Suggestion != "/echo" { - t.Fatal("should have echo cmd") + if !foundEcho { + t.Fatal("Couldn't find echo command") + } } } -func TestLogoutCommands(t *testing.T) { +func TestCreateCommand(t *testing.T) { Setup() - team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} - team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team) - - user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"} - user1 = Client.Must(Client.CreateUser(user1, "")).Data.(*model.User) - store.Must(Srv.Store.User().VerifyEmail(user1.Id)) - - Client.LoginByEmail(team.Name, user1.Email, "pwd") - - rs1 := Client.Must(Client.Command("", "/logout", false)).Data.(*model.Command) - if rs1.GotoLocation != "/logout" { - t.Fatal("failed to logout") - } -} - -func TestJoinCommands(t *testing.T) { - Setup() + enableCommands := *utils.Cfg.ServiceSettings.EnableCommands + defer func() { + utils.Cfg.ServiceSettings.EnableCommands = &enableCommands + }() + *utils.Cfg.ServiceSettings.EnableCommands = true team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team) - user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"} - user1 = Client.Must(Client.CreateUser(user1, "")).Data.(*model.User) - store.Must(Srv.Store.User().VerifyEmail(user1.Id)) - - Client.LoginByEmail(team.Name, user1.Email, "pwd") + user := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"} + user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User) + store.Must(Srv.Store.User().VerifyEmail(user.Id)) - channel1 := &model.Channel{DisplayName: "AA", Name: "aa" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id} - channel1 = Client.Must(Client.CreateChannel(channel1)).Data.(*model.Channel) - Client.Must(Client.LeaveChannel(channel1.Id)) + Client.LoginByEmail(team.Name, user.Email, "pwd") - channel2 := &model.Channel{DisplayName: "BB", Name: "bb" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id} - channel2 = Client.Must(Client.CreateChannel(channel2)).Data.(*model.Channel) - Client.Must(Client.LeaveChannel(channel2.Id)) + cmd := &model.Command{URL: "http://nowhere.com", Method: model.COMMAND_METHOD_POST} - user2 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"} - user2 = Client.Must(Client.CreateUser(user2, "")).Data.(*model.User) - store.Must(Srv.Store.User().VerifyEmail(user1.Id)) + if _, err := Client.CreateCommand(cmd); err == nil { + t.Fatal("should have failed because not admin") + } - data := make(map[string]string) - data["user_id"] = user2.Id - channel3 := Client.Must(Client.CreateDirectChannel(data)).Data.(*model.Channel) + c := &Context{} + c.RequestId = model.NewId() + c.IpAddress = "cmd_line" + UpdateRoles(c, user, model.ROLE_SYSTEM_ADMIN) + Client.LoginByEmail(team.Name, user.Email, "pwd") - rs1 := Client.Must(Client.Command("", "/join aa", true)).Data.(*model.Command) - if rs1.Suggestions[0].Suggestion != "/join "+channel1.Name { - t.Fatal("should have join cmd") + var rcmd *model.Command + if result, err := Client.CreateCommand(cmd); err != nil { + t.Fatal(err) + } else { + rcmd = result.Data.(*model.Command) } - rs2 := Client.Must(Client.Command("", "/join bb", true)).Data.(*model.Command) - if rs2.Suggestions[0].Suggestion != "/join "+channel2.Name { - t.Fatal("should have join cmd") + if rcmd.CreatorId != user.Id { + t.Fatal("user ids didn't match") } - rs3 := Client.Must(Client.Command("", "/join", true)).Data.(*model.Command) - if len(rs3.Suggestions) != 2 { - t.Fatal("should have 2 join cmd") + if rcmd.TeamId != team.Id { + t.Fatal("team ids didn't match") } - rs4 := Client.Must(Client.Command("", "/join ", true)).Data.(*model.Command) - if len(rs4.Suggestions) != 2 { - t.Fatal("should have 2 join cmd") + cmd = &model.Command{CreatorId: "123", TeamId: "456", URL: "http://nowhere.com", Method: model.COMMAND_METHOD_POST} + if result, err := Client.CreateCommand(cmd); err != nil { + t.Fatal(err) + } else { + if result.Data.(*model.Command).CreatorId != user.Id { + t.Fatal("bad user id wasn't overwritten") + } + if result.Data.(*model.Command).TeamId != team.Id { + t.Fatal("bad team id wasn't overwritten") + } } +} - rs5 := Client.Must(Client.Command("", "/join "+channel2.Name, false)).Data.(*model.Command) - if !strings.HasSuffix(rs5.GotoLocation, "/"+team.Name+"/channels/"+channel2.Name) { - t.Fatal("failed to join channel") - } +func TestListTeamCommands(t *testing.T) { + Setup() + enableCommands := *utils.Cfg.ServiceSettings.EnableCommands + defer func() { + utils.Cfg.ServiceSettings.EnableCommands = &enableCommands + }() + *utils.Cfg.ServiceSettings.EnableCommands = true - rs6 := Client.Must(Client.Command("", "/join "+channel3.Name, false)).Data.(*model.Command) - if strings.HasSuffix(rs6.GotoLocation, "/"+team.Name+"/channels/"+channel3.Name) { - t.Fatal("should not have joined direct message channel") - } + team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} + team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team) - c1 := Client.Must(Client.GetChannels("")).Data.(*model.ChannelList) + user := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"} + user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User) + store.Must(Srv.Store.User().VerifyEmail(user.Id)) - if len(c1.Channels) != 4 { // 4 because of town-square, off-topic and direct - t.Fatal("didn't join channel") - } + c := &Context{} + c.RequestId = model.NewId() + c.IpAddress = "cmd_line" + UpdateRoles(c, user, model.ROLE_SYSTEM_ADMIN) + Client.LoginByEmail(team.Name, user.Email, "pwd") + + cmd1 := &model.Command{URL: "http://nowhere.com", Method: model.COMMAND_METHOD_POST} + cmd1 = Client.Must(Client.CreateCommand(cmd1)).Data.(*model.Command) + + if result, err := Client.ListTeamCommands(); err != nil { + t.Fatal(err) + } else { + cmds := result.Data.([]*model.Command) - found := false - for _, c := range c1.Channels { - if c.Name == channel2.Name { - found = true - break + if len(cmds) != 1 { + t.Fatal("incorrect number of cmd") } } - if !found { - t.Fatal("didn't join channel") - } } -func TestEchoCommand(t *testing.T) { +func TestRegenToken(t *testing.T) { Setup() + enableCommands := *utils.Cfg.ServiceSettings.EnableCommands + defer func() { + utils.Cfg.ServiceSettings.EnableCommands = &enableCommands + }() + *utils.Cfg.ServiceSettings.EnableCommands = true team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team) - user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"} - user1 = Client.Must(Client.CreateUser(user1, "")).Data.(*model.User) - store.Must(Srv.Store.User().VerifyEmail(user1.Id)) - - Client.LoginByEmail(team.Name, user1.Email, "pwd") - - channel1 := &model.Channel{DisplayName: "AA", Name: "aa" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id} - channel1 = Client.Must(Client.CreateChannel(channel1)).Data.(*model.Channel) + user := &model.User{TeamId: team.Id, Email: model.NewId() + "corey+test@test.com", Nickname: "Corey Hulen", Password: "pwd"} + user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User) + store.Must(Srv.Store.User().VerifyEmail(user.Id)) - echoTestString := "/echo test" + c := &Context{} + c.RequestId = model.NewId() + c.IpAddress = "cmd_line" + UpdateRoles(c, user, model.ROLE_SYSTEM_ADMIN) + Client.LoginByEmail(team.Name, user.Email, "pwd") - r1 := Client.Must(Client.Command(channel1.Id, echoTestString, false)).Data.(*model.Command) - if r1.Response != model.RESP_EXECUTED { - t.Fatal("Echo command failed to execute") - } + cmd := &model.Command{URL: "http://nowhere.com", Method: model.COMMAND_METHOD_POST} + cmd = Client.Must(Client.CreateCommand(cmd)).Data.(*model.Command) - time.Sleep(100 * time.Millisecond) + data := make(map[string]string) + data["id"] = cmd.Id - p1 := Client.Must(Client.GetPosts(channel1.Id, 0, 2, "")).Data.(*model.PostList) - if len(p1.Order) != 1 { - t.Fatal("Echo command failed to send") + if result, err := Client.RegenCommandToken(data); err != nil { + t.Fatal(err) + } else { + if result.Data.(*model.Command).Token == cmd.Token { + t.Fatal("regen didn't work properly") + } } } -func TestLoadTestUrlCommand(t *testing.T) { +func TestDeleteCommand(t *testing.T) { Setup() - - // enable testing to use /loadtest but don't save it since we don't want to overwrite config.json - enableTesting := utils.Cfg.ServiceSettings.EnableTesting + enableCommands := *utils.Cfg.ServiceSettings.EnableCommands defer func() { - utils.Cfg.ServiceSettings.EnableTesting = enableTesting + utils.Cfg.ServiceSettings.EnableCommands = &enableCommands }() - - utils.Cfg.ServiceSettings.EnableTesting = true + *utils.Cfg.ServiceSettings.EnableCommands = true team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team) @@ -204,39 +185,24 @@ func TestLoadTestUrlCommand(t *testing.T) { user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User) store.Must(Srv.Store.User().VerifyEmail(user.Id)) + c := &Context{} + c.RequestId = model.NewId() + c.IpAddress = "cmd_line" + UpdateRoles(c, user, model.ROLE_SYSTEM_ADMIN) Client.LoginByEmail(team.Name, user.Email, "pwd") - channel := &model.Channel{DisplayName: "AA", Name: "aa" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id} - channel = Client.Must(Client.CreateChannel(channel)).Data.(*model.Channel) - - command := "/loadtest url " - if _, err := Client.Command(channel.Id, command, false); err == nil { - t.Fatal("/loadtest url with no url should've failed") - } - - command = "/loadtest url http://www.hopefullynonexistent.file/path/asdf/qwerty" - if _, err := Client.Command(channel.Id, command, false); err == nil { - t.Fatal("/loadtest url with invalid url should've failed") - } - - command = "/loadtest url https://raw.githubusercontent.com/mattermost/platform/master/README.md" - if r := Client.Must(Client.Command(channel.Id, command, false)).Data.(*model.Command); r.Response != model.RESP_EXECUTED { - t.Fatal("/loadtest url for README.md should've executed") - } + cmd := &model.Command{URL: "http://nowhere.com", Method: model.COMMAND_METHOD_POST} + cmd = Client.Must(Client.CreateCommand(cmd)).Data.(*model.Command) - command = "/loadtest url test-emoticons.md" - if r := Client.Must(Client.Command(channel.Id, command, false)).Data.(*model.Command); r.Response != model.RESP_EXECUTED { - t.Fatal("/loadtest url for test-emoticons.md should've executed") - } + data := make(map[string]string) + data["id"] = cmd.Id - command = "/loadtest url test-emoticons" - if r := Client.Must(Client.Command(channel.Id, command, false)).Data.(*model.Command); r.Response != model.RESP_EXECUTED { - t.Fatal("/loadtest url for test-emoticons should've executed") + if _, err := Client.DeleteCommand(data); err != nil { + t.Fatal(err) } - posts := Client.Must(Client.GetPosts(channel.Id, 0, 5, "")).Data.(*model.PostList) - // note that this may make more than 3 posts if files are too long to fit in an individual post - if len(posts.Order) < 3 { - t.Fatal("/loadtest url made too few posts, perhaps there needs to be a delay before GetPosts in the test?") + cmds := Client.Must(Client.ListTeamCommands()).Data.([]*model.Command) + if len(cmds) != 0 { + t.Fatal("delete didn't work properly") } } diff --git a/api/user.go b/api/user.go index a6b4fb654..ab64759cf 100644 --- a/api/user.go +++ b/api/user.go @@ -1450,6 +1450,10 @@ func PermanentDeleteUser(c *Context, user *model.User) *model.AppError { return result.Err } + if result := <-Srv.Store.Command().PermanentDeleteByUser(user.Id); result.Err != nil { + return result.Err + } + if result := <-Srv.Store.Preference().PermanentDeleteByUser(user.Id); result.Err != nil { return result.Err } diff --git a/api/webhook.go b/api/webhook.go index a9a88b7b8..33e7f957a 100644 --- a/api/webhook.go +++ b/api/webhook.go @@ -32,6 +32,14 @@ func createIncomingHook(c *Context, w http.ResponseWriter, r *http.Request) { return } + if *utils.Cfg.ServiceSettings.EnableOnlyAdminIntegrations { + if !(c.IsSystemAdmin() || c.IsTeamAdmin()) { + c.Err = model.NewAppError("createCommand", "Integrations have been limited to admins only.", "") + c.Err.StatusCode = http.StatusForbidden + return + } + } + c.LogAudit("attempt") hook := model.IncomingWebhookFromJson(r.Body) @@ -79,6 +87,14 @@ func deleteIncomingHook(c *Context, w http.ResponseWriter, r *http.Request) { return } + if *utils.Cfg.ServiceSettings.EnableOnlyAdminIntegrations { + if !(c.IsSystemAdmin() || c.IsTeamAdmin()) { + c.Err = model.NewAppError("createCommand", "Integrations have been limited to admins only.", "") + c.Err.StatusCode = http.StatusForbidden + return + } + } + c.LogAudit("attempt") props := model.MapFromJson(r.Body) @@ -116,7 +132,15 @@ func getIncomingHooks(c *Context, w http.ResponseWriter, r *http.Request) { return } - if result := <-Srv.Store.Webhook().GetIncomingByUser(c.Session.UserId); result.Err != nil { + if *utils.Cfg.ServiceSettings.EnableOnlyAdminIntegrations { + if !(c.IsSystemAdmin() || c.IsTeamAdmin()) { + c.Err = model.NewAppError("createCommand", "Integrations have been limited to admins only.", "") + c.Err.StatusCode = http.StatusForbidden + return + } + } + + if result := <-Srv.Store.Webhook().GetIncomingByTeam(c.Session.TeamId); result.Err != nil { c.Err = result.Err return } else { @@ -132,6 +156,14 @@ func createOutgoingHook(c *Context, w http.ResponseWriter, r *http.Request) { return } + if *utils.Cfg.ServiceSettings.EnableOnlyAdminIntegrations { + if !(c.IsSystemAdmin() || c.IsTeamAdmin()) { + c.Err = model.NewAppError("createCommand", "Integrations have been limited to admins only.", "") + c.Err.StatusCode = http.StatusForbidden + return + } + } + c.LogAudit("attempt") hook := model.OutgoingWebhookFromJson(r.Body) @@ -188,7 +220,15 @@ func getOutgoingHooks(c *Context, w http.ResponseWriter, r *http.Request) { return } - if result := <-Srv.Store.Webhook().GetOutgoingByCreator(c.Session.UserId); result.Err != nil { + if *utils.Cfg.ServiceSettings.EnableOnlyAdminIntegrations { + if !(c.IsSystemAdmin() || c.IsTeamAdmin()) { + c.Err = model.NewAppError("createCommand", "Integrations have been limited to admins only.", "") + c.Err.StatusCode = http.StatusForbidden + return + } + } + + if result := <-Srv.Store.Webhook().GetOutgoingByTeam(c.Session.TeamId); result.Err != nil { c.Err = result.Err return } else { @@ -204,6 +244,14 @@ func deleteOutgoingHook(c *Context, w http.ResponseWriter, r *http.Request) { return } + if *utils.Cfg.ServiceSettings.EnableOnlyAdminIntegrations { + if !(c.IsSystemAdmin() || c.IsTeamAdmin()) { + c.Err = model.NewAppError("createCommand", "Integrations have been limited to admins only.", "") + c.Err.StatusCode = http.StatusForbidden + return + } + } + c.LogAudit("attempt") props := model.MapFromJson(r.Body) @@ -241,6 +289,14 @@ func regenOutgoingHookToken(c *Context, w http.ResponseWriter, r *http.Request) return } + if *utils.Cfg.ServiceSettings.EnableOnlyAdminIntegrations { + if !(c.IsSystemAdmin() || c.IsTeamAdmin()) { + c.Err = model.NewAppError("createCommand", "Integrations have been limited to admins only.", "") + c.Err.StatusCode = http.StatusForbidden + return + } + } + c.LogAudit("attempt") props := model.MapFromJson(r.Body) @@ -258,7 +314,7 @@ func regenOutgoingHookToken(c *Context, w http.ResponseWriter, r *http.Request) } else { hook = result.Data.(*model.OutgoingWebhook) - if c.Session.UserId != hook.CreatorId && !c.IsTeamAdmin() { + if c.Session.TeamId != hook.TeamId && c.Session.UserId != hook.CreatorId && !c.IsTeamAdmin() { c.LogAudit("fail - inappropriate permissions") c.Err = model.NewAppError("regenOutgoingHookToken", "Inappropriate permissions to regenerate outcoming webhook token", "user_id="+c.Session.UserId) return diff --git a/api/webhook_test.go b/api/webhook_test.go index 85117ec18..89c06317f 100644 --- a/api/webhook_test.go +++ b/api/webhook_test.go @@ -13,6 +13,14 @@ import ( func TestCreateIncomingHook(t *testing.T) { Setup() + enableIncomingHooks := utils.Cfg.ServiceSettings.EnableIncomingWebhooks + enableOutgoingHooks := utils.Cfg.ServiceSettings.EnableOutgoingWebhooks + defer func() { + utils.Cfg.ServiceSettings.EnableIncomingWebhooks = enableIncomingHooks + utils.Cfg.ServiceSettings.EnableOutgoingWebhooks = enableOutgoingHooks + }() + utils.Cfg.ServiceSettings.EnableIncomingWebhooks = true + utils.Cfg.ServiceSettings.EnableOutgoingWebhooks = true team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team) @@ -21,6 +29,10 @@ func TestCreateIncomingHook(t *testing.T) { user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User) store.Must(Srv.Store.User().VerifyEmail(user.Id)) + c := &Context{} + c.RequestId = model.NewId() + c.IpAddress = "cmd_line" + UpdateRoles(c, user, model.ROLE_SYSTEM_ADMIN) Client.LoginByEmail(team.Name, user.Email, "pwd") channel1 := &model.Channel{DisplayName: "Test API Name", Name: "a" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id} @@ -76,6 +88,14 @@ func TestCreateIncomingHook(t *testing.T) { func TestListIncomingHooks(t *testing.T) { Setup() + enableIncomingHooks := utils.Cfg.ServiceSettings.EnableIncomingWebhooks + enableOutgoingHooks := utils.Cfg.ServiceSettings.EnableOutgoingWebhooks + defer func() { + utils.Cfg.ServiceSettings.EnableIncomingWebhooks = enableIncomingHooks + utils.Cfg.ServiceSettings.EnableOutgoingWebhooks = enableOutgoingHooks + }() + utils.Cfg.ServiceSettings.EnableIncomingWebhooks = true + utils.Cfg.ServiceSettings.EnableOutgoingWebhooks = true team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team) @@ -84,6 +104,10 @@ func TestListIncomingHooks(t *testing.T) { user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User) store.Must(Srv.Store.User().VerifyEmail(user.Id)) + c := &Context{} + c.RequestId = model.NewId() + c.IpAddress = "cmd_line" + UpdateRoles(c, user, model.ROLE_SYSTEM_ADMIN) Client.LoginByEmail(team.Name, user.Email, "pwd") channel1 := &model.Channel{DisplayName: "Test API Name", Name: "a" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id} @@ -114,6 +138,14 @@ func TestListIncomingHooks(t *testing.T) { func TestDeleteIncomingHook(t *testing.T) { Setup() + enableIncomingHooks := utils.Cfg.ServiceSettings.EnableIncomingWebhooks + enableOutgoingHooks := utils.Cfg.ServiceSettings.EnableOutgoingWebhooks + defer func() { + utils.Cfg.ServiceSettings.EnableIncomingWebhooks = enableIncomingHooks + utils.Cfg.ServiceSettings.EnableOutgoingWebhooks = enableOutgoingHooks + }() + utils.Cfg.ServiceSettings.EnableIncomingWebhooks = true + utils.Cfg.ServiceSettings.EnableOutgoingWebhooks = true team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team) @@ -122,6 +154,10 @@ func TestDeleteIncomingHook(t *testing.T) { user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User) store.Must(Srv.Store.User().VerifyEmail(user.Id)) + c := &Context{} + c.RequestId = model.NewId() + c.IpAddress = "cmd_line" + UpdateRoles(c, user, model.ROLE_SYSTEM_ADMIN) Client.LoginByEmail(team.Name, user.Email, "pwd") channel1 := &model.Channel{DisplayName: "Test API Name", Name: "a" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id} @@ -154,6 +190,14 @@ func TestDeleteIncomingHook(t *testing.T) { func TestCreateOutgoingHook(t *testing.T) { Setup() + enableIncomingHooks := utils.Cfg.ServiceSettings.EnableIncomingWebhooks + enableOutgoingHooks := utils.Cfg.ServiceSettings.EnableOutgoingWebhooks + defer func() { + utils.Cfg.ServiceSettings.EnableIncomingWebhooks = enableIncomingHooks + utils.Cfg.ServiceSettings.EnableOutgoingWebhooks = enableOutgoingHooks + }() + utils.Cfg.ServiceSettings.EnableIncomingWebhooks = true + utils.Cfg.ServiceSettings.EnableOutgoingWebhooks = true team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team) @@ -162,6 +206,10 @@ func TestCreateOutgoingHook(t *testing.T) { user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User) store.Must(Srv.Store.User().VerifyEmail(user.Id)) + c := &Context{} + c.RequestId = model.NewId() + c.IpAddress = "cmd_line" + UpdateRoles(c, user, model.ROLE_SYSTEM_ADMIN) Client.LoginByEmail(team.Name, user.Email, "pwd") channel1 := &model.Channel{DisplayName: "Test API Name", Name: "a" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id} @@ -217,6 +265,14 @@ func TestCreateOutgoingHook(t *testing.T) { func TestListOutgoingHooks(t *testing.T) { Setup() + enableIncomingHooks := utils.Cfg.ServiceSettings.EnableIncomingWebhooks + enableOutgoingHooks := utils.Cfg.ServiceSettings.EnableOutgoingWebhooks + defer func() { + utils.Cfg.ServiceSettings.EnableIncomingWebhooks = enableIncomingHooks + utils.Cfg.ServiceSettings.EnableOutgoingWebhooks = enableOutgoingHooks + }() + utils.Cfg.ServiceSettings.EnableIncomingWebhooks = true + utils.Cfg.ServiceSettings.EnableOutgoingWebhooks = true team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team) @@ -225,6 +281,10 @@ func TestListOutgoingHooks(t *testing.T) { user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User) store.Must(Srv.Store.User().VerifyEmail(user.Id)) + c := &Context{} + c.RequestId = model.NewId() + c.IpAddress = "cmd_line" + UpdateRoles(c, user, model.ROLE_SYSTEM_ADMIN) Client.LoginByEmail(team.Name, user.Email, "pwd") channel1 := &model.Channel{DisplayName: "Test API Name", Name: "a" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id} @@ -255,6 +315,14 @@ func TestListOutgoingHooks(t *testing.T) { func TestDeleteOutgoingHook(t *testing.T) { Setup() + enableIncomingHooks := utils.Cfg.ServiceSettings.EnableIncomingWebhooks + enableOutgoingHooks := utils.Cfg.ServiceSettings.EnableOutgoingWebhooks + defer func() { + utils.Cfg.ServiceSettings.EnableIncomingWebhooks = enableIncomingHooks + utils.Cfg.ServiceSettings.EnableOutgoingWebhooks = enableOutgoingHooks + }() + utils.Cfg.ServiceSettings.EnableIncomingWebhooks = true + utils.Cfg.ServiceSettings.EnableOutgoingWebhooks = true team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team) @@ -263,6 +331,10 @@ func TestDeleteOutgoingHook(t *testing.T) { user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User) store.Must(Srv.Store.User().VerifyEmail(user.Id)) + c := &Context{} + c.RequestId = model.NewId() + c.IpAddress = "cmd_line" + UpdateRoles(c, user, model.ROLE_SYSTEM_ADMIN) Client.LoginByEmail(team.Name, user.Email, "pwd") channel1 := &model.Channel{DisplayName: "Test API Name", Name: "a" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id} @@ -295,6 +367,14 @@ func TestDeleteOutgoingHook(t *testing.T) { func TestRegenOutgoingHookToken(t *testing.T) { Setup() + enableIncomingHooks := utils.Cfg.ServiceSettings.EnableIncomingWebhooks + enableOutgoingHooks := utils.Cfg.ServiceSettings.EnableOutgoingWebhooks + defer func() { + utils.Cfg.ServiceSettings.EnableIncomingWebhooks = enableIncomingHooks + utils.Cfg.ServiceSettings.EnableOutgoingWebhooks = enableOutgoingHooks + }() + utils.Cfg.ServiceSettings.EnableIncomingWebhooks = true + utils.Cfg.ServiceSettings.EnableOutgoingWebhooks = true team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team) @@ -303,6 +383,10 @@ func TestRegenOutgoingHookToken(t *testing.T) { user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User) store.Must(Srv.Store.User().VerifyEmail(user.Id)) + c := &Context{} + c.RequestId = model.NewId() + c.IpAddress = "cmd_line" + UpdateRoles(c, user, model.ROLE_SYSTEM_ADMIN) Client.LoginByEmail(team.Name, user.Email, "pwd") channel1 := &model.Channel{DisplayName: "Test API Name", Name: "a" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id} |