Files

350 lines
10 KiB
Go
Raw Permalink Normal View History

2014-05-05 20:52:25 -04:00
// Copyright 2014 The Gogs Authors. All rights reserved.
2017-05-29 09:17:15 +02:00
// Copyright 2017 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
2014-05-05 20:52:25 -04:00
2021-11-10 13:13:16 +08:00
package webhook
2014-05-05 20:52:25 -04:00
import (
"context"
2015-08-27 23:06:14 +08:00
"fmt"
"strings"
2014-05-05 20:52:25 -04:00
"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/modules/json"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/optional"
2022-11-03 19:23:20 +01:00
"code.gitea.io/gitea/modules/secret"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/timeutil"
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"
2021-08-12 14:43:08 +02:00
"xorm.io/builder"
2014-05-05 20:52:25 -04:00
)
2021-11-10 13:13:16 +08:00
// ErrWebhookNotExist represents a "WebhookNotExist" kind of error.
type ErrWebhookNotExist struct {
ID int64
}
// IsErrWebhookNotExist checks if an error is a ErrWebhookNotExist.
func IsErrWebhookNotExist(err error) bool {
_, ok := err.(ErrWebhookNotExist)
return ok
}
func (err ErrWebhookNotExist) Error() string {
return fmt.Sprintf("webhook does not exist [id: %d]", err.ID)
}
func (err ErrWebhookNotExist) Unwrap() error {
return util.ErrNotExist
}
2022-01-05 22:00:20 +01:00
// ErrHookTaskNotExist represents a "HookTaskNotExist" kind of error.
type ErrHookTaskNotExist struct {
2022-10-21 18:21:56 +02:00
TaskID int64
2022-01-05 22:00:20 +01:00
HookID int64
UUID string
}
2023-01-01 16:23:15 +01:00
// IsErrHookTaskNotExist checks if an error is a ErrHookTaskNotExist.
2022-01-05 22:00:20 +01:00
func IsErrHookTaskNotExist(err error) bool {
_, ok := err.(ErrHookTaskNotExist)
return ok
}
func (err ErrHookTaskNotExist) Error() string {
2022-10-21 18:21:56 +02:00
return fmt.Sprintf("hook task does not exist [task: %d, hook: %d, uuid: %s]", err.TaskID, err.HookID, err.UUID)
2022-01-05 22:00:20 +01:00
}
func (err ErrHookTaskNotExist) Unwrap() error {
return util.ErrNotExist
}
// HookContentType is the content type of a web hook
2014-06-08 04:45:34 -04:00
type HookContentType int
2014-05-05 20:52:25 -04:00
const (
// ContentTypeJSON is a JSON payload for web hooks
2016-11-07 21:58:22 +01:00
ContentTypeJSON HookContentType = iota + 1
// ContentTypeForm is an url-encoded form payload for web hook
2016-11-07 17:53:22 +01:00
ContentTypeForm
2014-05-05 20:52:25 -04:00
)
2014-11-13 12:57:00 -05:00
var hookContentTypes = map[string]HookContentType{
2016-11-07 21:58:22 +01:00
"json": ContentTypeJSON,
2016-11-07 17:53:22 +01:00
"form": ContentTypeForm,
2014-11-13 12:57:00 -05:00
}
// ToHookContentType returns HookContentType by given name.
func ToHookContentType(name string) HookContentType {
return hookContentTypes[name]
}
// HookTaskCleanupType is the type of cleanup to perform on hook_task
type HookTaskCleanupType int
const (
// OlderThan hook_task rows will be cleaned up by the age of the row
OlderThan HookTaskCleanupType = iota
// PerWebhook hook_task rows will be cleaned up by leaving the most recent deliveries for each webhook
PerWebhook
)
var hookTaskCleanupTypes = map[string]HookTaskCleanupType{
"OlderThan": OlderThan,
"PerWebhook": PerWebhook,
}
// ToHookTaskCleanupType returns HookTaskCleanupType by given name.
func ToHookTaskCleanupType(name string) HookTaskCleanupType {
return hookTaskCleanupTypes[name]
}
// Name returns the name of a given web hook's content type
2014-11-13 02:32:18 -05:00
func (t HookContentType) Name() string {
switch t {
2016-11-07 21:58:22 +01:00
case ContentTypeJSON:
2014-11-13 02:32:18 -05:00
return "json"
2016-11-07 17:53:22 +01:00
case ContentTypeForm:
2014-11-13 02:32:18 -05:00
return "form"
}
return ""
}
2014-11-13 12:57:00 -05:00
// IsValidHookContentType returns true if given name is a valid hook content type.
func IsValidHookContentType(name string) bool {
_, ok := hookContentTypes[name]
return ok
}
2014-06-08 04:45:34 -04:00
// Webhook represents a web hook object.
2014-05-05 20:52:25 -04:00
type Webhook struct {
2023-01-01 16:23:15 +01:00
ID int64 `xorm:"pk autoincr"`
RepoID int64 `xorm:"INDEX"` // An ID of 0 indicates either a default or system webhook
2023-03-10 15:28:32 +01:00
OwnerID int64 `xorm:"INDEX"`
2023-01-01 16:23:15 +01:00
IsSystemWebhook bool
URL string `xorm:"url TEXT"`
Name string `xorm:"VARCHAR(255) NOT NULL DEFAULT ''"`
2023-01-01 16:23:15 +01:00
HTTPMethod string `xorm:"http_method"`
ContentType HookContentType
Secret string `xorm:"TEXT"`
Events string `xorm:"TEXT"`
*webhook_module.HookEvent `xorm:"-"`
IsActive bool `xorm:"INDEX"`
Type webhook_module.HookType `xorm:"VARCHAR(16) 'type'"`
Meta string `xorm:"TEXT"` // store hook-specific attributes
LastStatus webhook_module.HookStatus // Last delivery status
2022-11-03 19:23:20 +01:00
// HeaderAuthorizationEncrypted should be accessed using HeaderAuthorization() and SetHeaderAuthorization()
HeaderAuthorizationEncrypted string `xorm:"TEXT"`
CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"`
UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"`
2014-05-05 20:52:25 -04:00
}
func init() {
db.RegisterModel(new(Webhook))
}
// AfterLoad updates the webhook object upon setting a column
func (w *Webhook) AfterLoad() {
2023-01-01 16:23:15 +01:00
w.HookEvent = &webhook_module.HookEvent{}
if err := json.Unmarshal([]byte(w.Events), w.HookEvent); err != nil {
2019-04-02 08:48:31 +01:00
log.Error("Unmarshal[%d]: %v", w.ID, err)
2014-05-05 20:52:25 -04:00
}
}
2015-08-27 23:06:14 +08:00
// History returns history of webhook by given conditions.
func (w *Webhook) History(ctx context.Context, page int) ([]*HookTask, error) {
return HookTasks(ctx, w.ID, page)
2015-08-27 23:06:14 +08:00
}
2014-06-08 04:54:52 -04:00
// UpdateEvent handles conversion from HookEvent to Events.
2014-06-08 04:45:34 -04:00
func (w *Webhook) UpdateEvent() error {
2014-05-05 21:36:08 -04:00
data, err := json.Marshal(w.HookEvent)
2014-05-05 20:52:25 -04:00
w.Events = string(data)
return err
}
2025-01-23 10:53:06 -08:00
func (w *Webhook) HasEvent(evt webhook_module.HookEventType) bool {
if w.SendEverything {
return true
}
2025-01-23 10:53:06 -08:00
if w.PushOnly {
return evt == webhook_module.HookEventPush
}
checkEvt := evt
switch evt {
case webhook_module.HookEventPullRequestReviewApproved, webhook_module.HookEventPullRequestReviewRejected, webhook_module.HookEventPullRequestReviewComment:
checkEvt = webhook_module.HookEventPullRequestReview
}
return w.HookEvents[checkEvt]
}
// EventsArray returns an array of hook events
2015-08-29 11:49:59 +08:00
func (w *Webhook) EventsArray() []string {
2025-01-23 10:53:06 -08:00
if w.SendEverything {
events := make([]string, 0, len(webhook_module.AllEvents()))
for _, evt := range webhook_module.AllEvents() {
events = append(events, string(evt))
}
return events
}
if w.PushOnly {
return []string{string(webhook_module.HookEventPush)}
}
2025-01-23 10:53:06 -08:00
events := make([]string, 0, len(w.HookEvents))
for event, enabled := range w.HookEvents {
if enabled {
events = append(events, string(event))
}
}
2015-08-29 11:49:59 +08:00
return events
}
2022-11-03 19:23:20 +01:00
// HeaderAuthorization returns the decrypted Authorization header.
// Not on the reference (*w), to be accessible on WebhooksNew.
func (w Webhook) HeaderAuthorization() (string, error) {
if w.HeaderAuthorizationEncrypted == "" {
return "", nil
}
return secret.DecryptSecret(setting.SecretKey, w.HeaderAuthorizationEncrypted)
}
// SetHeaderAuthorization encrypts and sets the Authorization header.
func (w *Webhook) SetHeaderAuthorization(cleartext string) error {
if cleartext == "" {
w.HeaderAuthorizationEncrypted = ""
return nil
}
ciphertext, err := secret.EncryptSecret(setting.SecretKey, cleartext)
if err != nil {
return err
}
w.HeaderAuthorizationEncrypted = ciphertext
return nil
}
2014-06-08 04:45:34 -04:00
// CreateWebhook creates a new web hook.
2021-11-10 13:13:16 +08:00
func CreateWebhook(ctx context.Context, w *Webhook) error {
w.Type = strings.TrimSpace(w.Type)
2021-11-10 13:13:16 +08:00
return db.Insert(ctx, w)
2014-05-05 20:52:25 -04:00
}
// CreateWebhooks creates multiple web hooks
func CreateWebhooks(ctx context.Context, ws []*Webhook) error {
// xorm returns err "no element on slice when insert" for empty slices.
if len(ws) == 0 {
return nil
}
2025-06-18 03:48:09 +02:00
for i := range ws {
ws[i].Type = strings.TrimSpace(ws[i].Type)
}
return db.Insert(ctx, ws)
}
2023-11-26 01:21:21 +08:00
// GetWebhookByID returns webhook of repository by given ID.
func GetWebhookByID(ctx context.Context, id int64) (*Webhook, error) {
bean := new(Webhook)
has, err := db.GetEngine(ctx).ID(id).Get(bean)
2014-05-05 21:36:08 -04:00
if err != nil {
return nil, err
} else if !has {
2023-11-26 01:21:21 +08:00
return nil, ErrWebhookNotExist{ID: id}
2014-05-05 21:36:08 -04:00
}
return bean, nil
}
// GetWebhookByRepoID returns webhook of repository by given ID.
func GetWebhookByRepoID(ctx context.Context, repoID, id int64) (*Webhook, error) {
2023-11-26 01:21:21 +08:00
webhook := new(Webhook)
has, err := db.GetEngine(ctx).Where("id=? AND repo_id=?", id, repoID).Get(webhook)
if err != nil {
return nil, err
} else if !has {
return nil, ErrWebhookNotExist{ID: id}
}
return webhook, nil
2014-05-05 21:36:08 -04:00
}
2023-03-10 15:28:32 +01:00
// GetWebhookByOwnerID returns webhook of a user or organization by given ID.
func GetWebhookByOwnerID(ctx context.Context, ownerID, id int64) (*Webhook, error) {
2023-11-26 01:21:21 +08:00
webhook := new(Webhook)
has, err := db.GetEngine(ctx).Where("id=? AND owner_id=?", id, ownerID).Get(webhook)
if err != nil {
return nil, err
} else if !has {
return nil, ErrWebhookNotExist{ID: id}
}
return webhook, nil
}
2021-08-12 14:43:08 +02:00
// ListWebhookOptions are options to filter webhooks on ListWebhooksByOpts
type ListWebhookOptions struct {
db.ListOptions
2021-08-12 14:43:08 +02:00
RepoID int64
2023-03-10 15:28:32 +01:00
OwnerID int64
IsActive optional.Option[bool]
2017-09-03 01:20:24 -07:00
}
func (opts ListWebhookOptions) ToConds() builder.Cond {
2021-08-12 14:43:08 +02:00
cond := builder.NewCond()
if opts.RepoID != 0 {
cond = cond.And(builder.Eq{"webhook.repo_id": opts.RepoID})
}
2023-03-10 15:28:32 +01:00
if opts.OwnerID != 0 {
cond = cond.And(builder.Eq{"webhook.owner_id": opts.OwnerID})
2021-08-12 14:43:08 +02:00
}
if opts.IsActive.Has() {
cond = cond.And(builder.Eq{"webhook.is_active": opts.IsActive.Value()})
2020-01-24 19:00:29 +00:00
}
2021-08-12 14:43:08 +02:00
return cond
}
2020-01-24 19:00:29 +00:00
2014-06-08 04:45:34 -04:00
// UpdateWebhook updates information of webhook.
func UpdateWebhook(ctx context.Context, w *Webhook) error {
_, err := db.GetEngine(ctx).ID(w.ID).AllCols().Update(w)
2014-06-08 04:45:34 -04:00
return err
}
2017-08-30 13:36:52 +08:00
// UpdateWebhookLastStatus updates last status of webhook.
func UpdateWebhookLastStatus(ctx context.Context, w *Webhook) error {
_, err := db.GetEngine(ctx).ID(w.ID).Cols("last_status").Update(w)
2017-08-30 13:36:52 +08:00
return err
}
2023-11-26 01:21:21 +08:00
// DeleteWebhookByID uses argument bean as query condition,
// ID must be specified and do not assign unnecessary fields.
2023-11-26 01:21:21 +08:00
func DeleteWebhookByID(ctx context.Context, id int64) (err error) {
return db.WithTx(ctx, func(ctx context.Context) error {
if count, err := db.DeleteByID[Webhook](ctx, id); err != nil {
return err
} else if count == 0 {
return ErrWebhookNotExist{ID: id}
} else if _, err = db.DeleteByBean(ctx, &HookTask{HookID: id}); err != nil {
return err
}
return nil
})
2014-05-05 21:36:08 -04:00
}
2014-06-08 04:45:34 -04:00
// DeleteWebhookByRepoID deletes webhook of repository by given ID.
func DeleteWebhookByRepoID(ctx context.Context, repoID, id int64) error {
2023-11-26 01:21:21 +08:00
if _, err := GetWebhookByRepoID(ctx, repoID, id); err != nil {
return err
}
return DeleteWebhookByID(ctx, id)
}
2023-03-10 15:28:32 +01:00
// DeleteWebhookByOwnerID deletes webhook of a user or organization by given ID.
func DeleteWebhookByOwnerID(ctx context.Context, ownerID, id int64) error {
2023-11-26 01:21:21 +08:00
if _, err := GetWebhookByOwnerID(ctx, ownerID, id); err != nil {
return err
}
return DeleteWebhookByID(ctx, id)
}