diff --git a/modules/setting/webhook.go b/modules/setting/webhook.go index c01261dbbd..b56c55c439 100644 --- a/modules/setting/webhook.go +++ b/modules/setting/webhook.go @@ -35,7 +35,7 @@ func loadWebhookFrom(rootCfg ConfigProvider) { Webhook.DeliverTimeout = sec.Key("DELIVER_TIMEOUT").MustInt(5) Webhook.SkipTLSVerify = sec.Key("SKIP_TLS_VERIFY").MustBool() Webhook.AllowedHostList = sec.Key("ALLOWED_HOST_LIST").MustString("") - Webhook.Types = []string{"gitea", "gogs", "slack", "discord", "dingtalk", "telegram", "msteams", "feishu", "matrix", "wechatwork", "packagist"} + Webhook.Types = []string{"forgejo", "gitea", "gogs", "slack", "discord", "dingtalk", "telegram", "msteams", "feishu", "matrix", "wechatwork", "packagist"} Webhook.PagingNum = sec.Key("PAGING_NUM").MustInt(10) Webhook.ProxyURL = sec.Key("PROXY_URL").MustString("") if Webhook.ProxyURL != "" { diff --git a/modules/structs/hook.go b/modules/structs/hook.go index 0babe84410..9afcebdc59 100644 --- a/modules/structs/hook.go +++ b/modules/structs/hook.go @@ -41,7 +41,7 @@ type CreateHookOptionConfig map[string]string // CreateHookOption options when create a hook type CreateHookOption struct { // required: true - // enum: dingtalk,discord,gitea,gogs,msteams,slack,telegram,feishu,wechatwork,packagist + // enum: forgejo,dingtalk,discord,gitea,gogs,msteams,slack,telegram,feishu,wechatwork,packagist Type string `json:"type" binding:"Required"` // required: true Config CreateHookOptionConfig `json:"config" binding:"Required"` diff --git a/modules/webhook/type.go b/modules/webhook/type.go index 7042d391b7..7f427f2ea8 100644 --- a/modules/webhook/type.go +++ b/modules/webhook/type.go @@ -72,6 +72,7 @@ type HookType = string // Types of webhooks const ( + FORGEJO HookType = "forgejo" GITEA HookType = "gitea" GOGS HookType = "gogs" SLACK HookType = "slack" diff --git a/routers/web/repo/setting/webhook.go b/routers/web/repo/setting/webhook.go index 33ea2c206b..718e2a0bc2 100644 --- a/routers/web/repo/setting/webhook.go +++ b/routers/web/repo/setting/webhook.go @@ -44,7 +44,7 @@ func Webhooks(ctx *context.Context) { ctx.Data["PageIsSettingsHooks"] = true ctx.Data["BaseLink"] = ctx.Repo.RepoLink + "/settings/hooks" ctx.Data["BaseLinkNew"] = ctx.Repo.RepoLink + "/settings/hooks" - ctx.Data["Description"] = ctx.Tr("repo.settings.hooks_desc", "https://docs.gitea.com/usage/webhooks") + ctx.Data["Description"] = ctx.Tr("repo.settings.hooks_desc", "https://forgejo.org/docs/latest/user/webhooks/") ws, err := webhook.ListWebhooksByOpts(ctx, &webhook.ListWebhookOptions{RepoID: ctx.Repo.Repository.ID}) if err != nil { @@ -309,6 +309,34 @@ func editWebhook(ctx *context.Context, params webhookParams) { ctx.Redirect(fmt.Sprintf("%s/%d", orCtx.Link, w.ID)) } +// ForgejoHooksNewPost response for creating Forgejo webhook +func ForgejoHooksNewPost(ctx *context.Context) { + createWebhook(ctx, forgejoHookParams(ctx)) +} + +// ForgejoHooksEditPost response for editing Forgejo webhook +func ForgejoHooksEditPost(ctx *context.Context) { + editWebhook(ctx, forgejoHookParams(ctx)) +} + +func forgejoHookParams(ctx *context.Context) webhookParams { + form := web.GetForm(ctx).(*forms.NewWebhookForm) + + contentType := webhook.ContentTypeJSON + if webhook.HookContentType(form.ContentType) == webhook.ContentTypeForm { + contentType = webhook.ContentTypeForm + } + + return webhookParams{ + Type: webhook_module.FORGEJO, + URL: form.PayloadURL, + ContentType: contentType, + Secret: form.Secret, + HTTPMethod: form.HTTPMethod, + WebhookForm: form.WebhookForm, + } +} + // GiteaHooksNewPost response for creating Gitea webhook func GiteaHooksNewPost(ctx *context.Context) { createWebhook(ctx, giteaHookParams(ctx)) diff --git a/routers/web/web.go b/routers/web/web.go index 54d2df2eed..f0f2faaf1d 100644 --- a/routers/web/web.go +++ b/routers/web/web.go @@ -390,6 +390,7 @@ func registerRoutes(m *web.Route) { addWebhookAddRoutes := func() { m.Get("/{type}/new", repo_setting.WebhooksNew) + m.Post("/forgejo/new", web.Bind(forms.NewWebhookForm{}), repo_setting.ForgejoHooksNewPost) m.Post("/gitea/new", web.Bind(forms.NewWebhookForm{}), repo_setting.GiteaHooksNewPost) m.Post("/gogs/new", web.Bind(forms.NewGogshookForm{}), repo_setting.GogsHooksNewPost) m.Post("/slack/new", web.Bind(forms.NewSlackHookForm{}), repo_setting.SlackHooksNewPost) @@ -404,6 +405,7 @@ func registerRoutes(m *web.Route) { } addWebhookEditRoutes := func() { + m.Post("/forgejo/{id}", web.Bind(forms.NewWebhookForm{}), repo_setting.ForgejoHooksEditPost) m.Post("/gitea/{id}", web.Bind(forms.NewWebhookForm{}), repo_setting.GiteaHooksEditPost) m.Post("/gogs/{id}", web.Bind(forms.NewGogshookForm{}), repo_setting.GogsHooksEditPost) m.Post("/slack/{id}", web.Bind(forms.NewSlackHookForm{}), repo_setting.SlackHooksEditPost) diff --git a/services/webhook/webhook.go b/services/webhook/webhook.go index 9d5dab85f7..f15c853331 100644 --- a/services/webhook/webhook.go +++ b/services/webhook/webhook.go @@ -70,7 +70,7 @@ var webhooks = map[webhook_module.HookType]*webhook{ // IsValidHookTaskType returns true if a webhook registered func IsValidHookTaskType(name string) bool { - if name == webhook_module.GITEA || name == webhook_module.GOGS { + if name == webhook_module.FORGEJO || name == webhook_module.GITEA || name == webhook_module.GOGS { return true } _, ok := webhooks[name] @@ -177,7 +177,7 @@ func PrepareWebhook(ctx context.Context, w *webhook_model.Webhook, event webhook // Avoid sending "0 new commits" to non-integration relevant webhooks (e.g. slack, discord, etc.). // Integration webhooks (e.g. drone) still receive the required data. if pushEvent, ok := p.(*api.PushPayload); ok && - w.Type != webhook_module.GITEA && w.Type != webhook_module.GOGS && + w.Type != webhook_module.FORGEJO && w.Type != webhook_module.GITEA && w.Type != webhook_module.GOGS && len(pushEvent.Commits) == 0 { return nil } diff --git a/templates/repo/settings/webhook/base_list.tmpl b/templates/repo/settings/webhook/base_list.tmpl index ed6e670d60..cafe88bb7c 100644 --- a/templates/repo/settings/webhook/base_list.tmpl +++ b/templates/repo/settings/webhook/base_list.tmpl @@ -4,6 +4,10 @@ <div class="ui jump dropdown"> <div class="ui primary tiny button">{{ctx.Locale.Tr "repo.settings.add_webhook"}}</div> <div class="menu"> + <a class="item" href="{{.BaseLinkNew}}/forgejo/new"> + {{template "shared/webhook/icon" (dict "HookType" "forgejo" "Size" 20)}} + {{.locale.Tr "repo.settings.web_hook_name_forgejo"}} + </a> <a class="item" href="{{.BaseLinkNew}}/gitea/new"> {{template "shared/webhook/icon" (dict "HookType" "gitea" "Size" 20)}} {{ctx.Locale.Tr "repo.settings.web_hook_name_gitea"}} diff --git a/templates/repo/settings/webhook/forgejo.tmpl b/templates/repo/settings/webhook/forgejo.tmpl new file mode 100644 index 0000000000..320b064f93 --- /dev/null +++ b/templates/repo/settings/webhook/forgejo.tmpl @@ -0,0 +1,40 @@ +{{if eq .HookType "forgejo"}} + <p>{{.locale.Tr "repo.settings.add_web_hook_desc" "https://forgejo.org/docs/latest/user/webhooks/" (.locale.Tr "repo.settings.web_hook_name_forgejo") | Str2html}}</p> + <form class="ui form" action="{{.BaseLink}}/forgejo/{{or .Webhook.ID "new"}}" method="post"> + {{template "base/disable_form_autofill"}} + {{.CsrfTokenHtml}} + <div class="required field {{if .Err_PayloadURL}}error{{end}}"> + <label for="payload_url">{{.locale.Tr "repo.settings.payload_url"}}</label> + <input id="payload_url" name="payload_url" type="url" value="{{.Webhook.URL}}" autofocus required> + </div> + <div class="field"> + <label>{{.locale.Tr "repo.settings.http_method"}}</label> + <div class="ui selection dropdown"> + <input type="hidden" id="http_method" name="http_method" value="{{if .Webhook.HTTPMethod}}{{.Webhook.HTTPMethod}}{{else}}POST{{end}}"> + <div class="default text"></div> + {{svg "octicon-triangle-down" 14 "dropdown icon"}} + <div class="menu"> + <div class="item" data-value="POST">POST</div> + <div class="item" data-value="GET">GET</div> + </div> + </div> + </div> + <div class="field"> + <label>{{.locale.Tr "repo.settings.content_type"}}</label> + <div class="ui selection dropdown"> + <input type="hidden" id="content_type" name="content_type" value="{{if .Webhook.ContentType}}{{.Webhook.ContentType}}{{else}}1{{end}}"> + <div class="default text"></div> + {{svg "octicon-triangle-down" 14 "dropdown icon"}} + <div class="menu"> + <div class="item" data-value="1">application/json</div> + <div class="item" data-value="2">application/x-www-form-urlencoded</div> + </div> + </div> + </div> + <div class="field {{if .Err_Secret}}error{{end}}"> + <label for="secret">{{.locale.Tr "repo.settings.secret"}}</label> + <input id="secret" name="secret" type="password" value="{{.Webhook.Secret}}" autocomplete="off"> + </div> + {{template "repo/settings/webhook/settings" .}} + </form> +{{end}} diff --git a/templates/repo/settings/webhook/gitea.tmpl b/templates/repo/settings/webhook/gitea.tmpl index 4fda6a7b39..743c5169cf 100644 --- a/templates/repo/settings/webhook/gitea.tmpl +++ b/templates/repo/settings/webhook/gitea.tmpl @@ -1,5 +1,5 @@ {{if eq .HookType "gitea"}} - <p>{{ctx.Locale.Tr "repo.settings.add_web_hook_desc" "https://docs.gitea.com/usage/webhooks" (ctx.Locale.Tr "repo.settings.web_hook_name_gitea") | Str2html}}</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") | Str2html}}</p> <form class="ui form" action="{{.BaseLink}}/gitea/{{or .Webhook.ID "new"}}" method="post"> {{template "base/disable_form_autofill"}} {{.CsrfTokenHtml}} diff --git a/templates/repo/settings/webhook/gogs.tmpl b/templates/repo/settings/webhook/gogs.tmpl index d2bd98c32c..62ca2e1d99 100644 --- a/templates/repo/settings/webhook/gogs.tmpl +++ b/templates/repo/settings/webhook/gogs.tmpl @@ -1,5 +1,5 @@ {{if eq .HookType "gogs"}} - <p>{{ctx.Locale.Tr "repo.settings.add_web_hook_desc" "https://docs.gitea.com/usage/webhooks" (ctx.Locale.Tr "repo.settings.web_hook_name_gogs") | Str2html}}</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") | Str2html}}</p> <form class="ui form" action="{{.BaseLink}}/gogs/{{or .Webhook.ID "new"}}" method="post"> {{template "base/disable_form_autofill"}} {{.CsrfTokenHtml}} diff --git a/templates/shared/webhook/icon.tmpl b/templates/shared/webhook/icon.tmpl index 84f9de266f..a6507bab5b 100644 --- a/templates/shared/webhook/icon.tmpl +++ b/templates/shared/webhook/icon.tmpl @@ -2,8 +2,10 @@ {{if .Size}} {{$size = .Size}} {{end}} -{{if eq .HookType "gitea"}} - <img width="{{$size}}" height="{{$size}}" src="{{AssetUrlPrefix}}/img/gitea.svg"> +{{if eq .HookType "forgejo"}} + <img width="{{$size}}" height="{{$size}}" src="{{AssetUrlPrefix}}/img/forgejo.svg"> +{{else if eq .HookType "gitea"}} + <img width="{{$size}}" height="{{$size}}" src="{{AssetUrlPrefix}}/img/gitea-original.svg"> {{else if eq .HookType "gogs"}} <img width="{{$size}}" height="{{$size}}" src="{{AssetUrlPrefix}}/img/gogs.ico"> {{else if eq .HookType "slack"}} diff --git a/templates/swagger/v1_json.tmpl b/templates/swagger/v1_json.tmpl index 4a6aca7b96..fe70ead8f3 100644 --- a/templates/swagger/v1_json.tmpl +++ b/templates/swagger/v1_json.tmpl @@ -17736,6 +17736,7 @@ "type": { "type": "string", "enum": [ + "forgejo", "dingtalk", "discord", "gitea", diff --git a/templates/webhook/new.tmpl b/templates/webhook/new.tmpl index a185c42b51..60c729eee3 100644 --- a/templates/webhook/new.tmpl +++ b/templates/webhook/new.tmpl @@ -5,6 +5,7 @@ </div> </h4> <div class="ui attached segment"> + {{template "repo/settings/webhook/forgejo" .ctxData}} {{template "repo/settings/webhook/gitea" .ctxData}} {{template "repo/settings/webhook/gogs" .ctxData}} {{template "repo/settings/webhook/slack" .ctxData}} diff --git a/tests/integration/links_test.go b/tests/integration/links_test.go index a3937dd697..11e6146d07 100644 --- a/tests/integration/links_test.go +++ b/tests/integration/links_test.go @@ -176,6 +176,36 @@ func TestLinksLogin(t *testing.T) { testLinksAsUser("user2", t) } +func TestRedirectsWebhooks(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + // + // A redirect means the route exists but not if it performs as intended. + // + for _, kind := range []string{"forgejo", "gitea"} { + redirects := []struct { + from string + to string + verb string + }{ + {from: "/user2/repo1/settings/hooks/" + kind + "/new", to: "/user/login", verb: "GET"}, + {from: "/user/settings/hooks/" + kind + "/new", to: "/user/login", verb: "GET"}, + {from: "/admin/system-hooks/" + kind + "/new", to: "/user/login", verb: "GET"}, + {from: "/admin/default-hooks/" + kind + "/new", to: "/user/login", verb: "GET"}, + {from: "/user2/repo1/settings/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: "/user2/repo1/settings/hooks/" + kind + "/1", to: "/", verb: "POST"}, + {from: "/admin/hooks/" + kind + "/1", to: "/", verb: "POST"}, + } + for _, info := range redirects { + req := NewRequest(t, info.verb, info.from) + resp := MakeRequest(t, req, http.StatusSeeOther) + assert.EqualValues(t, path.Join(setting.AppSubURL, info.to), test.RedirectURL(resp), info.from) + } + } +} + func TestRepoLinks(t *testing.T) { defer tests.PrepareTestEnv(t)()