From e1f4cc4bb004a0a0d4bb6d68ff328233f9f72aa0 Mon Sep 17 00:00:00 2001 From: =Corey Hulen Date: Fri, 8 Jan 2016 22:57:38 -0600 Subject: Adding web service methods --- api/command.go | 144 +++++++++++++++++++++++++++++++++++++++++++++++----- api/command_test.go | 75 ++++++++++++++++++++++++++- api/webhook.go | 58 ++++++++++++++++++++- model/client.go | 29 ++++++++++- model/command.go | 9 ++++ model/utils.go | 6 ++- 6 files changed, 303 insertions(+), 18 deletions(-) diff --git a/api/command.go b/api/command.go index 1e67453fb..d74643e15 100644 --- a/api/command.go +++ b/api/command.go @@ -37,37 +37,59 @@ func GetCommandProvidersProvider(name string) CommandProvider { return nil } +// cmds = map[string]string{ +// "logoutCommand": "/logout", +// "joinCommand": "/join", +// "loadTestCommand": "/loadtest", +// "echoCommand": "/echo", +// "shrugCommand": "/shrug", +// "meCommand": "/me", +// } + func InitCommand(r *mux.Router) { l4g.Debug("Initializing command api routes") sr := r.PathPrefix("/commands").Subrouter() - sr.Handle("/execute", ApiUserRequired(execute)).Methods("POST") - sr.Handle("/list", ApiUserRequired(listCommands)).Methods("POST") + sr.Handle("/execute", ApiUserRequired(executeCommand)).Methods("POST") + sr.Handle("/list", ApiUserRequired(listCommands)).Methods("GET") - sr.Handle("/create", ApiUserRequired(create)).Methods("POST") + sr.Handle("/create", ApiUserRequired(createCommand)).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") + sr.Handle("/regen_token", ApiUserRequired(regenCommandToken)).Methods("POST") + sr.Handle("/delete", ApiUserRequired(deleteCommand)).Methods("POST") } 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() - cpy.Token = "" - cpy.CreatorId = "" - cpy.Method = "" - cpy.URL = "" - cpy.Username = "" - cpy.IconURL = "" - commands = append(commands, &cpy) + if cpy.AutoComplete && !seen[cpy.Id] { + cpy.Sanatize() + seen[cpy.Trigger] = true + commands = append(commands, &cpy) + } + } + + 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) + } + } } w.Write([]byte(model.CommandListToJson(commands))) } -func execute(c *Context, w http.ResponseWriter, r *http.Request) { +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"]) @@ -108,7 +130,7 @@ func execute(c *Context, w http.ResponseWriter, r *http.Request) { } } -func create(c *Context, w http.ResponseWriter, r *http.Request) { +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 @@ -169,6 +191,100 @@ func listTeamCommands(c *Context, w http.ResponseWriter, r *http.Request) { } } +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 + } + + 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) + + id := props["id"] + if len(id) == 0 { + c.SetInvalidParam("regenCommandToken", "id") + return + } + + 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 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 + } + } + + cmd.Token = model.NewId() + + if result := <-Srv.Store.Command().Update(cmd); result.Err != nil { + c.Err = result.Err + return + } else { + w.Write([]byte(result.Data.(*model.Command).ToJson())) + } +} + +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 + } + + 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) + + id := props["id"] + if len(id) == 0 { + c.SetInvalidParam("deleteCommand", "id") + return + } + + 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 + } + } + + if err := (<-Srv.Store.Command().Delete(id, model.GetMillis())).Err; err != nil { + c.Err = err + return + } + + c.LogAudit("success") + w.Write([]byte(model.MapToJson(props))) +} + // func command(c *Context, w http.ResponseWriter, r *http.Request) { // props := model.MapFromJson(r.Body) diff --git a/api/command_test.go b/api/command_test.go index 8e0c2580e..b5c0558b8 100644 --- a/api/command_test.go +++ b/api/command_test.go @@ -107,6 +107,10 @@ func TestListTeamCommands(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") cmd1 := &model.Command{URL: "http://nowhere.com", Method: model.COMMAND_METHOD_POST} @@ -117,7 +121,7 @@ func TestListTeamCommands(t *testing.T) { } else { cmds := result.Data.([]*model.Command) - if len(hooks) != 1 { + if len(cmds) != 1 { t.Fatal("incorrect number of cmd") } } @@ -125,6 +129,75 @@ func TestListTeamCommands(t *testing.T) { *utils.Cfg.ServiceSettings.EnableCommands = false } +func TestRegenToken(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)) + + c := &Context{} + c.RequestId = model.NewId() + c.IpAddress = "cmd_line" + UpdateRoles(c, user, model.ROLE_SYSTEM_ADMIN) + Client.LoginByEmail(team.Name, user.Email, "pwd") + + cmd := &model.Command{URL: "http://nowhere.com", Method: model.COMMAND_METHOD_POST} + cmd = Client.Must(Client.CreateCommand(cmd)).Data.(*model.Command) + + data := make(map[string]string) + data["id"] = cmd.Id + + 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") + } + } + + *utils.Cfg.ServiceSettings.EnableCommands = false +} + +func TestDeleteCommand(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)) + + c := &Context{} + c.RequestId = model.NewId() + c.IpAddress = "cmd_line" + UpdateRoles(c, user, model.ROLE_SYSTEM_ADMIN) + Client.LoginByEmail(team.Name, user.Email, "pwd") + + cmd := &model.Command{URL: "http://nowhere.com", Method: model.COMMAND_METHOD_POST} + cmd = Client.Must(Client.CreateCommand(cmd)).Data.(*model.Command) + + data := make(map[string]string) + data["id"] = cmd.Id + + if _, err := Client.DeleteCommand(data); err != nil { + t.Fatal(err) + } + + cmds := Client.Must(Client.ListTeamCommands()).Data.([]*model.Command) + if len(cmds) != 0 { + t.Fatal("delete didn't work properly") + } + + *utils.Cfg.ServiceSettings.EnableCommands = false +} + // func TestSuggestRootCommands(t *testing.T) { // Setup() diff --git a/api/webhook.go b/api/webhook.go index 34c308879..de3d567ec 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,6 +132,14 @@ func getIncomingHooks(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 + } + } + if result := <-Srv.Store.Webhook().GetIncomingByUser(c.Session.UserId); result.Err != nil { c.Err = result.Err return @@ -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,6 +220,14 @@ func getOutgoingHooks(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 + } + } + if result := <-Srv.Store.Webhook().GetOutgoingByCreator(c.Session.UserId); result.Err != nil { c.Err = result.Err return @@ -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/model/client.go b/model/client.go index 83d1d316c..3a645e175 100644 --- a/model/client.go +++ b/model/client.go @@ -381,7 +381,16 @@ func (c *Client) Command(channelId string, command string, suggest bool) (*Resul } func (c *Client) ListCommands() (*Result, *AppError) { - if r, err := c.DoApiPost("/commands/list", ""); err != nil { + if r, err := c.DoApiGet("/commands/list", "", ""); err != nil { + return nil, err + } else { + return &Result{r.Header.Get(HEADER_REQUEST_ID), + r.Header.Get(HEADER_ETAG_SERVER), CommandListFromJson(r.Body)}, nil + } +} + +func (c *Client) ListTeamCommands() (*Result, *AppError) { + if r, err := c.DoApiGet("/commands/list_team_commands", "", ""); err != nil { return nil, err } else { return &Result{r.Header.Get(HEADER_REQUEST_ID), @@ -398,6 +407,24 @@ func (c *Client) CreateCommand(cmd *Command) (*Result, *AppError) { } } +func (c *Client) RegenCommandToken(data map[string]string) (*Result, *AppError) { + if r, err := c.DoApiPost("/commands/regen_token", MapToJson(data)); err != nil { + return nil, err + } else { + return &Result{r.Header.Get(HEADER_REQUEST_ID), + r.Header.Get(HEADER_ETAG_SERVER), CommandFromJson(r.Body)}, nil + } +} + +func (c *Client) DeleteCommand(data map[string]string) (*Result, *AppError) { + if r, err := c.DoApiPost("/commands/delete", MapToJson(data)); err != nil { + return nil, err + } else { + return &Result{r.Header.Get(HEADER_REQUEST_ID), + r.Header.Get(HEADER_ETAG_SERVER), MapFromJson(r.Body)}, nil + } +} + func (c *Client) GetAudits(id string, etag string) (*Result, *AppError) { if r, err := c.DoApiGet("/users/"+id+"/audits", "", etag); err != nil { return nil, err diff --git a/model/command.go b/model/command.go index 253021896..c917a46ea 100644 --- a/model/command.go +++ b/model/command.go @@ -133,3 +133,12 @@ func (o *Command) PreSave() { func (o *Command) PreUpdate() { o.UpdateAt = GetMillis() } + +func (o *Command) Sanatize() { + o.Token = "" + o.CreatorId = "" + o.Method = "" + o.URL = "" + o.Username = "" + o.IconURL = "" +} diff --git a/model/utils.go b/model/utils.go index 617c95efd..301e36f59 100644 --- a/model/utils.go +++ b/model/utils.go @@ -54,7 +54,11 @@ func AppErrorFromJson(data io.Reader) *AppError { if err == nil { return &er } else { - return NewAppError("AppErrorFromJson", "could not decode", err.Error()) + buf := new(bytes.Buffer) + buf.ReadFrom(data) + s := buf.String() + + return NewAppError("AppErrorFromJson", "could not decode", err.Error()+" "+s) } } -- cgit v1.2.3-1-g7c22