You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

webhook.go 15KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486
  1. // Copyright 2019 The Gitea Authors. All rights reserved.
  2. // Use of this source code is governed by a MIT-style
  3. // license that can be found in the LICENSE file.
  4. package webhook
  5. import (
  6. "code.gitea.io/gitea/models"
  7. "code.gitea.io/gitea/modules/log"
  8. "code.gitea.io/gitea/modules/notification/base"
  9. "code.gitea.io/gitea/modules/setting"
  10. api "code.gitea.io/gitea/modules/structs"
  11. webhook_module "code.gitea.io/gitea/modules/webhook"
  12. )
  13. type webhookNotifier struct {
  14. base.NullNotifier
  15. }
  16. var (
  17. _ base.Notifier = &webhookNotifier{}
  18. )
  19. // NewNotifier create a new webhookNotifier notifier
  20. func NewNotifier() base.Notifier {
  21. return &webhookNotifier{}
  22. }
  23. func (m *webhookNotifier) NotifyIssueClearLabels(doer *models.User, issue *models.Issue) {
  24. if err := issue.LoadPoster(); err != nil {
  25. log.Error("loadPoster: %v", err)
  26. return
  27. }
  28. if err := issue.LoadRepo(); err != nil {
  29. log.Error("LoadRepo: %v", err)
  30. return
  31. }
  32. mode, _ := models.AccessLevel(issue.Poster, issue.Repo)
  33. var err error
  34. if issue.IsPull {
  35. if err = issue.LoadPullRequest(); err != nil {
  36. log.Error("LoadPullRequest: %v", err)
  37. return
  38. }
  39. err = webhook_module.PrepareWebhooks(issue.Repo, models.HookEventPullRequest, &api.PullRequestPayload{
  40. Action: api.HookIssueLabelCleared,
  41. Index: issue.Index,
  42. PullRequest: issue.PullRequest.APIFormat(),
  43. Repository: issue.Repo.APIFormat(mode),
  44. Sender: doer.APIFormat(),
  45. })
  46. } else {
  47. err = webhook_module.PrepareWebhooks(issue.Repo, models.HookEventIssues, &api.IssuePayload{
  48. Action: api.HookIssueLabelCleared,
  49. Index: issue.Index,
  50. Issue: issue.APIFormat(),
  51. Repository: issue.Repo.APIFormat(mode),
  52. Sender: doer.APIFormat(),
  53. })
  54. }
  55. if err != nil {
  56. log.Error("PrepareWebhooks [is_pull: %v]: %v", issue.IsPull, err)
  57. }
  58. }
  59. func (m *webhookNotifier) NotifyForkRepository(doer *models.User, oldRepo, repo *models.Repository) {
  60. oldMode, _ := models.AccessLevel(doer, oldRepo)
  61. mode, _ := models.AccessLevel(doer, repo)
  62. // forked webhook
  63. if err := webhook_module.PrepareWebhooks(oldRepo, models.HookEventFork, &api.ForkPayload{
  64. Forkee: oldRepo.APIFormat(oldMode),
  65. Repo: repo.APIFormat(mode),
  66. Sender: doer.APIFormat(),
  67. }); err != nil {
  68. log.Error("PrepareWebhooks [repo_id: %d]: %v", oldRepo.ID, err)
  69. }
  70. u := repo.MustOwner()
  71. // Add to hook queue for created repo after session commit.
  72. if u.IsOrganization() {
  73. if err := webhook_module.PrepareWebhooks(repo, models.HookEventRepository, &api.RepositoryPayload{
  74. Action: api.HookRepoCreated,
  75. Repository: repo.APIFormat(models.AccessModeOwner),
  76. Organization: u.APIFormat(),
  77. Sender: doer.APIFormat(),
  78. }); err != nil {
  79. log.Error("PrepareWebhooks [repo_id: %d]: %v", repo.ID, err)
  80. }
  81. }
  82. }
  83. func (m *webhookNotifier) NotifyCreateRepository(doer *models.User, u *models.User, repo *models.Repository) {
  84. // Add to hook queue for created repo after session commit.
  85. if u.IsOrganization() {
  86. if err := webhook_module.PrepareWebhooks(repo, models.HookEventRepository, &api.RepositoryPayload{
  87. Action: api.HookRepoCreated,
  88. Repository: repo.APIFormat(models.AccessModeOwner),
  89. Organization: u.APIFormat(),
  90. Sender: doer.APIFormat(),
  91. }); err != nil {
  92. log.Error("PrepareWebhooks [repo_id: %d]: %v", repo.ID, err)
  93. }
  94. }
  95. }
  96. func (m *webhookNotifier) NotifyDeleteRepository(doer *models.User, repo *models.Repository) {
  97. u := repo.MustOwner()
  98. if u.IsOrganization() {
  99. if err := webhook_module.PrepareWebhooks(repo, models.HookEventRepository, &api.RepositoryPayload{
  100. Action: api.HookRepoDeleted,
  101. Repository: repo.APIFormat(models.AccessModeOwner),
  102. Organization: u.APIFormat(),
  103. Sender: doer.APIFormat(),
  104. }); err != nil {
  105. log.Error("PrepareWebhooks [repo_id: %d]: %v", repo.ID, err)
  106. }
  107. }
  108. }
  109. func (m *webhookNotifier) NotifyIssueChangeAssignee(doer *models.User, issue *models.Issue, assignee *models.User, removed bool, comment *models.Comment) {
  110. if issue.IsPull {
  111. mode, _ := models.AccessLevelUnit(doer, issue.Repo, models.UnitTypePullRequests)
  112. if err := issue.LoadPullRequest(); err != nil {
  113. log.Error("LoadPullRequest failed: %v", err)
  114. return
  115. }
  116. issue.PullRequest.Issue = issue
  117. apiPullRequest := &api.PullRequestPayload{
  118. Index: issue.Index,
  119. PullRequest: issue.PullRequest.APIFormat(),
  120. Repository: issue.Repo.APIFormat(mode),
  121. Sender: doer.APIFormat(),
  122. }
  123. if removed {
  124. apiPullRequest.Action = api.HookIssueUnassigned
  125. } else {
  126. apiPullRequest.Action = api.HookIssueAssigned
  127. }
  128. // Assignee comment triggers a webhook
  129. if err := webhook_module.PrepareWebhooks(issue.Repo, models.HookEventPullRequest, apiPullRequest); err != nil {
  130. log.Error("PrepareWebhooks [is_pull: %v, remove_assignee: %v]: %v", issue.IsPull, removed, err)
  131. return
  132. }
  133. } else {
  134. mode, _ := models.AccessLevelUnit(doer, issue.Repo, models.UnitTypeIssues)
  135. apiIssue := &api.IssuePayload{
  136. Index: issue.Index,
  137. Issue: issue.APIFormat(),
  138. Repository: issue.Repo.APIFormat(mode),
  139. Sender: doer.APIFormat(),
  140. }
  141. if removed {
  142. apiIssue.Action = api.HookIssueUnassigned
  143. } else {
  144. apiIssue.Action = api.HookIssueAssigned
  145. }
  146. // Assignee comment triggers a webhook
  147. if err := webhook_module.PrepareWebhooks(issue.Repo, models.HookEventIssues, apiIssue); err != nil {
  148. log.Error("PrepareWebhooks [is_pull: %v, remove_assignee: %v]: %v", issue.IsPull, removed, err)
  149. return
  150. }
  151. }
  152. }
  153. func (m *webhookNotifier) NotifyIssueChangeTitle(doer *models.User, issue *models.Issue, oldTitle string) {
  154. mode, _ := models.AccessLevel(issue.Poster, issue.Repo)
  155. var err error
  156. if issue.IsPull {
  157. if err = issue.LoadPullRequest(); err != nil {
  158. log.Error("LoadPullRequest failed: %v", err)
  159. return
  160. }
  161. issue.PullRequest.Issue = issue
  162. err = webhook_module.PrepareWebhooks(issue.Repo, models.HookEventPullRequest, &api.PullRequestPayload{
  163. Action: api.HookIssueEdited,
  164. Index: issue.Index,
  165. Changes: &api.ChangesPayload{
  166. Title: &api.ChangesFromPayload{
  167. From: oldTitle,
  168. },
  169. },
  170. PullRequest: issue.PullRequest.APIFormat(),
  171. Repository: issue.Repo.APIFormat(mode),
  172. Sender: doer.APIFormat(),
  173. })
  174. } else {
  175. err = webhook_module.PrepareWebhooks(issue.Repo, models.HookEventIssues, &api.IssuePayload{
  176. Action: api.HookIssueEdited,
  177. Index: issue.Index,
  178. Changes: &api.ChangesPayload{
  179. Title: &api.ChangesFromPayload{
  180. From: oldTitle,
  181. },
  182. },
  183. Issue: issue.APIFormat(),
  184. Repository: issue.Repo.APIFormat(mode),
  185. Sender: issue.Poster.APIFormat(),
  186. })
  187. }
  188. if err != nil {
  189. log.Error("PrepareWebhooks [is_pull: %v]: %v", issue.IsPull, err)
  190. }
  191. }
  192. func (m *webhookNotifier) NotifyIssueChangeStatus(doer *models.User, issue *models.Issue, isClosed bool) {
  193. mode, _ := models.AccessLevel(issue.Poster, issue.Repo)
  194. var err error
  195. if issue.IsPull {
  196. if err = issue.LoadPullRequest(); err != nil {
  197. log.Error("LoadPullRequest: %v", err)
  198. return
  199. }
  200. // Merge pull request calls issue.changeStatus so we need to handle separately.
  201. apiPullRequest := &api.PullRequestPayload{
  202. Index: issue.Index,
  203. PullRequest: issue.PullRequest.APIFormat(),
  204. Repository: issue.Repo.APIFormat(mode),
  205. Sender: doer.APIFormat(),
  206. }
  207. if isClosed {
  208. apiPullRequest.Action = api.HookIssueClosed
  209. } else {
  210. apiPullRequest.Action = api.HookIssueReOpened
  211. }
  212. err = webhook_module.PrepareWebhooks(issue.Repo, models.HookEventPullRequest, apiPullRequest)
  213. } else {
  214. apiIssue := &api.IssuePayload{
  215. Index: issue.Index,
  216. Issue: issue.APIFormat(),
  217. Repository: issue.Repo.APIFormat(mode),
  218. Sender: doer.APIFormat(),
  219. }
  220. if isClosed {
  221. apiIssue.Action = api.HookIssueClosed
  222. } else {
  223. apiIssue.Action = api.HookIssueReOpened
  224. }
  225. err = webhook_module.PrepareWebhooks(issue.Repo, models.HookEventIssues, apiIssue)
  226. }
  227. if err != nil {
  228. log.Error("PrepareWebhooks [is_pull: %v, is_closed: %v]: %v", issue.IsPull, isClosed, err)
  229. }
  230. }
  231. func (m *webhookNotifier) NotifyNewIssue(issue *models.Issue) {
  232. mode, _ := models.AccessLevel(issue.Poster, issue.Repo)
  233. if err := webhook_module.PrepareWebhooks(issue.Repo, models.HookEventIssues, &api.IssuePayload{
  234. Action: api.HookIssueOpened,
  235. Index: issue.Index,
  236. Issue: issue.APIFormat(),
  237. Repository: issue.Repo.APIFormat(mode),
  238. Sender: issue.Poster.APIFormat(),
  239. }); err != nil {
  240. log.Error("PrepareWebhooks: %v", err)
  241. }
  242. }
  243. func (m *webhookNotifier) NotifyIssueChangeContent(doer *models.User, issue *models.Issue, oldContent string) {
  244. mode, _ := models.AccessLevel(issue.Poster, issue.Repo)
  245. var err error
  246. if issue.IsPull {
  247. issue.PullRequest.Issue = issue
  248. err = webhook_module.PrepareWebhooks(issue.Repo, models.HookEventPullRequest, &api.PullRequestPayload{
  249. Action: api.HookIssueEdited,
  250. Index: issue.Index,
  251. Changes: &api.ChangesPayload{
  252. Body: &api.ChangesFromPayload{
  253. From: oldContent,
  254. },
  255. },
  256. PullRequest: issue.PullRequest.APIFormat(),
  257. Repository: issue.Repo.APIFormat(mode),
  258. Sender: doer.APIFormat(),
  259. })
  260. } else {
  261. err = webhook_module.PrepareWebhooks(issue.Repo, models.HookEventIssues, &api.IssuePayload{
  262. Action: api.HookIssueEdited,
  263. Index: issue.Index,
  264. Changes: &api.ChangesPayload{
  265. Body: &api.ChangesFromPayload{
  266. From: oldContent,
  267. },
  268. },
  269. Issue: issue.APIFormat(),
  270. Repository: issue.Repo.APIFormat(mode),
  271. Sender: doer.APIFormat(),
  272. })
  273. }
  274. if err != nil {
  275. log.Error("PrepareWebhooks [is_pull: %v]: %v", issue.IsPull, err)
  276. }
  277. }
  278. func (m *webhookNotifier) NotifyUpdateComment(doer *models.User, c *models.Comment, oldContent string) {
  279. if err := c.LoadPoster(); err != nil {
  280. log.Error("LoadPoster: %v", err)
  281. return
  282. }
  283. if err := c.LoadIssue(); err != nil {
  284. log.Error("LoadIssue: %v", err)
  285. return
  286. }
  287. if err := c.Issue.LoadAttributes(); err != nil {
  288. log.Error("LoadAttributes: %v", err)
  289. return
  290. }
  291. mode, _ := models.AccessLevel(doer, c.Issue.Repo)
  292. if err := webhook_module.PrepareWebhooks(c.Issue.Repo, models.HookEventIssueComment, &api.IssueCommentPayload{
  293. Action: api.HookIssueCommentEdited,
  294. Issue: c.Issue.APIFormat(),
  295. Comment: c.APIFormat(),
  296. Changes: &api.ChangesPayload{
  297. Body: &api.ChangesFromPayload{
  298. From: oldContent,
  299. },
  300. },
  301. Repository: c.Issue.Repo.APIFormat(mode),
  302. Sender: doer.APIFormat(),
  303. IsPull: c.Issue.IsPull,
  304. }); err != nil {
  305. log.Error("PrepareWebhooks [comment_id: %d]: %v", c.ID, err)
  306. }
  307. }
  308. func (m *webhookNotifier) NotifyCreateIssueComment(doer *models.User, repo *models.Repository,
  309. issue *models.Issue, comment *models.Comment) {
  310. mode, _ := models.AccessLevel(doer, repo)
  311. if err := webhook_module.PrepareWebhooks(repo, models.HookEventIssueComment, &api.IssueCommentPayload{
  312. Action: api.HookIssueCommentCreated,
  313. Issue: issue.APIFormat(),
  314. Comment: comment.APIFormat(),
  315. Repository: repo.APIFormat(mode),
  316. Sender: doer.APIFormat(),
  317. IsPull: issue.IsPull,
  318. }); err != nil {
  319. log.Error("PrepareWebhooks [comment_id: %d]: %v", comment.ID, err)
  320. }
  321. }
  322. func (m *webhookNotifier) NotifyDeleteComment(doer *models.User, comment *models.Comment) {
  323. if err := comment.LoadPoster(); err != nil {
  324. log.Error("LoadPoster: %v", err)
  325. return
  326. }
  327. if err := comment.LoadIssue(); err != nil {
  328. log.Error("LoadIssue: %v", err)
  329. return
  330. }
  331. if err := comment.Issue.LoadAttributes(); err != nil {
  332. log.Error("LoadAttributes: %v", err)
  333. return
  334. }
  335. mode, _ := models.AccessLevel(doer, comment.Issue.Repo)
  336. if err := webhook_module.PrepareWebhooks(comment.Issue.Repo, models.HookEventIssueComment, &api.IssueCommentPayload{
  337. Action: api.HookIssueCommentDeleted,
  338. Issue: comment.Issue.APIFormat(),
  339. Comment: comment.APIFormat(),
  340. Repository: comment.Issue.Repo.APIFormat(mode),
  341. Sender: doer.APIFormat(),
  342. IsPull: comment.Issue.IsPull,
  343. }); err != nil {
  344. log.Error("PrepareWebhooks [comment_id: %d]: %v", comment.ID, err)
  345. }
  346. }
  347. func (m *webhookNotifier) NotifyIssueChangeLabels(doer *models.User, issue *models.Issue,
  348. addedLabels []*models.Label, removedLabels []*models.Label) {
  349. var err error
  350. if err = issue.LoadRepo(); err != nil {
  351. log.Error("LoadRepo: %v", err)
  352. return
  353. }
  354. if err = issue.LoadPoster(); err != nil {
  355. log.Error("LoadPoster: %v", err)
  356. return
  357. }
  358. mode, _ := models.AccessLevel(issue.Poster, issue.Repo)
  359. if issue.IsPull {
  360. if err = issue.LoadPullRequest(); err != nil {
  361. log.Error("loadPullRequest: %v", err)
  362. return
  363. }
  364. if err = issue.PullRequest.LoadIssue(); err != nil {
  365. log.Error("LoadIssue: %v", err)
  366. return
  367. }
  368. err = webhook_module.PrepareWebhooks(issue.Repo, models.HookEventPullRequest, &api.PullRequestPayload{
  369. Action: api.HookIssueLabelUpdated,
  370. Index: issue.Index,
  371. PullRequest: issue.PullRequest.APIFormat(),
  372. Repository: issue.Repo.APIFormat(models.AccessModeNone),
  373. Sender: doer.APIFormat(),
  374. })
  375. } else {
  376. err = webhook_module.PrepareWebhooks(issue.Repo, models.HookEventIssues, &api.IssuePayload{
  377. Action: api.HookIssueLabelUpdated,
  378. Index: issue.Index,
  379. Issue: issue.APIFormat(),
  380. Repository: issue.Repo.APIFormat(mode),
  381. Sender: doer.APIFormat(),
  382. })
  383. }
  384. if err != nil {
  385. log.Error("PrepareWebhooks [is_pull: %v]: %v", issue.IsPull, err)
  386. }
  387. }
  388. func (m *webhookNotifier) NotifyIssueChangeMilestone(doer *models.User, issue *models.Issue, oldMilestoneID int64) {
  389. var hookAction api.HookIssueAction
  390. var err error
  391. if issue.MilestoneID > 0 {
  392. hookAction = api.HookIssueMilestoned
  393. } else {
  394. hookAction = api.HookIssueDemilestoned
  395. }
  396. if err = issue.LoadAttributes(); err != nil {
  397. log.Error("issue.LoadAttributes failed: %v", err)
  398. return
  399. }
  400. mode, _ := models.AccessLevel(doer, issue.Repo)
  401. if issue.IsPull {
  402. err = issue.PullRequest.LoadIssue()
  403. if err != nil {
  404. log.Error("LoadIssue: %v", err)
  405. return
  406. }
  407. err = webhook_module.PrepareWebhooks(issue.Repo, models.HookEventPullRequest, &api.PullRequestPayload{
  408. Action: hookAction,
  409. Index: issue.Index,
  410. PullRequest: issue.PullRequest.APIFormat(),
  411. Repository: issue.Repo.APIFormat(mode),
  412. Sender: doer.APIFormat(),
  413. })
  414. } else {
  415. err = webhook_module.PrepareWebhooks(issue.Repo, models.HookEventIssues, &api.IssuePayload{
  416. Action: hookAction,
  417. Index: issue.Index,
  418. Issue: issue.APIFormat(),
  419. Repository: issue.Repo.APIFormat(mode),
  420. Sender: doer.APIFormat(),
  421. })
  422. }
  423. if err != nil {
  424. log.Error("PrepareWebhooks [is_pull: %v]: %v", issue.IsPull, err)
  425. }
  426. }
  427. func (m *webhookNotifier) NotifyPushCommits(pusher *models.User, repo *models.Repository, refName, oldCommitID, newCommitID string, commits *models.PushCommits) {
  428. apiPusher := pusher.APIFormat()
  429. apiCommits, err := commits.ToAPIPayloadCommits(repo.RepoPath(), repo.HTMLURL())
  430. if err != nil {
  431. log.Error("commits.ToAPIPayloadCommits failed: %v", err)
  432. return
  433. }
  434. if err := webhook_module.PrepareWebhooks(repo, models.HookEventPush, &api.PushPayload{
  435. Ref: refName,
  436. Before: oldCommitID,
  437. After: newCommitID,
  438. CompareURL: setting.AppURL + commits.CompareURL,
  439. Commits: apiCommits,
  440. Repo: repo.APIFormat(models.AccessModeOwner),
  441. Pusher: apiPusher,
  442. Sender: apiPusher,
  443. }); err != nil {
  444. log.Error("PrepareWebhooks: %v", err)
  445. }
  446. }