diff options
author | ᴜɴᴋɴᴡᴏɴ <u@gogs.io> | 2020-03-22 22:07:22 +0800 |
---|---|---|
committer | GitHub <noreply@github.com> | 2020-03-22 22:07:22 +0800 |
commit | 22717a1c064511cf37c46af5e650baf7184cf25b (patch) | |
tree | f98bb991145605567f8b43506a7add855db0a90f /internal/route/repo | |
parent | 82e511ddb1d1e98ebe6b1931766b0835fc066883 (diff) |
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
Diffstat (limited to 'internal/route/repo')
-rw-r--r-- | internal/route/repo/webhook.go | 528 | ||||
-rw-r--r-- | internal/route/repo/webhook_test.go | 72 |
2 files changed, 318 insertions, 282 deletions
diff --git a/internal/route/repo/webhook.go b/internal/route/repo/webhook.go index 32c2069b..8e4b3a11 100644 --- a/internal/route/repo/webhook.go +++ b/internal/route/repo/webhook.go @@ -7,13 +7,13 @@ package repo import ( "fmt" "net/http" + "net/url" "strings" - jsoniter "github.com/json-iterator/go" - "github.com/unknwon/com" - - git "github.com/gogs/git-module" + "github.com/gogs/git-module" api "github.com/gogs/go-gogs-client" + jsoniter "github.com/json-iterator/go" + "gopkg.in/macaron.v1" "gogs.io/gogs/internal/conf" "gogs.io/gogs/internal/context" @@ -23,89 +23,163 @@ import ( ) const ( - WEBHOOKS = "repo/settings/webhook/base" - WEBHOOK_NEW = "repo/settings/webhook/new" - ORG_WEBHOOK_NEW = "org/settings/webhook_new" + tmplRepoSettingsWebhooks = "repo/settings/webhook/base" + tmplRepoSettingsWebhookNew = "repo/settings/webhook/new" + tmplOrgSettingsWebhooks = "org/settings/webhooks" + tmplOrgSettingsWebhookNew = "org/settings/webhook_new" ) -func Webhooks(c *context.Context) { - c.Data["Title"] = c.Tr("repo.settings.hooks") - c.Data["PageIsSettingsHooks"] = true - c.Data["BaseLink"] = c.Repo.RepoLink - c.Data["Description"] = c.Tr("repo.settings.hooks_desc", "https://github.com/gogs/docs-api/blob/master/Repositories/Webhooks.md") - c.Data["Types"] = conf.Webhook.Types - - ws, err := db.GetWebhooksByRepoID(c.Repo.Repository.ID) - if err != nil { - c.Error(err, "get webhooks by repository ID") - return +func InjectOrgRepoContext() macaron.Handler { + return func(c *context.Context) { + orCtx, err := getOrgRepoContext(c) + if err != nil { + c.Error(err, "get organization or repository context") + return + } + c.Map(orCtx) } - c.Data["Webhooks"] = ws - - c.Success(WEBHOOKS) } -type OrgRepoCtx struct { - OrgID int64 - RepoID int64 - Link string - NewTemplate string +type orgRepoContext struct { + OrgID int64 + RepoID int64 + Link string + TmplList string + TmplNew string } -// getOrgRepoCtx determines whether this is a repo context or organization context. -func getOrgRepoCtx(c *context.Context) (*OrgRepoCtx, error) { +// getOrgRepoContext determines whether this is a repo context or organization context. +func getOrgRepoContext(c *context.Context) (*orgRepoContext, error) { if len(c.Repo.RepoLink) > 0 { - c.Data["PageIsRepositoryContext"] = true - return &OrgRepoCtx{ - RepoID: c.Repo.Repository.ID, - Link: c.Repo.RepoLink, - NewTemplate: WEBHOOK_NEW, + c.PageIs("RepositoryContext") + return &orgRepoContext{ + RepoID: c.Repo.Repository.ID, + Link: c.Repo.RepoLink, + TmplList: tmplRepoSettingsWebhooks, + TmplNew: tmplRepoSettingsWebhookNew, }, nil } if len(c.Org.OrgLink) > 0 { - c.Data["PageIsOrganizationContext"] = true - return &OrgRepoCtx{ - OrgID: c.Org.Organization.ID, - Link: c.Org.OrgLink, - NewTemplate: ORG_WEBHOOK_NEW, + c.PageIs("OrganizationContext") + return &orgRepoContext{ + OrgID: c.Org.Organization.ID, + Link: c.Org.OrgLink, + TmplList: tmplOrgSettingsWebhooks, + TmplNew: tmplOrgSettingsWebhookNew, }, nil } - return nil, errors.New("Unable to set OrgRepo context") + return nil, errors.New("unable to determine context") } -func checkHookType(c *context.Context) string { +func Webhooks(c *context.Context, orCtx *orgRepoContext) { + c.Title("repo.settings.hooks") + c.PageIs("SettingsHooks") + c.Data["Types"] = conf.Webhook.Types + + var err error + var ws []*db.Webhook + if orCtx.RepoID > 0 { + c.Data["Description"] = c.Tr("repo.settings.hooks_desc") + ws, err = db.GetWebhooksByRepoID(orCtx.RepoID) + } else { + c.Data["Description"] = c.Tr("org.settings.hooks_desc") + ws, err = db.GetWebhooksByOrgID(orCtx.OrgID) + } + if err != nil { + c.Error(err, "get webhooks") + return + } + c.Data["Webhooks"] = ws + + c.Success(orCtx.TmplList) +} + +func WebhooksNew(c *context.Context, orCtx *orgRepoContext) { + c.Title("repo.settings.add_webhook") + c.PageIs("SettingsHooks") + c.PageIs("SettingsHooksNew") + + allowed := false hookType := strings.ToLower(c.Params(":type")) - if !com.IsSliceContainsStr(conf.Webhook.Types, hookType) { + for _, typ := range conf.Webhook.Types { + if hookType == typ { + allowed = true + c.Data["HookType"] = typ + break + } + } + if !allowed { c.NotFound() - return "" + return } - return hookType + + c.Success(orCtx.TmplNew) } -func WebhooksNew(c *context.Context) { - c.Data["Title"] = c.Tr("repo.settings.add_webhook") - c.Data["PageIsSettingsHooks"] = true - c.Data["PageIsSettingsHooksNew"] = true - c.Data["Webhook"] = db.Webhook{HookEvent: &db.HookEvent{}} +var localHostnames = []string{ + "localhost", + "127.0.0.1", + "::1", + "0:0:0:0:0:0:0:1", +} - orCtx, err := getOrgRepoCtx(c) - if err != nil { - c.Error(err, "get organization repository context") +// isLocalHostname returns true if given hostname is a known local address. +func isLocalHostname(hostname string) bool { + for _, local := range localHostnames { + if hostname == local { + return true + } + } + return false +} + +func validateWebhook(actor *db.User, l macaron.Locale, w *db.Webhook) (field string, msg string, ok bool) { + if !actor.IsAdmin { + // 🚨 SECURITY: Local addresses must not be allowed by non-admins to prevent SSRF, + // see https://github.com/gogs/gogs/issues/5366 for details. + payloadURL, err := url.Parse(w.URL) + if err != nil { + return "PayloadURL", l.Tr("repo.settings.webhook.err_cannot_parse_payload_url", err), false + } + + if isLocalHostname(payloadURL.Hostname()) { + return "PayloadURL", l.Tr("repo.settings.webhook.err_cannot_use_local_addresses"), false + } + } + + return "", "", true +} + +func validateAndCreateWebhook(c *context.Context, orCtx *orgRepoContext, w *db.Webhook) { + c.Data["Webhook"] = w + + if c.HasError() { + c.Success(orCtx.TmplNew) return } - c.Data["HookType"] = checkHookType(c) - if c.Written() { + field, msg, ok := validateWebhook(c.User, c.Locale, w) + if !ok { + c.FormErr(field) + c.RenderWithErr(msg, orCtx.TmplNew, nil) + return + } + + if err := w.UpdateEvent(); err != nil { + c.Error(err, "update event") + return + } else if err := db.CreateWebhook(w); err != nil { + c.Error(err, "create webhook") return } - c.Data["BaseLink"] = orCtx.Link - c.Success(orCtx.NewTemplate) + c.Flash.Success(c.Tr("repo.settings.add_hook_success")) + c.Redirect(orCtx.Link + "/settings/hooks") } -func ParseHookEvent(f form.Webhook) *db.HookEvent { +func toHookEvent(f form.Webhook) *db.HookEvent { return &db.HookEvent{ PushOnly: f.PushOnly(), SendEverything: f.SendEverything(), @@ -123,25 +197,12 @@ func ParseHookEvent(f form.Webhook) *db.HookEvent { } } -func WebHooksNewPost(c *context.Context, f form.NewWebhook) { - c.Data["Title"] = c.Tr("repo.settings.add_webhook") - c.Data["PageIsSettingsHooks"] = true - c.Data["PageIsSettingsHooksNew"] = true - c.Data["Webhook"] = db.Webhook{HookEvent: &db.HookEvent{}} +func WebhooksNewPost(c *context.Context, orCtx *orgRepoContext, f form.NewWebhook) { + c.Title("repo.settings.add_webhook") + c.PageIs("SettingsHooks") + c.PageIs("SettingsHooksNew") c.Data["HookType"] = "gogs" - orCtx, err := getOrgRepoCtx(c) - if err != nil { - c.Error(err, "get organization repository context") - return - } - c.Data["BaseLink"] = orCtx.Link - - if c.HasError() { - c.Success(orCtx.NewTemplate) - return - } - contentType := db.JSON if db.HookContentType(f.ContentType) == db.FORM { contentType = db.FORM @@ -149,49 +210,32 @@ func WebHooksNewPost(c *context.Context, f form.NewWebhook) { w := &db.Webhook{ RepoID: orCtx.RepoID, + OrgID: orCtx.OrgID, URL: f.PayloadURL, ContentType: contentType, Secret: f.Secret, - HookEvent: ParseHookEvent(f.Webhook), + HookEvent: toHookEvent(f.Webhook), IsActive: f.Active, HookTaskType: db.GOGS, - OrgID: orCtx.OrgID, } - if err := w.UpdateEvent(); err != nil { - c.Error(err, "update event") - return - } else if err := db.CreateWebhook(w); err != nil { - c.Error(err, "create webhook") - return - } - - c.Flash.Success(c.Tr("repo.settings.add_hook_success")) - c.Redirect(orCtx.Link + "/settings/hooks") + validateAndCreateWebhook(c, orCtx, w) } -func SlackHooksNewPost(c *context.Context, f form.NewSlackHook) { - c.Data["Title"] = c.Tr("repo.settings") - c.Data["PageIsSettingsHooks"] = true - c.Data["PageIsSettingsHooksNew"] = true - c.Data["Webhook"] = db.Webhook{HookEvent: &db.HookEvent{}} - - orCtx, err := getOrgRepoCtx(c) - if err != nil { - c.Error(err, "get organization repository context") - return - } - - if c.HasError() { - c.Success(orCtx.NewTemplate) - return - } +func WebhooksSlackNewPost(c *context.Context, orCtx *orgRepoContext, f form.NewSlackHook) { + c.Title("repo.settings.add_webhook") + c.PageIs("SettingsHooks") + c.PageIs("SettingsHooksNew") + c.Data["HookType"] = "slack" - meta, err := jsoniter.Marshal(&db.SlackMeta{ + meta := &db.SlackMeta{ Channel: f.Channel, Username: f.Username, IconURL: f.IconURL, Color: f.Color, - }) + } + c.Data["SlackMeta"] = meta + + p, err := jsoniter.Marshal(meta) if err != nil { c.Error(err, "marshal JSON") return @@ -201,47 +245,29 @@ func SlackHooksNewPost(c *context.Context, f form.NewSlackHook) { RepoID: orCtx.RepoID, URL: f.PayloadURL, ContentType: db.JSON, - HookEvent: ParseHookEvent(f.Webhook), + HookEvent: toHookEvent(f.Webhook), IsActive: f.Active, HookTaskType: db.SLACK, - Meta: string(meta), + Meta: string(p), OrgID: orCtx.OrgID, } - if err := w.UpdateEvent(); err != nil { - c.Error(err, "update event") - return - } else if err := db.CreateWebhook(w); err != nil { - c.Error(err, "create webhook") - return - } - - c.Flash.Success(c.Tr("repo.settings.add_hook_success")) - c.Redirect(orCtx.Link + "/settings/hooks") + validateAndCreateWebhook(c, orCtx, w) } -// FIXME: merge logic to Slack -func DiscordHooksNewPost(c *context.Context, f form.NewDiscordHook) { - c.Data["Title"] = c.Tr("repo.settings") - c.Data["PageIsSettingsHooks"] = true - c.Data["PageIsSettingsHooksNew"] = true - c.Data["Webhook"] = db.Webhook{HookEvent: &db.HookEvent{}} +func WebhooksDiscordNewPost(c *context.Context, orCtx *orgRepoContext, f form.NewDiscordHook) { + c.Title("repo.settings.add_webhook") + c.PageIs("SettingsHooks") + c.PageIs("SettingsHooksNew") + c.Data["HookType"] = "discord" - orCtx, err := getOrgRepoCtx(c) - if err != nil { - c.Error(err, "get organization repository context") - return - } - - if c.HasError() { - c.Success(orCtx.NewTemplate) - return - } - - meta, err := jsoniter.Marshal(&db.SlackMeta{ + meta := &db.SlackMeta{ Username: f.Username, IconURL: f.IconURL, Color: f.Color, - }) + } + c.Data["SlackMeta"] = meta + + p, err := jsoniter.Marshal(meta) if err != nil { c.Error(err, "marshal JSON") return @@ -251,72 +277,37 @@ func DiscordHooksNewPost(c *context.Context, f form.NewDiscordHook) { RepoID: orCtx.RepoID, URL: f.PayloadURL, ContentType: db.JSON, - HookEvent: ParseHookEvent(f.Webhook), + HookEvent: toHookEvent(f.Webhook), IsActive: f.Active, HookTaskType: db.DISCORD, - Meta: string(meta), + Meta: string(p), OrgID: orCtx.OrgID, } - if err := w.UpdateEvent(); err != nil { - c.Error(err, "update event") - return - } else if err := db.CreateWebhook(w); err != nil { - c.Error(err, "create webhook") - return - } - - c.Flash.Success(c.Tr("repo.settings.add_hook_success")) - c.Redirect(orCtx.Link + "/settings/hooks") + validateAndCreateWebhook(c, orCtx, w) } -func DingtalkHooksNewPost(c *context.Context, f form.NewDingtalkHook) { - c.Data["Title"] = c.Tr("repo.settings") - c.Data["PageIsSettingsHooks"] = true - c.Data["PageIsSettingsHooksNew"] = true - c.Data["Webhook"] = db.Webhook{HookEvent: &db.HookEvent{}} - - orCtx, err := getOrgRepoCtx(c) - if err != nil { - c.Error(err, "get organization repository context") - return - } - - if c.HasError() { - c.Success(orCtx.NewTemplate) - return - } +func WebhooksDingtalkNewPost(c *context.Context, orCtx *orgRepoContext, f form.NewDingtalkHook) { + c.Title("repo.settings.add_webhook") + c.PageIs("SettingsHooks") + c.PageIs("SettingsHooksNew") + c.Data["HookType"] = "dingtalk" w := &db.Webhook{ RepoID: orCtx.RepoID, URL: f.PayloadURL, ContentType: db.JSON, - HookEvent: ParseHookEvent(f.Webhook), + HookEvent: toHookEvent(f.Webhook), IsActive: f.Active, HookTaskType: db.DINGTALK, OrgID: orCtx.OrgID, } - if err := w.UpdateEvent(); err != nil { - c.Error(err, "update event") - return - } else if err := db.CreateWebhook(w); err != nil { - c.Error(err, "create webhook") - return - } - - c.Flash.Success(c.Tr("repo.settings.add_hook_success")) - c.Redirect(orCtx.Link + "/settings/hooks") + validateAndCreateWebhook(c, orCtx, w) } -func checkWebhook(c *context.Context) (*OrgRepoCtx, *db.Webhook) { - c.Data["RequireHighlightJS"] = true - - orCtx, err := getOrgRepoCtx(c) - if err != nil { - c.Error(err, "get organization repository context") - return nil, nil - } - c.Data["BaseLink"] = orCtx.Link +func loadWebhook(c *context.Context, orCtx *orgRepoContext) *db.Webhook { + c.RequireHighlightJS() + var err error var w *db.Webhook if orCtx.RepoID > 0 { w, err = db.GetWebhookOfRepoByID(c.Repo.Repository.ID, c.ParamsInt64(":id")) @@ -325,70 +316,61 @@ func checkWebhook(c *context.Context) (*OrgRepoCtx, *db.Webhook) { } if err != nil { c.NotFoundOrError(err, "get webhook") - return nil, nil + return nil } + c.Data["Webhook"] = w switch w.HookTaskType { case db.SLACK: - c.Data["SlackHook"] = w.GetSlackHook() + c.Data["SlackMeta"] = w.SlackMeta() c.Data["HookType"] = "slack" case db.DISCORD: - c.Data["SlackHook"] = w.GetSlackHook() + c.Data["SlackMeta"] = w.SlackMeta() c.Data["HookType"] = "discord" case db.DINGTALK: c.Data["HookType"] = "dingtalk" default: c.Data["HookType"] = "gogs" } + c.Data["FormURL"] = fmt.Sprintf("%s/settings/hooks/%s/%d", orCtx.Link, c.Data["HookType"], w.ID) + c.Data["DeleteURL"] = fmt.Sprintf("%s/settings/hooks/delete", orCtx.Link) c.Data["History"], err = w.History(1) if err != nil { c.Error(err, "get history") - return nil, nil + return nil } - return orCtx, w + return w } -func WebHooksEdit(c *context.Context) { - c.Data["Title"] = c.Tr("repo.settings.update_webhook") - c.Data["PageIsSettingsHooks"] = true - c.Data["PageIsSettingsHooksEdit"] = true +func WebhooksEdit(c *context.Context, orCtx *orgRepoContext) { + c.Title("repo.settings.update_webhook") + c.PageIs("SettingsHooks") + c.PageIs("SettingsHooksEdit") - orCtx, w := checkWebhook(c) + loadWebhook(c, orCtx) if c.Written() { return } - c.Data["Webhook"] = w - c.Success(orCtx.NewTemplate) + c.Success(orCtx.TmplNew) } -func WebHooksEditPost(c *context.Context, f form.NewWebhook) { - c.Data["Title"] = c.Tr("repo.settings.update_webhook") - c.Data["PageIsSettingsHooks"] = true - c.Data["PageIsSettingsHooksEdit"] = true - - orCtx, w := checkWebhook(c) - if c.Written() { - return - } +func validateAndUpdateWebhook(c *context.Context, orCtx *orgRepoContext, w *db.Webhook) { c.Data["Webhook"] = w if c.HasError() { - c.Success(orCtx.NewTemplate) + c.Success(orCtx.TmplNew) return } - contentType := db.JSON - if db.HookContentType(f.ContentType) == db.FORM { - contentType = db.FORM + field, msg, ok := validateWebhook(c.User, c.Locale, w) + if !ok { + c.FormErr(field) + c.RenderWithErr(msg, orCtx.TmplNew, nil) + return } - w.URL = f.PayloadURL - w.ContentType = contentType - w.Secret = f.Secret - w.HookEvent = ParseHookEvent(f.Webhook) - w.IsActive = f.Active if err := w.UpdateEvent(); err != nil { c.Error(err, "update event") return @@ -401,19 +383,36 @@ func WebHooksEditPost(c *context.Context, f form.NewWebhook) { c.Redirect(fmt.Sprintf("%s/settings/hooks/%d", orCtx.Link, w.ID)) } -func SlackHooksEditPost(c *context.Context, f form.NewSlackHook) { - c.Data["Title"] = c.Tr("repo.settings") - c.Data["PageIsSettingsHooks"] = true - c.Data["PageIsSettingsHooksEdit"] = true +func WebhooksEditPost(c *context.Context, orCtx *orgRepoContext, f form.NewWebhook) { + c.Title("repo.settings.update_webhook") + c.PageIs("SettingsHooks") + c.PageIs("SettingsHooksEdit") - orCtx, w := checkWebhook(c) + w := loadWebhook(c, orCtx) if c.Written() { return } - c.Data["Webhook"] = w - if c.HasError() { - c.Success(orCtx.NewTemplate) + contentType := db.JSON + if db.HookContentType(f.ContentType) == db.FORM { + contentType = db.FORM + } + + w.URL = f.PayloadURL + w.ContentType = contentType + w.Secret = f.Secret + w.HookEvent = toHookEvent(f.Webhook) + w.IsActive = f.Active + validateAndUpdateWebhook(c, orCtx, w) +} + +func WebhooksSlackEditPost(c *context.Context, orCtx *orgRepoContext, f form.NewSlackHook) { + c.Title("repo.settings.update_webhook") + c.PageIs("SettingsHooks") + c.PageIs("SettingsHooksEdit") + + w := loadWebhook(c, orCtx) + if c.Written() { return } @@ -430,36 +429,20 @@ func SlackHooksEditPost(c *context.Context, f form.NewSlackHook) { w.URL = f.PayloadURL w.Meta = string(meta) - w.HookEvent = ParseHookEvent(f.Webhook) + w.HookEvent = toHookEvent(f.Webhook) w.IsActive = f.Active - if err := w.UpdateEvent(); err != nil { - c.Error(err, "update event") - return - } else if err := db.UpdateWebhook(w); err != nil { - c.Error(err, "update webhook") - return - } - - c.Flash.Success(c.Tr("repo.settings.update_hook_success")) - c.Redirect(fmt.Sprintf("%s/settings/hooks/%d", orCtx.Link, w.ID)) + validateAndUpdateWebhook(c, orCtx, w) } -// FIXME: merge logic to Slack -func DiscordHooksEditPost(c *context.Context, f form.NewDiscordHook) { - c.Data["Title"] = c.Tr("repo.settings") - c.Data["PageIsSettingsHooks"] = true - c.Data["PageIsSettingsHooksEdit"] = true +func WebhooksDiscordEditPost(c *context.Context, orCtx *orgRepoContext, f form.NewDiscordHook) { + c.Title("repo.settings.update_webhook") + c.PageIs("SettingsHooks") + c.PageIs("SettingsHooksEdit") - orCtx, w := checkWebhook(c) + w := loadWebhook(c, orCtx) if c.Written() { return } - c.Data["Webhook"] = w - - if c.HasError() { - c.Success(orCtx.NewTemplate) - return - } meta, err := jsoniter.Marshal(&db.SlackMeta{ Username: f.Username, @@ -473,53 +456,28 @@ func DiscordHooksEditPost(c *context.Context, f form.NewDiscordHook) { w.URL = f.PayloadURL w.Meta = string(meta) - w.HookEvent = ParseHookEvent(f.Webhook) + w.HookEvent = toHookEvent(f.Webhook) w.IsActive = f.Active - if err := w.UpdateEvent(); err != nil { - c.Error(err, "update event") - return - } else if err := db.UpdateWebhook(w); err != nil { - c.Error(err, "update webhook") - return - } - - c.Flash.Success(c.Tr("repo.settings.update_hook_success")) - c.Redirect(fmt.Sprintf("%s/settings/hooks/%d", orCtx.Link, w.ID)) + validateAndUpdateWebhook(c, orCtx, w) } -func DingtalkHooksEditPost(c *context.Context, f form.NewDingtalkHook) { - c.Data["Title"] = c.Tr("repo.settings") - c.Data["PageIsSettingsHooks"] = true - c.Data["PageIsSettingsHooksEdit"] = true +func WebhooksDingtalkEditPost(c *context.Context, orCtx *orgRepoContext, f form.NewDingtalkHook) { + c.Title("repo.settings.update_webhook") + c.PageIs("SettingsHooks") + c.PageIs("SettingsHooksEdit") - orCtx, w := checkWebhook(c) + w := loadWebhook(c, orCtx) if c.Written() { return } - c.Data["Webhook"] = w - - if c.HasError() { - c.Success(orCtx.NewTemplate) - return - } w.URL = f.PayloadURL - w.HookEvent = ParseHookEvent(f.Webhook) + w.HookEvent = toHookEvent(f.Webhook) w.IsActive = f.Active - if err := w.UpdateEvent(); err != nil { - c.Error(err, "update event") - return - } else if err := db.UpdateWebhook(w); err != nil { - c.Error(err, "update webhook") - return - } - - c.Flash.Success(c.Tr("repo.settings.update_hook_success")) - c.Redirect(fmt.Sprintf("%s/settings/hooks/%d", orCtx.Link, w.ID)) + validateAndUpdateWebhook(c, orCtx, w) } func TestWebhook(c *context.Context) { - var ( commitID string commitMessage string @@ -634,14 +592,20 @@ func RedeliveryWebhook(c *context.Context) { c.Status(http.StatusOK) } -func DeleteWebhook(c *context.Context) { - if err := db.DeleteWebhookOfRepoByID(c.Repo.Repository.ID, c.QueryInt64("id")); err != nil { - c.Flash.Error("DeleteWebhookByRepoID: " + err.Error()) +func DeleteWebhook(c *context.Context, orCtx *orgRepoContext) { + var err error + if orCtx.RepoID > 0 { + err = db.DeleteWebhookOfRepoByID(orCtx.RepoID, c.QueryInt64("id")) } else { - c.Flash.Success(c.Tr("repo.settings.webhook_deletion_success")) + err = db.DeleteWebhookOfOrgByID(orCtx.OrgID, c.QueryInt64("id")) + } + if err != nil { + c.Error(err, "delete webhook") + return } + c.Flash.Success(c.Tr("repo.settings.webhook_deletion_success")) c.JSONSuccess(map[string]interface{}{ - "redirect": c.Repo.RepoLink + "/settings/hooks", + "redirect": orCtx.Link + "/settings/hooks", }) } diff --git a/internal/route/repo/webhook_test.go b/internal/route/repo/webhook_test.go new file mode 100644 index 00000000..02f27b92 --- /dev/null +++ b/internal/route/repo/webhook_test.go @@ -0,0 +1,72 @@ +// Copyright 2020 The Gogs 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 repo + +import ( + "testing" + + "github.com/stretchr/testify/assert" + + "gogs.io/gogs/internal/db" + "gogs.io/gogs/internal/mock" +) + +func Test_isLocalHostname(t *testing.T) { + tests := []struct { + hostname string + want bool + }{ + {hostname: "localhost", want: true}, + {hostname: "127.0.0.1", want: true}, + {hostname: "::1", want: true}, + {hostname: "0:0:0:0:0:0:0:1", want: true}, + + {hostname: "gogs.io", want: false}, + } + for _, test := range tests { + t.Run("", func(t *testing.T) { + assert.Equal(t, test.want, isLocalHostname(test.hostname)) + }) + } +} + +func Test_validateWebhook(t *testing.T) { + l := mock.NewLocale("en", func(s string, _ ...interface{}) string { + return s + }) + + tests := []struct { + name string + actor *db.User + webhook *db.Webhook + expField string + expMsg string + expOK bool + }{ + { + name: "admin bypass local address check", + actor: &db.User{IsAdmin: true}, + webhook: &db.Webhook{URL: "http://localhost:3306"}, + expOK: true, + }, + + { + name: "local address not allowed", + actor: &db.User{}, + webhook: &db.Webhook{URL: "http://localhost:3306"}, + expField: "PayloadURL", + expMsg: "repo.settings.webhook.err_cannot_use_local_addresses", + expOK: false, + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + field, msg, ok := validateWebhook(test.actor, l, test.webhook) + assert.Equal(t, test.expOK, ok) + assert.Equal(t, test.expMsg, msg) + assert.Equal(t, test.expField, field) + }) + } +} |