Merge pull request '[REFACTOR] webhook endpoint logic cleanup' (#2847) from oliverpool/forgejo:webhook_4_cleanup_endpoints into forgejo

Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/2847
Reviewed-by: Earl Warren <earl-warren@noreply.codeberg.org>
This commit is contained in:
Earl Warren 2024-03-28 05:46:00 +00:00
commit 57e7650d70
18 changed files with 123 additions and 153 deletions

View file

@ -15,7 +15,6 @@ var Webhook = struct {
DeliverTimeout int DeliverTimeout int
SkipTLSVerify bool SkipTLSVerify bool
AllowedHostList string AllowedHostList string
Types []string
PagingNum int PagingNum int
ProxyURL string ProxyURL string
ProxyURLFixed *url.URL ProxyURLFixed *url.URL
@ -35,7 +34,6 @@ func loadWebhookFrom(rootCfg ConfigProvider) {
Webhook.DeliverTimeout = sec.Key("DELIVER_TIMEOUT").MustInt(5) Webhook.DeliverTimeout = sec.Key("DELIVER_TIMEOUT").MustInt(5)
Webhook.SkipTLSVerify = sec.Key("SKIP_TLS_VERIFY").MustBool() Webhook.SkipTLSVerify = sec.Key("SKIP_TLS_VERIFY").MustBool()
Webhook.AllowedHostList = sec.Key("ALLOWED_HOST_LIST").MustString("") Webhook.AllowedHostList = sec.Key("ALLOWED_HOST_LIST").MustString("")
Webhook.Types = []string{"forgejo", "gitea", "gogs", "slack", "discord", "dingtalk", "telegram", "msteams", "feishu", "matrix", "wechatwork", "packagist"}
Webhook.PagingNum = sec.Key("PAGING_NUM").MustInt(10) Webhook.PagingNum = sec.Key("PAGING_NUM").MustInt(10)
Webhook.ProxyURL = sec.Key("PROXY_URL").MustString("") Webhook.ProxyURL = sec.Key("PROXY_URL").MustString("")
if Webhook.ProxyURL != "" { if Webhook.ProxyURL != "" {

View file

@ -10,7 +10,6 @@ import (
"net/http" "net/http"
"net/url" "net/url"
"path" "path"
"strings"
"code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/models/perm" "code.gitea.io/gitea/models/perm"
@ -22,7 +21,6 @@ import (
"code.gitea.io/gitea/modules/json" "code.gitea.io/gitea/modules/json"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
api "code.gitea.io/gitea/modules/structs" api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/modules/web/middleware" "code.gitea.io/gitea/modules/web/middleware"
webhook_module "code.gitea.io/gitea/modules/webhook" webhook_module "code.gitea.io/gitea/modules/webhook"
"code.gitea.io/gitea/services/context" "code.gitea.io/gitea/services/context"
@ -41,8 +39,8 @@ const (
tplAdminHookNew base.TplName = "admin/hook_new" tplAdminHookNew base.TplName = "admin/hook_new"
) )
// Webhooks render web hooks list page // WebhookList render web hooks list page
func Webhooks(ctx *context.Context) { func WebhookList(ctx *context.Context) {
ctx.Data["Title"] = ctx.Tr("repo.settings.hooks") ctx.Data["Title"] = ctx.Tr("repo.settings.hooks")
ctx.Data["PageIsSettingsHooks"] = true ctx.Data["PageIsSettingsHooks"] = true
ctx.Data["BaseLink"] = ctx.Repo.RepoLink + "/settings/hooks" ctx.Data["BaseLink"] = ctx.Repo.RepoLink + "/settings/hooks"
@ -111,17 +109,8 @@ func getOwnerRepoCtx(ctx *context.Context) (*ownerRepoCtx, error) {
return nil, errors.New("unable to set OwnerRepo context") return nil, errors.New("unable to set OwnerRepo context")
} }
func checkHookType(ctx *context.Context) string { // WebhookNew render creating webhook page
hookType := strings.ToLower(ctx.Params(":type")) func WebhookNew(ctx *context.Context) {
if !util.SliceContainsString(setting.Webhook.Types, hookType, true) {
ctx.NotFound("checkHookType", nil)
return ""
}
return hookType
}
// WebhooksNew render creating webhook page
func WebhooksNew(ctx *context.Context) {
ctx.Data["Title"] = ctx.Tr("repo.settings.add_webhook") ctx.Data["Title"] = ctx.Tr("repo.settings.add_webhook")
ctx.Data["Webhook"] = webhook.Webhook{HookEvent: &webhook_module.HookEvent{}} ctx.Data["Webhook"] = webhook.Webhook{HookEvent: &webhook_module.HookEvent{}}
@ -142,16 +131,12 @@ func WebhooksNew(ctx *context.Context) {
ctx.Data["PageIsSettingsHooksNew"] = true ctx.Data["PageIsSettingsHooksNew"] = true
} }
hookType := checkHookType(ctx) hookType := ctx.Params(":type")
ctx.Data["HookType"] = hookType if webhook_service.GetWebhookHandler(hookType) == nil {
if ctx.Written() { ctx.NotFound("GetWebhookHandler", nil)
return return
} }
if hookType == "discord" { ctx.Data["HookType"] = hookType
ctx.Data["DiscordHook"] = map[string]any{
"Username": "Gitea",
}
}
ctx.Data["BaseLink"] = orCtx.LinkNew ctx.Data["BaseLink"] = orCtx.LinkNew
ctx.Data["BaseLinkNew"] = orCtx.LinkNew ctx.Data["BaseLinkNew"] = orCtx.LinkNew
@ -191,21 +176,9 @@ func ParseHookEvent(form forms.WebhookForm) *webhook_module.HookEvent {
} }
} }
type webhookParams struct {
// Type should be imported from webhook package (webhook.XXX)
Type string
URL string
ContentType webhook.HookContentType
Secret string
HTTPMethod string
WebhookForm forms.WebhookForm
Meta any
}
func WebhookCreate(ctx *context.Context) { func WebhookCreate(ctx *context.Context) {
typ := ctx.Params(":type") hookType := ctx.Params(":type")
handler := webhook_service.GetWebhookHandler(typ) handler := webhook_service.GetWebhookHandler(hookType)
if handler == nil { if handler == nil {
ctx.NotFound("GetWebhookHandler", nil) ctx.NotFound("GetWebhookHandler", nil)
return return
@ -213,25 +186,14 @@ func WebhookCreate(ctx *context.Context) {
fields := handler.FormFields(func(form any) { fields := handler.FormFields(func(form any) {
errs := binding.Bind(ctx.Req, form) errs := binding.Bind(ctx.Req, form)
middleware.Validate(errs, ctx.Data, form, ctx.Locale) // error will be checked later in ctx.HasError middleware.Validate(errs, ctx.Data, form, ctx.Locale) // error checked below in ctx.HasError
}) })
createWebhook(ctx, webhookParams{
Type: typ,
URL: fields.URL,
ContentType: fields.ContentType,
Secret: fields.Secret,
HTTPMethod: fields.HTTPMethod,
WebhookForm: fields.WebhookForm,
Meta: fields.Metadata,
})
}
func createWebhook(ctx *context.Context, params webhookParams) {
ctx.Data["Title"] = ctx.Tr("repo.settings.add_webhook") ctx.Data["Title"] = ctx.Tr("repo.settings.add_webhook")
ctx.Data["PageIsSettingsHooks"] = true ctx.Data["PageIsSettingsHooks"] = true
ctx.Data["PageIsSettingsHooksNew"] = true ctx.Data["PageIsSettingsHooksNew"] = true
ctx.Data["Webhook"] = webhook.Webhook{HookEvent: &webhook_module.HookEvent{}} ctx.Data["Webhook"] = webhook.Webhook{HookEvent: &webhook_module.HookEvent{}}
ctx.Data["HookType"] = params.Type ctx.Data["HookType"] = hookType
orCtx, err := getOwnerRepoCtx(ctx) orCtx, err := getOwnerRepoCtx(ctx)
if err != nil { if err != nil {
@ -242,13 +204,29 @@ func createWebhook(ctx *context.Context, params webhookParams) {
ctx.Data["BaseLinkNew"] = orCtx.LinkNew ctx.Data["BaseLinkNew"] = orCtx.LinkNew
if ctx.HasError() { if ctx.HasError() {
// pre-fill the form with the submitted data
var w webhook.Webhook
w.URL = fields.URL
w.ContentType = fields.ContentType
w.Secret = fields.Secret
w.HookEvent = ParseHookEvent(fields.WebhookForm)
w.IsActive = fields.WebhookForm.Active
w.HTTPMethod = fields.HTTPMethod
err := w.SetHeaderAuthorization(fields.WebhookForm.AuthorizationHeader)
if err != nil {
ctx.ServerError("SetHeaderAuthorization", err)
return
}
ctx.Data["Webhook"] = w
ctx.Data["HookMetadata"] = fields.Metadata
ctx.HTML(http.StatusUnprocessableEntity, orCtx.NewTemplate) ctx.HTML(http.StatusUnprocessableEntity, orCtx.NewTemplate)
return return
} }
var meta []byte var meta []byte
if params.Meta != nil { if fields.Metadata != nil {
meta, err = json.Marshal(params.Meta) meta, err = json.Marshal(fields.Metadata)
if err != nil { if err != nil {
ctx.ServerError("Marshal", err) ctx.ServerError("Marshal", err)
return return
@ -257,18 +235,18 @@ func createWebhook(ctx *context.Context, params webhookParams) {
w := &webhook.Webhook{ w := &webhook.Webhook{
RepoID: orCtx.RepoID, RepoID: orCtx.RepoID,
URL: params.URL, URL: fields.URL,
HTTPMethod: params.HTTPMethod, HTTPMethod: fields.HTTPMethod,
ContentType: params.ContentType, ContentType: fields.ContentType,
Secret: params.Secret, Secret: fields.Secret,
HookEvent: ParseHookEvent(params.WebhookForm), HookEvent: ParseHookEvent(fields.WebhookForm),
IsActive: params.WebhookForm.Active, IsActive: fields.WebhookForm.Active,
Type: params.Type, Type: hookType,
Meta: string(meta), Meta: string(meta),
OwnerID: orCtx.OwnerID, OwnerID: orCtx.OwnerID,
IsSystemWebhook: orCtx.IsSystemWebhook, IsSystemWebhook: orCtx.IsSystemWebhook,
} }
err = w.SetHeaderAuthorization(params.WebhookForm.AuthorizationHeader) err = w.SetHeaderAuthorization(fields.WebhookForm.AuthorizationHeader)
if err != nil { if err != nil {
ctx.ServerError("SetHeaderAuthorization", err) ctx.ServerError("SetHeaderAuthorization", err)
return return
@ -286,29 +264,6 @@ func createWebhook(ctx *context.Context, params webhookParams) {
} }
func WebhookUpdate(ctx *context.Context) { func WebhookUpdate(ctx *context.Context) {
typ := ctx.Params(":type")
handler := webhook_service.GetWebhookHandler(typ)
if handler == nil {
ctx.NotFound("GetWebhookHandler", nil)
return
}
fields := handler.FormFields(func(form any) {
errs := binding.Bind(ctx.Req, form)
middleware.Validate(errs, ctx.Data, form, ctx.Locale) // error will be checked later in ctx.HasError
})
editWebhook(ctx, webhookParams{
Type: typ,
URL: fields.URL,
ContentType: fields.ContentType,
Secret: fields.Secret,
HTTPMethod: fields.HTTPMethod,
WebhookForm: fields.WebhookForm,
Meta: fields.Metadata,
})
}
func editWebhook(ctx *context.Context, params webhookParams) {
ctx.Data["Title"] = ctx.Tr("repo.settings.update_webhook") ctx.Data["Title"] = ctx.Tr("repo.settings.update_webhook")
ctx.Data["PageIsSettingsHooks"] = true ctx.Data["PageIsSettingsHooks"] = true
ctx.Data["PageIsSettingsHooksEdit"] = true ctx.Data["PageIsSettingsHooksEdit"] = true
@ -319,35 +274,48 @@ func editWebhook(ctx *context.Context, params webhookParams) {
} }
ctx.Data["Webhook"] = w ctx.Data["Webhook"] = w
handler := webhook_service.GetWebhookHandler(w.Type)
if handler == nil {
ctx.NotFound("GetWebhookHandler", nil)
return
}
fields := handler.FormFields(func(form any) {
errs := binding.Bind(ctx.Req, form)
middleware.Validate(errs, ctx.Data, form, ctx.Locale) // error checked below in ctx.HasError
})
// pre-fill the form with the submitted data
w.URL = fields.URL
w.ContentType = fields.ContentType
w.Secret = fields.Secret
w.HookEvent = ParseHookEvent(fields.WebhookForm)
w.IsActive = fields.WebhookForm.Active
w.HTTPMethod = fields.HTTPMethod
err := w.SetHeaderAuthorization(fields.WebhookForm.AuthorizationHeader)
if err != nil {
ctx.ServerError("SetHeaderAuthorization", err)
return
}
if ctx.HasError() { if ctx.HasError() {
ctx.Data["HookMetadata"] = fields.Metadata
ctx.HTML(http.StatusUnprocessableEntity, orCtx.NewTemplate) ctx.HTML(http.StatusUnprocessableEntity, orCtx.NewTemplate)
return return
} }
var meta []byte var meta []byte
var err error if fields.Metadata != nil {
if params.Meta != nil { meta, err = json.Marshal(fields.Metadata)
meta, err = json.Marshal(params.Meta)
if err != nil { if err != nil {
ctx.ServerError("Marshal", err) ctx.ServerError("Marshal", err)
return return
} }
} }
w.URL = params.URL
w.ContentType = params.ContentType
w.Secret = params.Secret
w.HookEvent = ParseHookEvent(params.WebhookForm)
w.IsActive = params.WebhookForm.Active
w.HTTPMethod = params.HTTPMethod
w.Meta = string(meta) w.Meta = string(meta)
err = w.SetHeaderAuthorization(params.WebhookForm.AuthorizationHeader)
if err != nil {
ctx.ServerError("SetHeaderAuthorization", err)
return
}
if err := w.UpdateEvent(); err != nil { if err := w.UpdateEvent(); err != nil {
ctx.ServerError("UpdateEvent", err) ctx.ServerError("UpdateEvent", err)
return return
@ -399,8 +367,8 @@ func checkWebhook(ctx *context.Context) (*ownerRepoCtx, *webhook.Webhook) {
return orCtx, w return orCtx, w
} }
// WebHooksEdit render editing web hook page // WebhookEdit render editing web hook page
func WebHooksEdit(ctx *context.Context) { func WebhookEdit(ctx *context.Context) {
ctx.Data["Title"] = ctx.Tr("repo.settings.update_webhook") ctx.Data["Title"] = ctx.Tr("repo.settings.update_webhook")
ctx.Data["PageIsSettingsHooks"] = true ctx.Data["PageIsSettingsHooks"] = true
ctx.Data["PageIsSettingsHooksEdit"] = true ctx.Data["PageIsSettingsHooksEdit"] = true
@ -414,8 +382,8 @@ func WebHooksEdit(ctx *context.Context) {
ctx.HTML(http.StatusOK, orCtx.NewTemplate) ctx.HTML(http.StatusOK, orCtx.NewTemplate)
} }
// TestWebhook test if web hook is work fine // WebhookTest test if web hook is work fine
func TestWebhook(ctx *context.Context) { func WebhookTest(ctx *context.Context) {
hookID := ctx.ParamsInt64(":id") hookID := ctx.ParamsInt64(":id")
w, err := webhook.GetWebhookByRepoID(ctx, ctx.Repo.Repository.ID, hookID) w, err := webhook.GetWebhookByRepoID(ctx, ctx.Repo.Repository.ID, hookID)
if err != nil { if err != nil {
@ -475,8 +443,8 @@ func TestWebhook(ctx *context.Context) {
} }
} }
// ReplayWebhook replays a webhook // WebhookReplay replays a webhook
func ReplayWebhook(ctx *context.Context) { func WebhookReplay(ctx *context.Context) {
hookTaskUUID := ctx.Params(":uuid") hookTaskUUID := ctx.Params(":uuid")
orCtx, w := checkWebhook(ctx) orCtx, w := checkWebhook(ctx)
@ -497,8 +465,8 @@ func ReplayWebhook(ctx *context.Context) {
ctx.Redirect(fmt.Sprintf("%s/%d", orCtx.Link, w.ID)) ctx.Redirect(fmt.Sprintf("%s/%d", orCtx.Link, w.ID))
} }
// DeleteWebhook delete a webhook // WebhookDelete delete a webhook
func DeleteWebhook(ctx *context.Context) { func WebhookDelete(ctx *context.Context) {
if err := webhook.DeleteWebhookByRepoID(ctx, ctx.Repo.Repository.ID, ctx.FormInt64("id")); err != nil { if err := webhook.DeleteWebhookByRepoID(ctx, ctx.Repo.Repository.ID, ctx.FormInt64("id")); err != nil {
ctx.Flash.Error("DeleteWebhookByRepoID: " + err.Error()) ctx.Flash.Error("DeleteWebhookByRepoID: " + err.Error())
} else { } else {

View file

@ -400,15 +400,6 @@ func registerRoutes(m *web.Route) {
} }
} }
addWebhookAddRoutes := func() {
m.Get("/{type}/new", repo_setting.WebhooksNew)
m.Post("/{type}/new", repo_setting.WebhookCreate)
}
addWebhookEditRoutes := func() {
m.Post("/{type}/{id:[0-9]+}", repo_setting.WebhookUpdate)
}
addSettingsVariablesRoutes := func() { addSettingsVariablesRoutes := func() {
m.Group("/variables", func() { m.Group("/variables", func() {
m.Get("", repo_setting.Variables) m.Get("", repo_setting.Variables)
@ -618,12 +609,13 @@ func registerRoutes(m *web.Route) {
m.Group("/hooks", func() { m.Group("/hooks", func() {
m.Get("", user_setting.Webhooks) m.Get("", user_setting.Webhooks)
m.Post("/delete", user_setting.DeleteWebhook) m.Post("/delete", user_setting.DeleteWebhook)
addWebhookAddRoutes() m.Get("/{type}/new", repo_setting.WebhookNew)
m.Post("/{type}/new", repo_setting.WebhookCreate)
m.Group("/{id}", func() { m.Group("/{id}", func() {
m.Get("", repo_setting.WebHooksEdit) m.Get("", repo_setting.WebhookEdit)
m.Post("/replay/{uuid}", repo_setting.ReplayWebhook) m.Post("", repo_setting.WebhookUpdate)
m.Post("/replay/{uuid}", repo_setting.WebhookReplay)
}) })
addWebhookEditRoutes()
}, webhooksEnabled) }, webhooksEnabled)
m.Group("/blocked_users", func() { m.Group("/blocked_users", func() {
@ -722,14 +714,15 @@ func registerRoutes(m *web.Route) {
m.Get("", admin.DefaultOrSystemWebhooks) m.Get("", admin.DefaultOrSystemWebhooks)
m.Post("/delete", admin.DeleteDefaultOrSystemWebhook) m.Post("/delete", admin.DeleteDefaultOrSystemWebhook)
m.Group("/{id}", func() { m.Group("/{id}", func() {
m.Get("", repo_setting.WebHooksEdit) m.Get("", repo_setting.WebhookEdit)
m.Post("/replay/{uuid}", repo_setting.ReplayWebhook) m.Post("", repo_setting.WebhookUpdate)
m.Post("/replay/{uuid}", repo_setting.WebhookReplay)
}) })
addWebhookEditRoutes()
}, webhooksEnabled) }, webhooksEnabled)
m.Group("/{configType:default-hooks|system-hooks}", func() { m.Group("/{configType:default-hooks|system-hooks}", func() {
addWebhookAddRoutes() m.Get("/{type}/new", repo_setting.WebhookNew)
m.Post("/{type}/new", repo_setting.WebhookCreate)
}) })
m.Group("/auths", func() { m.Group("/auths", func() {
@ -887,12 +880,13 @@ func registerRoutes(m *web.Route) {
m.Group("/hooks", func() { m.Group("/hooks", func() {
m.Get("", org.Webhooks) m.Get("", org.Webhooks)
m.Post("/delete", org.DeleteWebhook) m.Post("/delete", org.DeleteWebhook)
addWebhookAddRoutes() m.Get("/{type}/new", repo_setting.WebhookNew)
m.Post("/{type}/new", repo_setting.WebhookCreate)
m.Group("/{id}", func() { m.Group("/{id}", func() {
m.Get("", repo_setting.WebHooksEdit) m.Get("", repo_setting.WebhookEdit)
m.Post("/replay/{uuid}", repo_setting.ReplayWebhook) m.Post("", repo_setting.WebhookUpdate)
m.Post("/replay/{uuid}", repo_setting.WebhookReplay)
}) })
addWebhookEditRoutes()
}, webhooksEnabled) }, webhooksEnabled)
m.Group("/labels", func() { m.Group("/labels", func() {
@ -1059,15 +1053,16 @@ func registerRoutes(m *web.Route) {
}, context.GitHookService()) }, context.GitHookService())
m.Group("/hooks", func() { m.Group("/hooks", func() {
m.Get("", repo_setting.Webhooks) m.Get("", repo_setting.WebhookList)
m.Post("/delete", repo_setting.DeleteWebhook) m.Post("/delete", repo_setting.WebhookDelete)
addWebhookAddRoutes() m.Get("/{type}/new", repo_setting.WebhookNew)
m.Post("/{type}/new", repo_setting.WebhookCreate)
m.Group("/{id}", func() { m.Group("/{id}", func() {
m.Get("", repo_setting.WebHooksEdit) m.Get("", repo_setting.WebhookEdit)
m.Post("/test", repo_setting.TestWebhook) m.Post("", repo_setting.WebhookUpdate)
m.Post("/replay/{uuid}", repo_setting.ReplayWebhook) m.Post("/test", repo_setting.WebhookTest)
m.Post("/replay/{uuid}", repo_setting.WebhookReplay)
}) })
addWebhookEditRoutes()
}, webhooksEnabled) }, webhooksEnabled)
m.Group("/keys", func() { m.Group("/keys", func() {

View file

@ -1,6 +1,6 @@
{{if eq .HookType "dingtalk"}} {{if eq .HookType "dingtalk"}}
<p>{{ctx.Locale.Tr "repo.settings.add_web_hook_desc" "https://dingtalk.com" (ctx.Locale.Tr "repo.settings.web_hook_name_dingtalk")}}</p> <p>{{ctx.Locale.Tr "repo.settings.add_web_hook_desc" "https://dingtalk.com" (ctx.Locale.Tr "repo.settings.web_hook_name_dingtalk")}}</p>
<form class="ui form" action="{{.BaseLink}}/dingtalk/{{or .Webhook.ID "new"}}" method="post"> <form class="ui form" action="{{.BaseLink}}/{{or .Webhook.ID "dingtalk/new"}}" method="post">
{{.CsrfTokenHtml}} {{.CsrfTokenHtml}}
<div class="required field {{if .Err_PayloadURL}}error{{end}}"> <div class="required field {{if .Err_PayloadURL}}error{{end}}">
<label for="payload_url">{{ctx.Locale.Tr "repo.settings.payload_url"}}</label> <label for="payload_url">{{ctx.Locale.Tr "repo.settings.payload_url"}}</label>

View file

@ -1,6 +1,6 @@
{{if eq .HookType "discord"}} {{if eq .HookType "discord"}}
<p>{{ctx.Locale.Tr "repo.settings.add_web_hook_desc" "https://discord.com" (ctx.Locale.Tr "repo.settings.web_hook_name_discord")}}</p> <p>{{ctx.Locale.Tr "repo.settings.add_web_hook_desc" "https://discord.com" (ctx.Locale.Tr "repo.settings.web_hook_name_discord")}}</p>
<form class="ui form" action="{{.BaseLink}}/discord/{{or .Webhook.ID "new"}}" method="post"> <form class="ui form" action="{{.BaseLink}}/{{or .Webhook.ID "discord/new"}}" method="post">
{{.CsrfTokenHtml}} {{.CsrfTokenHtml}}
<div class="required field {{if .Err_PayloadURL}}error{{end}}"> <div class="required field {{if .Err_PayloadURL}}error{{end}}">
<label for="payload_url">{{ctx.Locale.Tr "repo.settings.payload_url"}}</label> <label for="payload_url">{{ctx.Locale.Tr "repo.settings.payload_url"}}</label>

View file

@ -1,7 +1,7 @@
{{if eq .HookType "feishu"}} {{if eq .HookType "feishu"}}
<p>{{ctx.Locale.Tr "repo.settings.add_web_hook_desc" "https://feishu.cn" (ctx.Locale.Tr "repo.settings.web_hook_name_feishu")}}</p> <p>{{ctx.Locale.Tr "repo.settings.add_web_hook_desc" "https://feishu.cn" (ctx.Locale.Tr "repo.settings.web_hook_name_feishu")}}</p>
<p>{{ctx.Locale.Tr "repo.settings.add_web_hook_desc" "https://larksuite.com" (ctx.Locale.Tr "repo.settings.web_hook_name_larksuite")}}</p> <p>{{ctx.Locale.Tr "repo.settings.add_web_hook_desc" "https://larksuite.com" (ctx.Locale.Tr "repo.settings.web_hook_name_larksuite")}}</p>
<form class="ui form" action="{{.BaseLink}}/feishu/{{or .Webhook.ID "new"}}" method="post"> <form class="ui form" action="{{.BaseLink}}/{{or .Webhook.ID "feishu/new"}}" method="post">
{{.CsrfTokenHtml}} {{.CsrfTokenHtml}}
<div class="required field {{if .Err_PayloadURL}}error{{end}}"> <div class="required field {{if .Err_PayloadURL}}error{{end}}">
<label for="payload_url">{{ctx.Locale.Tr "repo.settings.payload_url"}}</label> <label for="payload_url">{{ctx.Locale.Tr "repo.settings.payload_url"}}</label>

View file

@ -1,6 +1,6 @@
{{if eq .HookType "forgejo"}} {{if eq .HookType "forgejo"}}
<p>{{ctx.Locale.Tr "repo.settings.add_web_hook_desc" "https://forgejo.org/docs/latest/user/webhooks/" (ctx.Locale.Tr "repo.settings.web_hook_name_forgejo")}}</p> <p>{{ctx.Locale.Tr "repo.settings.add_web_hook_desc" "https://forgejo.org/docs/latest/user/webhooks/" (ctx.Locale.Tr "repo.settings.web_hook_name_forgejo")}}</p>
<form class="ui form" action="{{.BaseLink}}/forgejo/{{or .Webhook.ID "new"}}" method="post"> <form class="ui form" action="{{.BaseLink}}/{{or .Webhook.ID "forgejo/new"}}" method="post">
{{template "base/disable_form_autofill"}} {{template "base/disable_form_autofill"}}
{{.CsrfTokenHtml}} {{.CsrfTokenHtml}}
<div class="required field {{if .Err_PayloadURL}}error{{end}}"> <div class="required field {{if .Err_PayloadURL}}error{{end}}">

View file

@ -1,6 +1,6 @@
{{if eq .HookType "gitea"}} {{if eq .HookType "gitea"}}
<p>{{ctx.Locale.Tr "repo.settings.add_web_hook_desc" "https://forgejo.org/docs/latest/user/webhooks/" (ctx.Locale.Tr "repo.settings.web_hook_name_gitea")}}</p> <p>{{ctx.Locale.Tr "repo.settings.add_web_hook_desc" "https://forgejo.org/docs/latest/user/webhooks/" (ctx.Locale.Tr "repo.settings.web_hook_name_gitea")}}</p>
<form class="ui form" action="{{.BaseLink}}/gitea/{{or .Webhook.ID "new"}}" method="post"> <form class="ui form" action="{{.BaseLink}}/{{or .Webhook.ID "gitea/new"}}" method="post">
{{template "base/disable_form_autofill"}} {{template "base/disable_form_autofill"}}
{{.CsrfTokenHtml}} {{.CsrfTokenHtml}}
<div class="required field {{if .Err_PayloadURL}}error{{end}}"> <div class="required field {{if .Err_PayloadURL}}error{{end}}">

View file

@ -1,6 +1,6 @@
{{if eq .HookType "gogs"}} {{if eq .HookType "gogs"}}
<p>{{ctx.Locale.Tr "repo.settings.add_web_hook_desc" "https://forgejo.org/docs/latest/user/webhooks/" (ctx.Locale.Tr "repo.settings.web_hook_name_gogs")}}</p> <p>{{ctx.Locale.Tr "repo.settings.add_web_hook_desc" "https://forgejo.org/docs/latest/user/webhooks/" (ctx.Locale.Tr "repo.settings.web_hook_name_gogs")}}</p>
<form class="ui form" action="{{.BaseLink}}/gogs/{{or .Webhook.ID "new"}}" method="post"> <form class="ui form" action="{{.BaseLink}}/{{or .Webhook.ID "gogs/new"}}" method="post">
{{template "base/disable_form_autofill"}} {{template "base/disable_form_autofill"}}
{{.CsrfTokenHtml}} {{.CsrfTokenHtml}}
<div class="required field {{if .Err_PayloadURL}}error{{end}}"> <div class="required field {{if .Err_PayloadURL}}error{{end}}">

View file

@ -1,6 +1,6 @@
{{if eq .HookType "matrix"}} {{if eq .HookType "matrix"}}
<p>{{ctx.Locale.Tr "repo.settings.add_web_hook_desc" "https://matrix.org/" (ctx.Locale.Tr "repo.settings.web_hook_name_matrix")}}</p> <p>{{ctx.Locale.Tr "repo.settings.add_web_hook_desc" "https://matrix.org/" (ctx.Locale.Tr "repo.settings.web_hook_name_matrix")}}</p>
<form class="ui form" action="{{.BaseLink}}/matrix/{{or .Webhook.ID "new"}}" method="post"> <form class="ui form" action="{{.BaseLink}}/{{or .Webhook.ID "matrix/new"}}" method="post">
{{.CsrfTokenHtml}} {{.CsrfTokenHtml}}
<div class="required field {{if .Err_HomeserverURL}}error{{end}}"> <div class="required field {{if .Err_HomeserverURL}}error{{end}}">
<label for="homeserver_url">{{ctx.Locale.Tr "repo.settings.matrix.homeserver_url"}}</label> <label for="homeserver_url">{{ctx.Locale.Tr "repo.settings.matrix.homeserver_url"}}</label>

View file

@ -1,6 +1,6 @@
{{if eq .HookType "msteams"}} {{if eq .HookType "msteams"}}
<p>{{ctx.Locale.Tr "repo.settings.add_web_hook_desc" "https://teams.microsoft.com" (ctx.Locale.Tr "repo.settings.web_hook_name_msteams")}}</p> <p>{{ctx.Locale.Tr "repo.settings.add_web_hook_desc" "https://teams.microsoft.com" (ctx.Locale.Tr "repo.settings.web_hook_name_msteams")}}</p>
<form class="ui form" action="{{.BaseLink}}/msteams/{{or .Webhook.ID "new"}}" method="post"> <form class="ui form" action="{{.BaseLink}}/{{or .Webhook.ID "msteams/new"}}" method="post">
{{.CsrfTokenHtml}} {{.CsrfTokenHtml}}
<div class="required field {{if .Err_PayloadURL}}error{{end}}"> <div class="required field {{if .Err_PayloadURL}}error{{end}}">
<label for="payload_url">{{ctx.Locale.Tr "repo.settings.payload_url"}}</label> <label for="payload_url">{{ctx.Locale.Tr "repo.settings.payload_url"}}</label>

View file

@ -1,6 +1,6 @@
{{if eq .HookType "packagist"}} {{if eq .HookType "packagist"}}
<p>{{ctx.Locale.Tr "repo.settings.add_web_hook_desc" "https://packagist.org" (ctx.Locale.Tr "repo.settings.web_hook_name_packagist")}}</p> <p>{{ctx.Locale.Tr "repo.settings.add_web_hook_desc" "https://packagist.org" (ctx.Locale.Tr "repo.settings.web_hook_name_packagist")}}</p>
<form class="ui form" action="{{.BaseLink}}/packagist/{{or .Webhook.ID "new"}}" method="post"> <form class="ui form" action="{{.BaseLink}}/{{or .Webhook.ID "packagist/new"}}" method="post">
{{.CsrfTokenHtml}} {{.CsrfTokenHtml}}
<div class="required field {{if .Err_Username}}error{{end}}"> <div class="required field {{if .Err_Username}}error{{end}}">
<label for="username">{{ctx.Locale.Tr "repo.settings.packagist_username"}}</label> <label for="username">{{ctx.Locale.Tr "repo.settings.packagist_username"}}</label>

View file

@ -259,7 +259,7 @@
</div> </div>
<!-- Authorization Header --> <!-- Authorization Header -->
<div class="field{{if eq .HookType "matrix"}} required{{end}}"> <div class="field{{if eq .HookType "matrix"}} required{{end}} {{if .Err_AuthorizationHeader}}error{{end}}">
<label for="authorization_header">{{ctx.Locale.Tr "repo.settings.authorization_header"}}</label> <label for="authorization_header">{{ctx.Locale.Tr "repo.settings.authorization_header"}}</label>
<input id="authorization_header" name="authorization_header" type="text" value="{{.Webhook.HeaderAuthorization}}"{{if eq .HookType "matrix"}} placeholder="Bearer $access_token" required{{end}}> <input id="authorization_header" name="authorization_header" type="text" value="{{.Webhook.HeaderAuthorization}}"{{if eq .HookType "matrix"}} placeholder="Bearer $access_token" required{{end}}>
{{if ne .HookType "matrix"}}{{/* Matrix doesn't make the authorization optional but it is implied by the help string, should be changed.*/}} {{if ne .HookType "matrix"}}{{/* Matrix doesn't make the authorization optional but it is implied by the help string, should be changed.*/}}

View file

@ -1,6 +1,6 @@
{{if eq .HookType "slack"}} {{if eq .HookType "slack"}}
<p>{{ctx.Locale.Tr "repo.settings.add_web_hook_desc" "https://slack.com" (ctx.Locale.Tr "repo.settings.web_hook_name_slack")}}</p> <p>{{ctx.Locale.Tr "repo.settings.add_web_hook_desc" "https://slack.com" (ctx.Locale.Tr "repo.settings.web_hook_name_slack")}}</p>
<form class="ui form" action="{{.BaseLink}}/slack/{{or .Webhook.ID "new"}}" method="post"> <form class="ui form" action="{{.BaseLink}}/{{or .Webhook.ID "slack/new"}}" method="post">
{{.CsrfTokenHtml}} {{.CsrfTokenHtml}}
<div class="required field {{if .Err_PayloadURL}}error{{end}}"> <div class="required field {{if .Err_PayloadURL}}error{{end}}">
<label for="payload_url">{{ctx.Locale.Tr "repo.settings.payload_url"}}</label> <label for="payload_url">{{ctx.Locale.Tr "repo.settings.payload_url"}}</label>

View file

@ -1,6 +1,6 @@
{{if eq .HookType "telegram"}} {{if eq .HookType "telegram"}}
<p>{{ctx.Locale.Tr "repo.settings.add_web_hook_desc" "https://core.telegram.org/bots" (ctx.Locale.Tr "repo.settings.web_hook_name_telegram")}}</p> <p>{{ctx.Locale.Tr "repo.settings.add_web_hook_desc" "https://core.telegram.org/bots" (ctx.Locale.Tr "repo.settings.web_hook_name_telegram")}}</p>
<form class="ui form" action="{{.BaseLink}}/telegram/{{or .Webhook.ID "new"}}" method="post"> <form class="ui form" action="{{.BaseLink}}/{{or .Webhook.ID "telegram/new"}}" method="post">
{{.CsrfTokenHtml}} {{.CsrfTokenHtml}}
<div class="required field {{if .Err_BotToken}}error{{end}}"> <div class="required field {{if .Err_BotToken}}error{{end}}">
<label for="bot_token">{{ctx.Locale.Tr "repo.settings.bot_token"}}</label> <label for="bot_token">{{ctx.Locale.Tr "repo.settings.bot_token"}}</label>

View file

@ -1,6 +1,6 @@
{{if eq .HookType "wechatwork"}} {{if eq .HookType "wechatwork"}}
<p>{{ctx.Locale.Tr "repo.settings.add_web_hook_desc" "https://work.weixin.qq.com" (ctx.Locale.Tr "repo.settings.web_hook_name_wechatwork")}}</p> <p>{{ctx.Locale.Tr "repo.settings.add_web_hook_desc" "https://work.weixin.qq.com" (ctx.Locale.Tr "repo.settings.web_hook_name_wechatwork")}}</p>
<form class="ui form" action="{{.BaseLink}}/wechatwork/{{or .Webhook.ID "new"}}" method="post"> <form class="ui form" action="{{.BaseLink}}/{{or .Webhook.ID "wechatwork/new"}}" method="post">
{{.CsrfTokenHtml}} {{.CsrfTokenHtml}}
<div class="required field {{if .Err_PayloadURL}}error{{end}}"> <div class="required field {{if .Err_PayloadURL}}error{{end}}">
<label for="payload_url">{{ctx.Locale.Tr "repo.settings.payload_url"}}</label> <label for="payload_url">{{ctx.Locale.Tr "repo.settings.payload_url"}}</label>

View file

@ -193,8 +193,8 @@ func TestRedirectsWebhooks(t *testing.T) {
{from: "/user2/repo1/settings/hooks/" + kind + "/new", to: "/", verb: "POST"}, {from: "/user2/repo1/settings/hooks/" + kind + "/new", to: "/", verb: "POST"},
{from: "/admin/system-hooks/" + kind + "/new", to: "/", verb: "POST"}, {from: "/admin/system-hooks/" + kind + "/new", to: "/", verb: "POST"},
{from: "/admin/default-hooks/" + kind + "/new", to: "/", verb: "POST"}, {from: "/admin/default-hooks/" + kind + "/new", to: "/", verb: "POST"},
{from: "/user2/repo1/settings/hooks/" + kind + "/1", to: "/", verb: "POST"}, {from: "/user2/repo1/settings/hooks/1", to: "/", verb: "POST"},
{from: "/admin/hooks/" + kind + "/1", to: "/", verb: "POST"}, {from: "/admin/hooks/1", to: "/", verb: "POST"},
} }
for _, info := range redirects { for _, info := range redirects {
req := NewRequest(t, info.verb, info.from) req := NewRequest(t, info.verb, info.from)

View file

@ -52,7 +52,7 @@ func TestNewWebHookLink(t *testing.T) {
htmlDoc := NewHTMLParser(t, resp.Body) htmlDoc := NewHTMLParser(t, resp.Body)
assert.Equal(t, webhooksLen, htmlDoc.Find(`a[href^="`+baseurl+`/"][href$="/new"]`).Length(), "not all webhooks are listed in the 'new' dropdown on failure") assert.Equal(t, webhooksLen, htmlDoc.Find(`a[href^="`+baseurl+`/"][href$="/new"]`).Length(), "not all webhooks are listed in the 'new' dropdown on failure")
resp = session.MakeRequest(t, NewRequestWithValues(t, "POST", baseurl+"/gitea/1", map[string]string{"_csrf": csrfToken}), http.StatusUnprocessableEntity) resp = session.MakeRequest(t, NewRequestWithValues(t, "POST", baseurl+"/1", map[string]string{"_csrf": csrfToken}), http.StatusUnprocessableEntity)
htmlDoc = NewHTMLParser(t, resp.Body) htmlDoc = NewHTMLParser(t, resp.Body)
assert.Equal(t, webhooksLen, htmlDoc.Find(`a[href^="`+baseurl+`/"][href$="/new"]`).Length(), "not all webhooks are listed in the 'new' dropdown on failure") assert.Equal(t, webhooksLen, htmlDoc.Find(`a[href^="`+baseurl+`/"][href$="/new"]`).Length(), "not all webhooks are listed in the 'new' dropdown on failure")
} }
@ -330,7 +330,16 @@ func testWebhookForms(name string, session *TestSession, validFields map[string]
} }
} }
session.MakeRequest(t, NewRequestWithValues(t, "POST", "/user2/repo1/settings/hooks/"+name+"/new", payload), http.StatusUnprocessableEntity) resp := session.MakeRequest(t, NewRequestWithValues(t, "POST", "/user2/repo1/settings/hooks/"+name+"/new", payload), http.StatusUnprocessableEntity)
// check that the invalid form is pre-filled
htmlForm = NewHTMLParser(t, resp.Body).Find(`form[action^="/user2/repo1/settings/hooks/"]`)
for k, v := range payload {
if k == "_csrf" || k == "events" || v == "" {
// the 'events' is a radio input, which is buggy below
continue
}
assert.Equal(t, v, assertInput(t, htmlForm, k), "input %q did not contain value %q", k, v)
}
if t.Failed() { if t.Failed() {
t.Log(invalidPatch) t.Log(invalidPatch)
} }