code.gitea.io/gitea@v1.22.3/models/webhook/webhook.go (about)

     1  // Copyright 2014 The Gogs Authors. All rights reserved.
     2  // Copyright 2017 The Gitea Authors. All rights reserved.
     3  // SPDX-License-Identifier: MIT
     4  
     5  package webhook
     6  
     7  import (
     8  	"context"
     9  	"fmt"
    10  	"strings"
    11  
    12  	"code.gitea.io/gitea/models/db"
    13  	"code.gitea.io/gitea/modules/json"
    14  	"code.gitea.io/gitea/modules/log"
    15  	"code.gitea.io/gitea/modules/optional"
    16  	"code.gitea.io/gitea/modules/secret"
    17  	"code.gitea.io/gitea/modules/setting"
    18  	"code.gitea.io/gitea/modules/timeutil"
    19  	"code.gitea.io/gitea/modules/util"
    20  	webhook_module "code.gitea.io/gitea/modules/webhook"
    21  
    22  	"xorm.io/builder"
    23  )
    24  
    25  // ErrWebhookNotExist represents a "WebhookNotExist" kind of error.
    26  type ErrWebhookNotExist struct {
    27  	ID int64
    28  }
    29  
    30  // IsErrWebhookNotExist checks if an error is a ErrWebhookNotExist.
    31  func IsErrWebhookNotExist(err error) bool {
    32  	_, ok := err.(ErrWebhookNotExist)
    33  	return ok
    34  }
    35  
    36  func (err ErrWebhookNotExist) Error() string {
    37  	return fmt.Sprintf("webhook does not exist [id: %d]", err.ID)
    38  }
    39  
    40  func (err ErrWebhookNotExist) Unwrap() error {
    41  	return util.ErrNotExist
    42  }
    43  
    44  // ErrHookTaskNotExist represents a "HookTaskNotExist" kind of error.
    45  type ErrHookTaskNotExist struct {
    46  	TaskID int64
    47  	HookID int64
    48  	UUID   string
    49  }
    50  
    51  // IsErrHookTaskNotExist checks if an error is a ErrHookTaskNotExist.
    52  func IsErrHookTaskNotExist(err error) bool {
    53  	_, ok := err.(ErrHookTaskNotExist)
    54  	return ok
    55  }
    56  
    57  func (err ErrHookTaskNotExist) Error() string {
    58  	return fmt.Sprintf("hook task does not exist [task: %d, hook: %d, uuid: %s]", err.TaskID, err.HookID, err.UUID)
    59  }
    60  
    61  func (err ErrHookTaskNotExist) Unwrap() error {
    62  	return util.ErrNotExist
    63  }
    64  
    65  // HookContentType is the content type of a web hook
    66  type HookContentType int
    67  
    68  const (
    69  	// ContentTypeJSON is a JSON payload for web hooks
    70  	ContentTypeJSON HookContentType = iota + 1
    71  	// ContentTypeForm is an url-encoded form payload for web hook
    72  	ContentTypeForm
    73  )
    74  
    75  var hookContentTypes = map[string]HookContentType{
    76  	"json": ContentTypeJSON,
    77  	"form": ContentTypeForm,
    78  }
    79  
    80  // ToHookContentType returns HookContentType by given name.
    81  func ToHookContentType(name string) HookContentType {
    82  	return hookContentTypes[name]
    83  }
    84  
    85  // HookTaskCleanupType is the type of cleanup to perform on hook_task
    86  type HookTaskCleanupType int
    87  
    88  const (
    89  	// OlderThan hook_task rows will be cleaned up by the age of the row
    90  	OlderThan HookTaskCleanupType = iota
    91  	// PerWebhook hook_task rows will be cleaned up by leaving the most recent deliveries for each webhook
    92  	PerWebhook
    93  )
    94  
    95  var hookTaskCleanupTypes = map[string]HookTaskCleanupType{
    96  	"OlderThan":  OlderThan,
    97  	"PerWebhook": PerWebhook,
    98  }
    99  
   100  // ToHookTaskCleanupType returns HookTaskCleanupType by given name.
   101  func ToHookTaskCleanupType(name string) HookTaskCleanupType {
   102  	return hookTaskCleanupTypes[name]
   103  }
   104  
   105  // Name returns the name of a given web hook's content type
   106  func (t HookContentType) Name() string {
   107  	switch t {
   108  	case ContentTypeJSON:
   109  		return "json"
   110  	case ContentTypeForm:
   111  		return "form"
   112  	}
   113  	return ""
   114  }
   115  
   116  // IsValidHookContentType returns true if given name is a valid hook content type.
   117  func IsValidHookContentType(name string) bool {
   118  	_, ok := hookContentTypes[name]
   119  	return ok
   120  }
   121  
   122  // Webhook represents a web hook object.
   123  type Webhook struct {
   124  	ID                        int64 `xorm:"pk autoincr"`
   125  	RepoID                    int64 `xorm:"INDEX"` // An ID of 0 indicates either a default or system webhook
   126  	OwnerID                   int64 `xorm:"INDEX"`
   127  	IsSystemWebhook           bool
   128  	URL                       string `xorm:"url TEXT"`
   129  	HTTPMethod                string `xorm:"http_method"`
   130  	ContentType               HookContentType
   131  	Secret                    string `xorm:"TEXT"`
   132  	Events                    string `xorm:"TEXT"`
   133  	*webhook_module.HookEvent `xorm:"-"`
   134  	IsActive                  bool                      `xorm:"INDEX"`
   135  	Type                      webhook_module.HookType   `xorm:"VARCHAR(16) 'type'"`
   136  	Meta                      string                    `xorm:"TEXT"` // store hook-specific attributes
   137  	LastStatus                webhook_module.HookStatus // Last delivery status
   138  
   139  	// HeaderAuthorizationEncrypted should be accessed using HeaderAuthorization() and SetHeaderAuthorization()
   140  	HeaderAuthorizationEncrypted string `xorm:"TEXT"`
   141  
   142  	CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"`
   143  	UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"`
   144  }
   145  
   146  func init() {
   147  	db.RegisterModel(new(Webhook))
   148  }
   149  
   150  // AfterLoad updates the webhook object upon setting a column
   151  func (w *Webhook) AfterLoad() {
   152  	w.HookEvent = &webhook_module.HookEvent{}
   153  	if err := json.Unmarshal([]byte(w.Events), w.HookEvent); err != nil {
   154  		log.Error("Unmarshal[%d]: %v", w.ID, err)
   155  	}
   156  }
   157  
   158  // History returns history of webhook by given conditions.
   159  func (w *Webhook) History(ctx context.Context, page int) ([]*HookTask, error) {
   160  	return HookTasks(ctx, w.ID, page)
   161  }
   162  
   163  // UpdateEvent handles conversion from HookEvent to Events.
   164  func (w *Webhook) UpdateEvent() error {
   165  	data, err := json.Marshal(w.HookEvent)
   166  	w.Events = string(data)
   167  	return err
   168  }
   169  
   170  // HasCreateEvent returns true if hook enabled create event.
   171  func (w *Webhook) HasCreateEvent() bool {
   172  	return w.SendEverything ||
   173  		(w.ChooseEvents && w.HookEvents.Create)
   174  }
   175  
   176  // HasDeleteEvent returns true if hook enabled delete event.
   177  func (w *Webhook) HasDeleteEvent() bool {
   178  	return w.SendEverything ||
   179  		(w.ChooseEvents && w.HookEvents.Delete)
   180  }
   181  
   182  // HasForkEvent returns true if hook enabled fork event.
   183  func (w *Webhook) HasForkEvent() bool {
   184  	return w.SendEverything ||
   185  		(w.ChooseEvents && w.HookEvents.Fork)
   186  }
   187  
   188  // HasIssuesEvent returns true if hook enabled issues event.
   189  func (w *Webhook) HasIssuesEvent() bool {
   190  	return w.SendEverything ||
   191  		(w.ChooseEvents && w.HookEvents.Issues)
   192  }
   193  
   194  // HasIssuesAssignEvent returns true if hook enabled issues assign event.
   195  func (w *Webhook) HasIssuesAssignEvent() bool {
   196  	return w.SendEverything ||
   197  		(w.ChooseEvents && w.HookEvents.IssueAssign)
   198  }
   199  
   200  // HasIssuesLabelEvent returns true if hook enabled issues label event.
   201  func (w *Webhook) HasIssuesLabelEvent() bool {
   202  	return w.SendEverything ||
   203  		(w.ChooseEvents && w.HookEvents.IssueLabel)
   204  }
   205  
   206  // HasIssuesMilestoneEvent returns true if hook enabled issues milestone event.
   207  func (w *Webhook) HasIssuesMilestoneEvent() bool {
   208  	return w.SendEverything ||
   209  		(w.ChooseEvents && w.HookEvents.IssueMilestone)
   210  }
   211  
   212  // HasIssueCommentEvent returns true if hook enabled issue_comment event.
   213  func (w *Webhook) HasIssueCommentEvent() bool {
   214  	return w.SendEverything ||
   215  		(w.ChooseEvents && w.HookEvents.IssueComment)
   216  }
   217  
   218  // HasPushEvent returns true if hook enabled push event.
   219  func (w *Webhook) HasPushEvent() bool {
   220  	return w.PushOnly || w.SendEverything ||
   221  		(w.ChooseEvents && w.HookEvents.Push)
   222  }
   223  
   224  // HasPullRequestEvent returns true if hook enabled pull request event.
   225  func (w *Webhook) HasPullRequestEvent() bool {
   226  	return w.SendEverything ||
   227  		(w.ChooseEvents && w.HookEvents.PullRequest)
   228  }
   229  
   230  // HasPullRequestAssignEvent returns true if hook enabled pull request assign event.
   231  func (w *Webhook) HasPullRequestAssignEvent() bool {
   232  	return w.SendEverything ||
   233  		(w.ChooseEvents && w.HookEvents.PullRequestAssign)
   234  }
   235  
   236  // HasPullRequestLabelEvent returns true if hook enabled pull request label event.
   237  func (w *Webhook) HasPullRequestLabelEvent() bool {
   238  	return w.SendEverything ||
   239  		(w.ChooseEvents && w.HookEvents.PullRequestLabel)
   240  }
   241  
   242  // HasPullRequestMilestoneEvent returns true if hook enabled pull request milestone event.
   243  func (w *Webhook) HasPullRequestMilestoneEvent() bool {
   244  	return w.SendEverything ||
   245  		(w.ChooseEvents && w.HookEvents.PullRequestMilestone)
   246  }
   247  
   248  // HasPullRequestCommentEvent returns true if hook enabled pull_request_comment event.
   249  func (w *Webhook) HasPullRequestCommentEvent() bool {
   250  	return w.SendEverything ||
   251  		(w.ChooseEvents && w.HookEvents.PullRequestComment)
   252  }
   253  
   254  // HasPullRequestApprovedEvent returns true if hook enabled pull request review event.
   255  func (w *Webhook) HasPullRequestApprovedEvent() bool {
   256  	return w.SendEverything ||
   257  		(w.ChooseEvents && w.HookEvents.PullRequestReview)
   258  }
   259  
   260  // HasPullRequestRejectedEvent returns true if hook enabled pull request review event.
   261  func (w *Webhook) HasPullRequestRejectedEvent() bool {
   262  	return w.SendEverything ||
   263  		(w.ChooseEvents && w.HookEvents.PullRequestReview)
   264  }
   265  
   266  // HasPullRequestReviewCommentEvent returns true if hook enabled pull request review event.
   267  func (w *Webhook) HasPullRequestReviewCommentEvent() bool {
   268  	return w.SendEverything ||
   269  		(w.ChooseEvents && w.HookEvents.PullRequestReview)
   270  }
   271  
   272  // HasPullRequestSyncEvent returns true if hook enabled pull request sync event.
   273  func (w *Webhook) HasPullRequestSyncEvent() bool {
   274  	return w.SendEverything ||
   275  		(w.ChooseEvents && w.HookEvents.PullRequestSync)
   276  }
   277  
   278  // HasWikiEvent returns true if hook enabled wiki event.
   279  func (w *Webhook) HasWikiEvent() bool {
   280  	return w.SendEverything ||
   281  		(w.ChooseEvents && w.HookEvent.Wiki)
   282  }
   283  
   284  // HasReleaseEvent returns if hook enabled release event.
   285  func (w *Webhook) HasReleaseEvent() bool {
   286  	return w.SendEverything ||
   287  		(w.ChooseEvents && w.HookEvents.Release)
   288  }
   289  
   290  // HasRepositoryEvent returns if hook enabled repository event.
   291  func (w *Webhook) HasRepositoryEvent() bool {
   292  	return w.SendEverything ||
   293  		(w.ChooseEvents && w.HookEvents.Repository)
   294  }
   295  
   296  // HasPackageEvent returns if hook enabled package event.
   297  func (w *Webhook) HasPackageEvent() bool {
   298  	return w.SendEverything ||
   299  		(w.ChooseEvents && w.HookEvents.Package)
   300  }
   301  
   302  // HasPullRequestReviewRequestEvent returns true if hook enabled pull request review request event.
   303  func (w *Webhook) HasPullRequestReviewRequestEvent() bool {
   304  	return w.SendEverything ||
   305  		(w.ChooseEvents && w.HookEvents.PullRequestReviewRequest)
   306  }
   307  
   308  // EventCheckers returns event checkers
   309  func (w *Webhook) EventCheckers() []struct {
   310  	Has  func() bool
   311  	Type webhook_module.HookEventType
   312  } {
   313  	return []struct {
   314  		Has  func() bool
   315  		Type webhook_module.HookEventType
   316  	}{
   317  		{w.HasCreateEvent, webhook_module.HookEventCreate},
   318  		{w.HasDeleteEvent, webhook_module.HookEventDelete},
   319  		{w.HasForkEvent, webhook_module.HookEventFork},
   320  		{w.HasPushEvent, webhook_module.HookEventPush},
   321  		{w.HasIssuesEvent, webhook_module.HookEventIssues},
   322  		{w.HasIssuesAssignEvent, webhook_module.HookEventIssueAssign},
   323  		{w.HasIssuesLabelEvent, webhook_module.HookEventIssueLabel},
   324  		{w.HasIssuesMilestoneEvent, webhook_module.HookEventIssueMilestone},
   325  		{w.HasIssueCommentEvent, webhook_module.HookEventIssueComment},
   326  		{w.HasPullRequestEvent, webhook_module.HookEventPullRequest},
   327  		{w.HasPullRequestAssignEvent, webhook_module.HookEventPullRequestAssign},
   328  		{w.HasPullRequestLabelEvent, webhook_module.HookEventPullRequestLabel},
   329  		{w.HasPullRequestMilestoneEvent, webhook_module.HookEventPullRequestMilestone},
   330  		{w.HasPullRequestCommentEvent, webhook_module.HookEventPullRequestComment},
   331  		{w.HasPullRequestApprovedEvent, webhook_module.HookEventPullRequestReviewApproved},
   332  		{w.HasPullRequestRejectedEvent, webhook_module.HookEventPullRequestReviewRejected},
   333  		{w.HasPullRequestCommentEvent, webhook_module.HookEventPullRequestReviewComment},
   334  		{w.HasPullRequestSyncEvent, webhook_module.HookEventPullRequestSync},
   335  		{w.HasWikiEvent, webhook_module.HookEventWiki},
   336  		{w.HasRepositoryEvent, webhook_module.HookEventRepository},
   337  		{w.HasReleaseEvent, webhook_module.HookEventRelease},
   338  		{w.HasPackageEvent, webhook_module.HookEventPackage},
   339  		{w.HasPullRequestReviewRequestEvent, webhook_module.HookEventPullRequestReviewRequest},
   340  	}
   341  }
   342  
   343  // EventsArray returns an array of hook events
   344  func (w *Webhook) EventsArray() []string {
   345  	events := make([]string, 0, 7)
   346  
   347  	for _, c := range w.EventCheckers() {
   348  		if c.Has() {
   349  			events = append(events, string(c.Type))
   350  		}
   351  	}
   352  	return events
   353  }
   354  
   355  // HeaderAuthorization returns the decrypted Authorization header.
   356  // Not on the reference (*w), to be accessible on WebhooksNew.
   357  func (w Webhook) HeaderAuthorization() (string, error) {
   358  	if w.HeaderAuthorizationEncrypted == "" {
   359  		return "", nil
   360  	}
   361  	return secret.DecryptSecret(setting.SecretKey, w.HeaderAuthorizationEncrypted)
   362  }
   363  
   364  // SetHeaderAuthorization encrypts and sets the Authorization header.
   365  func (w *Webhook) SetHeaderAuthorization(cleartext string) error {
   366  	if cleartext == "" {
   367  		w.HeaderAuthorizationEncrypted = ""
   368  		return nil
   369  	}
   370  	ciphertext, err := secret.EncryptSecret(setting.SecretKey, cleartext)
   371  	if err != nil {
   372  		return err
   373  	}
   374  	w.HeaderAuthorizationEncrypted = ciphertext
   375  	return nil
   376  }
   377  
   378  // CreateWebhook creates a new web hook.
   379  func CreateWebhook(ctx context.Context, w *Webhook) error {
   380  	w.Type = strings.TrimSpace(w.Type)
   381  	return db.Insert(ctx, w)
   382  }
   383  
   384  // CreateWebhooks creates multiple web hooks
   385  func CreateWebhooks(ctx context.Context, ws []*Webhook) error {
   386  	// xorm returns err "no element on slice when insert" for empty slices.
   387  	if len(ws) == 0 {
   388  		return nil
   389  	}
   390  	for i := 0; i < len(ws); i++ {
   391  		ws[i].Type = strings.TrimSpace(ws[i].Type)
   392  	}
   393  	return db.Insert(ctx, ws)
   394  }
   395  
   396  // GetWebhookByID returns webhook of repository by given ID.
   397  func GetWebhookByID(ctx context.Context, id int64) (*Webhook, error) {
   398  	bean := new(Webhook)
   399  	has, err := db.GetEngine(ctx).ID(id).Get(bean)
   400  	if err != nil {
   401  		return nil, err
   402  	} else if !has {
   403  		return nil, ErrWebhookNotExist{ID: id}
   404  	}
   405  	return bean, nil
   406  }
   407  
   408  // GetWebhookByRepoID returns webhook of repository by given ID.
   409  func GetWebhookByRepoID(ctx context.Context, repoID, id int64) (*Webhook, error) {
   410  	webhook := new(Webhook)
   411  	has, err := db.GetEngine(ctx).Where("id=? AND repo_id=?", id, repoID).Get(webhook)
   412  	if err != nil {
   413  		return nil, err
   414  	} else if !has {
   415  		return nil, ErrWebhookNotExist{ID: id}
   416  	}
   417  	return webhook, nil
   418  }
   419  
   420  // GetWebhookByOwnerID returns webhook of a user or organization by given ID.
   421  func GetWebhookByOwnerID(ctx context.Context, ownerID, id int64) (*Webhook, error) {
   422  	webhook := new(Webhook)
   423  	has, err := db.GetEngine(ctx).Where("id=? AND owner_id=?", id, ownerID).Get(webhook)
   424  	if err != nil {
   425  		return nil, err
   426  	} else if !has {
   427  		return nil, ErrWebhookNotExist{ID: id}
   428  	}
   429  	return webhook, nil
   430  }
   431  
   432  // ListWebhookOptions are options to filter webhooks on ListWebhooksByOpts
   433  type ListWebhookOptions struct {
   434  	db.ListOptions
   435  	RepoID   int64
   436  	OwnerID  int64
   437  	IsActive optional.Option[bool]
   438  }
   439  
   440  func (opts ListWebhookOptions) ToConds() builder.Cond {
   441  	cond := builder.NewCond()
   442  	if opts.RepoID != 0 {
   443  		cond = cond.And(builder.Eq{"webhook.repo_id": opts.RepoID})
   444  	}
   445  	if opts.OwnerID != 0 {
   446  		cond = cond.And(builder.Eq{"webhook.owner_id": opts.OwnerID})
   447  	}
   448  	if opts.IsActive.Has() {
   449  		cond = cond.And(builder.Eq{"webhook.is_active": opts.IsActive.Value()})
   450  	}
   451  	return cond
   452  }
   453  
   454  // UpdateWebhook updates information of webhook.
   455  func UpdateWebhook(ctx context.Context, w *Webhook) error {
   456  	_, err := db.GetEngine(ctx).ID(w.ID).AllCols().Update(w)
   457  	return err
   458  }
   459  
   460  // UpdateWebhookLastStatus updates last status of webhook.
   461  func UpdateWebhookLastStatus(ctx context.Context, w *Webhook) error {
   462  	_, err := db.GetEngine(ctx).ID(w.ID).Cols("last_status").Update(w)
   463  	return err
   464  }
   465  
   466  // DeleteWebhookByID uses argument bean as query condition,
   467  // ID must be specified and do not assign unnecessary fields.
   468  func DeleteWebhookByID(ctx context.Context, id int64) (err error) {
   469  	ctx, committer, err := db.TxContext(ctx)
   470  	if err != nil {
   471  		return err
   472  	}
   473  	defer committer.Close()
   474  
   475  	if count, err := db.DeleteByID[Webhook](ctx, id); err != nil {
   476  		return err
   477  	} else if count == 0 {
   478  		return ErrWebhookNotExist{ID: id}
   479  	} else if _, err = db.DeleteByBean(ctx, &HookTask{HookID: id}); err != nil {
   480  		return err
   481  	}
   482  
   483  	return committer.Commit()
   484  }
   485  
   486  // DeleteWebhookByRepoID deletes webhook of repository by given ID.
   487  func DeleteWebhookByRepoID(ctx context.Context, repoID, id int64) error {
   488  	if _, err := GetWebhookByRepoID(ctx, repoID, id); err != nil {
   489  		return err
   490  	}
   491  	return DeleteWebhookByID(ctx, id)
   492  }
   493  
   494  // DeleteWebhookByOwnerID deletes webhook of a user or organization by given ID.
   495  func DeleteWebhookByOwnerID(ctx context.Context, ownerID, id int64) error {
   496  	if _, err := GetWebhookByOwnerID(ctx, ownerID, id); err != nil {
   497  		return err
   498  	}
   499  	return DeleteWebhookByID(ctx, id)
   500  }