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 12KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400
  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. api "code.gitea.io/gitea/modules/structs"
  10. )
  11. type webhookNotifier struct {
  12. base.NullNotifier
  13. }
  14. var (
  15. _ base.Notifier = &webhookNotifier{}
  16. )
  17. // NewNotifier create a new webhookNotifier notifier
  18. func NewNotifier() base.Notifier {
  19. return &webhookNotifier{}
  20. }
  21. func (m *webhookNotifier) NotifyIssueClearLabels(doer *models.User, issue *models.Issue) {
  22. if err := issue.LoadPoster(); err != nil {
  23. log.Error("loadPoster: %v", err)
  24. return
  25. }
  26. if err := issue.LoadRepo(); err != nil {
  27. log.Error("LoadRepo: %v", err)
  28. return
  29. }
  30. mode, _ := models.AccessLevel(issue.Poster, issue.Repo)
  31. var err error
  32. if issue.IsPull {
  33. if err = issue.LoadPullRequest(); err != nil {
  34. log.Error("LoadPullRequest: %v", err)
  35. return
  36. }
  37. err = models.PrepareWebhooks(issue.Repo, models.HookEventPullRequest, &api.PullRequestPayload{
  38. Action: api.HookIssueLabelCleared,
  39. Index: issue.Index,
  40. PullRequest: issue.PullRequest.APIFormat(),
  41. Repository: issue.Repo.APIFormat(mode),
  42. Sender: doer.APIFormat(),
  43. })
  44. } else {
  45. err = models.PrepareWebhooks(issue.Repo, models.HookEventIssues, &api.IssuePayload{
  46. Action: api.HookIssueLabelCleared,
  47. Index: issue.Index,
  48. Issue: issue.APIFormat(),
  49. Repository: issue.Repo.APIFormat(mode),
  50. Sender: doer.APIFormat(),
  51. })
  52. }
  53. if err != nil {
  54. log.Error("PrepareWebhooks [is_pull: %v]: %v", issue.IsPull, err)
  55. } else {
  56. go models.HookQueue.Add(issue.RepoID)
  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 := models.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. } else {
  70. go models.HookQueue.Add(oldRepo.ID)
  71. }
  72. u := repo.MustOwner()
  73. // Add to hook queue for created repo after session commit.
  74. if u.IsOrganization() {
  75. if err := models.PrepareWebhooks(repo, models.HookEventRepository, &api.RepositoryPayload{
  76. Action: api.HookRepoCreated,
  77. Repository: repo.APIFormat(models.AccessModeOwner),
  78. Organization: u.APIFormat(),
  79. Sender: doer.APIFormat(),
  80. }); err != nil {
  81. log.Error("PrepareWebhooks [repo_id: %d]: %v", repo.ID, err)
  82. } else {
  83. go models.HookQueue.Add(repo.ID)
  84. }
  85. }
  86. }
  87. func (m *webhookNotifier) NotifyCreateRepository(doer *models.User, u *models.User, repo *models.Repository) {
  88. // Add to hook queue for created repo after session commit.
  89. if u.IsOrganization() {
  90. if err := models.PrepareWebhooks(repo, models.HookEventRepository, &api.RepositoryPayload{
  91. Action: api.HookRepoCreated,
  92. Repository: repo.APIFormat(models.AccessModeOwner),
  93. Organization: u.APIFormat(),
  94. Sender: doer.APIFormat(),
  95. }); err != nil {
  96. log.Error("PrepareWebhooks [repo_id: %d]: %v", repo.ID, err)
  97. } else {
  98. go models.HookQueue.Add(repo.ID)
  99. }
  100. }
  101. }
  102. func (m *webhookNotifier) NotifyDeleteRepository(doer *models.User, repo *models.Repository) {
  103. u := repo.MustOwner()
  104. if u.IsOrganization() {
  105. if err := models.PrepareWebhooks(repo, models.HookEventRepository, &api.RepositoryPayload{
  106. Action: api.HookRepoDeleted,
  107. Repository: repo.APIFormat(models.AccessModeOwner),
  108. Organization: u.APIFormat(),
  109. Sender: doer.APIFormat(),
  110. }); err != nil {
  111. log.Error("PrepareWebhooks [repo_id: %d]: %v", repo.ID, err)
  112. }
  113. go models.HookQueue.Add(repo.ID)
  114. }
  115. }
  116. func (m *webhookNotifier) NotifyIssueChangeAssignee(doer *models.User, issue *models.Issue, assignee *models.User, removed bool, comment *models.Comment) {
  117. if issue.IsPull {
  118. mode, _ := models.AccessLevelUnit(doer, issue.Repo, models.UnitTypePullRequests)
  119. if err := issue.LoadPullRequest(); err != nil {
  120. log.Error("LoadPullRequest failed: %v", err)
  121. return
  122. }
  123. issue.PullRequest.Issue = issue
  124. apiPullRequest := &api.PullRequestPayload{
  125. Index: issue.Index,
  126. PullRequest: issue.PullRequest.APIFormat(),
  127. Repository: issue.Repo.APIFormat(mode),
  128. Sender: doer.APIFormat(),
  129. }
  130. if removed {
  131. apiPullRequest.Action = api.HookIssueUnassigned
  132. } else {
  133. apiPullRequest.Action = api.HookIssueAssigned
  134. }
  135. // Assignee comment triggers a webhook
  136. if err := models.PrepareWebhooks(issue.Repo, models.HookEventPullRequest, apiPullRequest); err != nil {
  137. log.Error("PrepareWebhooks [is_pull: %v, remove_assignee: %v]: %v", issue.IsPull, removed, err)
  138. return
  139. }
  140. } else {
  141. mode, _ := models.AccessLevelUnit(doer, issue.Repo, models.UnitTypeIssues)
  142. apiIssue := &api.IssuePayload{
  143. Index: issue.Index,
  144. Issue: issue.APIFormat(),
  145. Repository: issue.Repo.APIFormat(mode),
  146. Sender: doer.APIFormat(),
  147. }
  148. if removed {
  149. apiIssue.Action = api.HookIssueUnassigned
  150. } else {
  151. apiIssue.Action = api.HookIssueAssigned
  152. }
  153. // Assignee comment triggers a webhook
  154. if err := models.PrepareWebhooks(issue.Repo, models.HookEventIssues, apiIssue); err != nil {
  155. log.Error("PrepareWebhooks [is_pull: %v, remove_assignee: %v]: %v", issue.IsPull, removed, err)
  156. return
  157. }
  158. }
  159. go models.HookQueue.Add(issue.RepoID)
  160. }
  161. func (m *webhookNotifier) NotifyIssueChangeTitle(doer *models.User, issue *models.Issue, oldTitle string) {
  162. mode, _ := models.AccessLevel(issue.Poster, issue.Repo)
  163. var err error
  164. if issue.IsPull {
  165. if err = issue.LoadPullRequest(); err != nil {
  166. log.Error("LoadPullRequest failed: %v", err)
  167. return
  168. }
  169. issue.PullRequest.Issue = issue
  170. err = models.PrepareWebhooks(issue.Repo, models.HookEventPullRequest, &api.PullRequestPayload{
  171. Action: api.HookIssueEdited,
  172. Index: issue.Index,
  173. Changes: &api.ChangesPayload{
  174. Title: &api.ChangesFromPayload{
  175. From: oldTitle,
  176. },
  177. },
  178. PullRequest: issue.PullRequest.APIFormat(),
  179. Repository: issue.Repo.APIFormat(mode),
  180. Sender: doer.APIFormat(),
  181. })
  182. } else {
  183. err = models.PrepareWebhooks(issue.Repo, models.HookEventIssues, &api.IssuePayload{
  184. Action: api.HookIssueEdited,
  185. Index: issue.Index,
  186. Changes: &api.ChangesPayload{
  187. Title: &api.ChangesFromPayload{
  188. From: oldTitle,
  189. },
  190. },
  191. Issue: issue.APIFormat(),
  192. Repository: issue.Repo.APIFormat(mode),
  193. Sender: issue.Poster.APIFormat(),
  194. })
  195. }
  196. if err != nil {
  197. log.Error("PrepareWebhooks [is_pull: %v]: %v", issue.IsPull, err)
  198. } else {
  199. go models.HookQueue.Add(issue.RepoID)
  200. }
  201. }
  202. func (m *webhookNotifier) NotifyIssueChangeStatus(doer *models.User, issue *models.Issue, isClosed bool) {
  203. mode, _ := models.AccessLevel(issue.Poster, issue.Repo)
  204. var err error
  205. if issue.IsPull {
  206. if err = issue.LoadPullRequest(); err != nil {
  207. log.Error("LoadPullRequest: %v", err)
  208. return
  209. }
  210. // Merge pull request calls issue.changeStatus so we need to handle separately.
  211. apiPullRequest := &api.PullRequestPayload{
  212. Index: issue.Index,
  213. PullRequest: issue.PullRequest.APIFormat(),
  214. Repository: issue.Repo.APIFormat(mode),
  215. Sender: doer.APIFormat(),
  216. }
  217. if isClosed {
  218. apiPullRequest.Action = api.HookIssueClosed
  219. } else {
  220. apiPullRequest.Action = api.HookIssueReOpened
  221. }
  222. err = models.PrepareWebhooks(issue.Repo, models.HookEventPullRequest, apiPullRequest)
  223. } else {
  224. apiIssue := &api.IssuePayload{
  225. Index: issue.Index,
  226. Issue: issue.APIFormat(),
  227. Repository: issue.Repo.APIFormat(mode),
  228. Sender: doer.APIFormat(),
  229. }
  230. if isClosed {
  231. apiIssue.Action = api.HookIssueClosed
  232. } else {
  233. apiIssue.Action = api.HookIssueReOpened
  234. }
  235. err = models.PrepareWebhooks(issue.Repo, models.HookEventIssues, apiIssue)
  236. }
  237. if err != nil {
  238. log.Error("PrepareWebhooks [is_pull: %v, is_closed: %v]: %v", issue.IsPull, isClosed, err)
  239. } else {
  240. go models.HookQueue.Add(issue.Repo.ID)
  241. }
  242. }
  243. func (m *webhookNotifier) NotifyNewIssue(issue *models.Issue) {
  244. mode, _ := models.AccessLevel(issue.Poster, issue.Repo)
  245. if err := models.PrepareWebhooks(issue.Repo, models.HookEventIssues, &api.IssuePayload{
  246. Action: api.HookIssueOpened,
  247. Index: issue.Index,
  248. Issue: issue.APIFormat(),
  249. Repository: issue.Repo.APIFormat(mode),
  250. Sender: issue.Poster.APIFormat(),
  251. }); err != nil {
  252. log.Error("PrepareWebhooks: %v", err)
  253. } else {
  254. go models.HookQueue.Add(issue.RepoID)
  255. }
  256. }
  257. func (m *webhookNotifier) NotifyIssueChangeContent(doer *models.User, issue *models.Issue, oldContent string) {
  258. mode, _ := models.AccessLevel(issue.Poster, issue.Repo)
  259. var err error
  260. if issue.IsPull {
  261. issue.PullRequest.Issue = issue
  262. err = models.PrepareWebhooks(issue.Repo, models.HookEventPullRequest, &api.PullRequestPayload{
  263. Action: api.HookIssueEdited,
  264. Index: issue.Index,
  265. Changes: &api.ChangesPayload{
  266. Body: &api.ChangesFromPayload{
  267. From: oldContent,
  268. },
  269. },
  270. PullRequest: issue.PullRequest.APIFormat(),
  271. Repository: issue.Repo.APIFormat(mode),
  272. Sender: doer.APIFormat(),
  273. })
  274. } else {
  275. err = models.PrepareWebhooks(issue.Repo, models.HookEventIssues, &api.IssuePayload{
  276. Action: api.HookIssueEdited,
  277. Index: issue.Index,
  278. Changes: &api.ChangesPayload{
  279. Body: &api.ChangesFromPayload{
  280. From: oldContent,
  281. },
  282. },
  283. Issue: issue.APIFormat(),
  284. Repository: issue.Repo.APIFormat(mode),
  285. Sender: doer.APIFormat(),
  286. })
  287. }
  288. if err != nil {
  289. log.Error("PrepareWebhooks [is_pull: %v]: %v", issue.IsPull, err)
  290. } else {
  291. go models.HookQueue.Add(issue.RepoID)
  292. }
  293. }
  294. func (m *webhookNotifier) NotifyUpdateComment(doer *models.User, c *models.Comment, oldContent string) {
  295. if err := c.LoadPoster(); err != nil {
  296. log.Error("LoadPoster: %v", err)
  297. return
  298. }
  299. if err := c.LoadIssue(); err != nil {
  300. log.Error("LoadIssue: %v", err)
  301. return
  302. }
  303. if err := c.Issue.LoadAttributes(); err != nil {
  304. log.Error("LoadAttributes: %v", err)
  305. return
  306. }
  307. mode, _ := models.AccessLevel(doer, c.Issue.Repo)
  308. if err := models.PrepareWebhooks(c.Issue.Repo, models.HookEventIssueComment, &api.IssueCommentPayload{
  309. Action: api.HookIssueCommentEdited,
  310. Issue: c.Issue.APIFormat(),
  311. Comment: c.APIFormat(),
  312. Changes: &api.ChangesPayload{
  313. Body: &api.ChangesFromPayload{
  314. From: oldContent,
  315. },
  316. },
  317. Repository: c.Issue.Repo.APIFormat(mode),
  318. Sender: doer.APIFormat(),
  319. IsPull: c.Issue.IsPull,
  320. }); err != nil {
  321. log.Error("PrepareWebhooks [comment_id: %d]: %v", c.ID, err)
  322. } else {
  323. go models.HookQueue.Add(c.Issue.Repo.ID)
  324. }
  325. }
  326. func (m *webhookNotifier) NotifyCreateIssueComment(doer *models.User, repo *models.Repository,
  327. issue *models.Issue, comment *models.Comment) {
  328. mode, _ := models.AccessLevel(doer, repo)
  329. if err := models.PrepareWebhooks(repo, models.HookEventIssueComment, &api.IssueCommentPayload{
  330. Action: api.HookIssueCommentCreated,
  331. Issue: issue.APIFormat(),
  332. Comment: comment.APIFormat(),
  333. Repository: repo.APIFormat(mode),
  334. Sender: doer.APIFormat(),
  335. IsPull: issue.IsPull,
  336. }); err != nil {
  337. log.Error("PrepareWebhooks [comment_id: %d]: %v", comment.ID, err)
  338. } else {
  339. go models.HookQueue.Add(repo.ID)
  340. }
  341. }
  342. func (m *webhookNotifier) NotifyDeleteComment(doer *models.User, comment *models.Comment) {
  343. if err := comment.LoadPoster(); err != nil {
  344. log.Error("LoadPoster: %v", err)
  345. return
  346. }
  347. if err := comment.LoadIssue(); err != nil {
  348. log.Error("LoadIssue: %v", err)
  349. return
  350. }
  351. if err := comment.Issue.LoadAttributes(); err != nil {
  352. log.Error("LoadAttributes: %v", err)
  353. return
  354. }
  355. mode, _ := models.AccessLevel(doer, comment.Issue.Repo)
  356. if err := models.PrepareWebhooks(comment.Issue.Repo, models.HookEventIssueComment, &api.IssueCommentPayload{
  357. Action: api.HookIssueCommentDeleted,
  358. Issue: comment.Issue.APIFormat(),
  359. Comment: comment.APIFormat(),
  360. Repository: comment.Issue.Repo.APIFormat(mode),
  361. Sender: doer.APIFormat(),
  362. IsPull: comment.Issue.IsPull,
  363. }); err != nil {
  364. log.Error("PrepareWebhooks [comment_id: %d]: %v", comment.ID, err)
  365. } else {
  366. go models.HookQueue.Add(comment.Issue.Repo.ID)
  367. }
  368. }