gitea/routers/web/repo/webhook.go
lengyuqu 078e2b2c39
Add support for corporate WeChat webhooks (#15910)
* 企业微信webhook

* 企业微信webhook

* 企业微信webhook

* Update templates/admin/hook_new.tmpl

Co-authored-by: a1012112796 <1012112796@qq.com>

* Update services/webhook/wechatwork.go

Co-authored-by: a1012112796 <1012112796@qq.com>

* 修善wechatwork

* 修善wechatwork

* fix

* Update locale_cs-CZ.ini

fix

* fix build

* fix

* fix build

* make webhooks.zh-cn.md

* delet unnecessary blank line

* delet unnecessary blank line

* 企业微信webhook

* 企业微信webhook

* 企业微信webhook

* Update templates/admin/hook_new.tmpl

Co-authored-by: a1012112796 <1012112796@qq.com>

* Update services/webhook/wechatwork.go

Co-authored-by: a1012112796 <1012112796@qq.com>

* 修善wechatwork

* 修善wechatwork

* fix

* fix build

* fix

* fix build

* make webhooks.zh-cn.md

* delet unnecessary blank line

* delet unnecessary blank line

* 企业微信webhook

* 企业微信webhook

* 企业微信webhook

* 企业微信webhook

* 企业微信webhook

* fix

* fix

* 企业微信webhook

* 企业微信webhook

* 企业微信webhook

* fix wechat

* fix wechat

* fix wechat

* fix wechat

* Fix invalid params and typo of email templates (#16394)

Signed-off-by: Meano <meanocat@gmail.com>

* Add LRU mem cache implementation (#16226)

The current default memory cache implementation is unbounded in size and number of
objects cached. This is hardly ideal.

This PR proposes creating a TwoQueue LRU cache as the underlying cache for Gitea.
The cache is limited by the number of objects stored in the cache (rather than size)
for simplicity. The default number of objects is 50000 - which is perhaps too small
as most of our objects cached are going to be much less than 1kB.

It may be worth considering using a different LRU implementation that actively limits
sizes or avoids GC - however, this is just a beginning implementation.

Signed-off-by: Andrew Thornton <art27@cantab.net>

* [skip ci] Updated translations via Crowdin

* Replace `plugins/docker` with `techknowlogick/drone-docker`in ci (#16407)

* plugins/docker -> techknowlogick/drone-docker

* It is multi-arch

* docs: rewrite email setup (#16404)

* Add intro for both the docs page and mailer methods
  * Fix numbering level in SMTP section
  * Recommends implicit TLS

Signed-off-by: Bagas Sanjaya <bagasdotme@gmail.com>

* Validate Issue Index before querying DB (#16406)

* Fix external renderer (#16401)

* fix external renderer

* use GBackground context as fallback

* no fallback, return error

Co-authored-by: Lauris BH <lauris@nix.lv>

* Add checkbox to delete pull branch after successful merge (#16049)

* Add checkbox to delete pull branch after successful merge

* Omit DeleteBranchAfterMerge field in json

* Log a warning instead of error when PR head branch deleted

* Add DefaultDeleteBranchAfterMerge to PullRequestConfig

* Add support for delete_branch_after_merge via API

* Fix for API: the branch should be deleted from the HEAD repo

If head and base repo are the same, reuse the already opened ctx.Repo.GitRepo

* Don't delegate to CleanupBranch, only reuse branch deletion code

CleanupBranch contains too much logic that has already been performed by the Merge

* Reuse gitrepo in MergePullRequest

Co-authored-by: Andrew Thornton <art27@cantab.net>

* [skip ci] Updated translations via Crowdin

* Detect encoding changes while parsing diff (#16330)

* Detect encoding changes while parsing diff

* Let branch/tag name be a valid ref to get CI status (#16400)

* fix #16384#

* refactor: move shared helper func to utils package

* extend Tests

* use ctx.Repo.GitRepo if not nil

* fix

* fix

* 企业微信webhook

* 企业微信webhook

* 企业微信webhook

* fix build

* fix build

* Apply suggestions from code review

Co-authored-by: a1012112796 <1012112796@qq.com>
Co-authored-by: myheavily <myheavily>
Co-authored-by: zhaoxin <gitea@fake.local>
Co-authored-by: Meano <Meano@foxmail.com>
Co-authored-by: zeripath <art27@cantab.net>
Co-authored-by: GiteaBot <teabot@gitea.io>
Co-authored-by: 6543 <6543@obermui.de>
Co-authored-by: Bagas Sanjaya <bagasdotme@gmail.com>
Co-authored-by: Norwin <noerw@users.noreply.github.com>
Co-authored-by: Lauris BH <lauris@nix.lv>
Co-authored-by: Jimmy Praet <jimmy.praet@telenet.be>
Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
2021-07-23 12:41:27 +08:00

1211 lines
34 KiB
Go

// Copyright 2015 The Gogs Authors. All rights reserved.
// Copyright 2017 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 repo
import (
"errors"
"fmt"
"net/http"
"path"
"strings"
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/convert"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/setting"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/services/forms"
"code.gitea.io/gitea/services/webhook"
jsoniter "github.com/json-iterator/go"
)
const (
tplHooks base.TplName = "repo/settings/webhook/base"
tplHookNew base.TplName = "repo/settings/webhook/new"
tplOrgHookNew base.TplName = "org/settings/hook_new"
tplAdminHookNew base.TplName = "admin/hook_new"
)
// Webhooks render web hooks list page
func Webhooks(ctx *context.Context) {
ctx.Data["Title"] = ctx.Tr("repo.settings.hooks")
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.io/en-us/webhooks/")
ws, err := models.GetWebhooksByRepoID(ctx.Repo.Repository.ID, models.ListOptions{})
if err != nil {
ctx.ServerError("GetWebhooksByRepoID", err)
return
}
ctx.Data["Webhooks"] = ws
ctx.HTML(http.StatusOK, tplHooks)
}
type orgRepoCtx struct {
OrgID int64
RepoID int64
IsAdmin bool
IsSystemWebhook bool
Link string
LinkNew string
NewTemplate base.TplName
}
// getOrgRepoCtx determines whether this is a repo, organization, or admin (both default and system) context.
func getOrgRepoCtx(ctx *context.Context) (*orgRepoCtx, error) {
if len(ctx.Repo.RepoLink) > 0 {
return &orgRepoCtx{
RepoID: ctx.Repo.Repository.ID,
Link: path.Join(ctx.Repo.RepoLink, "settings/hooks"),
LinkNew: path.Join(ctx.Repo.RepoLink, "settings/hooks"),
NewTemplate: tplHookNew,
}, nil
}
if len(ctx.Org.OrgLink) > 0 {
return &orgRepoCtx{
OrgID: ctx.Org.Organization.ID,
Link: path.Join(ctx.Org.OrgLink, "settings/hooks"),
LinkNew: path.Join(ctx.Org.OrgLink, "settings/hooks"),
NewTemplate: tplOrgHookNew,
}, nil
}
if ctx.User.IsAdmin {
// Are we looking at default webhooks?
if ctx.Params(":configType") == "default-hooks" {
return &orgRepoCtx{
IsAdmin: true,
Link: path.Join(setting.AppSubURL, "/admin/hooks"),
LinkNew: path.Join(setting.AppSubURL, "/admin/default-hooks"),
NewTemplate: tplAdminHookNew,
}, nil
}
// Must be system webhooks instead
return &orgRepoCtx{
IsAdmin: true,
IsSystemWebhook: true,
Link: path.Join(setting.AppSubURL, "/admin/hooks"),
LinkNew: path.Join(setting.AppSubURL, "/admin/system-hooks"),
NewTemplate: tplAdminHookNew,
}, nil
}
return nil, errors.New("Unable to set OrgRepo context")
}
func checkHookType(ctx *context.Context) string {
hookType := strings.ToLower(ctx.Params(":type"))
if !util.IsStringInSlice(hookType, setting.Webhook.Types, true) {
ctx.NotFound("checkHookType", nil)
return ""
}
return hookType
}
// WebhooksNew render creating webhook page
func WebhooksNew(ctx *context.Context) {
ctx.Data["Title"] = ctx.Tr("repo.settings.add_webhook")
ctx.Data["Webhook"] = models.Webhook{HookEvent: &models.HookEvent{}}
orCtx, err := getOrgRepoCtx(ctx)
if err != nil {
ctx.ServerError("getOrgRepoCtx", err)
return
}
if orCtx.IsAdmin && orCtx.IsSystemWebhook {
ctx.Data["PageIsAdminSystemHooks"] = true
ctx.Data["PageIsAdminSystemHooksNew"] = true
} else if orCtx.IsAdmin {
ctx.Data["PageIsAdminDefaultHooks"] = true
ctx.Data["PageIsAdminDefaultHooksNew"] = true
} else {
ctx.Data["PageIsSettingsHooks"] = true
ctx.Data["PageIsSettingsHooksNew"] = true
}
hookType := checkHookType(ctx)
ctx.Data["HookType"] = hookType
if ctx.Written() {
return
}
if hookType == "discord" {
ctx.Data["DiscordHook"] = map[string]interface{}{
"Username": "Gitea",
"IconURL": setting.AppURL + "img/favicon.png",
}
}
ctx.Data["BaseLink"] = orCtx.LinkNew
ctx.HTML(http.StatusOK, orCtx.NewTemplate)
}
// ParseHookEvent convert web form content to models.HookEvent
func ParseHookEvent(form forms.WebhookForm) *models.HookEvent {
return &models.HookEvent{
PushOnly: form.PushOnly(),
SendEverything: form.SendEverything(),
ChooseEvents: form.ChooseEvents(),
HookEvents: models.HookEvents{
Create: form.Create,
Delete: form.Delete,
Fork: form.Fork,
Issues: form.Issues,
IssueAssign: form.IssueAssign,
IssueLabel: form.IssueLabel,
IssueMilestone: form.IssueMilestone,
IssueComment: form.IssueComment,
Release: form.Release,
Push: form.Push,
PullRequest: form.PullRequest,
PullRequestAssign: form.PullRequestAssign,
PullRequestLabel: form.PullRequestLabel,
PullRequestMilestone: form.PullRequestMilestone,
PullRequestComment: form.PullRequestComment,
PullRequestReview: form.PullRequestReview,
PullRequestSync: form.PullRequestSync,
Repository: form.Repository,
},
BranchFilter: form.BranchFilter,
}
}
// GiteaHooksNewPost response for creating Gitea webhook
func GiteaHooksNewPost(ctx *context.Context) {
form := web.GetForm(ctx).(*forms.NewWebhookForm)
ctx.Data["Title"] = ctx.Tr("repo.settings.add_webhook")
ctx.Data["PageIsSettingsHooks"] = true
ctx.Data["PageIsSettingsHooksNew"] = true
ctx.Data["Webhook"] = models.Webhook{HookEvent: &models.HookEvent{}}
ctx.Data["HookType"] = models.GITEA
orCtx, err := getOrgRepoCtx(ctx)
if err != nil {
ctx.ServerError("getOrgRepoCtx", err)
return
}
ctx.Data["BaseLink"] = orCtx.LinkNew
if ctx.HasError() {
ctx.HTML(http.StatusOK, orCtx.NewTemplate)
return
}
contentType := models.ContentTypeJSON
if models.HookContentType(form.ContentType) == models.ContentTypeForm {
contentType = models.ContentTypeForm
}
w := &models.Webhook{
RepoID: orCtx.RepoID,
URL: form.PayloadURL,
HTTPMethod: form.HTTPMethod,
ContentType: contentType,
Secret: form.Secret,
HookEvent: ParseHookEvent(form.WebhookForm),
IsActive: form.Active,
Type: models.GITEA,
OrgID: orCtx.OrgID,
IsSystemWebhook: orCtx.IsSystemWebhook,
}
if err := w.UpdateEvent(); err != nil {
ctx.ServerError("UpdateEvent", err)
return
} else if err := models.CreateWebhook(w); err != nil {
ctx.ServerError("CreateWebhook", err)
return
}
ctx.Flash.Success(ctx.Tr("repo.settings.add_hook_success"))
ctx.Redirect(orCtx.Link)
}
// GogsHooksNewPost response for creating webhook
func GogsHooksNewPost(ctx *context.Context) {
form := web.GetForm(ctx).(*forms.NewGogshookForm)
newGogsWebhookPost(ctx, *form, models.GOGS)
}
// newGogsWebhookPost response for creating gogs hook
func newGogsWebhookPost(ctx *context.Context, form forms.NewGogshookForm, kind models.HookType) {
ctx.Data["Title"] = ctx.Tr("repo.settings.add_webhook")
ctx.Data["PageIsSettingsHooks"] = true
ctx.Data["PageIsSettingsHooksNew"] = true
ctx.Data["Webhook"] = models.Webhook{HookEvent: &models.HookEvent{}}
ctx.Data["HookType"] = models.GOGS
orCtx, err := getOrgRepoCtx(ctx)
if err != nil {
ctx.ServerError("getOrgRepoCtx", err)
return
}
ctx.Data["BaseLink"] = orCtx.LinkNew
if ctx.HasError() {
ctx.HTML(http.StatusOK, orCtx.NewTemplate)
return
}
contentType := models.ContentTypeJSON
if models.HookContentType(form.ContentType) == models.ContentTypeForm {
contentType = models.ContentTypeForm
}
w := &models.Webhook{
RepoID: orCtx.RepoID,
URL: form.PayloadURL,
ContentType: contentType,
Secret: form.Secret,
HookEvent: ParseHookEvent(form.WebhookForm),
IsActive: form.Active,
Type: kind,
OrgID: orCtx.OrgID,
IsSystemWebhook: orCtx.IsSystemWebhook,
}
if err := w.UpdateEvent(); err != nil {
ctx.ServerError("UpdateEvent", err)
return
} else if err := models.CreateWebhook(w); err != nil {
ctx.ServerError("CreateWebhook", err)
return
}
ctx.Flash.Success(ctx.Tr("repo.settings.add_hook_success"))
ctx.Redirect(orCtx.Link)
}
// DiscordHooksNewPost response for creating discord hook
func DiscordHooksNewPost(ctx *context.Context) {
form := web.GetForm(ctx).(*forms.NewDiscordHookForm)
ctx.Data["Title"] = ctx.Tr("repo.settings")
ctx.Data["PageIsSettingsHooks"] = true
ctx.Data["PageIsSettingsHooksNew"] = true
ctx.Data["Webhook"] = models.Webhook{HookEvent: &models.HookEvent{}}
ctx.Data["HookType"] = models.DISCORD
orCtx, err := getOrgRepoCtx(ctx)
if err != nil {
ctx.ServerError("getOrgRepoCtx", err)
return
}
if ctx.HasError() {
ctx.HTML(http.StatusOK, orCtx.NewTemplate)
return
}
json := jsoniter.ConfigCompatibleWithStandardLibrary
meta, err := json.Marshal(&webhook.DiscordMeta{
Username: form.Username,
IconURL: form.IconURL,
})
if err != nil {
ctx.ServerError("Marshal", err)
return
}
w := &models.Webhook{
RepoID: orCtx.RepoID,
URL: form.PayloadURL,
ContentType: models.ContentTypeJSON,
HookEvent: ParseHookEvent(form.WebhookForm),
IsActive: form.Active,
Type: models.DISCORD,
Meta: string(meta),
OrgID: orCtx.OrgID,
IsSystemWebhook: orCtx.IsSystemWebhook,
}
if err := w.UpdateEvent(); err != nil {
ctx.ServerError("UpdateEvent", err)
return
} else if err := models.CreateWebhook(w); err != nil {
ctx.ServerError("CreateWebhook", err)
return
}
ctx.Flash.Success(ctx.Tr("repo.settings.add_hook_success"))
ctx.Redirect(orCtx.Link)
}
// DingtalkHooksNewPost response for creating dingtalk hook
func DingtalkHooksNewPost(ctx *context.Context) {
form := web.GetForm(ctx).(*forms.NewDingtalkHookForm)
ctx.Data["Title"] = ctx.Tr("repo.settings")
ctx.Data["PageIsSettingsHooks"] = true
ctx.Data["PageIsSettingsHooksNew"] = true
ctx.Data["Webhook"] = models.Webhook{HookEvent: &models.HookEvent{}}
ctx.Data["HookType"] = models.DINGTALK
orCtx, err := getOrgRepoCtx(ctx)
if err != nil {
ctx.ServerError("getOrgRepoCtx", err)
return
}
if ctx.HasError() {
ctx.HTML(http.StatusOK, orCtx.NewTemplate)
return
}
w := &models.Webhook{
RepoID: orCtx.RepoID,
URL: form.PayloadURL,
ContentType: models.ContentTypeJSON,
HookEvent: ParseHookEvent(form.WebhookForm),
IsActive: form.Active,
Type: models.DINGTALK,
Meta: "",
OrgID: orCtx.OrgID,
IsSystemWebhook: orCtx.IsSystemWebhook,
}
if err := w.UpdateEvent(); err != nil {
ctx.ServerError("UpdateEvent", err)
return
} else if err := models.CreateWebhook(w); err != nil {
ctx.ServerError("CreateWebhook", err)
return
}
ctx.Flash.Success(ctx.Tr("repo.settings.add_hook_success"))
ctx.Redirect(orCtx.Link)
}
// TelegramHooksNewPost response for creating telegram hook
func TelegramHooksNewPost(ctx *context.Context) {
form := web.GetForm(ctx).(*forms.NewTelegramHookForm)
ctx.Data["Title"] = ctx.Tr("repo.settings")
ctx.Data["PageIsSettingsHooks"] = true
ctx.Data["PageIsSettingsHooksNew"] = true
ctx.Data["Webhook"] = models.Webhook{HookEvent: &models.HookEvent{}}
ctx.Data["HookType"] = models.TELEGRAM
orCtx, err := getOrgRepoCtx(ctx)
if err != nil {
ctx.ServerError("getOrgRepoCtx", err)
return
}
if ctx.HasError() {
ctx.HTML(http.StatusOK, orCtx.NewTemplate)
return
}
json := jsoniter.ConfigCompatibleWithStandardLibrary
meta, err := json.Marshal(&webhook.TelegramMeta{
BotToken: form.BotToken,
ChatID: form.ChatID,
})
if err != nil {
ctx.ServerError("Marshal", err)
return
}
w := &models.Webhook{
RepoID: orCtx.RepoID,
URL: fmt.Sprintf("https://api.telegram.org/bot%s/sendMessage?chat_id=%s", form.BotToken, form.ChatID),
ContentType: models.ContentTypeJSON,
HookEvent: ParseHookEvent(form.WebhookForm),
IsActive: form.Active,
Type: models.TELEGRAM,
Meta: string(meta),
OrgID: orCtx.OrgID,
IsSystemWebhook: orCtx.IsSystemWebhook,
}
if err := w.UpdateEvent(); err != nil {
ctx.ServerError("UpdateEvent", err)
return
} else if err := models.CreateWebhook(w); err != nil {
ctx.ServerError("CreateWebhook", err)
return
}
ctx.Flash.Success(ctx.Tr("repo.settings.add_hook_success"))
ctx.Redirect(orCtx.Link)
}
// MatrixHooksNewPost response for creating a Matrix hook
func MatrixHooksNewPost(ctx *context.Context) {
form := web.GetForm(ctx).(*forms.NewMatrixHookForm)
ctx.Data["Title"] = ctx.Tr("repo.settings")
ctx.Data["PageIsSettingsHooks"] = true
ctx.Data["PageIsSettingsHooksNew"] = true
ctx.Data["Webhook"] = models.Webhook{HookEvent: &models.HookEvent{}}
ctx.Data["HookType"] = models.MATRIX
orCtx, err := getOrgRepoCtx(ctx)
if err != nil {
ctx.ServerError("getOrgRepoCtx", err)
return
}
if ctx.HasError() {
ctx.HTML(http.StatusOK, orCtx.NewTemplate)
return
}
json := jsoniter.ConfigCompatibleWithStandardLibrary
meta, err := json.Marshal(&webhook.MatrixMeta{
HomeserverURL: form.HomeserverURL,
Room: form.RoomID,
AccessToken: form.AccessToken,
MessageType: form.MessageType,
})
if err != nil {
ctx.ServerError("Marshal", err)
return
}
w := &models.Webhook{
RepoID: orCtx.RepoID,
URL: fmt.Sprintf("%s/_matrix/client/r0/rooms/%s/send/m.room.message", form.HomeserverURL, form.RoomID),
ContentType: models.ContentTypeJSON,
HTTPMethod: "PUT",
HookEvent: ParseHookEvent(form.WebhookForm),
IsActive: form.Active,
Type: models.MATRIX,
Meta: string(meta),
OrgID: orCtx.OrgID,
IsSystemWebhook: orCtx.IsSystemWebhook,
}
if err := w.UpdateEvent(); err != nil {
ctx.ServerError("UpdateEvent", err)
return
} else if err := models.CreateWebhook(w); err != nil {
ctx.ServerError("CreateWebhook", err)
return
}
ctx.Flash.Success(ctx.Tr("repo.settings.add_hook_success"))
ctx.Redirect(orCtx.Link)
}
// MSTeamsHooksNewPost response for creating MS Teams hook
func MSTeamsHooksNewPost(ctx *context.Context) {
form := web.GetForm(ctx).(*forms.NewMSTeamsHookForm)
ctx.Data["Title"] = ctx.Tr("repo.settings")
ctx.Data["PageIsSettingsHooks"] = true
ctx.Data["PageIsSettingsHooksNew"] = true
ctx.Data["Webhook"] = models.Webhook{HookEvent: &models.HookEvent{}}
ctx.Data["HookType"] = models.MSTEAMS
orCtx, err := getOrgRepoCtx(ctx)
if err != nil {
ctx.ServerError("getOrgRepoCtx", err)
return
}
if ctx.HasError() {
ctx.HTML(http.StatusOK, orCtx.NewTemplate)
return
}
w := &models.Webhook{
RepoID: orCtx.RepoID,
URL: form.PayloadURL,
ContentType: models.ContentTypeJSON,
HookEvent: ParseHookEvent(form.WebhookForm),
IsActive: form.Active,
Type: models.MSTEAMS,
Meta: "",
OrgID: orCtx.OrgID,
IsSystemWebhook: orCtx.IsSystemWebhook,
}
if err := w.UpdateEvent(); err != nil {
ctx.ServerError("UpdateEvent", err)
return
} else if err := models.CreateWebhook(w); err != nil {
ctx.ServerError("CreateWebhook", err)
return
}
ctx.Flash.Success(ctx.Tr("repo.settings.add_hook_success"))
ctx.Redirect(orCtx.Link)
}
// SlackHooksNewPost response for creating slack hook
func SlackHooksNewPost(ctx *context.Context) {
form := web.GetForm(ctx).(*forms.NewSlackHookForm)
ctx.Data["Title"] = ctx.Tr("repo.settings")
ctx.Data["PageIsSettingsHooks"] = true
ctx.Data["PageIsSettingsHooksNew"] = true
ctx.Data["Webhook"] = models.Webhook{HookEvent: &models.HookEvent{}}
ctx.Data["HookType"] = models.SLACK
orCtx, err := getOrgRepoCtx(ctx)
if err != nil {
ctx.ServerError("getOrgRepoCtx", err)
return
}
if ctx.HasError() {
ctx.HTML(http.StatusOK, orCtx.NewTemplate)
return
}
if form.HasInvalidChannel() {
ctx.Flash.Error(ctx.Tr("repo.settings.add_webhook.invalid_channel_name"))
ctx.Redirect(orCtx.LinkNew + "/slack/new")
return
}
json := jsoniter.ConfigCompatibleWithStandardLibrary
meta, err := json.Marshal(&webhook.SlackMeta{
Channel: strings.TrimSpace(form.Channel),
Username: form.Username,
IconURL: form.IconURL,
Color: form.Color,
})
if err != nil {
ctx.ServerError("Marshal", err)
return
}
w := &models.Webhook{
RepoID: orCtx.RepoID,
URL: form.PayloadURL,
ContentType: models.ContentTypeJSON,
HookEvent: ParseHookEvent(form.WebhookForm),
IsActive: form.Active,
Type: models.SLACK,
Meta: string(meta),
OrgID: orCtx.OrgID,
IsSystemWebhook: orCtx.IsSystemWebhook,
}
if err := w.UpdateEvent(); err != nil {
ctx.ServerError("UpdateEvent", err)
return
} else if err := models.CreateWebhook(w); err != nil {
ctx.ServerError("CreateWebhook", err)
return
}
ctx.Flash.Success(ctx.Tr("repo.settings.add_hook_success"))
ctx.Redirect(orCtx.Link)
}
// FeishuHooksNewPost response for creating feishu hook
func FeishuHooksNewPost(ctx *context.Context) {
form := web.GetForm(ctx).(*forms.NewFeishuHookForm)
ctx.Data["Title"] = ctx.Tr("repo.settings")
ctx.Data["PageIsSettingsHooks"] = true
ctx.Data["PageIsSettingsHooksNew"] = true
ctx.Data["Webhook"] = models.Webhook{HookEvent: &models.HookEvent{}}
ctx.Data["HookType"] = models.FEISHU
orCtx, err := getOrgRepoCtx(ctx)
if err != nil {
ctx.ServerError("getOrgRepoCtx", err)
return
}
if ctx.HasError() {
ctx.HTML(http.StatusOK, orCtx.NewTemplate)
return
}
w := &models.Webhook{
RepoID: orCtx.RepoID,
URL: form.PayloadURL,
ContentType: models.ContentTypeJSON,
HookEvent: ParseHookEvent(form.WebhookForm),
IsActive: form.Active,
Type: models.FEISHU,
Meta: "",
OrgID: orCtx.OrgID,
IsSystemWebhook: orCtx.IsSystemWebhook,
}
if err := w.UpdateEvent(); err != nil {
ctx.ServerError("UpdateEvent", err)
return
} else if err := models.CreateWebhook(w); err != nil {
ctx.ServerError("CreateWebhook", err)
return
}
ctx.Flash.Success(ctx.Tr("repo.settings.add_hook_success"))
ctx.Redirect(orCtx.Link)
}
// WechatworkHooksNewPost response for creating wechatwork hook
func WechatworkHooksNewPost(ctx *context.Context) {
form := web.GetForm(ctx).(*forms.NewWechatWorkHookForm)
ctx.Data["Title"] = ctx.Tr("repo.settings")
ctx.Data["PageIsSettingsHooks"] = true
ctx.Data["PageIsSettingsHooksNew"] = true
ctx.Data["Webhook"] = models.Webhook{HookEvent: &models.HookEvent{}}
ctx.Data["HookType"] = models.WECHATWORK
orCtx, err := getOrgRepoCtx(ctx)
if err != nil {
ctx.ServerError("getOrgRepoCtx", err)
return
}
if ctx.HasError() {
ctx.HTML(http.StatusOK, orCtx.NewTemplate)
return
}
w := &models.Webhook{
RepoID: orCtx.RepoID,
URL: form.PayloadURL,
ContentType: models.ContentTypeJSON,
HookEvent: ParseHookEvent(form.WebhookForm),
IsActive: form.Active,
Type: models.WECHATWORK,
Meta: "",
OrgID: orCtx.OrgID,
IsSystemWebhook: orCtx.IsSystemWebhook,
}
if err := w.UpdateEvent(); err != nil {
ctx.ServerError("UpdateEvent", err)
return
} else if err := models.CreateWebhook(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, *models.Webhook) {
ctx.Data["RequireHighlightJS"] = true
orCtx, err := getOrgRepoCtx(ctx)
if err != nil {
ctx.ServerError("getOrgRepoCtx", err)
return nil, nil
}
ctx.Data["BaseLink"] = orCtx.Link
var w *models.Webhook
if orCtx.RepoID > 0 {
w, err = models.GetWebhookByRepoID(ctx.Repo.Repository.ID, ctx.ParamsInt64(":id"))
} else if orCtx.OrgID > 0 {
w, err = models.GetWebhookByOrgID(ctx.Org.Organization.ID, ctx.ParamsInt64(":id"))
} else if orCtx.IsAdmin {
w, err = models.GetSystemOrDefaultWebhook(ctx.ParamsInt64(":id"))
}
if err != nil || w == nil {
if models.IsErrWebhookNotExist(err) {
ctx.NotFound("GetWebhookByID", nil)
} else {
ctx.ServerError("GetWebhookByID", err)
}
return nil, nil
}
ctx.Data["HookType"] = w.Type
switch w.Type {
case models.SLACK:
ctx.Data["SlackHook"] = webhook.GetSlackHook(w)
case models.DISCORD:
ctx.Data["DiscordHook"] = webhook.GetDiscordHook(w)
case models.TELEGRAM:
ctx.Data["TelegramHook"] = webhook.GetTelegramHook(w)
case models.MATRIX:
ctx.Data["MatrixHook"] = webhook.GetMatrixHook(w)
}
ctx.Data["History"], err = w.History(1)
if err != nil {
ctx.ServerError("History", err)
}
return orCtx, w
}
// WebHooksEdit render editing web hook page
func WebHooksEdit(ctx *context.Context) {
ctx.Data["Title"] = ctx.Tr("repo.settings.update_webhook")
ctx.Data["PageIsSettingsHooks"] = true
ctx.Data["PageIsSettingsHooksEdit"] = true
orCtx, w := checkWebhook(ctx)
if ctx.Written() {
return
}
ctx.Data["Webhook"] = w
ctx.HTML(http.StatusOK, orCtx.NewTemplate)
}
// WebHooksEditPost response for editing web hook
func WebHooksEditPost(ctx *context.Context) {
form := web.GetForm(ctx).(*forms.NewWebhookForm)
ctx.Data["Title"] = ctx.Tr("repo.settings.update_webhook")
ctx.Data["PageIsSettingsHooks"] = true
ctx.Data["PageIsSettingsHooksEdit"] = true
orCtx, w := checkWebhook(ctx)
if ctx.Written() {
return
}
ctx.Data["Webhook"] = w
if ctx.HasError() {
ctx.HTML(http.StatusOK, orCtx.NewTemplate)
return
}
contentType := models.ContentTypeJSON
if models.HookContentType(form.ContentType) == models.ContentTypeForm {
contentType = models.ContentTypeForm
}
w.URL = form.PayloadURL
w.ContentType = contentType
w.Secret = form.Secret
w.HookEvent = ParseHookEvent(form.WebhookForm)
w.IsActive = form.Active
w.HTTPMethod = form.HTTPMethod
if err := w.UpdateEvent(); err != nil {
ctx.ServerError("UpdateEvent", err)
return
} else if err := models.UpdateWebhook(w); err != nil {
ctx.ServerError("WebHooksEditPost", err)
return
}
ctx.Flash.Success(ctx.Tr("repo.settings.update_hook_success"))
ctx.Redirect(fmt.Sprintf("%s/%d", orCtx.Link, w.ID))
}
// GogsHooksEditPost response for editing gogs hook
func GogsHooksEditPost(ctx *context.Context) {
form := web.GetForm(ctx).(*forms.NewGogshookForm)
ctx.Data["Title"] = ctx.Tr("repo.settings.update_webhook")
ctx.Data["PageIsSettingsHooks"] = true
ctx.Data["PageIsSettingsHooksEdit"] = true
orCtx, w := checkWebhook(ctx)
if ctx.Written() {
return
}
ctx.Data["Webhook"] = w
if ctx.HasError() {
ctx.HTML(http.StatusOK, orCtx.NewTemplate)
return
}
contentType := models.ContentTypeJSON
if models.HookContentType(form.ContentType) == models.ContentTypeForm {
contentType = models.ContentTypeForm
}
w.URL = form.PayloadURL
w.ContentType = contentType
w.Secret = form.Secret
w.HookEvent = ParseHookEvent(form.WebhookForm)
w.IsActive = form.Active
if err := w.UpdateEvent(); err != nil {
ctx.ServerError("UpdateEvent", err)
return
} else if err := models.UpdateWebhook(w); err != nil {
ctx.ServerError("GogsHooksEditPost", err)
return
}
ctx.Flash.Success(ctx.Tr("repo.settings.update_hook_success"))
ctx.Redirect(fmt.Sprintf("%s/%d", orCtx.Link, w.ID))
}
// SlackHooksEditPost response for editing slack hook
func SlackHooksEditPost(ctx *context.Context) {
form := web.GetForm(ctx).(*forms.NewSlackHookForm)
ctx.Data["Title"] = ctx.Tr("repo.settings")
ctx.Data["PageIsSettingsHooks"] = true
ctx.Data["PageIsSettingsHooksEdit"] = true
orCtx, w := checkWebhook(ctx)
if ctx.Written() {
return
}
ctx.Data["Webhook"] = w
if ctx.HasError() {
ctx.HTML(http.StatusOK, orCtx.NewTemplate)
return
}
if form.HasInvalidChannel() {
ctx.Flash.Error(ctx.Tr("repo.settings.add_webhook.invalid_channel_name"))
ctx.Redirect(fmt.Sprintf("%s/%d", orCtx.Link, w.ID))
return
}
json := jsoniter.ConfigCompatibleWithStandardLibrary
meta, err := json.Marshal(&webhook.SlackMeta{
Channel: strings.TrimSpace(form.Channel),
Username: form.Username,
IconURL: form.IconURL,
Color: form.Color,
})
if err != nil {
ctx.ServerError("Marshal", err)
return
}
w.URL = form.PayloadURL
w.Meta = string(meta)
w.HookEvent = ParseHookEvent(form.WebhookForm)
w.IsActive = form.Active
if err := w.UpdateEvent(); err != nil {
ctx.ServerError("UpdateEvent", err)
return
} else if err := models.UpdateWebhook(w); err != nil {
ctx.ServerError("UpdateWebhook", err)
return
}
ctx.Flash.Success(ctx.Tr("repo.settings.update_hook_success"))
ctx.Redirect(fmt.Sprintf("%s/%d", orCtx.Link, w.ID))
}
// DiscordHooksEditPost response for editing discord hook
func DiscordHooksEditPost(ctx *context.Context) {
form := web.GetForm(ctx).(*forms.NewDiscordHookForm)
ctx.Data["Title"] = ctx.Tr("repo.settings")
ctx.Data["PageIsSettingsHooks"] = true
ctx.Data["PageIsSettingsHooksEdit"] = true
orCtx, w := checkWebhook(ctx)
if ctx.Written() {
return
}
ctx.Data["Webhook"] = w
if ctx.HasError() {
ctx.HTML(http.StatusOK, orCtx.NewTemplate)
return
}
json := jsoniter.ConfigCompatibleWithStandardLibrary
meta, err := json.Marshal(&webhook.DiscordMeta{
Username: form.Username,
IconURL: form.IconURL,
})
if err != nil {
ctx.ServerError("Marshal", err)
return
}
w.URL = form.PayloadURL
w.Meta = string(meta)
w.HookEvent = ParseHookEvent(form.WebhookForm)
w.IsActive = form.Active
if err := w.UpdateEvent(); err != nil {
ctx.ServerError("UpdateEvent", err)
return
} else if err := models.UpdateWebhook(w); err != nil {
ctx.ServerError("UpdateWebhook", err)
return
}
ctx.Flash.Success(ctx.Tr("repo.settings.update_hook_success"))
ctx.Redirect(fmt.Sprintf("%s/%d", orCtx.Link, w.ID))
}
// DingtalkHooksEditPost response for editing discord hook
func DingtalkHooksEditPost(ctx *context.Context) {
form := web.GetForm(ctx).(*forms.NewDingtalkHookForm)
ctx.Data["Title"] = ctx.Tr("repo.settings")
ctx.Data["PageIsSettingsHooks"] = true
ctx.Data["PageIsSettingsHooksEdit"] = true
orCtx, w := checkWebhook(ctx)
if ctx.Written() {
return
}
ctx.Data["Webhook"] = w
if ctx.HasError() {
ctx.HTML(http.StatusOK, orCtx.NewTemplate)
return
}
w.URL = form.PayloadURL
w.HookEvent = ParseHookEvent(form.WebhookForm)
w.IsActive = form.Active
if err := w.UpdateEvent(); err != nil {
ctx.ServerError("UpdateEvent", err)
return
} else if err := models.UpdateWebhook(w); err != nil {
ctx.ServerError("UpdateWebhook", err)
return
}
ctx.Flash.Success(ctx.Tr("repo.settings.update_hook_success"))
ctx.Redirect(fmt.Sprintf("%s/%d", orCtx.Link, w.ID))
}
// TelegramHooksEditPost response for editing discord hook
func TelegramHooksEditPost(ctx *context.Context) {
form := web.GetForm(ctx).(*forms.NewTelegramHookForm)
ctx.Data["Title"] = ctx.Tr("repo.settings")
ctx.Data["PageIsSettingsHooks"] = true
ctx.Data["PageIsSettingsHooksEdit"] = true
orCtx, w := checkWebhook(ctx)
if ctx.Written() {
return
}
ctx.Data["Webhook"] = w
if ctx.HasError() {
ctx.HTML(http.StatusOK, orCtx.NewTemplate)
return
}
json := jsoniter.ConfigCompatibleWithStandardLibrary
meta, err := json.Marshal(&webhook.TelegramMeta{
BotToken: form.BotToken,
ChatID: form.ChatID,
})
if err != nil {
ctx.ServerError("Marshal", err)
return
}
w.Meta = string(meta)
w.URL = fmt.Sprintf("https://api.telegram.org/bot%s/sendMessage?chat_id=%s", form.BotToken, form.ChatID)
w.HookEvent = ParseHookEvent(form.WebhookForm)
w.IsActive = form.Active
if err := w.UpdateEvent(); err != nil {
ctx.ServerError("UpdateEvent", err)
return
} else if err := models.UpdateWebhook(w); err != nil {
ctx.ServerError("UpdateWebhook", err)
return
}
ctx.Flash.Success(ctx.Tr("repo.settings.update_hook_success"))
ctx.Redirect(fmt.Sprintf("%s/%d", orCtx.Link, w.ID))
}
// MatrixHooksEditPost response for editing a Matrix hook
func MatrixHooksEditPost(ctx *context.Context) {
form := web.GetForm(ctx).(*forms.NewMatrixHookForm)
ctx.Data["Title"] = ctx.Tr("repo.settings")
ctx.Data["PageIsSettingsHooks"] = true
ctx.Data["PageIsSettingsHooksEdit"] = true
orCtx, w := checkWebhook(ctx)
if ctx.Written() {
return
}
ctx.Data["Webhook"] = w
if ctx.HasError() {
ctx.HTML(http.StatusOK, orCtx.NewTemplate)
return
}
json := jsoniter.ConfigCompatibleWithStandardLibrary
meta, err := json.Marshal(&webhook.MatrixMeta{
HomeserverURL: form.HomeserverURL,
Room: form.RoomID,
AccessToken: form.AccessToken,
MessageType: form.MessageType,
})
if err != nil {
ctx.ServerError("Marshal", err)
return
}
w.Meta = string(meta)
w.URL = fmt.Sprintf("%s/_matrix/client/r0/rooms/%s/send/m.room.message", form.HomeserverURL, form.RoomID)
w.HookEvent = ParseHookEvent(form.WebhookForm)
w.IsActive = form.Active
if err := w.UpdateEvent(); err != nil {
ctx.ServerError("UpdateEvent", err)
return
} else if err := models.UpdateWebhook(w); err != nil {
ctx.ServerError("UpdateWebhook", err)
return
}
ctx.Flash.Success(ctx.Tr("repo.settings.update_hook_success"))
ctx.Redirect(fmt.Sprintf("%s/%d", orCtx.Link, w.ID))
}
// MSTeamsHooksEditPost response for editing MS Teams hook
func MSTeamsHooksEditPost(ctx *context.Context) {
form := web.GetForm(ctx).(*forms.NewMSTeamsHookForm)
ctx.Data["Title"] = ctx.Tr("repo.settings")
ctx.Data["PageIsSettingsHooks"] = true
ctx.Data["PageIsSettingsHooksEdit"] = true
orCtx, w := checkWebhook(ctx)
if ctx.Written() {
return
}
ctx.Data["Webhook"] = w
if ctx.HasError() {
ctx.HTML(http.StatusOK, orCtx.NewTemplate)
return
}
w.URL = form.PayloadURL
w.HookEvent = ParseHookEvent(form.WebhookForm)
w.IsActive = form.Active
if err := w.UpdateEvent(); err != nil {
ctx.ServerError("UpdateEvent", err)
return
} else if err := models.UpdateWebhook(w); err != nil {
ctx.ServerError("UpdateWebhook", err)
return
}
ctx.Flash.Success(ctx.Tr("repo.settings.update_hook_success"))
ctx.Redirect(fmt.Sprintf("%s/%d", orCtx.Link, w.ID))
}
// FeishuHooksEditPost response for editing feishu hook
func FeishuHooksEditPost(ctx *context.Context) {
form := web.GetForm(ctx).(*forms.NewFeishuHookForm)
ctx.Data["Title"] = ctx.Tr("repo.settings")
ctx.Data["PageIsSettingsHooks"] = true
ctx.Data["PageIsSettingsHooksEdit"] = true
orCtx, w := checkWebhook(ctx)
if ctx.Written() {
return
}
ctx.Data["Webhook"] = w
if ctx.HasError() {
ctx.HTML(http.StatusOK, orCtx.NewTemplate)
return
}
w.URL = form.PayloadURL
w.HookEvent = ParseHookEvent(form.WebhookForm)
w.IsActive = form.Active
if err := w.UpdateEvent(); err != nil {
ctx.ServerError("UpdateEvent", err)
return
} else if err := models.UpdateWebhook(w); err != nil {
ctx.ServerError("UpdateWebhook", err)
return
}
ctx.Flash.Success(ctx.Tr("repo.settings.update_hook_success"))
ctx.Redirect(fmt.Sprintf("%s/%d", orCtx.Link, w.ID))
}
// WechatworkHooksEditPost response for editing wechatwork hook
func WechatworkHooksEditPost(ctx *context.Context) {
form := web.GetForm(ctx).(*forms.NewWechatWorkHookForm)
ctx.Data["Title"] = ctx.Tr("repo.settings")
ctx.Data["PageIsSettingsHooks"] = true
ctx.Data["PageIsSettingsHooksEdit"] = true
orCtx, w := checkWebhook(ctx)
if ctx.Written() {
return
}
ctx.Data["Webhook"] = w
if ctx.HasError() {
ctx.HTML(http.StatusOK, orCtx.NewTemplate)
return
}
w.URL = form.PayloadURL
w.HookEvent = ParseHookEvent(form.WebhookForm)
w.IsActive = form.Active
if err := w.UpdateEvent(); err != nil {
ctx.ServerError("UpdateEvent", err)
return
} else if err := models.UpdateWebhook(w); err != nil {
ctx.ServerError("UpdateWebhook", err)
return
}
ctx.Flash.Success(ctx.Tr("repo.settings.update_hook_success"))
ctx.Redirect(fmt.Sprintf("%s/%d", orCtx.Link, w.ID))
}
// TestWebhook test if web hook is work fine
func TestWebhook(ctx *context.Context) {
hookID := ctx.ParamsInt64(":id")
w, err := models.GetWebhookByRepoID(ctx.Repo.Repository.ID, hookID)
if err != nil {
ctx.Flash.Error("GetWebhookByID: " + err.Error())
ctx.Status(500)
return
}
// Grab latest commit or fake one if it's empty repository.
commit := ctx.Repo.Commit
if commit == nil {
ghost := models.NewGhostUser()
commit = &git.Commit{
ID: git.MustIDFromString(git.EmptySHA),
Author: ghost.NewGitSig(),
Committer: ghost.NewGitSig(),
CommitMessage: "This is a fake commit",
}
}
apiUser := convert.ToUserWithAccessMode(ctx.User, models.AccessModeNone)
apiCommit := &api.PayloadCommit{
ID: commit.ID.String(),
Message: commit.Message(),
URL: ctx.Repo.Repository.HTMLURL() + "/commit/" + commit.ID.String(),
Author: &api.PayloadUser{
Name: commit.Author.Name,
Email: commit.Author.Email,
},
Committer: &api.PayloadUser{
Name: commit.Committer.Name,
Email: commit.Committer.Email,
},
}
p := &api.PushPayload{
Ref: git.BranchPrefix + ctx.Repo.Repository.DefaultBranch,
Before: commit.ID.String(),
After: commit.ID.String(),
Commits: []*api.PayloadCommit{apiCommit},
HeadCommit: apiCommit,
Repo: convert.ToRepo(ctx.Repo.Repository, models.AccessModeNone),
Pusher: apiUser,
Sender: apiUser,
}
if err := webhook.PrepareWebhook(w, ctx.Repo.Repository, models.HookEventPush, p); err != nil {
ctx.Flash.Error("PrepareWebhook: " + err.Error())
ctx.Status(500)
} else {
ctx.Flash.Info(ctx.Tr("repo.settings.webhook.test_delivery_success"))
ctx.Status(200)
}
}
// DeleteWebhook delete a webhook
func DeleteWebhook(ctx *context.Context) {
if err := models.DeleteWebhookByRepoID(ctx.Repo.Repository.ID, ctx.QueryInt64("id")); err != nil {
ctx.Flash.Error("DeleteWebhookByRepoID: " + err.Error())
} else {
ctx.Flash.Success(ctx.Tr("repo.settings.webhook_deletion_success"))
}
ctx.JSON(http.StatusOK, map[string]interface{}{
"redirect": ctx.Repo.RepoLink + "/settings/hooks",
})
}