From c81b26b0e52f55fcdf94b50a14bca3dfc375e2a9 Mon Sep 17 00:00:00 2001 From: oliverpool <3864879+oliverpool@users.noreply.github.com> Date: Thu, 11 Aug 2022 17:48:23 +0200 Subject: [PATCH] refactor webhook *NewPost (#20729) * refactor webhook *NewPost * remove empty values * always show errs.Message * remove utils.IsValidSlackChannel * move IsValidSlackChannel to services/webhook package * binding: handle empty Message case * make IsValidSlackChannel more strict --- modules/web/middleware/binding.go | 11 +- routers/api/v1/utils/hook.go | 6 +- routers/utils/utils.go | 19 - routers/utils/utils_test.go | 17 - routers/web/repo/webhook.go | 577 +++++++----------------------- services/forms/repo_form.go | 14 +- services/webhook/slack.go | 11 + services/webhook/slack_test.go | 19 + 8 files changed, 179 insertions(+), 495 deletions(-) diff --git a/modules/web/middleware/binding.go b/modules/web/middleware/binding.go index 88a3920f6e..636e655b9e 100644 --- a/modules/web/middleware/binding.go +++ b/modules/web/middleware/binding.go @@ -136,7 +136,16 @@ func Validate(errs binding.Errors, data map[string]interface{}, f Form, l transl case validation.ErrRegexPattern: data["ErrorMsg"] = trName + l.Tr("form.regex_pattern_error", errs[0].Message) default: - data["ErrorMsg"] = l.Tr("form.unknown_error") + " " + errs[0].Classification + msg := errs[0].Classification + if msg != "" && errs[0].Message != "" { + msg += ": " + } + + msg += errs[0].Message + if msg == "" { + msg = l.Tr("form.unknown_error") + } + data["ErrorMsg"] = trName + ": " + msg } return errs } diff --git a/routers/api/v1/utils/hook.go b/routers/api/v1/utils/hook.go index f0dc595ad5..ba008f587c 100644 --- a/routers/api/v1/utils/hook.go +++ b/routers/api/v1/utils/hook.go @@ -15,7 +15,6 @@ import ( "code.gitea.io/gitea/modules/json" api "code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/util" - "code.gitea.io/gitea/routers/utils" webhook_service "code.gitea.io/gitea/services/webhook" ) @@ -141,14 +140,15 @@ func addHook(ctx *context.APIContext, form *api.CreateHookOption, orgID, repoID ctx.Error(http.StatusUnprocessableEntity, "", "Missing config option: channel") return nil, false } + channel = strings.TrimSpace(channel) - if !utils.IsValidSlackChannel(channel) { + if !webhook_service.IsValidSlackChannel(channel) { ctx.Error(http.StatusBadRequest, "", "Invalid slack channel name") return nil, false } meta, err := json.Marshal(&webhook_service.SlackMeta{ - Channel: strings.TrimSpace(channel), + Channel: channel, Username: form.Config["username"], IconURL: form.Config["icon_url"], Color: form.Config["color"], diff --git a/routers/utils/utils.go b/routers/utils/utils.go index f15bc1e62e..66eaa1d9ce 100644 --- a/routers/utils/utils.go +++ b/routers/utils/utils.go @@ -20,25 +20,6 @@ func RemoveUsernameParameterSuffix(name string) string { return name } -// IsValidSlackChannel validates a channel name conforms to what slack expects. -// It makes sure a channel name cannot be empty and invalid ( only an # ) -func IsValidSlackChannel(channelName string) bool { - switch len(strings.TrimSpace(channelName)) { - case 0: - return false - case 1: - // Keep default behaviour where a channel name is still - // valid without an # - // But if it contains only an #, it should be regarded as - // invalid - if channelName[0] == '#' { - return false - } - } - - return true -} - // SanitizeFlashErrorString will sanitize a flash error string func SanitizeFlashErrorString(x string) string { return strings.ReplaceAll(html.EscapeString(x), "\n", "
") diff --git a/routers/utils/utils_test.go b/routers/utils/utils_test.go index f49ed77b6f..42cf948e30 100644 --- a/routers/utils/utils_test.go +++ b/routers/utils/utils_test.go @@ -18,23 +18,6 @@ func TestRemoveUsernameParameterSuffix(t *testing.T) { assert.Equal(t, "", RemoveUsernameParameterSuffix("")) } -func TestIsValidSlackChannel(t *testing.T) { - tt := []struct { - channelName string - expected bool - }{ - {"gitea", true}, - {" ", false}, - {"#", false}, - {"gitea ", true}, - {" gitea", true}, - } - - for _, v := range tt { - assert.Equal(t, v.expected, IsValidSlackChannel(v.channelName)) - } -} - func TestIsExternalURL(t *testing.T) { setting.AppURL = "https://try.gitea.io/" type test struct { diff --git a/routers/web/repo/webhook.go b/routers/web/repo/webhook.go index a9b14ee21f..d4419a1e10 100644 --- a/routers/web/repo/webhook.go +++ b/routers/web/repo/webhook.go @@ -185,14 +185,22 @@ func ParseHookEvent(form forms.WebhookForm) *webhook.HookEvent { } } -// GiteaHooksNewPost response for creating Gitea webhook -func GiteaHooksNewPost(ctx *context.Context) { - form := web.GetForm(ctx).(*forms.NewWebhookForm) +type webhookCreationParams struct { + URL string + ContentType webhook.HookContentType + Secret string + HTTPMethod string + WebhookForm forms.WebhookForm + Type string + Meta interface{} +} + +func createWebhook(ctx *context.Context, params webhookCreationParams) { ctx.Data["Title"] = ctx.Tr("repo.settings.add_webhook") ctx.Data["PageIsSettingsHooks"] = true ctx.Data["PageIsSettingsHooksNew"] = true ctx.Data["Webhook"] = webhook.Webhook{HookEvent: &webhook.HookEvent{}} - ctx.Data["HookType"] = webhook.GITEA + ctx.Data["HookType"] = params.Type orCtx, err := getOrgRepoCtx(ctx) if err != nil { @@ -206,20 +214,25 @@ func GiteaHooksNewPost(ctx *context.Context) { return } - contentType := webhook.ContentTypeJSON - if webhook.HookContentType(form.ContentType) == webhook.ContentTypeForm { - contentType = webhook.ContentTypeForm + var meta []byte + if params.Meta != nil { + meta, err = json.Marshal(params.Meta) + if err != nil { + ctx.ServerError("Marshal", err) + return + } } w := &webhook.Webhook{ RepoID: orCtx.RepoID, - URL: form.PayloadURL, - HTTPMethod: form.HTTPMethod, - ContentType: contentType, - Secret: form.Secret, - HookEvent: ParseHookEvent(form.WebhookForm), - IsActive: form.Active, - Type: webhook.GITEA, + URL: params.URL, + HTTPMethod: params.HTTPMethod, + ContentType: params.ContentType, + Secret: params.Secret, + HookEvent: ParseHookEvent(params.WebhookForm), + IsActive: params.WebhookForm.Active, + Type: params.Type, + Meta: string(meta), OrgID: orCtx.OrgID, IsSystemWebhook: orCtx.IsSystemWebhook, } @@ -235,503 +248,175 @@ func GiteaHooksNewPost(ctx *context.Context) { ctx.Redirect(orCtx.Link) } +// GiteaHooksNewPost response for creating Gitea webhook +func GiteaHooksNewPost(ctx *context.Context) { + form := web.GetForm(ctx).(*forms.NewWebhookForm) + + contentType := webhook.ContentTypeJSON + if webhook.HookContentType(form.ContentType) == webhook.ContentTypeForm { + contentType = webhook.ContentTypeForm + } + + createWebhook(ctx, webhookCreationParams{ + URL: form.PayloadURL, + ContentType: contentType, + Secret: form.Secret, + HTTPMethod: form.HTTPMethod, + WebhookForm: form.WebhookForm, + Type: webhook.GITEA, + }) +} + // GogsHooksNewPost response for creating webhook func GogsHooksNewPost(ctx *context.Context) { form := web.GetForm(ctx).(*forms.NewGogshookForm) - newGogsWebhookPost(ctx, *form, webhook.GOGS) -} - -// newGogsWebhookPost response for creating gogs hook -func newGogsWebhookPost(ctx *context.Context, form forms.NewGogshookForm, kind webhook.HookType) { - ctx.Data["Title"] = ctx.Tr("repo.settings.add_webhook") - ctx.Data["PageIsSettingsHooks"] = true - ctx.Data["PageIsSettingsHooksNew"] = true - ctx.Data["Webhook"] = webhook.Webhook{HookEvent: &webhook.HookEvent{}} - ctx.Data["HookType"] = webhook.GOGS - - orCtx, err := getOrgRepoCtx(ctx) - if err != nil { - ctx.ServerError("getOrgRepoCtx", err) - return - } - ctx.Data["BaseLink"] = orCtx.LinkNew - - if ctx.HasError() { - ctx.HTML(http.StatusOK, orCtx.NewTemplate) - return - } contentType := webhook.ContentTypeJSON if webhook.HookContentType(form.ContentType) == webhook.ContentTypeForm { contentType = webhook.ContentTypeForm } - w := &webhook.Webhook{ - RepoID: orCtx.RepoID, - URL: form.PayloadURL, - ContentType: contentType, - Secret: form.Secret, - HookEvent: ParseHookEvent(form.WebhookForm), - IsActive: form.Active, - Type: kind, - OrgID: orCtx.OrgID, - IsSystemWebhook: orCtx.IsSystemWebhook, - } - if err := w.UpdateEvent(); err != nil { - ctx.ServerError("UpdateEvent", err) - return - } else if err := webhook.CreateWebhook(ctx, w); err != nil { - ctx.ServerError("CreateWebhook", err) - return - } - - ctx.Flash.Success(ctx.Tr("repo.settings.add_hook_success")) - ctx.Redirect(orCtx.Link) + createWebhook(ctx, webhookCreationParams{ + URL: form.PayloadURL, + ContentType: contentType, + Secret: form.Secret, + WebhookForm: form.WebhookForm, + Type: webhook.GOGS, + }) } // DiscordHooksNewPost response for creating discord hook func DiscordHooksNewPost(ctx *context.Context) { form := web.GetForm(ctx).(*forms.NewDiscordHookForm) - ctx.Data["Title"] = ctx.Tr("repo.settings") - ctx.Data["PageIsSettingsHooks"] = true - ctx.Data["PageIsSettingsHooksNew"] = true - ctx.Data["Webhook"] = webhook.Webhook{HookEvent: &webhook.HookEvent{}} - ctx.Data["HookType"] = webhook.DISCORD - orCtx, err := getOrgRepoCtx(ctx) - if err != nil { - ctx.ServerError("getOrgRepoCtx", err) - return - } - - if ctx.HasError() { - ctx.HTML(http.StatusOK, orCtx.NewTemplate) - return - } - - meta, err := json.Marshal(&webhook_service.DiscordMeta{ - Username: form.Username, - IconURL: form.IconURL, + createWebhook(ctx, webhookCreationParams{ + URL: form.PayloadURL, + ContentType: webhook.ContentTypeJSON, + WebhookForm: form.WebhookForm, + Type: webhook.DISCORD, + Meta: &webhook_service.DiscordMeta{ + Username: form.Username, + IconURL: form.IconURL, + }, }) - if err != nil { - ctx.ServerError("Marshal", err) - return - } - - w := &webhook.Webhook{ - RepoID: orCtx.RepoID, - URL: form.PayloadURL, - ContentType: webhook.ContentTypeJSON, - HookEvent: ParseHookEvent(form.WebhookForm), - IsActive: form.Active, - Type: webhook.DISCORD, - Meta: string(meta), - OrgID: orCtx.OrgID, - IsSystemWebhook: orCtx.IsSystemWebhook, - } - if err := w.UpdateEvent(); err != nil { - ctx.ServerError("UpdateEvent", err) - return - } else if err := webhook.CreateWebhook(ctx, w); err != nil { - ctx.ServerError("CreateWebhook", err) - return - } - - ctx.Flash.Success(ctx.Tr("repo.settings.add_hook_success")) - ctx.Redirect(orCtx.Link) } // DingtalkHooksNewPost response for creating dingtalk hook func DingtalkHooksNewPost(ctx *context.Context) { form := web.GetForm(ctx).(*forms.NewDingtalkHookForm) - ctx.Data["Title"] = ctx.Tr("repo.settings") - ctx.Data["PageIsSettingsHooks"] = true - ctx.Data["PageIsSettingsHooksNew"] = true - ctx.Data["Webhook"] = webhook.Webhook{HookEvent: &webhook.HookEvent{}} - ctx.Data["HookType"] = webhook.DINGTALK - orCtx, err := getOrgRepoCtx(ctx) - if err != nil { - ctx.ServerError("getOrgRepoCtx", err) - return - } - - if ctx.HasError() { - ctx.HTML(http.StatusOK, orCtx.NewTemplate) - return - } - - w := &webhook.Webhook{ - RepoID: orCtx.RepoID, - URL: form.PayloadURL, - ContentType: webhook.ContentTypeJSON, - HookEvent: ParseHookEvent(form.WebhookForm), - IsActive: form.Active, - Type: webhook.DINGTALK, - Meta: "", - OrgID: orCtx.OrgID, - IsSystemWebhook: orCtx.IsSystemWebhook, - } - if err := w.UpdateEvent(); err != nil { - ctx.ServerError("UpdateEvent", err) - return - } else if err := webhook.CreateWebhook(ctx, w); err != nil { - ctx.ServerError("CreateWebhook", err) - return - } - - ctx.Flash.Success(ctx.Tr("repo.settings.add_hook_success")) - ctx.Redirect(orCtx.Link) + createWebhook(ctx, webhookCreationParams{ + URL: form.PayloadURL, + ContentType: webhook.ContentTypeJSON, + WebhookForm: form.WebhookForm, + Type: webhook.DINGTALK, + }) } // TelegramHooksNewPost response for creating telegram hook func TelegramHooksNewPost(ctx *context.Context) { form := web.GetForm(ctx).(*forms.NewTelegramHookForm) - ctx.Data["Title"] = ctx.Tr("repo.settings") - ctx.Data["PageIsSettingsHooks"] = true - ctx.Data["PageIsSettingsHooksNew"] = true - ctx.Data["Webhook"] = webhook.Webhook{HookEvent: &webhook.HookEvent{}} - ctx.Data["HookType"] = webhook.TELEGRAM - orCtx, err := getOrgRepoCtx(ctx) - if err != nil { - ctx.ServerError("getOrgRepoCtx", err) - return - } - - if ctx.HasError() { - ctx.HTML(http.StatusOK, orCtx.NewTemplate) - return - } - - meta, err := json.Marshal(&webhook_service.TelegramMeta{ - BotToken: form.BotToken, - ChatID: form.ChatID, + createWebhook(ctx, webhookCreationParams{ + URL: fmt.Sprintf("https://api.telegram.org/bot%s/sendMessage?chat_id=%s", url.PathEscape(form.BotToken), url.QueryEscape(form.ChatID)), + ContentType: webhook.ContentTypeJSON, + WebhookForm: form.WebhookForm, + Type: webhook.TELEGRAM, + Meta: &webhook_service.TelegramMeta{ + BotToken: form.BotToken, + ChatID: form.ChatID, + }, }) - if err != nil { - ctx.ServerError("Marshal", err) - return - } - - w := &webhook.Webhook{ - RepoID: orCtx.RepoID, - URL: fmt.Sprintf("https://api.telegram.org/bot%s/sendMessage?chat_id=%s", url.PathEscape(form.BotToken), url.QueryEscape(form.ChatID)), - ContentType: webhook.ContentTypeJSON, - HookEvent: ParseHookEvent(form.WebhookForm), - IsActive: form.Active, - Type: webhook.TELEGRAM, - Meta: string(meta), - OrgID: orCtx.OrgID, - IsSystemWebhook: orCtx.IsSystemWebhook, - } - if err := w.UpdateEvent(); err != nil { - ctx.ServerError("UpdateEvent", err) - return - } else if err := webhook.CreateWebhook(ctx, w); err != nil { - ctx.ServerError("CreateWebhook", err) - return - } - - ctx.Flash.Success(ctx.Tr("repo.settings.add_hook_success")) - ctx.Redirect(orCtx.Link) } // MatrixHooksNewPost response for creating a Matrix hook func MatrixHooksNewPost(ctx *context.Context) { form := web.GetForm(ctx).(*forms.NewMatrixHookForm) - ctx.Data["Title"] = ctx.Tr("repo.settings") - ctx.Data["PageIsSettingsHooks"] = true - ctx.Data["PageIsSettingsHooksNew"] = true - ctx.Data["Webhook"] = webhook.Webhook{HookEvent: &webhook.HookEvent{}} - ctx.Data["HookType"] = webhook.MATRIX - orCtx, err := getOrgRepoCtx(ctx) - if err != nil { - ctx.ServerError("getOrgRepoCtx", err) - return - } - - if ctx.HasError() { - ctx.HTML(http.StatusOK, orCtx.NewTemplate) - return - } - - meta, err := json.Marshal(&webhook_service.MatrixMeta{ - HomeserverURL: form.HomeserverURL, - Room: form.RoomID, - AccessToken: form.AccessToken, - MessageType: form.MessageType, + createWebhook(ctx, webhookCreationParams{ + URL: fmt.Sprintf("%s/_matrix/client/r0/rooms/%s/send/m.room.message", form.HomeserverURL, url.PathEscape(form.RoomID)), + ContentType: webhook.ContentTypeJSON, + HTTPMethod: http.MethodPut, + WebhookForm: form.WebhookForm, + Type: webhook.MATRIX, + Meta: &webhook_service.MatrixMeta{ + HomeserverURL: form.HomeserverURL, + Room: form.RoomID, + AccessToken: form.AccessToken, + MessageType: form.MessageType, + }, }) - if err != nil { - ctx.ServerError("Marshal", err) - return - } - - w := &webhook.Webhook{ - RepoID: orCtx.RepoID, - URL: fmt.Sprintf("%s/_matrix/client/r0/rooms/%s/send/m.room.message", form.HomeserverURL, url.PathEscape(form.RoomID)), - ContentType: webhook.ContentTypeJSON, - HTTPMethod: "PUT", - HookEvent: ParseHookEvent(form.WebhookForm), - IsActive: form.Active, - Type: webhook.MATRIX, - Meta: string(meta), - OrgID: orCtx.OrgID, - IsSystemWebhook: orCtx.IsSystemWebhook, - } - if err := w.UpdateEvent(); err != nil { - ctx.ServerError("UpdateEvent", err) - return - } else if err := webhook.CreateWebhook(ctx, w); err != nil { - ctx.ServerError("CreateWebhook", err) - return - } - - ctx.Flash.Success(ctx.Tr("repo.settings.add_hook_success")) - ctx.Redirect(orCtx.Link) } // MSTeamsHooksNewPost response for creating MS Teams hook func MSTeamsHooksNewPost(ctx *context.Context) { form := web.GetForm(ctx).(*forms.NewMSTeamsHookForm) - ctx.Data["Title"] = ctx.Tr("repo.settings") - ctx.Data["PageIsSettingsHooks"] = true - ctx.Data["PageIsSettingsHooksNew"] = true - ctx.Data["Webhook"] = webhook.Webhook{HookEvent: &webhook.HookEvent{}} - ctx.Data["HookType"] = webhook.MSTEAMS - orCtx, err := getOrgRepoCtx(ctx) - if err != nil { - ctx.ServerError("getOrgRepoCtx", err) - return - } - - if ctx.HasError() { - ctx.HTML(http.StatusOK, orCtx.NewTemplate) - return - } - - w := &webhook.Webhook{ - RepoID: orCtx.RepoID, - URL: form.PayloadURL, - ContentType: webhook.ContentTypeJSON, - HookEvent: ParseHookEvent(form.WebhookForm), - IsActive: form.Active, - Type: webhook.MSTEAMS, - Meta: "", - OrgID: orCtx.OrgID, - IsSystemWebhook: orCtx.IsSystemWebhook, - } - if err := w.UpdateEvent(); err != nil { - ctx.ServerError("UpdateEvent", err) - return - } else if err := webhook.CreateWebhook(ctx, w); err != nil { - ctx.ServerError("CreateWebhook", err) - return - } - - ctx.Flash.Success(ctx.Tr("repo.settings.add_hook_success")) - ctx.Redirect(orCtx.Link) + createWebhook(ctx, webhookCreationParams{ + URL: form.PayloadURL, + ContentType: webhook.ContentTypeJSON, + WebhookForm: form.WebhookForm, + Type: webhook.MSTEAMS, + }) } // SlackHooksNewPost response for creating slack hook func SlackHooksNewPost(ctx *context.Context) { form := web.GetForm(ctx).(*forms.NewSlackHookForm) - ctx.Data["Title"] = ctx.Tr("repo.settings") - ctx.Data["PageIsSettingsHooks"] = true - ctx.Data["PageIsSettingsHooksNew"] = true - ctx.Data["Webhook"] = webhook.Webhook{HookEvent: &webhook.HookEvent{}} - ctx.Data["HookType"] = webhook.SLACK - orCtx, err := getOrgRepoCtx(ctx) - if err != nil { - ctx.ServerError("getOrgRepoCtx", err) - return - } - - if ctx.HasError() { - ctx.HTML(http.StatusOK, orCtx.NewTemplate) - return - } - - if form.HasInvalidChannel() { - ctx.Flash.Error(ctx.Tr("repo.settings.add_webhook.invalid_channel_name")) - ctx.Redirect(orCtx.LinkNew + "/slack/new") - return - } - - meta, err := json.Marshal(&webhook_service.SlackMeta{ - Channel: strings.TrimSpace(form.Channel), - Username: form.Username, - IconURL: form.IconURL, - Color: form.Color, + createWebhook(ctx, webhookCreationParams{ + URL: form.PayloadURL, + ContentType: webhook.ContentTypeJSON, + WebhookForm: form.WebhookForm, + Type: webhook.SLACK, + Meta: &webhook_service.SlackMeta{ + Channel: strings.TrimSpace(form.Channel), + Username: form.Username, + IconURL: form.IconURL, + Color: form.Color, + }, }) - if err != nil { - ctx.ServerError("Marshal", err) - return - } - - w := &webhook.Webhook{ - RepoID: orCtx.RepoID, - URL: form.PayloadURL, - ContentType: webhook.ContentTypeJSON, - HookEvent: ParseHookEvent(form.WebhookForm), - IsActive: form.Active, - Type: webhook.SLACK, - Meta: string(meta), - OrgID: orCtx.OrgID, - IsSystemWebhook: orCtx.IsSystemWebhook, - } - if err := w.UpdateEvent(); err != nil { - ctx.ServerError("UpdateEvent", err) - return - } else if err := webhook.CreateWebhook(ctx, w); err != nil { - ctx.ServerError("CreateWebhook", err) - return - } - - ctx.Flash.Success(ctx.Tr("repo.settings.add_hook_success")) - ctx.Redirect(orCtx.Link) } // FeishuHooksNewPost response for creating feishu hook func FeishuHooksNewPost(ctx *context.Context) { form := web.GetForm(ctx).(*forms.NewFeishuHookForm) - ctx.Data["Title"] = ctx.Tr("repo.settings") - ctx.Data["PageIsSettingsHooks"] = true - ctx.Data["PageIsSettingsHooksNew"] = true - ctx.Data["Webhook"] = webhook.Webhook{HookEvent: &webhook.HookEvent{}} - ctx.Data["HookType"] = webhook.FEISHU - orCtx, err := getOrgRepoCtx(ctx) - if err != nil { - ctx.ServerError("getOrgRepoCtx", err) - return - } - - if ctx.HasError() { - ctx.HTML(http.StatusOK, orCtx.NewTemplate) - return - } - - w := &webhook.Webhook{ - RepoID: orCtx.RepoID, - URL: form.PayloadURL, - ContentType: webhook.ContentTypeJSON, - HookEvent: ParseHookEvent(form.WebhookForm), - IsActive: form.Active, - Type: webhook.FEISHU, - Meta: "", - OrgID: orCtx.OrgID, - IsSystemWebhook: orCtx.IsSystemWebhook, - } - if err := w.UpdateEvent(); err != nil { - ctx.ServerError("UpdateEvent", err) - return - } else if err := webhook.CreateWebhook(ctx, w); err != nil { - ctx.ServerError("CreateWebhook", err) - return - } - - ctx.Flash.Success(ctx.Tr("repo.settings.add_hook_success")) - ctx.Redirect(orCtx.Link) + createWebhook(ctx, webhookCreationParams{ + URL: form.PayloadURL, + ContentType: webhook.ContentTypeJSON, + WebhookForm: form.WebhookForm, + Type: webhook.FEISHU, + }) } // WechatworkHooksNewPost response for creating wechatwork hook func WechatworkHooksNewPost(ctx *context.Context) { form := web.GetForm(ctx).(*forms.NewWechatWorkHookForm) - ctx.Data["Title"] = ctx.Tr("repo.settings") - ctx.Data["PageIsSettingsHooks"] = true - ctx.Data["PageIsSettingsHooksNew"] = true - ctx.Data["Webhook"] = webhook.Webhook{HookEvent: &webhook.HookEvent{}} - ctx.Data["HookType"] = webhook.WECHATWORK - - orCtx, err := getOrgRepoCtx(ctx) - if err != nil { - ctx.ServerError("getOrgRepoCtx", err) - return - } - - if ctx.HasError() { - ctx.HTML(http.StatusOK, orCtx.NewTemplate) - return - } - - w := &webhook.Webhook{ - RepoID: orCtx.RepoID, - URL: form.PayloadURL, - ContentType: webhook.ContentTypeJSON, - HookEvent: ParseHookEvent(form.WebhookForm), - IsActive: form.Active, - Type: webhook.WECHATWORK, - Meta: "", - OrgID: orCtx.OrgID, - IsSystemWebhook: orCtx.IsSystemWebhook, - } - if err := w.UpdateEvent(); err != nil { - ctx.ServerError("UpdateEvent", err) - return - } else if err := webhook.CreateWebhook(ctx, w); err != nil { - ctx.ServerError("CreateWebhook", err) - return - } - - ctx.Flash.Success(ctx.Tr("repo.settings.add_hook_success")) - ctx.Redirect(orCtx.Link) + createWebhook(ctx, webhookCreationParams{ + URL: form.PayloadURL, + ContentType: webhook.ContentTypeJSON, + WebhookForm: form.WebhookForm, + Type: webhook.WECHATWORK, + }) } // PackagistHooksNewPost response for creating packagist hook func PackagistHooksNewPost(ctx *context.Context) { form := web.GetForm(ctx).(*forms.NewPackagistHookForm) - ctx.Data["Title"] = ctx.Tr("repo.settings") - ctx.Data["PageIsSettingsHooks"] = true - ctx.Data["PageIsSettingsHooksNew"] = true - ctx.Data["Webhook"] = webhook.Webhook{HookEvent: &webhook.HookEvent{}} - ctx.Data["HookType"] = webhook.PACKAGIST - orCtx, err := getOrgRepoCtx(ctx) - if err != nil { - ctx.ServerError("getOrgRepoCtx", err) - return - } - - if ctx.HasError() { - ctx.HTML(http.StatusOK, orCtx.NewTemplate) - return - } - - meta, err := json.Marshal(&webhook_service.PackagistMeta{ - Username: form.Username, - APIToken: form.APIToken, - PackageURL: form.PackageURL, + createWebhook(ctx, webhookCreationParams{ + URL: fmt.Sprintf("https://packagist.org/api/update-package?username=%s&apiToken=%s", url.QueryEscape(form.Username), url.QueryEscape(form.APIToken)), + ContentType: webhook.ContentTypeJSON, + WebhookForm: form.WebhookForm, + Type: webhook.PACKAGIST, + Meta: &webhook_service.PackagistMeta{ + Username: form.Username, + APIToken: form.APIToken, + PackageURL: form.PackageURL, + }, }) - if err != nil { - ctx.ServerError("Marshal", err) - return - } - - w := &webhook.Webhook{ - RepoID: orCtx.RepoID, - URL: fmt.Sprintf("https://packagist.org/api/update-package?username=%s&apiToken=%s", url.QueryEscape(form.Username), url.QueryEscape(form.APIToken)), - ContentType: webhook.ContentTypeJSON, - HookEvent: ParseHookEvent(form.WebhookForm), - IsActive: form.Active, - Type: webhook.PACKAGIST, - Meta: string(meta), - OrgID: orCtx.OrgID, - IsSystemWebhook: orCtx.IsSystemWebhook, - } - if err := w.UpdateEvent(); err != nil { - ctx.ServerError("UpdateEvent", err) - return - } else if err := webhook.CreateWebhook(ctx, w); err != nil { - ctx.ServerError("CreateWebhook", err) - return - } - - ctx.Flash.Success(ctx.Tr("repo.settings.add_hook_success")) - ctx.Redirect(orCtx.Link) } func checkWebhook(ctx *context.Context) (*orgRepoCtx, *webhook.Webhook) { @@ -894,12 +579,6 @@ func SlackHooksEditPost(ctx *context.Context) { return } - if form.HasInvalidChannel() { - ctx.Flash.Error(ctx.Tr("repo.settings.add_webhook.invalid_channel_name")) - ctx.Redirect(fmt.Sprintf("%s/%d", orCtx.Link, w.ID)) - return - } - meta, err := json.Marshal(&webhook_service.SlackMeta{ Channel: strings.TrimSpace(form.Channel), Username: form.Username, diff --git a/services/forms/repo_form.go b/services/forms/repo_form.go index afecc205f3..7a4a2123eb 100644 --- a/services/forms/repo_form.go +++ b/services/forms/repo_form.go @@ -17,7 +17,7 @@ import ( "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/web/middleware" - "code.gitea.io/gitea/routers/utils" + "code.gitea.io/gitea/services/webhook" "gitea.com/go-chi/binding" ) @@ -305,14 +305,16 @@ type NewSlackHookForm struct { // Validate validates the fields func (f *NewSlackHookForm) Validate(req *http.Request, errs binding.Errors) binding.Errors { ctx := context.GetContext(req) + if !webhook.IsValidSlackChannel(strings.TrimSpace(f.Channel)) { + errs = append(errs, binding.Error{ + FieldNames: []string{"Channel"}, + Classification: "", + Message: ctx.Tr("repo.settings.add_webhook.invalid_channel_name"), + }) + } return middleware.Validate(errs, ctx.Data, f, ctx.Locale) } -// HasInvalidChannel validates the channel name is in the right format -func (f NewSlackHookForm) HasInvalidChannel() bool { - return !utils.IsValidSlackChannel(f.Channel) -} - // NewDiscordHookForm form for creating discord hook type NewDiscordHookForm struct { PayloadURL string `binding:"Required;ValidUrl"` diff --git a/services/webhook/slack.go b/services/webhook/slack.go index 11e1d3c081..e3d0d406de 100644 --- a/services/webhook/slack.go +++ b/services/webhook/slack.go @@ -7,6 +7,7 @@ package webhook import ( "errors" "fmt" + "regexp" "strings" webhook_model "code.gitea.io/gitea/models/webhook" @@ -286,3 +287,13 @@ func GetSlackPayload(p api.Payloader, event webhook_model.HookEventType, meta st return convertPayloader(s, p, event) } + +var slackChannel = regexp.MustCompile(`^#?[a-z0-9_-]{1,80}$`) + +// IsValidSlackChannel validates a channel name conforms to what slack expects: +// https://api.slack.com/methods/conversations.rename#naming +// Conversation names can only contain lowercase letters, numbers, hyphens, and underscores, and must be 80 characters or less. +// Gitea accepts if it starts with a #. +func IsValidSlackChannel(name string) bool { + return slackChannel.MatchString(name) +} diff --git a/services/webhook/slack_test.go b/services/webhook/slack_test.go index 8278afb69a..0f08785d25 100644 --- a/services/webhook/slack_test.go +++ b/services/webhook/slack_test.go @@ -170,3 +170,22 @@ func TestSlackJSONPayload(t *testing.T) { require.NoError(t, err) assert.NotEmpty(t, json) } + +func TestIsValidSlackChannel(t *testing.T) { + tt := []struct { + channelName string + expected bool + }{ + {"gitea", true}, + {"#gitea", true}, + {" ", false}, + {"#", false}, + {" #", false}, + {"gitea ", false}, + {" gitea", false}, + } + + for _, v := range tt { + assert.Equal(t, v.expected, IsValidSlackChannel(v.channelName)) + } +}