diff --git a/models/webhook/hooktask.go b/models/webhook/hooktask.go
index 1d19ebd24e76b..8a9a3ffd1d6aa 100644
--- a/models/webhook/hooktask.go
+++ b/models/webhook/hooktask.go
@@ -116,6 +116,9 @@ type HookTask struct {
RequestInfo *HookRequest `xorm:"-"`
ResponseContent string `xorm:"TEXT"`
ResponseInfo *HookResponse `xorm:"-"`
+
+ // Used for Auth Headers.
+ BearerToken string
}
func init() {
diff --git a/models/webhook/webhook.go b/models/webhook/webhook.go
index ffc9b72b64d88..fba4a566e8510 100644
--- a/models/webhook/webhook.go
+++ b/models/webhook/webhook.go
@@ -162,6 +162,7 @@ const (
MATRIX HookType = "matrix"
WECHATWORK HookType = "wechatwork"
PACKAGIST HookType = "packagist"
+ CUSTOM HookType = "custom"
)
// HookStatus is the status of a web hook
diff --git a/modules/setting/webhook.go b/modules/setting/webhook.go
index 0bfd7dcb4dd3f..8974f92e3876d 100644
--- a/modules/setting/webhook.go
+++ b/modules/setting/webhook.go
@@ -36,7 +36,7 @@ func newWebhookService() {
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{"gitea", "gogs", "slack", "discord", "dingtalk", "telegram", "msteams", "feishu", "matrix", "wechatwork", "packagist", "custom"}
Webhook.PagingNum = sec.Key("PAGING_NUM").MustInt(10)
Webhook.ProxyURL = sec.Key("PROXY_URL").MustString("")
if Webhook.ProxyURL != "" {
diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini
index f52fef3c0590b..ed49cdf522c41 100644
--- a/options/locale/locale_en-US.ini
+++ b/options/locale/locale_en-US.ini
@@ -1957,6 +1957,8 @@ settings.hook_type = Hook Type
settings.slack_token = Token
settings.slack_domain = Domain
settings.slack_channel = Channel
+settings.custom_host_url = Host URL
+settings.custom_auth_token = Custom Auth Token
settings.add_web_hook_desc = Integrate %s into your repository.
settings.web_hook_name_gitea = Gitea
settings.web_hook_name_gogs = Gogs
@@ -1971,6 +1973,7 @@ settings.web_hook_name_feishu = Feishu
settings.web_hook_name_larksuite = Lark Suite
settings.web_hook_name_wechatwork = WeCom (Wechat Work)
settings.web_hook_name_packagist = Packagist
+settings.web_hook_name_custom = Custom
settings.packagist_username = Packagist username
settings.packagist_api_token = API token
settings.packagist_package_url = Packagist package URL
diff --git a/public/img/gitea_custom.svg b/public/img/gitea_custom.svg
new file mode 100644
index 0000000000000..d027534502820
--- /dev/null
+++ b/public/img/gitea_custom.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/routers/web/repo/webhook.go b/routers/web/repo/webhook.go
index fb984de7f5857..a2e0649103224 100644
--- a/routers/web/repo/webhook.go
+++ b/routers/web/repo/webhook.go
@@ -735,6 +735,65 @@ func PackagistHooksNewPost(ctx *context.Context) {
ctx.Redirect(orCtx.Link)
}
+// CustomHooksNewPost response for creating Custom hook
+func CustomHooksNewPost(ctx *context.Context) {
+ form := web.GetForm(ctx).(*forms.NewCustomHookForm)
+ ctx.Data["Title"] = ctx.Tr("repo.settings.add_webhook")
+ ctx.Data["PageIsSettingHooks"] = true
+ ctx.Data["PageIsSettingHooksNew"] = true
+ ctx.Data["Webhook"] = webhook.Webhook{HookEvent: &webhook.HookEvent{}}
+ ctx.Data["HookType"] = webhook.CUSTOM
+
+ orCtx, err := getOrgRepoCtx(ctx)
+ if err != nil {
+ ctx.ServerError("getOrgRepoCtx", err)
+ }
+ ctx.Data["BaseLink"] = orCtx.Link
+
+ if ctx.HasError() {
+ ctx.HTML(200, orCtx.NewTemplate)
+ return
+ }
+
+ meta, err := json.Marshal(&webhook_service.CustomMeta{
+ HostURL: form.HostURL,
+ AuthToken: form.AuthToken,
+ })
+ if err != nil {
+ ctx.ServerError("Marshal", err)
+ return
+ }
+
+ payloadURL, err := buildCustomURL(form)
+ if err != nil {
+ ctx.ServerError("buildCustomURL", err)
+ return
+ }
+
+ w := &webhook.Webhook{
+ RepoID: orCtx.RepoID,
+ URL: payloadURL,
+ ContentType: webhook.ContentTypeForm,
+ HookEvent: ParseHookEvent(form.WebhookForm),
+ IsActive: form.Active,
+ Type: webhook.CUSTOM,
+ HTTPMethod: http.MethodPost,
+ 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(db.DefaultContext, 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) {
ctx.Data["RequireHighlightJS"] = true
@@ -774,6 +833,8 @@ func checkWebhook(ctx *context.Context) (*orgRepoCtx, *webhook.Webhook) {
ctx.Data["MatrixHook"] = webhook_service.GetMatrixHook(w)
case webhook.PACKAGIST:
ctx.Data["PackagistHook"] = webhook_service.GetPackagistHook(w)
+ case webhook.CUSTOM:
+ ctx.Data["CustomHook"] = webhook_service.GetCustomHook(w)
}
ctx.Data["History"], err = w.History(1)
@@ -1236,6 +1297,75 @@ func PackagistHooksEditPost(ctx *context.Context) {
ctx.Redirect(fmt.Sprintf("%s/%d", orCtx.Link, w.ID))
}
+// CustomHooksEditPost response for editing custom hook
+func CustomHookEditPost(ctx *context.Context) {
+ form := web.GetForm(ctx).(*forms.NewCustomHookForm)
+ ctx.Data["Title"] = ctx.Tr("repo.settings")
+ ctx.Data["PageIsSettingHooks"] = true
+ ctx.Data["PageIsSettingHooksNew"] = true
+ ctx.Data["Webhook"] = webhook.Webhook{HookEvent: &webhook.HookEvent{}}
+ ctx.Data["HookType"] = webhook.CUSTOM
+
+ orCtx, err := getOrgRepoCtx(ctx)
+ if err != nil {
+ ctx.ServerError("getOrgRepoCtx", err)
+ }
+ ctx.Data["BaseLink"] = orCtx.Link
+
+ if ctx.HasError() {
+ ctx.HTML(200, orCtx.NewTemplate)
+ return
+ }
+
+ meta, err := json.Marshal(&webhook_service.CustomMeta{
+ HostURL: form.HostURL,
+ AuthToken: form.AuthToken,
+ })
+ if err != nil {
+ ctx.ServerError("Marshal", err)
+ return
+ }
+
+ payloadURL, err := buildCustomURL(form)
+ if err != nil {
+ ctx.ServerError("buildCustomURL", err)
+ return
+ }
+
+ w := &webhook.Webhook{
+ RepoID: orCtx.RepoID,
+ URL: payloadURL,
+ ContentType: webhook.ContentTypeForm,
+ HookEvent: ParseHookEvent(form.WebhookForm),
+ IsActive: form.Active,
+ Type: webhook.CUSTOM,
+ HTTPMethod: http.MethodPost,
+ 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(db.DefaultContext, w); err != nil {
+ ctx.ServerError("CreateWebhook", err)
+ return
+ }
+
+ ctx.Flash.Success(ctx.Tr("repo.settings.add_hook_success"))
+ ctx.Redirect(orCtx.Link)
+}
+
+// buildCustomURL returns the correct REST API url for a Custom POST request.
+func buildCustomURL(meta *forms.NewCustomHookForm) (string, error) {
+ tcURL, err := url.Parse(meta.HostURL)
+ if err != nil {
+ return "", err
+ }
+
+ return tcURL.String(), nil
+}
+
// TestWebhook test if web hook is work fine
func TestWebhook(ctx *context.Context) {
hookID := ctx.ParamsInt64(":id")
diff --git a/routers/web/web.go b/routers/web/web.go
index d8c197fb967e2..909fe23f66df5 100644
--- a/routers/web/web.go
+++ b/routers/web/web.go
@@ -457,6 +457,7 @@ func RegisterRoutes(m *web.Route) {
m.Post("/feishu/{id}", bindIgnErr(forms.NewFeishuHookForm{}), repo.FeishuHooksEditPost)
m.Post("/wechatwork/{id}", bindIgnErr(forms.NewWechatWorkHookForm{}), repo.WechatworkHooksEditPost)
m.Post("/packagist/{id}", bindIgnErr(forms.NewPackagistHookForm{}), repo.PackagistHooksEditPost)
+ m.Post("/custom/{id}", bindIgnErr(forms.NewCustomHookForm{}), repo.CustomHookEditPost)
}, webhooksEnabled)
m.Group("/{configType:default-hooks|system-hooks}", func() {
@@ -472,6 +473,7 @@ func RegisterRoutes(m *web.Route) {
m.Post("/feishu/new", bindIgnErr(forms.NewFeishuHookForm{}), repo.FeishuHooksNewPost)
m.Post("/wechatwork/new", bindIgnErr(forms.NewWechatWorkHookForm{}), repo.WechatworkHooksNewPost)
m.Post("/packagist/new", bindIgnErr(forms.NewPackagistHookForm{}), repo.PackagistHooksNewPost)
+ m.Post("/custom/{id}", bindIgnErr(forms.NewCustomHookForm{}), repo.CustomHooksNewPost)
})
m.Group("/auths", func() {
@@ -570,6 +572,7 @@ func RegisterRoutes(m *web.Route) {
m.Post("/msteams/new", bindIgnErr(forms.NewMSTeamsHookForm{}), repo.MSTeamsHooksNewPost)
m.Post("/feishu/new", bindIgnErr(forms.NewFeishuHookForm{}), repo.FeishuHooksNewPost)
m.Post("/wechatwork/new", bindIgnErr(forms.NewWechatWorkHookForm{}), repo.WechatworkHooksNewPost)
+ m.Post("/custom/new", bindIgnErr(forms.NewCustomHookForm{}), repo.CustomHooksNewPost)
m.Group("/{id}", func() {
m.Get("", repo.WebHooksEdit)
m.Post("/replay/{uuid}", repo.ReplayWebhook)
@@ -584,6 +587,7 @@ func RegisterRoutes(m *web.Route) {
m.Post("/msteams/{id}", bindIgnErr(forms.NewMSTeamsHookForm{}), repo.MSTeamsHooksEditPost)
m.Post("/feishu/{id}", bindIgnErr(forms.NewFeishuHookForm{}), repo.FeishuHooksEditPost)
m.Post("/wechatwork/{id}", bindIgnErr(forms.NewWechatWorkHookForm{}), repo.WechatworkHooksEditPost)
+ m.Post("/custom/{id}", bindIgnErr(forms.NewCustomHookForm{}), repo.CustomHookEditPost)
}, webhooksEnabled)
m.Group("/labels", func() {
@@ -668,6 +672,7 @@ func RegisterRoutes(m *web.Route) {
m.Post("/feishu/new", bindIgnErr(forms.NewFeishuHookForm{}), repo.FeishuHooksNewPost)
m.Post("/wechatwork/new", bindIgnErr(forms.NewWechatWorkHookForm{}), repo.WechatworkHooksNewPost)
m.Post("/packagist/new", bindIgnErr(forms.NewPackagistHookForm{}), repo.PackagistHooksNewPost)
+ m.Post("/custom/new", bindIgnErr(forms.NewCustomHookForm{}), repo.CustomHooksNewPost)
m.Group("/{id}", func() {
m.Get("", repo.WebHooksEdit)
m.Post("/test", repo.TestWebhook)
@@ -684,6 +689,7 @@ func RegisterRoutes(m *web.Route) {
m.Post("/feishu/{id}", bindIgnErr(forms.NewFeishuHookForm{}), repo.FeishuHooksEditPost)
m.Post("/wechatwork/{id}", bindIgnErr(forms.NewWechatWorkHookForm{}), repo.WechatworkHooksEditPost)
m.Post("/packagist/{id}", bindIgnErr(forms.NewPackagistHookForm{}), repo.PackagistHooksEditPost)
+ m.Post("/custom/{id}", bindIgnErr(forms.NewCustomHookForm{}), repo.CustomHookEditPost)
}, webhooksEnabled)
m.Group("/keys", func() {
diff --git a/services/forms/repo_form.go b/services/forms/repo_form.go
index da709ef800240..9739f31925a73 100644
--- a/services/forms/repo_form.go
+++ b/services/forms/repo_form.go
@@ -404,6 +404,12 @@ type NewPackagistHookForm struct {
WebhookForm
}
+type NewCustomHookForm struct {
+ HostURL string `binding:"Required;ValidUrl"`
+ AuthToken string
+ WebhookForm
+}
+
// Validate validates the fields
func (f *NewPackagistHookForm) Validate(req *http.Request, errs binding.Errors) binding.Errors {
ctx := context.GetContext(req)
diff --git a/services/webhook/custom.go b/services/webhook/custom.go
new file mode 100644
index 0000000000000..9d525bf1898d2
--- /dev/null
+++ b/services/webhook/custom.go
@@ -0,0 +1,35 @@
+// Copyright 2022 The Gitea Authors. All rights reserved.
+// Use of this source code is governed by a MIT-style
+// license that can be found in the LICENSE file.package webhook
+
+package webhook
+
+import (
+ webhook_model "code.gitea.io/gitea/models/webhook"
+ "code.gitea.io/gitea/modules/json"
+ "code.gitea.io/gitea/modules/log"
+ api "code.gitea.io/gitea/modules/structs"
+)
+
+type (
+ // Custom contains metadata for the Custom WebHook
+ CustomMeta struct {
+ HostURL string `json:"host_url"`
+ AuthToken string `json:"auth_token,omitempty"`
+ }
+)
+
+// GetCustomPayload returns the payload as-is
+func GetCustomPayload(p api.Payloader, event webhook_model.HookEventType, meta string) (api.Payloader, error) {
+ // TODO: add optional body on POST.
+ return p, nil
+}
+
+// GetCustomHook returns Custom metadata
+func GetCustomHook(w *webhook_model.Webhook) *CustomMeta {
+ s := &CustomMeta{}
+ if err := json.Unmarshal([]byte(w.Meta), s); err != nil {
+ log.Error("webhook.GetCustomHook(%d): %v", w.ID, err)
+ }
+ return s
+}
diff --git a/services/webhook/custom_test.go b/services/webhook/custom_test.go
new file mode 100644
index 0000000000000..6af6b5d7edbf5
--- /dev/null
+++ b/services/webhook/custom_test.go
@@ -0,0 +1,52 @@
+// Copyright 2022 The Gitea Authors. All rights reserved.
+// Use of this source code is governed by a MIT-style
+// license that can be found in the LICENSE file.package webhook
+
+package webhook
+
+import (
+ "testing"
+
+ webhook_model "code.gitea.io/gitea/models/webhook"
+
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
+
+ _ "github.com/mattn/go-sqlite3"
+)
+
+func TestGetCustomPayload(t *testing.T) {
+ t.Run("Payload isn't altered.", func(t *testing.T) {
+ p := createTestPayload()
+
+ pl, err := GetCustomPayload(p, webhook_model.HookEventPush, "")
+ require.NoError(t, err)
+ require.Equal(t, p, pl)
+ })
+}
+
+func TestWebhook_GetCustomHook(t *testing.T) {
+ // Run with bearer token
+ t.Run("GetCustomHook", func(t *testing.T) {
+ w := &webhook_model.Webhook{
+ Meta: `{"host_url": "http://localhost.com", "auth_token": "testToken"}`,
+ }
+
+ customHook := GetCustomHook(w)
+ assert.Equal(t, *customHook, CustomMeta{
+ HostURL: "http://localhost.com",
+ AuthToken: "testToken",
+ })
+ })
+ // Run without bearer token
+ t.Run("GetCustomHook", func(t *testing.T) {
+ w := &webhook_model.Webhook{
+ Meta: `{"host_url": "http://localhost.com"}`,
+ }
+
+ customHook := GetCustomHook(w)
+ assert.Equal(t, *customHook, CustomMeta{
+ HostURL: "http://localhost.com",
+ })
+ })
+}
diff --git a/services/webhook/deliver.go b/services/webhook/deliver.go
index 88b709cb41e74..4f95105468cb5 100644
--- a/services/webhook/deliver.go
+++ b/services/webhook/deliver.go
@@ -116,6 +116,9 @@ func Deliver(t *webhook_model.HookTask) error {
event := t.EventType.Event()
eventType := string(t.EventType)
+ if t.BearerToken != "" {
+ req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", t.BearerToken))
+ }
req.Header.Add("X-Gitea-Delivery", t.UUID)
req.Header.Add("X-Gitea-Event", event)
req.Header.Add("X-Gitea-Event-Type", eventType)
diff --git a/services/webhook/webhook.go b/services/webhook/webhook.go
index 607fac963452f..bcd13d3a911cc 100644
--- a/services/webhook/webhook.go
+++ b/services/webhook/webhook.go
@@ -62,6 +62,10 @@ var webhooks = map[webhook_model.HookType]*webhook{
name: webhook_model.PACKAGIST,
payloadCreator: GetPackagistPayload,
},
+ webhook_model.CUSTOM: {
+ name: webhook_model.CUSTOM,
+ payloadCreator: GetCustomPayload,
+ },
}
// RegisterWebhook registers a webhook
@@ -170,11 +174,19 @@ func prepareWebhook(w *webhook_model.Webhook, repo *repo_model.Repository, event
payloader = p
}
+ // Load bearer token
+ var authToken string
+ switch w.Type {
+ case webhook_model.CUSTOM:
+ authToken = GetCustomHook(w).AuthToken
+ }
+
if err = webhook_model.CreateHookTask(&webhook_model.HookTask{
- RepoID: repo.ID,
- HookID: w.ID,
- Payloader: payloader,
- EventType: event,
+ RepoID: repo.ID,
+ HookID: w.ID,
+ Payloader: payloader,
+ EventType: event,
+ BearerToken: authToken,
}); err != nil {
return fmt.Errorf("CreateHookTask: %v", err)
}
diff --git a/templates/admin/hook_new.tmpl b/templates/admin/hook_new.tmpl
index 049e54ef833cf..c47c326b353da 100644
--- a/templates/admin/hook_new.tmpl
+++ b/templates/admin/hook_new.tmpl
@@ -36,6 +36,8 @@
{{else if eq .HookType "packagist"}}
+ {{else if eq .HookType "custom"}}
+
{{end}}
@@ -51,6 +53,7 @@
{{template "repo/settings/webhook/matrix" .}}
{{template "repo/settings/webhook/wechatwork" .}}
{{template "repo/settings/webhook/packagist" .}}
+ {{template "repo/settings/webhook/custom" .}}
{{template "repo/settings/webhook/history" .}}
diff --git a/templates/org/settings/hook_new.tmpl b/templates/org/settings/hook_new.tmpl
index 5e8ebb51e9427..d0e7484b7e214 100644
--- a/templates/org/settings/hook_new.tmpl
+++ b/templates/org/settings/hook_new.tmpl
@@ -31,6 +31,8 @@
{{else if eq .HookType "packagist"}}
+ {{else if eq .HookType "custom"}}
+
{{end}}
@@ -46,6 +48,7 @@
{{template "repo/settings/webhook/matrix" .}}
{{template "repo/settings/webhook/wechatwork" .}}
{{template "repo/settings/webhook/packagist" .}}
+ {{template "repo/settings/webhook/custom" .}}
{{template "repo/settings/webhook/history" .}}
diff --git a/templates/repo/settings/webhook/base_list.tmpl b/templates/repo/settings/webhook/base_list.tmpl
index b1a3771bdba11..22b870b15bb04 100644
--- a/templates/repo/settings/webhook/base_list.tmpl
+++ b/templates/repo/settings/webhook/base_list.tmpl
@@ -37,6 +37,9 @@
{{.i18n.Tr "repo.settings.web_hook_name_packagist"}}
+
+
{{.i18n.Tr "repo.settings.web_hook_name_custom"}}
+
diff --git a/templates/repo/settings/webhook/custom.tmpl b/templates/repo/settings/webhook/custom.tmpl
new file mode 100644
index 0000000000000..bbc5580ada6b7
--- /dev/null
+++ b/templates/repo/settings/webhook/custom.tmpl
@@ -0,0 +1,16 @@
+{{if eq .HookType "custom"}}
+
{{.i18n.Tr "repo.settings.add_web_hook_desc" "https://docs.gitea.io/en-us/webhooks/" (.i18n.Tr "repo.settings.web_hook_name_custom") | Str2html}}
+ +{{end}} diff --git a/templates/repo/settings/webhook/new.tmpl b/templates/repo/settings/webhook/new.tmpl index a438a4c71a3d7..7245827d6dad4 100644 --- a/templates/repo/settings/webhook/new.tmpl +++ b/templates/repo/settings/webhook/new.tmpl @@ -29,6 +29,8 @@