diff options
Diffstat (limited to 'api')
-rw-r--r-- | api/command.go | 178 | ||||
-rw-r--r-- | api/command_echo.go | 81 | ||||
-rw-r--r-- | api/command_echo_test.go | 42 | ||||
-rw-r--r-- | api/command_test.go | 130 | ||||
-rw-r--r-- | api/user.go | 4 |
5 files changed, 399 insertions, 36 deletions
diff --git a/api/command.go b/api/command.go index cff30cdbb..1e67453fb 100644 --- a/api/command.go +++ b/api/command.go @@ -4,45 +4,169 @@ package api import ( - // "io" - // "net/http" + //"io" + "net/http" // "path" // "strconv" - // "strings" + "strings" // "time" l4g "code.google.com/p/log4go" "github.com/gorilla/mux" - // "github.com/mattermost/platform/model" - // "github.com/mattermost/platform/utils" + "github.com/mattermost/platform/model" + "github.com/mattermost/platform/utils" ) -// type commandHandler func(c *Context, command *model.Command) bool +type CommandProvider interface { + GetCommand() *model.Command + DoCommand(c *Context, channelId string, message string) *model.CommandResponse +} -// 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 +var commandProviders = make(map[string]CommandProvider) + +func RegisterCommandProvider(newProvider CommandProvider) { + commandProviders[newProvider.GetCommand().Trigger] = newProvider +} + +func GetCommandProvidersProvider(name string) CommandProvider { + provider, ok := commandProviders[name] + if ok { + return provider + } + + return nil +} func InitCommand(r *mux.Router) { l4g.Debug("Initializing command api routes") - // r.Handle("/command", ApiUserRequired(command)).Methods("POST") + + sr := r.PathPrefix("/commands").Subrouter() + + sr.Handle("/execute", ApiUserRequired(execute)).Methods("POST") + sr.Handle("/list", ApiUserRequired(listCommands)).Methods("POST") + + sr.Handle("/create", ApiUserRequired(create)).Methods("POST") + sr.Handle("/list_team_commands", ApiUserRequired(listTeamCommands)).Methods("GET") + // sr.Handle("/regen_token", ApiUserRequired(regenOutgoingHookToken)).Methods("POST") + // sr.Handle("/delete", ApiUserRequired(deleteOutgoingHook)).Methods("POST") +} + +func listCommands(c *Context, w http.ResponseWriter, r *http.Request) { + commands := make([]*model.Command, 0, 32) + for _, value := range commandProviders { + cpy := *value.GetCommand() + cpy.Token = "" + cpy.CreatorId = "" + cpy.Method = "" + cpy.URL = "" + cpy.Username = "" + cpy.IconURL = "" + commands = append(commands, &cpy) + } + + w.Write([]byte(model.CommandListToJson(commands))) +} + +func execute(c *Context, w http.ResponseWriter, r *http.Request) { + props := model.MapFromJson(r.Body) + command := strings.TrimSpace(props["command"]) + channelId := strings.TrimSpace(props["channelId"]) + + if len(command) <= 1 || strings.Index(command, "/") != 0 { + c.Err = model.NewAppError("command", "Command must start with /", "") + return + } + + if len(channelId) > 0 { + cchan := Srv.Store.Channel().CheckPermissionsTo(c.Session.TeamId, channelId, c.Session.UserId) + + if !c.HasPermissionsToChannel(cchan, "checkCommand") { + return + } + } + + parts := strings.Split(command, " ") + trigger := parts[0][1:] + provider := GetCommandProvidersProvider(trigger) + + if provider != nil { + message := strings.Join(parts[1:], " ") + response := provider.DoCommand(c, channelId, message) + + 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", "") + } + } + + w.Write([]byte(response.ToJson())) + } else { + c.Err = model.NewAppError("command", "Command with a trigger of '"+trigger+"' not found", "") + } +} + +func create(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 *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") + + cmd := model.CommandFromJson(r.Body) + + if cmd == nil { + c.SetInvalidParam("createCommand", "command") + return + } + + cmd.CreatorId = c.Session.UserId + cmd.TeamId = c.Session.TeamId + + 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())) + } +} + +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 *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.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))) + } } // func command(c *Context, w http.ResponseWriter, r *http.Request) { 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..40a0bb4b9 --- /dev/null +++ b/api/command_echo_test.go @@ -0,0 +1,42 @@ +// Copyright (c) 2015 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_test.go b/api/command_test.go index 8b996b9eb..8e0c2580e 100644 --- a/api/command_test.go +++ b/api/command_test.go @@ -3,15 +3,127 @@ package api -// import ( -// "strings" -// "testing" -// "time" - -// "github.com/mattermost/platform/model" -// "github.com/mattermost/platform/store" -// "github.com/mattermost/platform/utils" -// ) +import ( + "testing" + + "github.com/mattermost/platform/model" + "github.com/mattermost/platform/store" + "github.com/mattermost/platform/utils" +) + +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} + 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") + + if results, err := Client.ListCommands(); err != nil { + t.Fatal(err) + } else { + commands := results.Data.([]*model.Command) + foundEcho := false + + for _, command := range commands { + if command.Trigger == "echo" { + foundEcho = true + } + } + + if !foundEcho { + t.Fatal("Couldn't find echo command") + } + } +} + +func TestCreateCommand(t *testing.T) { + Setup() + *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) + + 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)) + + Client.LoginByEmail(team.Name, user.Email, "pwd") + + cmd := &model.Command{URL: "http://nowhere.com", Method: model.COMMAND_METHOD_POST} + + if _, err := Client.CreateCommand(cmd); err == nil { + t.Fatal("should have failed because not admin") + } + + c := &Context{} + c.RequestId = model.NewId() + c.IpAddress = "cmd_line" + UpdateRoles(c, user, model.ROLE_SYSTEM_ADMIN) + Client.LoginByEmail(team.Name, user.Email, "pwd") + + var rcmd *model.Command + if result, err := Client.CreateCommand(cmd); err != nil { + t.Fatal(err) + } else { + rcmd = result.Data.(*model.Command) + } + + if rcmd.CreatorId != user.Id { + t.Fatal("user ids didn't match") + } + + if rcmd.TeamId != team.Id { + t.Fatal("team ids didn't match") + } + + 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") + } + } + + *utils.Cfg.ServiceSettings.EnableCommands = false +} + +func TestListTeamCommands(t *testing.T) { + Setup() + *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) + + 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)) + + 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) + + if len(hooks) != 1 { + t.Fatal("incorrect number of cmd") + } + } + + *utils.Cfg.ServiceSettings.EnableCommands = false +} // func TestSuggestRootCommands(t *testing.T) { // Setup() diff --git a/api/user.go b/api/user.go index d4c7fcaf5..494296fb5 100644 --- a/api/user.go +++ b/api/user.go @@ -1435,6 +1435,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 } |