Skip to content

Commit 0b4843e

Browse files
unknwonmpsonntag
authored andcommitted
webhook: overhaul route handlers (#6002)
* Overual route handlers and fixes #5366 * Merge routes for repo and org * Inject OrgRepoContext * DRY validateWebhook * DRY c.HasError * Add tests * Update CHANGELOG
1 parent d16c165 commit 0b4843e

26 files changed

+601
-571
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ All notable changes to Gogs are documented in this file.
4040
- [Security] Potential ability to delete files outside a repository.
4141
- [Security] Potential ability to set primary email on others' behalf from their verified emails.
4242
- [Security] Potential XSS attack via `.ipynb`. [#5170](https://github.com/gogs/gogs/issues/5170)
43+
- [Security] Potential SSRF attack via webhooks. [#5366](https://github.com/gogs/gogs/issues/5366)
4344
- [Security] Potential CSRF attack in admin panel. [#5367](https://github.com/gogs/gogs/issues/5367)
4445
- [Security] Potential RCE on mirror repositories. [#5767](https://github.com/gogs/gogs/issues/5767)
4546
- [Security] Potential XSS attack with raw markdown API. [#5907](https://github.com/gogs/gogs/pull/5907)

conf/locale/locale_en-US.ini

+6-2
Original file line numberDiff line numberDiff line change
@@ -807,8 +807,10 @@ settings.invite_collaborator = Invite Collaborator
807807
settings.invite_placeholder = Enter mail adress to invite
808808
settings.invite_collaborator_warn = Be aware that this will send out invitation mails!
809809
settings.org_not_allowed_to_be_collaborator = Organization is not allowed to be added as a collaborator.
810-
settings.add_webhook = Add Webhook
811-
settings.hooks_desc = Webhooks are much like basic HTTP POST event triggers. Whenever something occurs in GIN, we will handle the notification to the target host you specify. Learn more in this <a target="_blank" href="%s">Webhooks Guide</a>.
810+
settings.hooks_desc = Webhooks are much like basic HTTP POST event triggers. Whenever something occurs in Gogs, we will handle the notification to the target host you specify.
811+
settings.webhooks.add_new = Add a new webhook:
812+
settings.webhooks.choose_a_type = Choose a type...
813+
settings.add_webhook = Add webhook
812814
settings.webhook_deletion = Delete Webhook
813815
settings.webhook_deletion_desc = Delete this webhook will remove its information and all delivery history. Do you want to continue?
814816
settings.webhook_deletion_success = Webhook has been deleted successfully!
@@ -822,6 +824,8 @@ settings.webhook.response = Response
822824
settings.webhook.headers = Headers
823825
settings.webhook.payload = Payload
824826
settings.webhook.body = Body
827+
settings.webhook.err_cannot_parse_payload_url = Cannot parse payload URL: %v
828+
settings.webhook.err_cannot_use_local_addresses = Non admins are not allowed to use local addresses.
825829
settings.githooks_desc = Git Hooks are powered by Git itself, you can edit files of supported hooks in the list below to perform custom operations.
826830
settings.githook_edit_desc = If the hook is inactive, sample content will be presented. Leaving content to an empty value will disable this hook.
827831
settings.githook_name = Hook Name

internal/assets/conf/conf_gen.go

+4-4
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

internal/assets/public/public_gen.go

+24-24
Large diffs are not rendered by default.

internal/assets/templates/templates_gen.go

+28-28
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

internal/cmd/web.go

+19-26
Original file line numberDiff line numberDiff line change
@@ -364,6 +364,23 @@ func runWeb(c *cli.Context) error {
364364
reqRepoAdmin := context.RequireRepoAdmin()
365365
reqRepoWriter := context.RequireRepoWriter()
366366

367+
webhookRoutes := func() {
368+
m.Group("", func() {
369+
m.Get("", repo.Webhooks)
370+
m.Post("/delete", repo.DeleteWebhook)
371+
m.Get("/:type/new", repo.WebhooksNew)
372+
m.Post("/gogs/new", bindIgnErr(form.NewWebhook{}), repo.WebhooksNewPost)
373+
m.Post("/slack/new", bindIgnErr(form.NewSlackHook{}), repo.WebhooksSlackNewPost)
374+
m.Post("/discord/new", bindIgnErr(form.NewDiscordHook{}), repo.WebhooksDiscordNewPost)
375+
m.Post("/dingtalk/new", bindIgnErr(form.NewDingtalkHook{}), repo.WebhooksDingtalkNewPost)
376+
m.Get("/:id", repo.WebhooksEdit)
377+
m.Post("/gogs/:id", bindIgnErr(form.NewWebhook{}), repo.WebhooksEditPost)
378+
m.Post("/slack/:id", bindIgnErr(form.NewSlackHook{}), repo.WebhooksSlackEditPost)
379+
m.Post("/discord/:id", bindIgnErr(form.NewDiscordHook{}), repo.WebhooksDiscordEditPost)
380+
m.Post("/dingtalk/:id", bindIgnErr(form.NewDingtalkHook{}), repo.WebhooksDingtalkEditPost)
381+
}, repo.InjectOrgRepoContext())
382+
}
383+
367384
// ***** START: Organization *****
368385
m.Group("/org", func() {
369386
m.Group("", func() {
@@ -404,20 +421,7 @@ func runWeb(c *cli.Context) error {
404421
m.Post("/avatar", binding.MultipartForm(form.Avatar{}), org.SettingsAvatar)
405422
m.Post("/avatar/delete", org.SettingsDeleteAvatar)
406423

407-
m.Group("/hooks", func() {
408-
m.Get("", org.Webhooks)
409-
m.Post("/delete", org.DeleteWebhook)
410-
m.Get("/:type/new", repo.WebhooksNew)
411-
m.Post("/gogs/new", bindIgnErr(form.NewWebhook{}), repo.WebHooksNewPost)
412-
m.Post("/slack/new", bindIgnErr(form.NewSlackHook{}), repo.SlackHooksNewPost)
413-
m.Post("/discord/new", bindIgnErr(form.NewDiscordHook{}), repo.DiscordHooksNewPost)
414-
m.Post("/dingtalk/new", bindIgnErr(form.NewDingtalkHook{}), repo.DingtalkHooksNewPost)
415-
m.Get("/:id", repo.WebHooksEdit)
416-
m.Post("/gogs/:id", bindIgnErr(form.NewWebhook{}), repo.WebHooksEditPost)
417-
m.Post("/slack/:id", bindIgnErr(form.NewSlackHook{}), repo.SlackHooksEditPost)
418-
m.Post("/discord/:id", bindIgnErr(form.NewDiscordHook{}), repo.DiscordHooksEditPost)
419-
m.Post("/dingtalk/:id", bindIgnErr(form.NewDingtalkHook{}), repo.DingtalkHooksEditPost)
420-
})
424+
m.Group("/hooks", webhookRoutes)
421425

422426
m.Route("/delete", "GET,POST", org.SettingsDelete)
423427
})
@@ -464,20 +468,9 @@ func runWeb(c *cli.Context) error {
464468
})
465469

466470
m.Group("/hooks", func() {
467-
m.Get("", repo.Webhooks)
468-
m.Post("/delete", repo.DeleteWebhook)
469-
m.Get("/:type/new", repo.WebhooksNew)
470-
m.Post("/gogs/new", bindIgnErr(form.NewWebhook{}), repo.WebHooksNewPost)
471-
m.Post("/slack/new", bindIgnErr(form.NewSlackHook{}), repo.SlackHooksNewPost)
472-
m.Post("/discord/new", bindIgnErr(form.NewDiscordHook{}), repo.DiscordHooksNewPost)
473-
m.Post("/dingtalk/new", bindIgnErr(form.NewDingtalkHook{}), repo.DingtalkHooksNewPost)
474-
m.Post("/gogs/:id", bindIgnErr(form.NewWebhook{}), repo.WebHooksEditPost)
475-
m.Post("/slack/:id", bindIgnErr(form.NewSlackHook{}), repo.SlackHooksEditPost)
476-
m.Post("/discord/:id", bindIgnErr(form.NewDiscordHook{}), repo.DiscordHooksEditPost)
477-
m.Post("/dingtalk/:id", bindIgnErr(form.NewDingtalkHook{}), repo.DingtalkHooksEditPost)
471+
webhookRoutes()
478472

479473
m.Group("/:id", func() {
480-
m.Get("", repo.WebHooksEdit)
481474
m.Post("/test", repo.TestWebhook)
482475
m.Post("/redelivery", repo.RedeliveryWebhook)
483476
})

internal/conf/conf.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ var File *ini.File
6060
//
6161
// NOTE: The order of loading configuration sections matters as one may depend on another.
6262
//
63-
// ⚠️ WARNING: Do not print anything in this function other than wanrings.
63+
// ⚠️ WARNING: Do not print anything in this function other than warnings.
6464
func Init(customConf string) error {
6565
var err error
6666
File, err = ini.LoadSources(ini.LoadOptions{

internal/db/webhook.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -136,10 +136,10 @@ func (w *Webhook) AfterSet(colName string, _ xorm.Cell) {
136136
}
137137
}
138138

139-
func (w *Webhook) GetSlackHook() *SlackMeta {
139+
func (w *Webhook) SlackMeta() *SlackMeta {
140140
s := &SlackMeta{}
141141
if err := jsoniter.Unmarshal([]byte(w.Meta), s); err != nil {
142-
log.Error("GetSlackHook [%d]: %v", w.ID, err)
142+
log.Error("Failed to get Slack meta [webhook_id: %d]: %v", w.ID, err)
143143
}
144144
return s
145145
}

internal/mock/locale.go

+33
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
// Copyright 2020 The Gogs 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 mock
6+
7+
import (
8+
"gopkg.in/macaron.v1"
9+
)
10+
11+
var _ macaron.Locale = (*Locale)(nil)
12+
13+
// Locale is a mock that implements macaron.Locale.
14+
type Locale struct {
15+
lang string
16+
tr func(string, ...interface{}) string
17+
}
18+
19+
// NewLocale creates a new mock for macaron.Locale.
20+
func NewLocale(lang string, tr func(string, ...interface{}) string) *Locale {
21+
return &Locale{
22+
lang: lang,
23+
tr: tr,
24+
}
25+
}
26+
27+
func (l *Locale) Language() string {
28+
return l.lang
29+
}
30+
31+
func (l *Locale) Tr(format string, args ...interface{}) string {
32+
return l.tr(format, args...)
33+
}

internal/route/api/v1/convert/convert.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ func ToHook(repoLink string, w *db.Webhook) *api.Hook {
7575
"content_type": w.ContentType.Name(),
7676
}
7777
if w.HookTaskType == db.SLACK {
78-
s := w.GetSlackHook()
78+
s := w.SlackMeta()
7979
config["channel"] = s.Channel
8080
config["username"] = s.Username
8181
config["icon_url"] = s.IconURL

internal/route/org/setting.go

+2-32
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,8 @@ import (
1717
)
1818

1919
const (
20-
SETTINGS_OPTIONS = "org/settings/options"
21-
SETTINGS_DELETE = "org/settings/delete"
22-
SETTINGS_WEBHOOKS = "org/settings/webhooks"
20+
SETTINGS_OPTIONS = "org/settings/options"
21+
SETTINGS_DELETE = "org/settings/delete"
2322
)
2423

2524
func Settings(c *context.Context) {
@@ -136,32 +135,3 @@ func SettingsDelete(c *context.Context) {
136135

137136
c.Success(SETTINGS_DELETE)
138137
}
139-
140-
func Webhooks(c *context.Context) {
141-
c.Title("org.settings")
142-
c.Data["PageIsSettingsHooks"] = true
143-
c.Data["BaseLink"] = c.Org.OrgLink
144-
c.Data["Description"] = c.Tr("org.settings.hooks_desc")
145-
c.Data["Types"] = conf.Webhook.Types
146-
147-
ws, err := db.GetWebhooksByOrgID(c.Org.Organization.ID)
148-
if err != nil {
149-
c.Error(err, "get webhooks by organization ID")
150-
return
151-
}
152-
153-
c.Data["Webhooks"] = ws
154-
c.Success(SETTINGS_WEBHOOKS)
155-
}
156-
157-
func DeleteWebhook(c *context.Context) {
158-
if err := db.DeleteWebhookOfOrgByID(c.Org.Organization.ID, c.QueryInt64("id")); err != nil {
159-
c.Flash.Error("DeleteWebhookByOrgID: " + err.Error())
160-
} else {
161-
c.Flash.Success(c.Tr("repo.settings.webhook_deletion_success"))
162-
}
163-
164-
c.JSONSuccess( map[string]interface{}{
165-
"redirect": c.Org.OrgLink + "/settings/hooks",
166-
})
167-
}

0 commit comments

Comments
 (0)