Skip to content

Commit a9f4489

Browse files
jamesorlakinlunnylafriks
authored
System-wide webhooks (#10546)
* Create system webhook column (and migration) * Create system webhook DB methods Based on the default webhook ones * Modify router to handle system webhooks and default ones * Remove old unused admin nav template * Adjust orgRepoCtx to differentiate system and default webhook URLs * Assign IsSystemWebhook when creating webhooks * Correctly use booleans for IsSystemWebhook * Use system webhooks when preparing webhooks for payload * Add UI and locale changes * Use router params to differentiate admin hook pages * Fix deleting admin webhooks and rename method * Add clarity to webhook docs * Revert "Remove old unused admin nav template" This reverts commit 191a20a. * Rename WebHooksNewPost to GiteaHooksNewPost for clarity * Reintroduce blank line lost during merge conflict Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com> Co-authored-by: Lauris BH <lauris@nix.lv>
1 parent b8551f8 commit a9f4489

File tree

10 files changed

+232
-122
lines changed

10 files changed

+232
-122
lines changed

docs/content/doc/features/webhooks.en-us.md

+5-5
Original file line numberDiff line numberDiff line change
@@ -15,24 +15,24 @@ menu:
1515

1616
# Webhooks
1717

18-
Gitea supports web hooks for repository events. This can be found in the settings
19-
page `/:username/:reponame/settings/hooks`. All event pushes are POST requests.
20-
The methods currently supported are:
18+
Gitea supports web hooks for repository events. This can be configured in the settings
19+
page `/:username/:reponame/settings/hooks` by a repository admin. Webhooks can also be configured on a per-organization and whole system basis.
20+
All event pushes are POST requests. The methods currently supported are:
2121

22-
- Gitea
22+
- Gitea (can also be a GET request)
2323
- Gogs
2424
- Slack
2525
- Discord
2626
- Dingtalk
2727
- Telegram
2828
- Microsoft Teams
29+
- Feishu
2930

3031
### Event information
3132

3233
The following is an example of event information that will be sent by Gitea to
3334
a Payload URL:
3435

35-
3636
```
3737
X-GitHub-Delivery: f6266f16-1bf3-46a5-9ea4-602e06ead473
3838
X-GitHub-Event: push

models/migrations/migrations.go

+2
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,8 @@ var migrations = []Migration{
194194
NewMigration("remove dependencies from deleted repositories", purgeUnusedDependencies),
195195
// v130 -> v131
196196
NewMigration("Expand webhooks for more granularity", expandWebhooks),
197+
// v131 -> v132
198+
NewMigration("Add IsSystemWebhook column to webhooks table", addSystemWebhookColumn),
197199
}
198200

199201
// Migrate database to current version

models/migrations/v131.go

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
// Copyright 2020 The Gitea Authors. All rights reserved.
2+
// Use of this source code is governed by a MIT-style
3+
// license that can be found in the LICENSE file.
4+
5+
package migrations
6+
7+
import (
8+
"fmt"
9+
10+
"xorm.io/xorm"
11+
)
12+
13+
func addSystemWebhookColumn(x *xorm.Engine) error {
14+
type Webhook struct {
15+
IsSystemWebhook bool `xorm:"NOT NULL DEFAULT false"`
16+
}
17+
18+
if err := x.Sync2(new(Webhook)); err != nil {
19+
return fmt.Errorf("Sync2: %v", err)
20+
}
21+
return nil
22+
}

models/webhook.go

+46-19
Original file line numberDiff line numberDiff line change
@@ -99,21 +99,22 @@ const (
9999

100100
// Webhook represents a web hook object.
101101
type Webhook struct {
102-
ID int64 `xorm:"pk autoincr"`
103-
RepoID int64 `xorm:"INDEX"`
104-
OrgID int64 `xorm:"INDEX"`
105-
URL string `xorm:"url TEXT"`
106-
Signature string `xorm:"TEXT"`
107-
HTTPMethod string `xorm:"http_method"`
108-
ContentType HookContentType
109-
Secret string `xorm:"TEXT"`
110-
Events string `xorm:"TEXT"`
111-
*HookEvent `xorm:"-"`
112-
IsSSL bool `xorm:"is_ssl"`
113-
IsActive bool `xorm:"INDEX"`
114-
HookTaskType HookTaskType
115-
Meta string `xorm:"TEXT"` // store hook-specific attributes
116-
LastStatus HookStatus // Last delivery status
102+
ID int64 `xorm:"pk autoincr"`
103+
RepoID int64 `xorm:"INDEX"` // An ID of 0 indicates either a default or system webhook
104+
OrgID int64 `xorm:"INDEX"`
105+
IsSystemWebhook bool
106+
URL string `xorm:"url TEXT"`
107+
Signature string `xorm:"TEXT"`
108+
HTTPMethod string `xorm:"http_method"`
109+
ContentType HookContentType
110+
Secret string `xorm:"TEXT"`
111+
Events string `xorm:"TEXT"`
112+
*HookEvent `xorm:"-"`
113+
IsSSL bool `xorm:"is_ssl"`
114+
IsActive bool `xorm:"INDEX"`
115+
HookTaskType HookTaskType
116+
Meta string `xorm:"TEXT"` // store hook-specific attributes
117+
LastStatus HookStatus // Last delivery status
117118

118119
CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"`
119120
UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"`
@@ -401,7 +402,7 @@ func GetWebhooksByOrgID(orgID int64, listOptions ListOptions) ([]*Webhook, error
401402
func GetDefaultWebhook(id int64) (*Webhook, error) {
402403
webhook := &Webhook{ID: id}
403404
has, err := x.
404-
Where("repo_id=? AND org_id=?", 0, 0).
405+
Where("repo_id=? AND org_id=? AND is_system_webhook=?", 0, 0, false).
405406
Get(webhook)
406407
if err != nil {
407408
return nil, err
@@ -419,7 +420,33 @@ func GetDefaultWebhooks() ([]*Webhook, error) {
419420
func getDefaultWebhooks(e Engine) ([]*Webhook, error) {
420421
webhooks := make([]*Webhook, 0, 5)
421422
return webhooks, e.
422-
Where("repo_id=? AND org_id=?", 0, 0).
423+
Where("repo_id=? AND org_id=? AND is_system_webhook=?", 0, 0, false).
424+
Find(&webhooks)
425+
}
426+
427+
// GetSystemWebhook returns admin system webhook by given ID.
428+
func GetSystemWebhook(id int64) (*Webhook, error) {
429+
webhook := &Webhook{ID: id}
430+
has, err := x.
431+
Where("repo_id=? AND org_id=? AND is_system_webhook=?", 0, 0, true).
432+
Get(webhook)
433+
if err != nil {
434+
return nil, err
435+
} else if !has {
436+
return nil, ErrWebhookNotExist{id}
437+
}
438+
return webhook, nil
439+
}
440+
441+
// GetSystemWebhooks returns all admin system webhooks.
442+
func GetSystemWebhooks() ([]*Webhook, error) {
443+
return getSystemWebhooks(x)
444+
}
445+
446+
func getSystemWebhooks(e Engine) ([]*Webhook, error) {
447+
webhooks := make([]*Webhook, 0, 5)
448+
return webhooks, e.
449+
Where("repo_id=? AND org_id=? AND is_system_webhook=?", 0, 0, true).
423450
Find(&webhooks)
424451
}
425452

@@ -471,8 +498,8 @@ func DeleteWebhookByOrgID(orgID, id int64) error {
471498
})
472499
}
473500

474-
// DeleteDefaultWebhook deletes an admin-default webhook by given ID.
475-
func DeleteDefaultWebhook(id int64) error {
501+
// DeleteDefaultSystemWebhook deletes an admin-configured default or system webhook (where Org and Repo ID both 0)
502+
func DeleteDefaultSystemWebhook(id int64) error {
476503
sess := x.NewSession()
477504
defer sess.Close()
478505
if err := sess.Begin(); err != nil {

modules/webhook/webhook.go

+7
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,13 @@ func prepareWebhooks(repo *models.Repository, event models.HookEventType, p api.
181181
ws = append(ws, orgHooks...)
182182
}
183183

184+
// Add any admin-defined system webhooks
185+
systemHooks, err := models.GetSystemWebhooks()
186+
if err != nil {
187+
return fmt.Errorf("GetSystemWebhooks: %v", err)
188+
}
189+
ws = append(ws, systemHooks...)
190+
184191
if len(ws) == 0 {
185192
return nil
186193
}

options/locale/locale_en-US.ini

+5
Original file line numberDiff line numberDiff line change
@@ -1753,6 +1753,7 @@ users = User Accounts
17531753
organizations = Organizations
17541754
repositories = Repositories
17551755
hooks = Default Webhooks
1756+
systemhooks = System Webhooks
17561757
authentication = Authentication Sources
17571758
emails = User Emails
17581759
config = Configuration
@@ -1889,6 +1890,10 @@ hooks.desc = Webhooks automatically make HTTP POST requests to a server when cer
18891890
hooks.add_webhook = Add Default Webhook
18901891
hooks.update_webhook = Update Default Webhook
18911892

1893+
systemhooks.desc = Webhooks automatically make HTTP POST requests to a server when certain Gitea events trigger. Webhooks defined will act on all repositories on the system, so please consider any performance implications this may have. Read more in the <a target="_blank" rel="noopener" href="https://docs.gitea.io/en-us/webhooks/">webhooks guide</a>.
1894+
systemhooks.add_webhook = Add System Webhook
1895+
systemhooks.update_webhook = Update System Webhook
1896+
18921897
auths.auth_manage_panel = Authentication Source Management
18931898
auths.new = Add Authentication Source
18941899
auths.name = Name

routers/admin/hooks.go

+34-15
Original file line numberDiff line numberDiff line change
@@ -12,36 +12,55 @@ import (
1212
)
1313

1414
const (
15-
// tplAdminHooks template path for render hook settings
15+
// tplAdminHooks template path to render hook settings
1616
tplAdminHooks base.TplName = "admin/hooks"
1717
)
1818

19-
// DefaultWebhooks render admin-default webhook list page
20-
func DefaultWebhooks(ctx *context.Context) {
21-
ctx.Data["Title"] = ctx.Tr("admin.hooks")
22-
ctx.Data["PageIsAdminHooks"] = true
23-
ctx.Data["BaseLink"] = setting.AppSubURL + "/admin/hooks"
24-
ctx.Data["Description"] = ctx.Tr("admin.hooks.desc")
19+
// DefaultOrSystemWebhooks renders both admin default and system webhook list pages
20+
func DefaultOrSystemWebhooks(ctx *context.Context) {
21+
var ws []*models.Webhook
22+
var err error
23+
24+
// Are we looking at default webhooks?
25+
if ctx.Params(":configType") == "hooks" {
26+
ctx.Data["Title"] = ctx.Tr("admin.hooks")
27+
ctx.Data["Description"] = ctx.Tr("admin.hooks.desc")
28+
ctx.Data["PageIsAdminHooks"] = true
29+
ctx.Data["BaseLink"] = setting.AppSubURL + "/admin/hooks"
30+
ws, err = models.GetDefaultWebhooks()
31+
} else {
32+
ctx.Data["Title"] = ctx.Tr("admin.systemhooks")
33+
ctx.Data["Description"] = ctx.Tr("admin.systemhooks.desc")
34+
ctx.Data["PageIsAdminSystemHooks"] = true
35+
ctx.Data["BaseLink"] = setting.AppSubURL + "/admin/system-hooks"
36+
ws, err = models.GetSystemWebhooks()
37+
}
2538

26-
ws, err := models.GetDefaultWebhooks()
2739
if err != nil {
28-
ctx.ServerError("GetWebhooksDefaults", err)
40+
ctx.ServerError("GetWebhooksAdmin", err)
2941
return
3042
}
3143

3244
ctx.Data["Webhooks"] = ws
3345
ctx.HTML(200, tplAdminHooks)
3446
}
3547

36-
// DeleteDefaultWebhook response for delete admin-default webhook
37-
func DeleteDefaultWebhook(ctx *context.Context) {
38-
if err := models.DeleteDefaultWebhook(ctx.QueryInt64("id")); err != nil {
48+
// DeleteDefaultOrSystemWebhook handler to delete an admin-defined system or default webhook
49+
func DeleteDefaultOrSystemWebhook(ctx *context.Context) {
50+
if err := models.DeleteDefaultSystemWebhook(ctx.QueryInt64("id")); err != nil {
3951
ctx.Flash.Error("DeleteDefaultWebhook: " + err.Error())
4052
} else {
4153
ctx.Flash.Success(ctx.Tr("repo.settings.webhook_deletion_success"))
4254
}
4355

44-
ctx.JSON(200, map[string]interface{}{
45-
"redirect": setting.AppSubURL + "/admin/hooks",
46-
})
56+
// Are we looking at default webhooks?
57+
if ctx.Params(":configType") == "hooks" {
58+
ctx.JSON(200, map[string]interface{}{
59+
"redirect": setting.AppSubURL + "/admin/hooks",
60+
})
61+
} else {
62+
ctx.JSON(200, map[string]interface{}{
63+
"redirect": setting.AppSubURL + "/admin/system-hooks",
64+
})
65+
}
4766
}

0 commit comments

Comments
 (0)