From 313835348385941234bb8d8276e4390c3750f578 Mon Sep 17 00:00:00 2001 From: oliverpool Date: Tue, 9 Aug 2022 10:06:54 +0200 Subject: [PATCH 1/7] refactor webhook *NewPost --- modules/web/middleware/binding.go | 9 +- routers/web/repo/webhook.go | 601 ++++++++---------------------- services/forms/repo_form.go | 11 +- 3 files changed, 169 insertions(+), 452 deletions(-) diff --git a/modules/web/middleware/binding.go b/modules/web/middleware/binding.go index 88a3920f6e67c..29a7d59652d49 100644 --- a/modules/web/middleware/binding.go +++ b/modules/web/middleware/binding.go @@ -136,7 +136,14 @@ 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 == "" { + msg = errs[0].Message + } + if msg == "" { + msg = l.Tr("form.unknown_error") + } + data["ErrorMsg"] = trName + ": " + msg } return errs } diff --git a/routers/web/repo/webhook.go b/routers/web/repo/webhook.go index a9b14ee21f453..0c84af2e228b9 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,199 @@ func GiteaHooksNewPost(ctx *context.Context) { ctx.Redirect(orCtx.Link) } -// 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 +// GiteaHooksNewPost response for creating Gitea webhook +func GiteaHooksNewPost(ctx *context.Context) { + form := web.GetForm(ctx).(*forms.NewWebhookForm) - orCtx, err := getOrgRepoCtx(ctx) - if err != nil { - ctx.ServerError("getOrgRepoCtx", err) - return + contentType := webhook.ContentTypeJSON + if webhook.HookContentType(form.ContentType) == webhook.ContentTypeForm { + contentType = webhook.ContentTypeForm } - ctx.Data["BaseLink"] = orCtx.LinkNew - if ctx.HasError() { - ctx.HTML(http.StatusOK, orCtx.NewTemplate) - return - } + createWebhook(ctx, webhookCreationParams{ + URL: form.PayloadURL, + ContentType: contentType, + Secret: form.Secret, + HTTPMethod: form.HTTPMethod, + WebhookForm: form.WebhookForm, + Type: webhook.GITEA, + Meta: nil, + }) +} + +// GogsHooksNewPost response for creating webhook +func GogsHooksNewPost(ctx *context.Context) { + form := web.GetForm(ctx).(*forms.NewGogshookForm) 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, + HTTPMethod: "", + WebhookForm: form.WebhookForm, + Type: webhook.GOGS, + Meta: nil, + }) } // 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, + Secret: "", + HTTPMethod: "", + 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, + Secret: "", + HTTPMethod: "", + WebhookForm: form.WebhookForm, + Type: webhook.DINGTALK, + Meta: nil, + }) } // 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, + Secret: "", + HTTPMethod: "", + 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, + Secret: "", + HTTPMethod: "PUT", + 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, + Secret: "", + HTTPMethod: "", + WebhookForm: form.WebhookForm, + Type: webhook.MSTEAMS, + Meta: nil, + }) } // 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, + Secret: "", + HTTPMethod: "", + 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, + Secret: "", + HTTPMethod: "", + WebhookForm: form.WebhookForm, + Type: webhook.FEISHU, + Meta: nil, + }) } // 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, + Secret: "", + HTTPMethod: "", + WebhookForm: form.WebhookForm, + Type: webhook.WECHATWORK, + Meta: nil, + }) } // 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, + Secret: "", + HTTPMethod: "", + 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 +603,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 afecc205f31e8..abfbf3cc9c939 100644 --- a/services/forms/repo_form.go +++ b/services/forms/repo_form.go @@ -305,11 +305,18 @@ type NewSlackHookForm struct { // Validate validates the fields func (f *NewSlackHookForm) Validate(req *http.Request, errs binding.Errors) binding.Errors { ctx := context.GetContext(req) + if f.hasInvalidChannel() { + 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 { +// hasInvalidChannel validates the channel name is in the right format +func (f NewSlackHookForm) hasInvalidChannel() bool { return !utils.IsValidSlackChannel(f.Channel) } From 8eec61f3a3aa1566d133ad654796a898defac161 Mon Sep 17 00:00:00 2001 From: oliverpool Date: Tue, 9 Aug 2022 11:55:34 +0200 Subject: [PATCH 2/7] remove empty values --- routers/web/repo/webhook.go | 26 +------------------------- 1 file changed, 1 insertion(+), 25 deletions(-) diff --git a/routers/web/repo/webhook.go b/routers/web/repo/webhook.go index 0c84af2e228b9..d4419a1e10957 100644 --- a/routers/web/repo/webhook.go +++ b/routers/web/repo/webhook.go @@ -264,7 +264,6 @@ func GiteaHooksNewPost(ctx *context.Context) { HTTPMethod: form.HTTPMethod, WebhookForm: form.WebhookForm, Type: webhook.GITEA, - Meta: nil, }) } @@ -281,10 +280,8 @@ func GogsHooksNewPost(ctx *context.Context) { URL: form.PayloadURL, ContentType: contentType, Secret: form.Secret, - HTTPMethod: "", WebhookForm: form.WebhookForm, Type: webhook.GOGS, - Meta: nil, }) } @@ -295,8 +292,6 @@ func DiscordHooksNewPost(ctx *context.Context) { createWebhook(ctx, webhookCreationParams{ URL: form.PayloadURL, ContentType: webhook.ContentTypeJSON, - Secret: "", - HTTPMethod: "", WebhookForm: form.WebhookForm, Type: webhook.DISCORD, Meta: &webhook_service.DiscordMeta{ @@ -313,11 +308,8 @@ func DingtalkHooksNewPost(ctx *context.Context) { createWebhook(ctx, webhookCreationParams{ URL: form.PayloadURL, ContentType: webhook.ContentTypeJSON, - Secret: "", - HTTPMethod: "", WebhookForm: form.WebhookForm, Type: webhook.DINGTALK, - Meta: nil, }) } @@ -328,8 +320,6 @@ func TelegramHooksNewPost(ctx *context.Context) { 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, - Secret: "", - HTTPMethod: "", WebhookForm: form.WebhookForm, Type: webhook.TELEGRAM, Meta: &webhook_service.TelegramMeta{ @@ -346,8 +336,7 @@ func MatrixHooksNewPost(ctx *context.Context) { 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, - Secret: "", - HTTPMethod: "PUT", + HTTPMethod: http.MethodPut, WebhookForm: form.WebhookForm, Type: webhook.MATRIX, Meta: &webhook_service.MatrixMeta{ @@ -366,11 +355,8 @@ func MSTeamsHooksNewPost(ctx *context.Context) { createWebhook(ctx, webhookCreationParams{ URL: form.PayloadURL, ContentType: webhook.ContentTypeJSON, - Secret: "", - HTTPMethod: "", WebhookForm: form.WebhookForm, Type: webhook.MSTEAMS, - Meta: nil, }) } @@ -381,8 +367,6 @@ func SlackHooksNewPost(ctx *context.Context) { createWebhook(ctx, webhookCreationParams{ URL: form.PayloadURL, ContentType: webhook.ContentTypeJSON, - Secret: "", - HTTPMethod: "", WebhookForm: form.WebhookForm, Type: webhook.SLACK, Meta: &webhook_service.SlackMeta{ @@ -401,11 +385,8 @@ func FeishuHooksNewPost(ctx *context.Context) { createWebhook(ctx, webhookCreationParams{ URL: form.PayloadURL, ContentType: webhook.ContentTypeJSON, - Secret: "", - HTTPMethod: "", WebhookForm: form.WebhookForm, Type: webhook.FEISHU, - Meta: nil, }) } @@ -416,11 +397,8 @@ func WechatworkHooksNewPost(ctx *context.Context) { createWebhook(ctx, webhookCreationParams{ URL: form.PayloadURL, ContentType: webhook.ContentTypeJSON, - Secret: "", - HTTPMethod: "", WebhookForm: form.WebhookForm, Type: webhook.WECHATWORK, - Meta: nil, }) } @@ -431,8 +409,6 @@ func PackagistHooksNewPost(ctx *context.Context) { 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, - Secret: "", - HTTPMethod: "", WebhookForm: form.WebhookForm, Type: webhook.PACKAGIST, Meta: &webhook_service.PackagistMeta{ From d70c1042ccbce0908f046af60ee9464d8a51a9c7 Mon Sep 17 00:00:00 2001 From: oliverpool Date: Tue, 9 Aug 2022 12:01:10 +0200 Subject: [PATCH 3/7] always show errs.Message --- modules/web/middleware/binding.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/modules/web/middleware/binding.go b/modules/web/middleware/binding.go index 29a7d59652d49..f4848ae7d4062 100644 --- a/modules/web/middleware/binding.go +++ b/modules/web/middleware/binding.go @@ -137,9 +137,11 @@ func Validate(errs binding.Errors, data map[string]interface{}, f Form, l transl data["ErrorMsg"] = trName + l.Tr("form.regex_pattern_error", errs[0].Message) default: msg := errs[0].Classification - if msg == "" { - msg = errs[0].Message + if msg != "" { + msg += ": " } + + msg += errs[0].Message if msg == "" { msg = l.Tr("form.unknown_error") } From a2a1dc643d58770a08434f044f80a8e03305119c Mon Sep 17 00:00:00 2001 From: oliverpool Date: Tue, 9 Aug 2022 13:31:00 +0200 Subject: [PATCH 4/7] remove utils.IsValidSlackChannel --- routers/api/v1/utils/hook.go | 10 +++++++--- routers/utils/utils.go | 19 ------------------- routers/utils/utils_test.go | 17 ----------------- services/forms/repo_form.go | 8 +++----- services/forms/repo_form_test.go | 19 +++++++++++++++++++ 5 files changed, 29 insertions(+), 44 deletions(-) diff --git a/routers/api/v1/utils/hook.go b/routers/api/v1/utils/hook.go index f0dc595ad5ccb..dc0d16c27de84 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 !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"], @@ -170,6 +170,10 @@ func addHook(ctx *context.APIContext, form *api.CreateHookOption, orgID, repoID return w, true } +func isValidSlackChannel(name string) bool { + return name != "" && name != "#" +} + // EditOrgHook edit webhook `w` according to `form`. Writes to `ctx` accordingly func EditOrgHook(ctx *context.APIContext, form *api.EditHookOption, hookID int64) { org := ctx.Org.Organization diff --git a/routers/utils/utils.go b/routers/utils/utils.go index f15bc1e62e4be..66eaa1d9ce17a 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 f49ed77b6fc08..42cf948e3091d 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/services/forms/repo_form.go b/services/forms/repo_form.go index abfbf3cc9c939..a19f24a1fe26b 100644 --- a/services/forms/repo_form.go +++ b/services/forms/repo_form.go @@ -17,7 +17,6 @@ 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" "gitea.com/go-chi/binding" ) @@ -305,7 +304,7 @@ type NewSlackHookForm struct { // Validate validates the fields func (f *NewSlackHookForm) Validate(req *http.Request, errs binding.Errors) binding.Errors { ctx := context.GetContext(req) - if f.hasInvalidChannel() { + if !isValidSlackChannel(strings.TrimSpace(f.Channel)) { errs = append(errs, binding.Error{ FieldNames: []string{"Channel"}, Classification: "", @@ -315,9 +314,8 @@ func (f *NewSlackHookForm) Validate(req *http.Request, errs binding.Errors) bind 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) +func isValidSlackChannel(name string) bool { + return name != "" && name != "#" } // NewDiscordHookForm form for creating discord hook diff --git a/services/forms/repo_form_test.go b/services/forms/repo_form_test.go index 198437f7ea322..723fd3e07c261 100644 --- a/services/forms/repo_form_test.go +++ b/services/forms/repo_form_test.go @@ -5,6 +5,7 @@ package forms import ( + "strings" "testing" "code.gitea.io/gitea/modules/setting" @@ -63,3 +64,21 @@ func TestIssueLock_HasValidReason(t *testing.T) { assert.Equal(t, v.expected, v.form.HasValidReason()) } } + +func TestIsValidSlackChannel(t *testing.T) { + tt := []struct { + channelName string + expected bool + }{ + {"gitea", true}, + {" ", false}, + {"#", false}, + {" #", false}, + {"gitea ", true}, + {" gitea", true}, + } + + for _, v := range tt { + assert.Equal(t, v.expected, isValidSlackChannel(strings.TrimSpace(v.channelName))) + } +} From 8a103119191ed92ad1a2c8ba37b984afddb66dd2 Mon Sep 17 00:00:00 2001 From: oliverpool Date: Tue, 9 Aug 2022 13:42:16 +0200 Subject: [PATCH 5/7] move IsValidSlackChannel to services/webhook package --- routers/api/v1/utils/hook.go | 6 +----- services/forms/repo_form.go | 7 ++----- services/forms/repo_form_test.go | 19 ------------------- services/webhook/slack.go | 7 +++++++ services/webhook/slack_test.go | 19 +++++++++++++++++++ 5 files changed, 29 insertions(+), 29 deletions(-) diff --git a/routers/api/v1/utils/hook.go b/routers/api/v1/utils/hook.go index dc0d16c27de84..ba008f587c69e 100644 --- a/routers/api/v1/utils/hook.go +++ b/routers/api/v1/utils/hook.go @@ -142,7 +142,7 @@ func addHook(ctx *context.APIContext, form *api.CreateHookOption, orgID, repoID } channel = strings.TrimSpace(channel) - if !isValidSlackChannel(channel) { + if !webhook_service.IsValidSlackChannel(channel) { ctx.Error(http.StatusBadRequest, "", "Invalid slack channel name") return nil, false } @@ -170,10 +170,6 @@ func addHook(ctx *context.APIContext, form *api.CreateHookOption, orgID, repoID return w, true } -func isValidSlackChannel(name string) bool { - return name != "" && name != "#" -} - // EditOrgHook edit webhook `w` according to `form`. Writes to `ctx` accordingly func EditOrgHook(ctx *context.APIContext, form *api.EditHookOption, hookID int64) { org := ctx.Org.Organization diff --git a/services/forms/repo_form.go b/services/forms/repo_form.go index a19f24a1fe26b..7a4a2123ebd2e 100644 --- a/services/forms/repo_form.go +++ b/services/forms/repo_form.go @@ -17,6 +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/services/webhook" "gitea.com/go-chi/binding" ) @@ -304,7 +305,7 @@ type NewSlackHookForm struct { // Validate validates the fields func (f *NewSlackHookForm) Validate(req *http.Request, errs binding.Errors) binding.Errors { ctx := context.GetContext(req) - if !isValidSlackChannel(strings.TrimSpace(f.Channel)) { + if !webhook.IsValidSlackChannel(strings.TrimSpace(f.Channel)) { errs = append(errs, binding.Error{ FieldNames: []string{"Channel"}, Classification: "", @@ -314,10 +315,6 @@ func (f *NewSlackHookForm) Validate(req *http.Request, errs binding.Errors) bind return middleware.Validate(errs, ctx.Data, f, ctx.Locale) } -func isValidSlackChannel(name string) bool { - return name != "" && name != "#" -} - // NewDiscordHookForm form for creating discord hook type NewDiscordHookForm struct { PayloadURL string `binding:"Required;ValidUrl"` diff --git a/services/forms/repo_form_test.go b/services/forms/repo_form_test.go index 723fd3e07c261..198437f7ea322 100644 --- a/services/forms/repo_form_test.go +++ b/services/forms/repo_form_test.go @@ -5,7 +5,6 @@ package forms import ( - "strings" "testing" "code.gitea.io/gitea/modules/setting" @@ -64,21 +63,3 @@ func TestIssueLock_HasValidReason(t *testing.T) { assert.Equal(t, v.expected, v.form.HasValidReason()) } } - -func TestIsValidSlackChannel(t *testing.T) { - tt := []struct { - channelName string - expected bool - }{ - {"gitea", true}, - {" ", false}, - {"#", false}, - {" #", false}, - {"gitea ", true}, - {" gitea", true}, - } - - for _, v := range tt { - assert.Equal(t, v.expected, isValidSlackChannel(strings.TrimSpace(v.channelName))) - } -} diff --git a/services/webhook/slack.go b/services/webhook/slack.go index 11e1d3c081c28..3bb8b2435ab76 100644 --- a/services/webhook/slack.go +++ b/services/webhook/slack.go @@ -286,3 +286,10 @@ func GetSlackPayload(p api.Payloader, event webhook_model.HookEventType, meta st return convertPayloader(s, p, event) } + +// IsValidSlackChannel validates a channel name conforms to what slack expects. +// It makes sure a channel name cannot be empty and invalid ( only an # ). +// The caller should call strings.TrimSpace first if needed. +func IsValidSlackChannel(name string) bool { + return name != "" && name != "#" +} diff --git a/services/webhook/slack_test.go b/services/webhook/slack_test.go index 8278afb69a8c6..36aef4f5ab734 100644 --- a/services/webhook/slack_test.go +++ b/services/webhook/slack_test.go @@ -5,6 +5,7 @@ package webhook import ( + "strings" "testing" webhook_model "code.gitea.io/gitea/models/webhook" @@ -170,3 +171,21 @@ 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}, + {" ", false}, + {"#", false}, + {" #", false}, + {"gitea ", true}, + {" gitea", true}, + } + + for _, v := range tt { + assert.Equal(t, v.expected, IsValidSlackChannel(strings.TrimSpace(v.channelName))) + } +} From 9f09545ea93d57c07a56bc71e44c2338eef84f42 Mon Sep 17 00:00:00 2001 From: oliverpool Date: Tue, 9 Aug 2022 20:59:01 +0200 Subject: [PATCH 6/7] binding: handle empty Message case --- modules/web/middleware/binding.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/web/middleware/binding.go b/modules/web/middleware/binding.go index f4848ae7d4062..636e655b9e956 100644 --- a/modules/web/middleware/binding.go +++ b/modules/web/middleware/binding.go @@ -137,7 +137,7 @@ func Validate(errs binding.Errors, data map[string]interface{}, f Form, l transl data["ErrorMsg"] = trName + l.Tr("form.regex_pattern_error", errs[0].Message) default: msg := errs[0].Classification - if msg != "" { + if msg != "" && errs[0].Message != "" { msg += ": " } From 24c6d900f332b90e6219b6126df5591cd7e45da4 Mon Sep 17 00:00:00 2001 From: oliverpool Date: Thu, 11 Aug 2022 11:45:43 +0200 Subject: [PATCH 7/7] make IsValidSlackChannel more strict --- services/webhook/slack.go | 12 ++++++++---- services/webhook/slack_test.go | 8 ++++---- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/services/webhook/slack.go b/services/webhook/slack.go index 3bb8b2435ab76..e3d0d406de3ff 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" @@ -287,9 +288,12 @@ func GetSlackPayload(p api.Payloader, event webhook_model.HookEventType, meta st return convertPayloader(s, p, event) } -// IsValidSlackChannel validates a channel name conforms to what slack expects. -// It makes sure a channel name cannot be empty and invalid ( only an # ). -// The caller should call strings.TrimSpace first if needed. +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 name != "" && name != "#" + return slackChannel.MatchString(name) } diff --git a/services/webhook/slack_test.go b/services/webhook/slack_test.go index 36aef4f5ab734..0f08785d25e4c 100644 --- a/services/webhook/slack_test.go +++ b/services/webhook/slack_test.go @@ -5,7 +5,6 @@ package webhook import ( - "strings" "testing" webhook_model "code.gitea.io/gitea/models/webhook" @@ -178,14 +177,15 @@ func TestIsValidSlackChannel(t *testing.T) { expected bool }{ {"gitea", true}, + {"#gitea", true}, {" ", false}, {"#", false}, {" #", false}, - {"gitea ", true}, - {" gitea", true}, + {"gitea ", false}, + {" gitea", false}, } for _, v := range tt { - assert.Equal(t, v.expected, IsValidSlackChannel(strings.TrimSpace(v.channelName))) + assert.Equal(t, v.expected, IsValidSlackChannel(v.channelName)) } }