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"
2021-11-10 08:13:16 +03:00
webhook_model "code.gitea.io/gitea/models/webhook"
2019-11-02 01:51:22 +03:00
"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-11-10 08:13:16 +03:00
name webhook_model . HookType
payloadCreator func ( p api . Payloader , event webhook_model . HookEventType , meta string ) ( api . Payloader , error )
2020-12-08 13:41:14 +03:00
}
var (
2021-11-10 08:13:16 +03:00
webhooks = map [ webhook_model . HookType ] * webhook {
webhook_model . SLACK : {
name : webhook_model . SLACK ,
2020-12-08 13:41:14 +03:00
payloadCreator : GetSlackPayload ,
} ,
2021-11-10 08:13:16 +03:00
webhook_model . DISCORD : {
name : webhook_model . DISCORD ,
2020-12-08 13:41:14 +03:00
payloadCreator : GetDiscordPayload ,
} ,
2021-11-10 08:13:16 +03:00
webhook_model . DINGTALK : {
name : webhook_model . DINGTALK ,
2020-12-08 13:41:14 +03:00
payloadCreator : GetDingtalkPayload ,
} ,
2021-11-10 08:13:16 +03:00
webhook_model . TELEGRAM : {
name : webhook_model . TELEGRAM ,
2020-12-08 13:41:14 +03:00
payloadCreator : GetTelegramPayload ,
} ,
2021-11-10 08:13:16 +03:00
webhook_model . MSTEAMS : {
name : webhook_model . MSTEAMS ,
2020-12-08 13:41:14 +03:00
payloadCreator : GetMSTeamsPayload ,
} ,
2021-11-10 08:13:16 +03:00
webhook_model . FEISHU : {
name : webhook_model . FEISHU ,
2020-12-08 13:41:14 +03:00
payloadCreator : GetFeishuPayload ,
} ,
2021-11-10 08:13:16 +03:00
webhook_model . MATRIX : {
name : webhook_model . MATRIX ,
2020-12-08 13:41:14 +03:00
payloadCreator : GetMatrixPayload ,
} ,
2021-11-10 08:13:16 +03:00
webhook_model . WECHATWORK : {
name : webhook_model . WECHATWORK ,
2021-07-23 07:41:27 +03:00
payloadCreator : GetWechatworkPayload ,
} ,
2020-12-08 13:41:14 +03:00
}
)
// RegisterWebhook registers a webhook
func RegisterWebhook ( name string , webhook * webhook ) {
2021-11-10 08:13:16 +03:00
webhooks [ webhook_model . HookType ( name ) ] = webhook
2020-12-08 13:41:14 +03:00
}
// IsValidHookTaskType returns true if a webhook registered
func IsValidHookTaskType ( name string ) bool {
2021-11-10 08:13:16 +03:00
if name == webhook_model . GITEA || name == webhook_model . GOGS {
2020-12-12 18:33:19 +03:00
return true
}
2021-11-10 08:13:16 +03:00
_ , ok := webhooks [ webhook_model . 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.
2021-11-10 08:13:16 +03:00
func PrepareWebhook ( w * webhook_model . Webhook , repo * models . Repository , event webhook_model . 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
}
2021-11-10 08:13:16 +03:00
func checkBranch ( w * webhook_model . Webhook , branch string ) bool {
2019-11-02 01:51:22 +03:00
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 )
}
2021-11-10 08:13:16 +03:00
func prepareWebhook ( w * webhook_model . Webhook , repo * models . Repository , event webhook_model . 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 &&
2021-11-10 08:13:16 +03:00
w . Type != webhook_model . GITEA && w . Type != webhook_model . 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
}
2021-11-10 08:13:16 +03:00
if err = webhook_model . CreateHookTask ( & webhook_model . 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.
2021-11-10 08:13:16 +03:00
func PrepareWebhooks ( repo * models . Repository , event webhook_model . 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
}
2021-11-10 08:13:16 +03:00
func prepareWebhooks ( repo * models . Repository , event webhook_model . HookEventType , p api . Payloader ) error {
ws , err := webhook_model . ListWebhooksByOpts ( & webhook_model . ListWebhookOptions {
2021-08-12 15:43:08 +03:00
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-11-10 08:13:16 +03:00
orgHooks , err := webhook_model . ListWebhooksByOpts ( & webhook_model . ListWebhookOptions {
2021-08-12 15:43:08 +03:00
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
2021-11-10 08:13:16 +03:00
systemHooks , err := webhook_model . GetSystemWebhooks ( )
2020-03-09 01:08:05 +03:00
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
}