gitea源码

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349
  1. // Copyright 2014 The Gogs Authors. All rights reserved.
  2. // Copyright 2017 The Gitea Authors. All rights reserved.
  3. // SPDX-License-Identifier: MIT
  4. package webhook
  5. import (
  6. "context"
  7. "fmt"
  8. "strings"
  9. "code.gitea.io/gitea/models/db"
  10. "code.gitea.io/gitea/modules/json"
  11. "code.gitea.io/gitea/modules/log"
  12. "code.gitea.io/gitea/modules/optional"
  13. "code.gitea.io/gitea/modules/secret"
  14. "code.gitea.io/gitea/modules/setting"
  15. "code.gitea.io/gitea/modules/timeutil"
  16. "code.gitea.io/gitea/modules/util"
  17. webhook_module "code.gitea.io/gitea/modules/webhook"
  18. "xorm.io/builder"
  19. )
  20. // ErrWebhookNotExist represents a "WebhookNotExist" kind of error.
  21. type ErrWebhookNotExist struct {
  22. ID int64
  23. }
  24. // IsErrWebhookNotExist checks if an error is a ErrWebhookNotExist.
  25. func IsErrWebhookNotExist(err error) bool {
  26. _, ok := err.(ErrWebhookNotExist)
  27. return ok
  28. }
  29. func (err ErrWebhookNotExist) Error() string {
  30. return fmt.Sprintf("webhook does not exist [id: %d]", err.ID)
  31. }
  32. func (err ErrWebhookNotExist) Unwrap() error {
  33. return util.ErrNotExist
  34. }
  35. // ErrHookTaskNotExist represents a "HookTaskNotExist" kind of error.
  36. type ErrHookTaskNotExist struct {
  37. TaskID int64
  38. HookID int64
  39. UUID string
  40. }
  41. // IsErrHookTaskNotExist checks if an error is a ErrHookTaskNotExist.
  42. func IsErrHookTaskNotExist(err error) bool {
  43. _, ok := err.(ErrHookTaskNotExist)
  44. return ok
  45. }
  46. func (err ErrHookTaskNotExist) Error() string {
  47. return fmt.Sprintf("hook task does not exist [task: %d, hook: %d, uuid: %s]", err.TaskID, err.HookID, err.UUID)
  48. }
  49. func (err ErrHookTaskNotExist) Unwrap() error {
  50. return util.ErrNotExist
  51. }
  52. // HookContentType is the content type of a web hook
  53. type HookContentType int
  54. const (
  55. // ContentTypeJSON is a JSON payload for web hooks
  56. ContentTypeJSON HookContentType = iota + 1
  57. // ContentTypeForm is an url-encoded form payload for web hook
  58. ContentTypeForm
  59. )
  60. var hookContentTypes = map[string]HookContentType{
  61. "json": ContentTypeJSON,
  62. "form": ContentTypeForm,
  63. }
  64. // ToHookContentType returns HookContentType by given name.
  65. func ToHookContentType(name string) HookContentType {
  66. return hookContentTypes[name]
  67. }
  68. // HookTaskCleanupType is the type of cleanup to perform on hook_task
  69. type HookTaskCleanupType int
  70. const (
  71. // OlderThan hook_task rows will be cleaned up by the age of the row
  72. OlderThan HookTaskCleanupType = iota
  73. // PerWebhook hook_task rows will be cleaned up by leaving the most recent deliveries for each webhook
  74. PerWebhook
  75. )
  76. var hookTaskCleanupTypes = map[string]HookTaskCleanupType{
  77. "OlderThan": OlderThan,
  78. "PerWebhook": PerWebhook,
  79. }
  80. // ToHookTaskCleanupType returns HookTaskCleanupType by given name.
  81. func ToHookTaskCleanupType(name string) HookTaskCleanupType {
  82. return hookTaskCleanupTypes[name]
  83. }
  84. // Name returns the name of a given web hook's content type
  85. func (t HookContentType) Name() string {
  86. switch t {
  87. case ContentTypeJSON:
  88. return "json"
  89. case ContentTypeForm:
  90. return "form"
  91. }
  92. return ""
  93. }
  94. // IsValidHookContentType returns true if given name is a valid hook content type.
  95. func IsValidHookContentType(name string) bool {
  96. _, ok := hookContentTypes[name]
  97. return ok
  98. }
  99. // Webhook represents a web hook object.
  100. type Webhook struct {
  101. ID int64 `xorm:"pk autoincr"`
  102. RepoID int64 `xorm:"INDEX"` // An ID of 0 indicates either a default or system webhook
  103. OwnerID int64 `xorm:"INDEX"`
  104. IsSystemWebhook bool
  105. URL string `xorm:"url TEXT"`
  106. HTTPMethod string `xorm:"http_method"`
  107. ContentType HookContentType
  108. Secret string `xorm:"TEXT"`
  109. Events string `xorm:"TEXT"`
  110. *webhook_module.HookEvent `xorm:"-"`
  111. IsActive bool `xorm:"INDEX"`
  112. Type webhook_module.HookType `xorm:"VARCHAR(16) 'type'"`
  113. Meta string `xorm:"TEXT"` // store hook-specific attributes
  114. LastStatus webhook_module.HookStatus // Last delivery status
  115. // HeaderAuthorizationEncrypted should be accessed using HeaderAuthorization() and SetHeaderAuthorization()
  116. HeaderAuthorizationEncrypted string `xorm:"TEXT"`
  117. CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"`
  118. UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"`
  119. }
  120. func init() {
  121. db.RegisterModel(new(Webhook))
  122. }
  123. // AfterLoad updates the webhook object upon setting a column
  124. func (w *Webhook) AfterLoad() {
  125. w.HookEvent = &webhook_module.HookEvent{}
  126. if err := json.Unmarshal([]byte(w.Events), w.HookEvent); err != nil {
  127. log.Error("Unmarshal[%d]: %v", w.ID, err)
  128. }
  129. }
  130. // History returns history of webhook by given conditions.
  131. func (w *Webhook) History(ctx context.Context, page int) ([]*HookTask, error) {
  132. return HookTasks(ctx, w.ID, page)
  133. }
  134. // UpdateEvent handles conversion from HookEvent to Events.
  135. func (w *Webhook) UpdateEvent() error {
  136. data, err := json.Marshal(w.HookEvent)
  137. w.Events = string(data)
  138. return err
  139. }
  140. func (w *Webhook) HasEvent(evt webhook_module.HookEventType) bool {
  141. if w.SendEverything {
  142. return true
  143. }
  144. if w.PushOnly {
  145. return evt == webhook_module.HookEventPush
  146. }
  147. checkEvt := evt
  148. switch evt {
  149. case webhook_module.HookEventPullRequestReviewApproved, webhook_module.HookEventPullRequestReviewRejected, webhook_module.HookEventPullRequestReviewComment:
  150. checkEvt = webhook_module.HookEventPullRequestReview
  151. }
  152. return w.HookEvents[checkEvt]
  153. }
  154. // EventsArray returns an array of hook events
  155. func (w *Webhook) EventsArray() []string {
  156. if w.SendEverything {
  157. events := make([]string, 0, len(webhook_module.AllEvents()))
  158. for _, evt := range webhook_module.AllEvents() {
  159. events = append(events, string(evt))
  160. }
  161. return events
  162. }
  163. if w.PushOnly {
  164. return []string{string(webhook_module.HookEventPush)}
  165. }
  166. events := make([]string, 0, len(w.HookEvents))
  167. for event, enabled := range w.HookEvents {
  168. if enabled {
  169. events = append(events, string(event))
  170. }
  171. }
  172. return events
  173. }
  174. // HeaderAuthorization returns the decrypted Authorization header.
  175. // Not on the reference (*w), to be accessible on WebhooksNew.
  176. func (w Webhook) HeaderAuthorization() (string, error) {
  177. if w.HeaderAuthorizationEncrypted == "" {
  178. return "", nil
  179. }
  180. return secret.DecryptSecret(setting.SecretKey, w.HeaderAuthorizationEncrypted)
  181. }
  182. // SetHeaderAuthorization encrypts and sets the Authorization header.
  183. func (w *Webhook) SetHeaderAuthorization(cleartext string) error {
  184. if cleartext == "" {
  185. w.HeaderAuthorizationEncrypted = ""
  186. return nil
  187. }
  188. ciphertext, err := secret.EncryptSecret(setting.SecretKey, cleartext)
  189. if err != nil {
  190. return err
  191. }
  192. w.HeaderAuthorizationEncrypted = ciphertext
  193. return nil
  194. }
  195. // CreateWebhook creates a new web hook.
  196. func CreateWebhook(ctx context.Context, w *Webhook) error {
  197. w.Type = strings.TrimSpace(w.Type)
  198. return db.Insert(ctx, w)
  199. }
  200. // CreateWebhooks creates multiple web hooks
  201. func CreateWebhooks(ctx context.Context, ws []*Webhook) error {
  202. // xorm returns err "no element on slice when insert" for empty slices.
  203. if len(ws) == 0 {
  204. return nil
  205. }
  206. for i := range ws {
  207. ws[i].Type = strings.TrimSpace(ws[i].Type)
  208. }
  209. return db.Insert(ctx, ws)
  210. }
  211. // GetWebhookByID returns webhook of repository by given ID.
  212. func GetWebhookByID(ctx context.Context, id int64) (*Webhook, error) {
  213. bean := new(Webhook)
  214. has, err := db.GetEngine(ctx).ID(id).Get(bean)
  215. if err != nil {
  216. return nil, err
  217. } else if !has {
  218. return nil, ErrWebhookNotExist{ID: id}
  219. }
  220. return bean, nil
  221. }
  222. // GetWebhookByRepoID returns webhook of repository by given ID.
  223. func GetWebhookByRepoID(ctx context.Context, repoID, id int64) (*Webhook, error) {
  224. webhook := new(Webhook)
  225. has, err := db.GetEngine(ctx).Where("id=? AND repo_id=?", id, repoID).Get(webhook)
  226. if err != nil {
  227. return nil, err
  228. } else if !has {
  229. return nil, ErrWebhookNotExist{ID: id}
  230. }
  231. return webhook, nil
  232. }
  233. // GetWebhookByOwnerID returns webhook of a user or organization by given ID.
  234. func GetWebhookByOwnerID(ctx context.Context, ownerID, id int64) (*Webhook, error) {
  235. webhook := new(Webhook)
  236. has, err := db.GetEngine(ctx).Where("id=? AND owner_id=?", id, ownerID).Get(webhook)
  237. if err != nil {
  238. return nil, err
  239. } else if !has {
  240. return nil, ErrWebhookNotExist{ID: id}
  241. }
  242. return webhook, nil
  243. }
  244. // ListWebhookOptions are options to filter webhooks on ListWebhooksByOpts
  245. type ListWebhookOptions struct {
  246. db.ListOptions
  247. RepoID int64
  248. OwnerID int64
  249. IsActive optional.Option[bool]
  250. }
  251. func (opts ListWebhookOptions) ToConds() builder.Cond {
  252. cond := builder.NewCond()
  253. if opts.RepoID != 0 {
  254. cond = cond.And(builder.Eq{"webhook.repo_id": opts.RepoID})
  255. }
  256. if opts.OwnerID != 0 {
  257. cond = cond.And(builder.Eq{"webhook.owner_id": opts.OwnerID})
  258. }
  259. if opts.IsActive.Has() {
  260. cond = cond.And(builder.Eq{"webhook.is_active": opts.IsActive.Value()})
  261. }
  262. return cond
  263. }
  264. // UpdateWebhook updates information of webhook.
  265. func UpdateWebhook(ctx context.Context, w *Webhook) error {
  266. _, err := db.GetEngine(ctx).ID(w.ID).AllCols().Update(w)
  267. return err
  268. }
  269. // UpdateWebhookLastStatus updates last status of webhook.
  270. func UpdateWebhookLastStatus(ctx context.Context, w *Webhook) error {
  271. _, err := db.GetEngine(ctx).ID(w.ID).Cols("last_status").Update(w)
  272. return err
  273. }
  274. // DeleteWebhookByID uses argument bean as query condition,
  275. // ID must be specified and do not assign unnecessary fields.
  276. func DeleteWebhookByID(ctx context.Context, id int64) (err error) {
  277. return db.WithTx(ctx, func(ctx context.Context) error {
  278. if count, err := db.DeleteByID[Webhook](ctx, id); err != nil {
  279. return err
  280. } else if count == 0 {
  281. return ErrWebhookNotExist{ID: id}
  282. } else if _, err = db.DeleteByBean(ctx, &HookTask{HookID: id}); err != nil {
  283. return err
  284. }
  285. return nil
  286. })
  287. }
  288. // DeleteWebhookByRepoID deletes webhook of repository by given ID.
  289. func DeleteWebhookByRepoID(ctx context.Context, repoID, id int64) error {
  290. if _, err := GetWebhookByRepoID(ctx, repoID, id); err != nil {
  291. return err
  292. }
  293. return DeleteWebhookByID(ctx, id)
  294. }
  295. // DeleteWebhookByOwnerID deletes webhook of a user or organization by given ID.
  296. func DeleteWebhookByOwnerID(ctx context.Context, ownerID, id int64) error {
  297. if _, err := GetWebhookByOwnerID(ctx, ownerID, id); err != nil {
  298. return err
  299. }
  300. return DeleteWebhookByID(ctx, id)
  301. }