gitea源码

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233
  1. // Copyright 2019 The Gitea Authors. All rights reserved.
  2. // SPDX-License-Identifier: MIT
  3. package webhook
  4. import (
  5. "context"
  6. "errors"
  7. "fmt"
  8. "net/http"
  9. "strings"
  10. "code.gitea.io/gitea/models/db"
  11. repo_model "code.gitea.io/gitea/models/repo"
  12. user_model "code.gitea.io/gitea/models/user"
  13. webhook_model "code.gitea.io/gitea/models/webhook"
  14. "code.gitea.io/gitea/modules/git"
  15. "code.gitea.io/gitea/modules/glob"
  16. "code.gitea.io/gitea/modules/graceful"
  17. "code.gitea.io/gitea/modules/log"
  18. "code.gitea.io/gitea/modules/optional"
  19. "code.gitea.io/gitea/modules/queue"
  20. "code.gitea.io/gitea/modules/setting"
  21. api "code.gitea.io/gitea/modules/structs"
  22. "code.gitea.io/gitea/modules/util"
  23. webhook_module "code.gitea.io/gitea/modules/webhook"
  24. )
  25. type Requester func(context.Context, *webhook_model.Webhook, *webhook_model.HookTask) (req *http.Request, body []byte, err error)
  26. var webhookRequesters = map[webhook_module.HookType]Requester{}
  27. func RegisterWebhookRequester(hookType webhook_module.HookType, requester Requester) {
  28. webhookRequesters[hookType] = requester
  29. }
  30. // IsValidHookTaskType returns true if a webhook registered
  31. func IsValidHookTaskType(name string) bool {
  32. if name == webhook_module.GITEA || name == webhook_module.GOGS {
  33. return true
  34. }
  35. _, ok := webhookRequesters[name]
  36. return ok
  37. }
  38. // hookQueue is a global queue of web hooks
  39. var hookQueue *queue.WorkerPoolQueue[int64]
  40. // getPayloadBranch returns branch for hook event, if applicable.
  41. func getPayloadBranch(p api.Payloader) string {
  42. switch pp := p.(type) {
  43. case *api.CreatePayload:
  44. if pp.RefType == "branch" {
  45. return pp.Ref
  46. }
  47. case *api.DeletePayload:
  48. if pp.RefType == "branch" {
  49. return pp.Ref
  50. }
  51. case *api.PushPayload:
  52. if strings.HasPrefix(pp.Ref, git.BranchPrefix) {
  53. return pp.Ref[len(git.BranchPrefix):]
  54. }
  55. }
  56. return ""
  57. }
  58. // EventSource represents the source of a webhook action. Repository and/or Owner must be set.
  59. type EventSource struct {
  60. Repository *repo_model.Repository
  61. Owner *user_model.User
  62. }
  63. // handle delivers hook tasks
  64. func handler(items ...int64) []int64 {
  65. ctx := graceful.GetManager().HammerContext()
  66. for _, taskID := range items {
  67. task, err := webhook_model.GetHookTaskByID(ctx, taskID)
  68. if err != nil {
  69. if errors.Is(err, util.ErrNotExist) {
  70. log.Warn("GetHookTaskByID[%d] warn: %v", taskID, err)
  71. } else {
  72. log.Error("GetHookTaskByID[%d] failed: %v", taskID, err)
  73. }
  74. continue
  75. }
  76. if task.IsDelivered {
  77. // Already delivered in the meantime
  78. log.Trace("Task[%d] has already been delivered", task.ID)
  79. continue
  80. }
  81. if err := Deliver(ctx, task); err != nil {
  82. log.Error("Unable to deliver webhook task[%d]: %v", task.ID, err)
  83. }
  84. }
  85. return nil
  86. }
  87. func enqueueHookTask(taskID int64) error {
  88. err := hookQueue.Push(taskID)
  89. if err != nil && err != queue.ErrAlreadyInQueue {
  90. return err
  91. }
  92. return nil
  93. }
  94. func checkBranch(w *webhook_model.Webhook, branch string) bool {
  95. if w.BranchFilter == "" || w.BranchFilter == "*" {
  96. return true
  97. }
  98. g, err := glob.Compile(w.BranchFilter)
  99. if err != nil {
  100. // should not really happen as BranchFilter is validated
  101. log.Error("CheckBranch failed: %s", err)
  102. return false
  103. }
  104. return g.Match(branch)
  105. }
  106. // PrepareWebhook creates a hook task and enqueues it for processing.
  107. // The payload is saved as-is. The adjustments depending on the webhook type happen
  108. // right before delivery, in the [Deliver] method.
  109. func PrepareWebhook(ctx context.Context, w *webhook_model.Webhook, event webhook_module.HookEventType, p api.Payloader) error {
  110. // Skip sending if webhooks are disabled.
  111. if setting.DisableWebhooks {
  112. return nil
  113. }
  114. if !w.HasEvent(event) {
  115. return nil
  116. }
  117. // Avoid sending "0 new commits" to non-integration relevant webhooks (e.g. slack, discord, etc.).
  118. // Integration webhooks (e.g. drone) still receive the required data.
  119. if pushEvent, ok := p.(*api.PushPayload); ok &&
  120. w.Type != webhook_module.GITEA && w.Type != webhook_module.GOGS &&
  121. len(pushEvent.Commits) == 0 {
  122. return nil
  123. }
  124. // If payload has no associated branch (e.g. it's a new tag, issue, etc.),
  125. // branch filter has no effect.
  126. if branch := getPayloadBranch(p); branch != "" {
  127. if !checkBranch(w, branch) {
  128. log.Info("Branch %q doesn't match branch filter %q, skipping", branch, w.BranchFilter)
  129. return nil
  130. }
  131. }
  132. payload, err := p.JSONPayload()
  133. if err != nil {
  134. return fmt.Errorf("JSONPayload for %s: %w", event, err)
  135. }
  136. task, err := webhook_model.CreateHookTask(ctx, &webhook_model.HookTask{
  137. HookID: w.ID,
  138. PayloadContent: string(payload),
  139. EventType: event,
  140. PayloadVersion: 2,
  141. })
  142. if err != nil {
  143. return fmt.Errorf("CreateHookTask for %s: %w", event, err)
  144. }
  145. return enqueueHookTask(task.ID)
  146. }
  147. // PrepareWebhooks adds new webhooks to task queue for given payload.
  148. func PrepareWebhooks(ctx context.Context, source EventSource, event webhook_module.HookEventType, p api.Payloader) error {
  149. owner := source.Owner
  150. var ws []*webhook_model.Webhook
  151. if source.Repository != nil {
  152. repoHooks, err := db.Find[webhook_model.Webhook](ctx, webhook_model.ListWebhookOptions{
  153. RepoID: source.Repository.ID,
  154. IsActive: optional.Some(true),
  155. })
  156. if err != nil {
  157. return fmt.Errorf("ListWebhooksByOpts: %w", err)
  158. }
  159. ws = append(ws, repoHooks...)
  160. owner = source.Repository.MustOwner(ctx)
  161. }
  162. // append additional webhooks of a user or organization
  163. if owner != nil {
  164. ownerHooks, err := db.Find[webhook_model.Webhook](ctx, webhook_model.ListWebhookOptions{
  165. OwnerID: owner.ID,
  166. IsActive: optional.Some(true),
  167. })
  168. if err != nil {
  169. return fmt.Errorf("ListWebhooksByOpts: %w", err)
  170. }
  171. ws = append(ws, ownerHooks...)
  172. }
  173. // Add any admin-defined system webhooks
  174. systemHooks, err := webhook_model.GetSystemWebhooks(ctx, optional.Some(true))
  175. if err != nil {
  176. return fmt.Errorf("GetSystemWebhooks: %w", err)
  177. }
  178. ws = append(ws, systemHooks...)
  179. if len(ws) == 0 {
  180. return nil
  181. }
  182. for _, w := range ws {
  183. if err := PrepareWebhook(ctx, w, event, p); err != nil {
  184. return err
  185. }
  186. }
  187. return nil
  188. }
  189. // ReplayHookTask replays a webhook task
  190. func ReplayHookTask(ctx context.Context, w *webhook_model.Webhook, uuid string) error {
  191. task, err := webhook_model.ReplayHookTask(ctx, w.ID, uuid)
  192. if err != nil {
  193. return err
  194. }
  195. return enqueueHookTask(task.ID)
  196. }