From c155359e58dd2f8f477d5314feb175f6f3093f8d Mon Sep 17 00:00:00 2001 From: Kerry Dougherty Date: Fri, 2 Nov 2018 13:39:38 -0400 Subject: [PATCH] MM-12369 Add Create Outgoing Webhook Command (#9779) * add create outgoing webhook command * add create outgoing webhook command --- cmd/mattermost/commands/webhook.go | 97 +++++++++++++++++++++++++ cmd/mattermost/commands/webhook_test.go | 65 ++++++++++++++++- 2 files changed, 159 insertions(+), 3 deletions(-) diff --git a/cmd/mattermost/commands/webhook.go b/cmd/mattermost/commands/webhook.go index d91d076a3d..3869cf8637 100644 --- a/cmd/mattermost/commands/webhook.go +++ b/cmd/mattermost/commands/webhook.go @@ -5,6 +5,7 @@ package commands import ( "fmt" + "strings" "github.com/mattermost/mattermost-server/model" "github.com/pkg/errors" @@ -40,6 +41,15 @@ var WebhookModifyIncomingCmd = &cobra.Command{ RunE: modifyIncomingWebhookCmdF, } +var WebhookCreateOutgoingCmd = &cobra.Command{ + Use: "create-outgoing", + Short: "Create outgoing webhook", + Long: "create outgoing webhook which allows external posting of messages from a specific channel", + Example: ` webhook create-outgoing --team myteam --user myusername --display-name mywebhook --trigger-words "build\ntest" --urls http://localhost:8000/my-webhook-handler + webhook create-outgoing --team myteam --channel mychannel --user myusername --display-name mywebhook --description "My cool webhook" --trigger-when 1 --trigger-words "build\ntest" --icon http://localhost:8000/my-slash-handler-bot-icon.png --urls http://localhost:8000/my-webhook-handler --content-type "application/json"`, + RunE: createOutgoingWebhookCmdF, +} + func listWebhookCmdF(command *cobra.Command, args []string) error { app, err := InitDBCommandContextCobra(command) if err != nil { @@ -177,6 +187,81 @@ func modifyIncomingWebhookCmdF(command *cobra.Command, args []string) error { return nil } +func createOutgoingWebhookCmdF(command *cobra.Command, args []string) error { + app, err := InitDBCommandContextCobra(command) + if err != nil { + return err + } + defer app.Shutdown() + + teamArg, errTeam := command.Flags().GetString("team") + if errTeam != nil || teamArg == "" { + return errors.New("Team is required") + } + team := getTeamFromTeamArg(app, teamArg) + if team == nil { + return errors.New("Unable to find team: " + teamArg) + } + + userArg, errUser := command.Flags().GetString("user") + if errUser != nil || userArg == "" { + return errors.New("User is required") + } + user := getUserFromUserArg(app, userArg) + if user == nil { + return errors.New("Unable to find user: " + userArg) + } + + displayName, errName := command.Flags().GetString("display-name") + if errName != nil || displayName == "" { + return errors.New("Display name is required") + } + + triggerWordsString, errWords := command.Flags().GetString("trigger-words") + if errWords != nil || triggerWordsString == "" { + return errors.New("Trigger word or words required") + } + triggerWords := strings.Split(triggerWordsString, "\n") + + callbackURLsString, errURL := command.Flags().GetString("urls") + if errURL != nil || callbackURLsString == "" { + return errors.New("Callback URL or URLs required") + } + callbackURLs := strings.Split(callbackURLsString, "\n") + + triggerWhen, _ := command.Flags().GetInt("trigger-when") + description, _ := command.Flags().GetString("description") + contentType, _ := command.Flags().GetString("content-type") + iconURL, _ := command.Flags().GetString("icon") + + outgoingWebhook := &model.OutgoingWebhook{ + CreatorId: user.Id, + Username: user.Username, + TeamId: team.Id, + TriggerWords: triggerWords, + TriggerWhen: triggerWhen, + CallbackURLs: callbackURLs, + DisplayName: displayName, + Description: description, + ContentType: contentType, + IconURL: iconURL, + } + + channelArg, _ := command.Flags().GetString("channel") + if channelArg != "" { + channel := getChannelFromChannelArg(app, channelArg) + if channel != nil { + outgoingWebhook.ChannelId = channel.Id + } + } + + if _, err := app.CreateOutgoingWebhook(outgoingWebhook); err != nil { + return err + } + + return nil +} + func init() { WebhookCreateIncomingCmd.Flags().String("channel", "", "Channel ID") WebhookCreateIncomingCmd.Flags().String("user", "", "User ID") @@ -191,10 +276,22 @@ func init() { WebhookModifyIncomingCmd.Flags().String("icon", "", "Icon URL") WebhookModifyIncomingCmd.Flags().Bool("lock-to-channel", false, "Lock to channel") + WebhookCreateOutgoingCmd.Flags().String("team", "", "Team name or ID (required)") + WebhookCreateOutgoingCmd.Flags().String("channel", "", "Channel name or ID") + WebhookCreateOutgoingCmd.Flags().String("user", "", "User username, email, or ID (required)") + WebhookCreateOutgoingCmd.Flags().String("display-name", "", "Outgoing webhook display name (required)") + WebhookCreateOutgoingCmd.Flags().String("description", "", "Outgoing webhook description") + WebhookCreateOutgoingCmd.Flags().String("trigger-words", "", "Words to trigger webhook (word1\nword2) (required)") + WebhookCreateOutgoingCmd.Flags().Int("trigger-when", 0, "When to trigger webhook (either when trigger word is first (enter 1) or when it's anywhere (enter 0))") + WebhookCreateOutgoingCmd.Flags().String("icon", "", "Icon URL") + WebhookCreateOutgoingCmd.Flags().String("urls", "", "Callback URLs (url1\nurl2) (required)") + WebhookCreateOutgoingCmd.Flags().String("content-type", "", "Content-type") + WebhookCmd.AddCommand( WebhookListCmd, WebhookCreateIncomingCmd, WebhookModifyIncomingCmd, + WebhookCreateOutgoingCmd, ) RootCmd.AddCommand(WebhookCmd) diff --git a/cmd/mattermost/commands/webhook_test.go b/cmd/mattermost/commands/webhook_test.go index 9791096769..6da960835e 100644 --- a/cmd/mattermost/commands/webhook_test.go +++ b/cmd/mattermost/commands/webhook_test.go @@ -116,9 +116,9 @@ func TestModifyIncomingWebhook(t *testing.T) { displayName := "myhookincname" incomingWebhook := &model.IncomingWebhook{ - ChannelId: th.BasicChannel.Id, - DisplayName: displayName, - Description: description, + ChannelId: th.BasicChannel.Id, + DisplayName: displayName, + Description: description, } oldHook, err := th.App.CreateIncomingWebhookForChannel(th.BasicUser.Id, th.BasicChannel, incomingWebhook) @@ -150,3 +150,62 @@ func TestModifyIncomingWebhook(t *testing.T) { t.Fatal("Failed to update incoming webhook") } } + +func TestCreateOutgoingWebhook(t *testing.T) { + th := api4.Setup().InitBasic().InitSystemAdmin() + defer th.TearDown() + + th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableIncomingWebhooks = true }) + th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableOutgoingWebhooks = true }) + th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnablePostUsernameOverride = true }) + th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnablePostIconOverride = true }) + + defaultRolePermissions := th.SaveDefaultRolePermissions() + defer func() { + th.RestoreDefaultRolePermissions(defaultRolePermissions) + }() + th.AddPermissionToRole(model.PERMISSION_MANAGE_WEBHOOKS.Id, model.TEAM_ADMIN_ROLE_ID) + th.RemovePermissionFromRole(model.PERMISSION_MANAGE_WEBHOOKS.Id, model.TEAM_USER_ROLE_ID) + + // team, user, display name, trigger words, callback urls are required + team := th.BasicTeam.Id + user := th.BasicUser.Id + displayName := "totally radical webhook" + triggerWords := "build\ndefenestrate" + callbackURLs := "http://localhost:8000/my-webhook-handler\nhttp://localhost:8000/my-webhook-handler2" + + // should fail because team is not specified + require.Error(t, RunCommand(t, "webhook", "create-outgoing", "--display-name", displayName, "--trigger-words", triggerWords, "--urls", callbackURLs, "--user", user)) + + // should fail because user is not specified + require.Error(t, RunCommand(t, "webhook", "create-outgoing", "--team", team, "--display-name", displayName, "--trigger-words", triggerWords, "--urls", callbackURLs)) + + // should fail because display name is not specified + require.Error(t, RunCommand(t, "webhook", "create-outgoing", "--team", team, "--trigger-words", triggerWords, "--urls", callbackURLs, "--user", user)) + + // should fail because trigger words are not specified + require.Error(t, RunCommand(t, "webhook", "create-outgoing", "--team", team, "--display-name", displayName, "--urls", callbackURLs, "--user", user)) + + // should fail because callback URLs are not specified + require.Error(t, RunCommand(t, "webhook", "create-outgoing", "--team", team, "--display-name", displayName, "--trigger-words", triggerWords, "--user", user)) + + // should fail because outgoing webhooks cannot be made for private channels + require.Error(t, RunCommand(t, "webhook", "create-outgoing", "--team", team, "--channel", th.BasicPrivateChannel.Id, "--display-name", displayName, "--trigger-words", triggerWords, "--urls", callbackURLs, "--user", user)) + + CheckCommand(t, "webhook", "create-outgoing", "--team", team, "--channel", th.BasicChannel.Id, "--display-name", displayName, "--trigger-words", triggerWords, "--urls", callbackURLs, "--user", user) + + webhooks, err := th.App.GetOutgoingWebhooksPage(0, 1000) + if err != nil { + t.Fatal("Unable to retreive outgoing webhooks") + } + + found := false + for _, webhook := range webhooks { + if webhook.DisplayName == displayName && webhook.CreatorId == th.BasicUser.Id { + found = true + } + } + if !found { + t.Fatal("Failed to create incoming webhook") + } +}