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 (
2022-05-20 17:08:52 +03:00
"context"
2019-11-02 01:51:22 +03:00
"fmt"
2022-04-01 19:34:57 +03:00
"strconv"
2019-11-02 01:51:22 +03:00
"strings"
2022-05-20 17:08:52 +03:00
"code.gitea.io/gitea/models/db"
2021-12-10 04:27:50 +03:00
repo_model "code.gitea.io/gitea/models/repo"
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"
2022-04-25 21:03:01 +03:00
"code.gitea.io/gitea/modules/graceful"
2019-11-02 01:51:22 +03:00
"code.gitea.io/gitea/modules/log"
2022-04-25 21:03:01 +03:00
"code.gitea.io/gitea/modules/queue"
2019-11-02 01:51:22 +03:00
"code.gitea.io/gitea/modules/setting"
api "code.gitea.io/gitea/modules/structs"
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
}
2022-01-20 20:46:10 +03:00
var webhooks = map [ webhook_model . HookType ] * webhook {
webhook_model . SLACK : {
name : webhook_model . SLACK ,
payloadCreator : GetSlackPayload ,
} ,
webhook_model . DISCORD : {
name : webhook_model . DISCORD ,
payloadCreator : GetDiscordPayload ,
} ,
webhook_model . DINGTALK : {
name : webhook_model . DINGTALK ,
payloadCreator : GetDingtalkPayload ,
} ,
webhook_model . TELEGRAM : {
name : webhook_model . TELEGRAM ,
payloadCreator : GetTelegramPayload ,
} ,
webhook_model . MSTEAMS : {
name : webhook_model . MSTEAMS ,
payloadCreator : GetMSTeamsPayload ,
} ,
webhook_model . FEISHU : {
name : webhook_model . FEISHU ,
payloadCreator : GetFeishuPayload ,
} ,
webhook_model . MATRIX : {
name : webhook_model . MATRIX ,
payloadCreator : GetMatrixPayload ,
} ,
webhook_model . WECHATWORK : {
name : webhook_model . WECHATWORK ,
payloadCreator : GetWechatworkPayload ,
} ,
2022-01-23 16:46:30 +03:00
webhook_model . PACKAGIST : {
name : webhook_model . PACKAGIST ,
payloadCreator : GetPackagistPayload ,
} ,
2022-01-20 20:46:10 +03:00
}
2020-12-08 13:41:14 +03:00
// RegisterWebhook registers a webhook
func RegisterWebhook ( name string , webhook * webhook ) {
2022-06-20 13:02:49 +03:00
webhooks [ 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
}
2022-06-20 13:02:49 +03:00
_ , ok := webhooks [ 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
2022-04-25 21:03:01 +03:00
var hookQueue queue . UniqueQueue
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 ""
}
2022-04-25 21:03:01 +03:00
// handle passed PR IDs and test the PRs
func handle ( data ... queue . Data ) [ ] queue . Data {
for _ , datum := range data {
repoIDStr := datum . ( string )
log . Trace ( "DeliverHooks [repo_id: %v]" , repoIDStr )
repoID , err := strconv . ParseInt ( repoIDStr , 10 , 64 )
if err != nil {
log . Error ( "Invalid repo ID: %s" , repoIDStr )
continue
}
tasks , err := webhook_model . FindRepoUndeliveredHookTasks ( repoID )
if err != nil {
log . Error ( "Get repository [%d] hook tasks: %v" , repoID , err )
continue
}
for _ , t := range tasks {
if err = Deliver ( graceful . GetManager ( ) . HammerContext ( ) , t ) ; err != nil {
log . Error ( "deliver: %v" , err )
}
}
}
return nil
}
func addToTask ( repoID int64 ) error {
err := hookQueue . PushFunc ( strconv . FormatInt ( repoID , 10 ) , nil )
if err != nil && err != queue . ErrAlreadyInQueue {
return err
}
return nil
}
2019-11-02 01:51:22 +03:00
// PrepareWebhook adds special webhook to task queue for given payload.
2021-12-10 04:27:50 +03:00
func PrepareWebhook ( w * webhook_model . Webhook , repo * repo_model . 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
}
2022-04-25 21:03:01 +03:00
return addToTask ( repo . ID )
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-12-10 04:27:50 +03:00
func prepareWebhook ( w * webhook_model . Webhook , repo * repo_model . 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-12-10 04:27:50 +03:00
func PrepareWebhooks ( repo * repo_model . Repository , event webhook_model . HookEventType , p api . Payloader ) error {
2022-05-20 17:08:52 +03:00
if err := prepareWebhooks ( db . DefaultContext , repo , event , p ) ; err != nil {
2019-11-02 05:35:12 +03:00
return err
}
2022-04-25 21:03:01 +03:00
return addToTask ( repo . ID )
2019-11-02 01:51:22 +03:00
}
2022-05-20 17:08:52 +03:00
func prepareWebhooks ( ctx context . Context , repo * repo_model . Repository , event webhook_model . HookEventType , p api . Payloader ) error {
ws , err := webhook_model . ListWebhooksByOpts ( ctx , & 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
2022-05-20 17:08:52 +03:00
orgHooks , err := webhook_model . ListWebhooksByOpts ( ctx , & 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
2022-05-20 17:08:52 +03:00
systemHooks , err := webhook_model . GetSystemWebhooks ( ctx , util . OptionalBoolTrue )
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
}
2022-01-06 00:00:20 +03:00
// ReplayHookTask replays a webhook task
func ReplayHookTask ( w * webhook_model . Webhook , uuid string ) error {
t , err := webhook_model . ReplayHookTask ( w . ID , uuid )
if err != nil {
return err
}
2022-04-25 21:03:01 +03:00
return addToTask ( t . RepoID )
2022-01-06 00:00:20 +03:00
}