summaryrefslogtreecommitdiffstats
path: root/models/webhook/webhook.go
diff options
context:
space:
mode:
authorDaniel Baumann <daniel@debian.org>2024-10-18 20:33:49 +0200
committerDaniel Baumann <daniel@debian.org>2024-12-12 23:57:56 +0100
commite68b9d00a6e05b3a941f63ffb696f91e554ac5ec (patch)
tree97775d6c13b0f416af55314eb6a89ef792474615 /models/webhook/webhook.go
parentInitial commit. (diff)
downloadforgejo-e68b9d00a6e05b3a941f63ffb696f91e554ac5ec.tar.xz
forgejo-e68b9d00a6e05b3a941f63ffb696f91e554ac5ec.zip
Adding upstream version 9.0.3.
Signed-off-by: Daniel Baumann <daniel@debian.org>
Diffstat (limited to '')
-rw-r--r--models/webhook/webhook.go516
1 files changed, 516 insertions, 0 deletions
diff --git a/models/webhook/webhook.go b/models/webhook/webhook.go
new file mode 100644
index 0000000..f3370f3
--- /dev/null
+++ b/models/webhook/webhook.go
@@ -0,0 +1,516 @@
+// Copyright 2014 The Gogs Authors. All rights reserved.
+// Copyright 2017 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package webhook
+
+import (
+ "context"
+ "fmt"
+ "strings"
+
+ "code.gitea.io/gitea/models/db"
+ "code.gitea.io/gitea/modules/json"
+ "code.gitea.io/gitea/modules/log"
+ "code.gitea.io/gitea/modules/optional"
+ "code.gitea.io/gitea/modules/secret"
+ "code.gitea.io/gitea/modules/setting"
+ "code.gitea.io/gitea/modules/timeutil"
+ "code.gitea.io/gitea/modules/util"
+ webhook_module "code.gitea.io/gitea/modules/webhook"
+
+ "xorm.io/builder"
+)
+
+// 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
+}
+
+// ErrHookTaskNotExist represents a "HookTaskNotExist" kind of error.
+type ErrHookTaskNotExist struct {
+ TaskID int64
+ HookID int64
+ UUID string
+}
+
+// IsErrHookTaskNotExist checks if an error is a ErrHookTaskNotExist.
+func IsErrHookTaskNotExist(err error) bool {
+ _, ok := err.(ErrHookTaskNotExist)
+ return ok
+}
+
+func (err ErrHookTaskNotExist) Error() string {
+ return fmt.Sprintf("hook task does not exist [task: %d, hook: %d, uuid: %s]", err.TaskID, err.HookID, err.UUID)
+}
+
+func (err ErrHookTaskNotExist) Unwrap() error {
+ return util.ErrNotExist
+}
+
+// HookContentType is the content type of a web hook
+type HookContentType int
+
+const (
+ // ContentTypeJSON is a JSON payload for web hooks
+ ContentTypeJSON HookContentType = iota + 1
+ // ContentTypeForm is an url-encoded form payload for web hook
+ ContentTypeForm
+)
+
+var hookContentTypes = map[string]HookContentType{
+ "json": ContentTypeJSON,
+ "form": ContentTypeForm,
+}
+
+// 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
+func (t HookContentType) Name() string {
+ switch t {
+ case ContentTypeJSON:
+ return "json"
+ case ContentTypeForm:
+ return "form"
+ }
+ return ""
+}
+
+// IsValidHookContentType returns true if given name is a valid hook content type.
+func IsValidHookContentType(name string) bool {
+ _, ok := hookContentTypes[name]
+ return ok
+}
+
+// Webhook represents a web hook object.
+type Webhook struct {
+ ID int64 `xorm:"pk autoincr"`
+ RepoID int64 `xorm:"INDEX"` // An ID of 0 indicates either a default or system webhook
+ OwnerID int64 `xorm:"INDEX"`
+ IsSystemWebhook bool
+ URL string `xorm:"url TEXT"`
+ 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
+
+ // HeaderAuthorizationEncrypted should be accessed using HeaderAuthorization() and SetHeaderAuthorization()
+ HeaderAuthorizationEncrypted string `xorm:"TEXT"`
+
+ CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"`
+ UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"`
+}
+
+func init() {
+ db.RegisterModel(new(Webhook))
+}
+
+// AfterLoad updates the webhook object upon setting a column
+func (w *Webhook) AfterLoad() {
+ w.HookEvent = &webhook_module.HookEvent{}
+ if err := json.Unmarshal([]byte(w.Events), w.HookEvent); err != nil {
+ log.Error("Unmarshal[%d]: %v", w.ID, err)
+ }
+}
+
+// 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)
+}
+
+// UpdateEvent handles conversion from HookEvent to Events.
+func (w *Webhook) UpdateEvent() error {
+ data, err := json.Marshal(w.HookEvent)
+ w.Events = string(data)
+ return err
+}
+
+// HasCreateEvent returns true if hook enabled create event.
+func (w *Webhook) HasCreateEvent() bool {
+ return w.SendEverything ||
+ (w.ChooseEvents && w.HookEvents.Create)
+}
+
+// HasDeleteEvent returns true if hook enabled delete event.
+func (w *Webhook) HasDeleteEvent() bool {
+ return w.SendEverything ||
+ (w.ChooseEvents && w.HookEvents.Delete)
+}
+
+// HasForkEvent returns true if hook enabled fork event.
+func (w *Webhook) HasForkEvent() bool {
+ return w.SendEverything ||
+ (w.ChooseEvents && w.HookEvents.Fork)
+}
+
+// HasIssuesEvent returns true if hook enabled issues event.
+func (w *Webhook) HasIssuesEvent() bool {
+ return w.SendEverything ||
+ (w.ChooseEvents && w.HookEvents.Issues)
+}
+
+// HasIssuesAssignEvent returns true if hook enabled issues assign event.
+func (w *Webhook) HasIssuesAssignEvent() bool {
+ return w.SendEverything ||
+ (w.ChooseEvents && w.HookEvents.IssueAssign)
+}
+
+// HasIssuesLabelEvent returns true if hook enabled issues label event.
+func (w *Webhook) HasIssuesLabelEvent() bool {
+ return w.SendEverything ||
+ (w.ChooseEvents && w.HookEvents.IssueLabel)
+}
+
+// HasIssuesMilestoneEvent returns true if hook enabled issues milestone event.
+func (w *Webhook) HasIssuesMilestoneEvent() bool {
+ return w.SendEverything ||
+ (w.ChooseEvents && w.HookEvents.IssueMilestone)
+}
+
+// HasIssueCommentEvent returns true if hook enabled issue_comment event.
+func (w *Webhook) HasIssueCommentEvent() bool {
+ return w.SendEverything ||
+ (w.ChooseEvents && w.HookEvents.IssueComment)
+}
+
+// HasPushEvent returns true if hook enabled push event.
+func (w *Webhook) HasPushEvent() bool {
+ return w.PushOnly || w.SendEverything ||
+ (w.ChooseEvents && w.HookEvents.Push)
+}
+
+// HasPullRequestEvent returns true if hook enabled pull request event.
+func (w *Webhook) HasPullRequestEvent() bool {
+ return w.SendEverything ||
+ (w.ChooseEvents && w.HookEvents.PullRequest)
+}
+
+// HasPullRequestAssignEvent returns true if hook enabled pull request assign event.
+func (w *Webhook) HasPullRequestAssignEvent() bool {
+ return w.SendEverything ||
+ (w.ChooseEvents && w.HookEvents.PullRequestAssign)
+}
+
+// HasPullRequestLabelEvent returns true if hook enabled pull request label event.
+func (w *Webhook) HasPullRequestLabelEvent() bool {
+ return w.SendEverything ||
+ (w.ChooseEvents && w.HookEvents.PullRequestLabel)
+}
+
+// HasPullRequestMilestoneEvent returns true if hook enabled pull request milestone event.
+func (w *Webhook) HasPullRequestMilestoneEvent() bool {
+ return w.SendEverything ||
+ (w.ChooseEvents && w.HookEvents.PullRequestMilestone)
+}
+
+// HasPullRequestCommentEvent returns true if hook enabled pull_request_comment event.
+func (w *Webhook) HasPullRequestCommentEvent() bool {
+ return w.SendEverything ||
+ (w.ChooseEvents && w.HookEvents.PullRequestComment)
+}
+
+// HasPullRequestApprovedEvent returns true if hook enabled pull request review event.
+func (w *Webhook) HasPullRequestApprovedEvent() bool {
+ return w.SendEverything ||
+ (w.ChooseEvents && w.HookEvents.PullRequestReview)
+}
+
+// HasPullRequestRejectedEvent returns true if hook enabled pull request review event.
+func (w *Webhook) HasPullRequestRejectedEvent() bool {
+ return w.SendEverything ||
+ (w.ChooseEvents && w.HookEvents.PullRequestReview)
+}
+
+// HasPullRequestReviewCommentEvent returns true if hook enabled pull request review event.
+func (w *Webhook) HasPullRequestReviewCommentEvent() bool {
+ return w.SendEverything ||
+ (w.ChooseEvents && w.HookEvents.PullRequestReview)
+}
+
+// HasPullRequestSyncEvent returns true if hook enabled pull request sync event.
+func (w *Webhook) HasPullRequestSyncEvent() bool {
+ return w.SendEverything ||
+ (w.ChooseEvents && w.HookEvents.PullRequestSync)
+}
+
+// HasWikiEvent returns true if hook enabled wiki event.
+func (w *Webhook) HasWikiEvent() bool {
+ return w.SendEverything ||
+ (w.ChooseEvents && w.HookEvent.Wiki)
+}
+
+// HasReleaseEvent returns if hook enabled release event.
+func (w *Webhook) HasReleaseEvent() bool {
+ return w.SendEverything ||
+ (w.ChooseEvents && w.HookEvents.Release)
+}
+
+// HasRepositoryEvent returns if hook enabled repository event.
+func (w *Webhook) HasRepositoryEvent() bool {
+ return w.SendEverything ||
+ (w.ChooseEvents && w.HookEvents.Repository)
+}
+
+// HasPackageEvent returns if hook enabled package event.
+func (w *Webhook) HasPackageEvent() bool {
+ return w.SendEverything ||
+ (w.ChooseEvents && w.HookEvents.Package)
+}
+
+// HasPullRequestReviewRequestEvent returns true if hook enabled pull request review request event.
+func (w *Webhook) HasPullRequestReviewRequestEvent() bool {
+ return w.SendEverything ||
+ (w.ChooseEvents && w.HookEvents.PullRequestReviewRequest)
+}
+
+// EventCheckers returns event checkers
+func (w *Webhook) EventCheckers() []struct {
+ Has func() bool
+ Type webhook_module.HookEventType
+} {
+ return []struct {
+ Has func() bool
+ Type webhook_module.HookEventType
+ }{
+ {w.HasCreateEvent, webhook_module.HookEventCreate},
+ {w.HasDeleteEvent, webhook_module.HookEventDelete},
+ {w.HasForkEvent, webhook_module.HookEventFork},
+ {w.HasPushEvent, webhook_module.HookEventPush},
+ {w.HasIssuesEvent, webhook_module.HookEventIssues},
+ {w.HasIssuesAssignEvent, webhook_module.HookEventIssueAssign},
+ {w.HasIssuesLabelEvent, webhook_module.HookEventIssueLabel},
+ {w.HasIssuesMilestoneEvent, webhook_module.HookEventIssueMilestone},
+ {w.HasIssueCommentEvent, webhook_module.HookEventIssueComment},
+ {w.HasPullRequestEvent, webhook_module.HookEventPullRequest},
+ {w.HasPullRequestAssignEvent, webhook_module.HookEventPullRequestAssign},
+ {w.HasPullRequestLabelEvent, webhook_module.HookEventPullRequestLabel},
+ {w.HasPullRequestMilestoneEvent, webhook_module.HookEventPullRequestMilestone},
+ {w.HasPullRequestCommentEvent, webhook_module.HookEventPullRequestComment},
+ {w.HasPullRequestApprovedEvent, webhook_module.HookEventPullRequestReviewApproved},
+ {w.HasPullRequestRejectedEvent, webhook_module.HookEventPullRequestReviewRejected},
+ {w.HasPullRequestCommentEvent, webhook_module.HookEventPullRequestReviewComment},
+ {w.HasPullRequestSyncEvent, webhook_module.HookEventPullRequestSync},
+ {w.HasWikiEvent, webhook_module.HookEventWiki},
+ {w.HasRepositoryEvent, webhook_module.HookEventRepository},
+ {w.HasReleaseEvent, webhook_module.HookEventRelease},
+ {w.HasPackageEvent, webhook_module.HookEventPackage},
+ {w.HasPullRequestReviewRequestEvent, webhook_module.HookEventPullRequestReviewRequest},
+ }
+}
+
+// EventsArray returns an array of hook events
+func (w *Webhook) EventsArray() []string {
+ events := make([]string, 0, 7)
+
+ for _, c := range w.EventCheckers() {
+ if c.Has() {
+ events = append(events, string(c.Type))
+ }
+ }
+ return events
+}
+
+// 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)
+}
+
+// HeaderAuthorizationTrimPrefix returns the decrypted Authorization with a specified prefix trimmed.
+func (w Webhook) HeaderAuthorizationTrimPrefix(prefix string) (string, error) {
+ s, err := w.HeaderAuthorization()
+ if err != nil {
+ return "", err
+ }
+ return strings.TrimPrefix(s, prefix), nil
+}
+
+// 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
+}
+
+// CreateWebhook creates a new web hook.
+func CreateWebhook(ctx context.Context, w *Webhook) error {
+ w.Type = strings.TrimSpace(w.Type)
+ return db.Insert(ctx, w)
+}
+
+// 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
+ }
+ for i := 0; i < len(ws); i++ {
+ ws[i].Type = strings.TrimSpace(ws[i].Type)
+ }
+ return db.Insert(ctx, ws)
+}
+
+// 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)
+ if err != nil {
+ return nil, err
+ } else if !has {
+ return nil, ErrWebhookNotExist{ID: id}
+ }
+ return bean, nil
+}
+
+// GetWebhookByRepoID returns webhook of repository by given ID.
+func GetWebhookByRepoID(ctx context.Context, repoID, id int64) (*Webhook, error) {
+ 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
+}
+
+// GetWebhookByOwnerID returns webhook of a user or organization by given ID.
+func GetWebhookByOwnerID(ctx context.Context, ownerID, id int64) (*Webhook, error) {
+ 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
+}
+
+// ListWebhookOptions are options to filter webhooks on ListWebhooksByOpts
+type ListWebhookOptions struct {
+ db.ListOptions
+ RepoID int64
+ OwnerID int64
+ IsActive optional.Option[bool]
+}
+
+func (opts ListWebhookOptions) ToConds() builder.Cond {
+ cond := builder.NewCond()
+ if opts.RepoID != 0 {
+ cond = cond.And(builder.Eq{"webhook.repo_id": opts.RepoID})
+ }
+ if opts.OwnerID != 0 {
+ cond = cond.And(builder.Eq{"webhook.owner_id": opts.OwnerID})
+ }
+ if opts.IsActive.Has() {
+ cond = cond.And(builder.Eq{"webhook.is_active": opts.IsActive.Value()})
+ }
+ return cond
+}
+
+var _ db.FindOptionsOrder = ListWebhookOptions{}
+
+// ToOrders implements db.FindOptionsOrder, to sort the webhooks by id asc
+func (opts ListWebhookOptions) ToOrders() string {
+ return "webhook.id"
+}
+
+// UpdateWebhook updates information of webhook.
+func UpdateWebhook(ctx context.Context, w *Webhook) error {
+ _, err := db.GetEngine(ctx).ID(w.ID).AllCols().Update(w)
+ return err
+}
+
+// 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)
+ return err
+}
+
+// DeleteWebhookByID uses argument bean as query condition,
+// ID must be specified and do not assign unnecessary fields.
+func DeleteWebhookByID(ctx context.Context, id int64) (err error) {
+ ctx, committer, err := db.TxContext(ctx)
+ if err != nil {
+ return err
+ }
+ defer committer.Close()
+
+ 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 committer.Commit()
+}
+
+// DeleteWebhookByRepoID deletes webhook of repository by given ID.
+func DeleteWebhookByRepoID(ctx context.Context, repoID, id int64) error {
+ if _, err := GetWebhookByRepoID(ctx, repoID, id); err != nil {
+ return err
+ }
+ return DeleteWebhookByID(ctx, id)
+}
+
+// DeleteWebhookByOwnerID deletes webhook of a user or organization by given ID.
+func DeleteWebhookByOwnerID(ctx context.Context, ownerID, id int64) error {
+ if _, err := GetWebhookByOwnerID(ctx, ownerID, id); err != nil {
+ return err
+ }
+ return DeleteWebhookByID(ctx, id)
+}