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 }