Files

238 lines
6.4 KiB
Go
Raw Permalink Normal View History

// Copyright 2019 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package webhook
import (
"context"
"errors"
"fmt"
2024-03-07 23:18:38 +01:00
"net/http"
"code.gitea.io/gitea/models/db"
repo_model "code.gitea.io/gitea/models/repo"
2022-10-21 18:21:56 +02:00
user_model "code.gitea.io/gitea/models/user"
2021-11-10 13:13:16 +08:00
webhook_model "code.gitea.io/gitea/models/webhook"
"code.gitea.io/gitea/modules/git"
2025-09-14 02:01:00 +08:00
"code.gitea.io/gitea/modules/glob"
"code.gitea.io/gitea/modules/graceful"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/optional"
"code.gitea.io/gitea/modules/queue"
"code.gitea.io/gitea/modules/setting"
api "code.gitea.io/gitea/modules/structs"
2021-08-12 14:43:08 +02:00
"code.gitea.io/gitea/modules/util"
2023-01-01 16:23:15 +01:00
webhook_module "code.gitea.io/gitea/modules/webhook"
)
type Requester func(context.Context, *webhook_model.Webhook, *webhook_model.HookTask) (req *http.Request, body []byte, err error)
var webhookRequesters = map[webhook_module.HookType]Requester{}
func RegisterWebhookRequester(hookType webhook_module.HookType, requester Requester) {
webhookRequesters[hookType] = requester
2022-01-20 18:46:10 +01:00
}
// IsValidHookTaskType returns true if a webhook registered
func IsValidHookTaskType(name string) bool {
2023-01-01 16:23:15 +01:00
if name == webhook_module.GITEA || name == webhook_module.GOGS {
return true
}
2024-03-07 23:18:38 +01:00
_, ok := webhookRequesters[name]
return ok
}
2019-11-02 10:35:12 +08:00
// hookQueue is a global queue of web hooks
2023-05-08 19:49:59 +08:00
var hookQueue *queue.WorkerPoolQueue[int64]
// getPayloadRef returns the full ref name for hook event, if applicable.
func getPayloadRef(p api.Payloader) git.RefName {
switch pp := p.(type) {
case *api.CreatePayload:
switch pp.RefType {
case "branch":
return git.RefNameFromBranch(pp.Ref)
case "tag":
return git.RefNameFromTag(pp.Ref)
}
case *api.DeletePayload:
switch pp.RefType {
case "branch":
return git.RefNameFromBranch(pp.Ref)
case "tag":
return git.RefNameFromTag(pp.Ref)
}
case *api.PushPayload:
return git.RefName(pp.Ref)
}
return ""
}
2022-10-21 18:21:56 +02:00
// EventSource represents the source of a webhook action. Repository and/or Owner must be set.
type EventSource struct {
Repository *repo_model.Repository
Owner *user_model.User
}
2022-10-21 18:21:56 +02:00
// handle delivers hook tasks
2023-05-08 19:49:59 +08:00
func handler(items ...int64) []int64 {
2022-10-21 18:21:56 +02:00
ctx := graceful.GetManager().HammerContext()
2023-05-08 19:49:59 +08:00
for _, taskID := range items {
task, err := webhook_model.GetHookTaskByID(ctx, taskID)
if err != nil {
if errors.Is(err, util.ErrNotExist) {
log.Warn("GetHookTaskByID[%d] warn: %v", taskID, err)
} else {
log.Error("GetHookTaskByID[%d] failed: %v", taskID, err)
}
continue
}
if task.IsDelivered {
// Already delivered in the meantime
log.Trace("Task[%d] has already been delivered", task.ID)
continue
}
if err := Deliver(ctx, task); err != nil {
log.Error("Unable to deliver webhook task[%d]: %v", task.ID, err)
}
}
2022-10-21 18:21:56 +02:00
return nil
}
func enqueueHookTask(taskID int64) error {
err := hookQueue.Push(taskID)
if err != nil && err != queue.ErrAlreadyInQueue {
return err
}
return nil
}
func checkBranchFilter(branchFilter string, ref git.RefName) bool {
if branchFilter == "" || branchFilter == "*" || branchFilter == "**" {
return true
}
g, err := glob.Compile(branchFilter)
if err != nil {
// should not really happen as BranchFilter is validated
log.Debug("checkBranchFilter failed to compile filer %q, err: %s", branchFilter, err)
return false
}
if ref.IsBranch() && g.Match(ref.BranchName()) {
return true
}
return g.Match(ref.String())
}
2024-03-07 23:18:38 +01:00
// PrepareWebhook creates a hook task and enqueues it for processing.
// The payload is saved as-is. The adjustments depending on the webhook type happen
// right before delivery, in the [Deliver] method.
2023-01-01 16:23:15 +01:00
func PrepareWebhook(ctx context.Context, w *webhook_model.Webhook, event webhook_module.HookEventType, p api.Payloader) error {
2021-02-11 18:34:34 +01:00
// Skip sending if webhooks are disabled.
if setting.DisableWebhooks {
return nil
}
2025-01-23 10:53:06 -08:00
if !w.HasEvent(event) {
return nil
}
// 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 &&
2023-01-01 16:23:15 +01:00
w.Type != webhook_module.GITEA && w.Type != webhook_module.GOGS &&
len(pushEvent.Commits) == 0 {
return nil
}
// If payload has no associated branch (e.g. it's a new tag, issue, etc.), branch filter has no effect.
if ref := getPayloadRef(p); ref != "" {
// Check the payload's git ref against the webhook's branch filter.
if !checkBranchFilter(w.BranchFilter, ref) {
return nil
}
}
2024-03-07 23:18:38 +01:00
payload, err := p.JSONPayload()
if err != nil {
return fmt.Errorf("JSONPayload for %s: %w", event, err)
}
2022-10-21 18:21:56 +02:00
task, err := webhook_model.CreateHookTask(ctx, &webhook_model.HookTask{
2024-03-07 23:18:38 +01:00
HookID: w.ID,
PayloadContent: string(payload),
EventType: event,
PayloadVersion: 2,
2022-10-21 18:21:56 +02:00
})
if err != nil {
2024-03-07 23:18:38 +01:00
return fmt.Errorf("CreateHookTask for %s: %w", event, err)
}
2022-10-21 18:21:56 +02:00
return enqueueHookTask(task.ID)
}
// PrepareWebhooks adds new webhooks to task queue for given payload.
2023-01-01 16:23:15 +01:00
func PrepareWebhooks(ctx context.Context, source EventSource, event webhook_module.HookEventType, p api.Payloader) error {
2022-10-21 18:21:56 +02:00
owner := source.Owner
2019-11-02 10:35:12 +08:00
2022-10-21 18:21:56 +02:00
var ws []*webhook_model.Webhook
2022-10-21 18:21:56 +02:00
if source.Repository != nil {
repoHooks, err := db.Find[webhook_model.Webhook](ctx, webhook_model.ListWebhookOptions{
2022-10-21 18:21:56 +02:00
RepoID: source.Repository.ID,
IsActive: optional.Some(true),
2022-10-21 18:21:56 +02:00
})
if err != nil {
return fmt.Errorf("ListWebhooksByOpts: %w", err)
2022-10-21 18:21:56 +02:00
}
ws = append(ws, repoHooks...)
owner = source.Repository.MustOwner(ctx)
}
2023-03-10 15:28:32 +01:00
// append additional webhooks of a user or organization
if owner != nil {
ownerHooks, err := db.Find[webhook_model.Webhook](ctx, webhook_model.ListWebhookOptions{
2023-03-10 15:28:32 +01:00
OwnerID: owner.ID,
IsActive: optional.Some(true),
2021-08-12 14:43:08 +02:00
})
if err != nil {
return fmt.Errorf("ListWebhooksByOpts: %w", err)
}
2023-03-10 15:28:32 +01:00
ws = append(ws, ownerHooks...)
}
2020-03-08 22:08:05 +00:00
// Add any admin-defined system webhooks
systemHooks, err := webhook_model.GetSystemWebhooks(ctx, optional.Some(true))
2020-03-08 22:08:05 +00:00
if err != nil {
return fmt.Errorf("GetSystemWebhooks: %w", err)
2020-03-08 22:08:05 +00:00
}
ws = append(ws, systemHooks...)
if len(ws) == 0 {
return nil
}
for _, w := range ws {
2022-10-21 18:21:56 +02:00
if err := PrepareWebhook(ctx, w, event, p); err != nil {
return err
}
}
return nil
}
2022-01-05 22:00:20 +01:00
// ReplayHookTask replays a webhook task
2022-10-21 18:21:56 +02:00
func ReplayHookTask(ctx context.Context, w *webhook_model.Webhook, uuid string) error {
task, err := webhook_model.ReplayHookTask(ctx, w.ID, uuid)
2022-01-05 22:00:20 +01:00
if err != nil {
return err
}
return enqueueHookTask(task.ID)
2022-01-05 22:00:20 +01:00
}