2019-11-02 01:51:22 +03:00
// Copyright 2019 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
import (
"fmt"
"strings"
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/sync"
2021-08-12 15:43:08 +03:00
"code.gitea.io/gitea/modules/util"
2019-11-02 01:51:22 +03:00
"github.com/gobwas/glob"
)
2020-12-08 13:41:14 +03:00
type webhook struct {
2021-06-27 22:21:09 +03:00
name models . HookType
2020-12-08 13:41:14 +03:00
payloadCreator func ( p api . Payloader , event models . HookEventType , meta string ) ( api . Payloader , error )
}
var (
2021-06-27 22:21:09 +03:00
webhooks = map [ models . HookType ] * webhook {
2020-12-08 13:41:14 +03:00
models . SLACK : {
name : models . SLACK ,
payloadCreator : GetSlackPayload ,
} ,
models . DISCORD : {
name : models . DISCORD ,
payloadCreator : GetDiscordPayload ,
} ,
models . DINGTALK : {
name : models . DINGTALK ,
payloadCreator : GetDingtalkPayload ,
} ,
models . TELEGRAM : {
name : models . TELEGRAM ,
payloadCreator : GetTelegramPayload ,
} ,
models . MSTEAMS : {
name : models . MSTEAMS ,
payloadCreator : GetMSTeamsPayload ,
} ,
models . FEISHU : {
name : models . FEISHU ,
payloadCreator : GetFeishuPayload ,
} ,
models . MATRIX : {
name : models . MATRIX ,
payloadCreator : GetMatrixPayload ,
} ,
2021-07-23 07:41:27 +03:00
models . WECHATWORK : {
name : models . WECHATWORK ,
payloadCreator : GetWechatworkPayload ,
} ,
2020-12-08 13:41:14 +03:00
}
)
// RegisterWebhook registers a webhook
func RegisterWebhook ( name string , webhook * webhook ) {
2021-06-27 22:21:09 +03:00
webhooks [ models . HookType ( name ) ] = webhook
2020-12-08 13:41:14 +03:00
}
// IsValidHookTaskType returns true if a webhook registered
func IsValidHookTaskType ( name string ) bool {
2020-12-12 18:33:19 +03:00
if name == models . GITEA || name == models . GOGS {
return true
}
2021-06-27 22:21:09 +03:00
_ , ok := webhooks [ models . HookType ( name ) ]
2020-12-08 13:41:14 +03:00
return ok
}
2019-11-02 05:35:12 +03:00
// hookQueue is a global queue of web hooks
var hookQueue = sync . NewUniqueQueue ( setting . Webhook . QueueLength )
2019-11-02 01:51:22 +03:00
// getPayloadBranch returns branch for hook event, if applicable.
func getPayloadBranch ( p api . Payloader ) string {
switch pp := p . ( type ) {
case * api . CreatePayload :
if pp . RefType == "branch" {
return pp . Ref
}
case * api . DeletePayload :
if pp . RefType == "branch" {
return pp . Ref
}
case * api . PushPayload :
if strings . HasPrefix ( pp . Ref , git . BranchPrefix ) {
return pp . Ref [ len ( git . BranchPrefix ) : ]
}
}
return ""
}
// PrepareWebhook adds special webhook to task queue for given payload.
func PrepareWebhook ( w * models . Webhook , repo * models . Repository , event models . HookEventType , p api . Payloader ) error {
2019-11-02 05:35:12 +03:00
if err := prepareWebhook ( w , repo , event , p ) ; err != nil {
return err
}
go hookQueue . Add ( repo . ID )
return nil
2019-11-02 01:51:22 +03:00
}
func checkBranch ( w * models . Webhook , branch string ) bool {
if w . BranchFilter == "" || w . BranchFilter == "*" {
return true
}
g , err := glob . Compile ( w . BranchFilter )
if err != nil {
// should not really happen as BranchFilter is validated
log . Error ( "CheckBranch failed: %s" , err )
return false
}
return g . Match ( branch )
}
func prepareWebhook ( w * models . Webhook , repo * models . Repository , event models . HookEventType , p api . Payloader ) error {
2021-02-11 20:34:34 +03:00
// Skip sending if webhooks are disabled.
if setting . DisableWebhooks {
return nil
}
2019-11-02 01:51:22 +03:00
for _ , e := range w . EventCheckers ( ) {
if event == e . Type {
if ! e . Has ( ) {
return nil
}
2021-02-22 21:54:01 +03:00
break
2019-11-02 01:51:22 +03:00
}
}
2020-09-03 05:46:02 +03:00
// Avoid sending "0 new commits" to non-integration relevant webhooks (e.g. slack, discord, etc.).
// Integration webhooks (e.g. drone) still receive the required data.
if pushEvent , ok := p . ( * api . PushPayload ) ; ok &&
2020-12-09 20:20:13 +03:00
w . Type != models . GITEA && w . Type != models . GOGS &&
2020-09-03 05:46:02 +03:00
len ( pushEvent . Commits ) == 0 {
return nil
}
2019-11-02 01:51:22 +03:00
// If payload has no associated branch (e.g. it's a new tag, issue, etc.),
// branch filter has no effect.
if branch := getPayloadBranch ( p ) ; branch != "" {
if ! checkBranch ( w , branch ) {
log . Info ( "Branch %q doesn't match branch filter %q, skipping" , branch , w . BranchFilter )
return nil
}
}
var payloader api . Payloader
var err error
2021-01-06 18:11:23 +03:00
webhook , ok := webhooks [ w . Type ]
2020-12-08 13:41:14 +03:00
if ok {
payloader , err = webhook . payloadCreator ( p , event , w . Meta )
2020-03-28 16:09:55 +03:00
if err != nil {
2020-12-09 20:20:13 +03:00
return fmt . Errorf ( "create payload for %s[%s]: %v" , w . Type , event , err )
2020-03-28 16:09:55 +03:00
}
2020-12-08 13:41:14 +03:00
} else {
2019-11-02 01:51:22 +03:00
payloader = p
}
if err = models . CreateHookTask ( & models . HookTask {
2021-06-27 22:21:09 +03:00
RepoID : repo . ID ,
HookID : w . ID ,
Payloader : payloader ,
EventType : event ,
2019-11-02 01:51:22 +03:00
} ) ; err != nil {
return fmt . Errorf ( "CreateHookTask: %v" , err )
}
return nil
}
// PrepareWebhooks adds new webhooks to task queue for given payload.
func PrepareWebhooks ( repo * models . Repository , event models . HookEventType , p api . Payloader ) error {
2019-11-02 05:35:12 +03:00
if err := prepareWebhooks ( repo , event , p ) ; err != nil {
return err
}
go hookQueue . Add ( repo . ID )
return nil
2019-11-02 01:51:22 +03:00
}
func prepareWebhooks ( repo * models . Repository , event models . HookEventType , p api . Payloader ) error {
2021-08-12 15:43:08 +03:00
ws , err := models . ListWebhooksByOpts ( & models . ListWebhookOptions {
RepoID : repo . ID ,
IsActive : util . OptionalBoolTrue ,
} )
2019-11-02 01:51:22 +03:00
if err != nil {
return fmt . Errorf ( "GetActiveWebhooksByRepoID: %v" , err )
}
// check if repo belongs to org and append additional webhooks
if repo . MustOwner ( ) . IsOrganization ( ) {
// get hooks for org
2021-08-12 15:43:08 +03:00
orgHooks , err := models . ListWebhooksByOpts ( & models . ListWebhookOptions {
OrgID : repo . OwnerID ,
IsActive : util . OptionalBoolTrue ,
} )
2019-11-02 01:51:22 +03:00
if err != nil {
return fmt . Errorf ( "GetActiveWebhooksByOrgID: %v" , err )
}
ws = append ( ws , orgHooks ... )
}
2020-03-09 01:08:05 +03:00
// Add any admin-defined system webhooks
systemHooks , err := models . GetSystemWebhooks ( )
if err != nil {
return fmt . Errorf ( "GetSystemWebhooks: %v" , err )
}
ws = append ( ws , systemHooks ... )
2019-11-02 01:51:22 +03:00
if len ( ws ) == 0 {
return nil
}
for _ , w := range ws {
if err = prepareWebhook ( w , repo , event , p ) ; err != nil {
return err
}
}
return nil
}