webhook.go 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559
  1. // Copyright 2015 The Gogs Authors. All rights reserved.
  2. // Copyright 2018 Gitote. All rights reserved.
  3. //
  4. // This source code is licensed under the MIT license found in the
  5. // LICENSE file in the root directory of this source tree.
  6. package repo
  7. import (
  8. "fmt"
  9. "gitote/gitote/models"
  10. "gitote/gitote/models/errors"
  11. "gitote/gitote/pkg/context"
  12. "gitote/gitote/pkg/form"
  13. "gitote/gitote/pkg/setting"
  14. "strings"
  15. "github.com/Unknwon/com"
  16. "github.com/json-iterator/go"
  17. git "gitlab.com/gitote/git-module"
  18. api "gitlab.com/gitote/go-gitote-client"
  19. )
  20. const (
  21. WEBHOOKS = "repo/settings/webhook/base"
  22. WEBHOOK_NEW = "repo/settings/webhook/new"
  23. ORG_WEBHOOK_NEW = "org/settings/webhook_new"
  24. )
  25. func Webhooks(c *context.Context) {
  26. c.Data["Title"] = c.Tr("repo.settings.hooks")
  27. c.Data["PageIsSettingsHooks"] = true
  28. c.Data["BaseLink"] = c.Repo.RepoLink
  29. c.Data["Description"] = c.Tr("repo.settings.hooks_desc", "https://gitlab.com/gitote/gitote-client/wiki/Repositories-Webhooks")
  30. c.Data["Types"] = setting.Webhook.Types
  31. ws, err := models.GetWebhooksByRepoID(c.Repo.Repository.ID)
  32. if err != nil {
  33. c.Handle(500, "GetWebhooksByRepoID", err)
  34. return
  35. }
  36. c.Data["Webhooks"] = ws
  37. c.HTML(200, WEBHOOKS)
  38. }
  39. type OrgRepoCtx struct {
  40. OrgID int64
  41. RepoID int64
  42. Link string
  43. NewTemplate string
  44. }
  45. // getOrgRepoCtx determines whether this is a repo context or organization context.
  46. func getOrgRepoCtx(c *context.Context) (*OrgRepoCtx, error) {
  47. if len(c.Repo.RepoLink) > 0 {
  48. c.Data["PageIsRepositoryContext"] = true
  49. return &OrgRepoCtx{
  50. RepoID: c.Repo.Repository.ID,
  51. Link: c.Repo.RepoLink,
  52. NewTemplate: WEBHOOK_NEW,
  53. }, nil
  54. }
  55. if len(c.Org.OrgLink) > 0 {
  56. c.Data["PageIsOrganizationContext"] = true
  57. return &OrgRepoCtx{
  58. OrgID: c.Org.Organization.ID,
  59. Link: c.Org.OrgLink,
  60. NewTemplate: ORG_WEBHOOK_NEW,
  61. }, nil
  62. }
  63. return nil, errors.New("Unable to set OrgRepo context")
  64. }
  65. func checkHookType(c *context.Context) string {
  66. hookType := strings.ToLower(c.Params(":type"))
  67. if !com.IsSliceContainsStr(setting.Webhook.Types, hookType) {
  68. c.Handle(404, "checkHookType", nil)
  69. return ""
  70. }
  71. return hookType
  72. }
  73. func WebhooksNew(c *context.Context) {
  74. c.Data["Title"] = c.Tr("repo.settings.add_webhook")
  75. c.Data["PageIsSettingsHooks"] = true
  76. c.Data["PageIsSettingsHooksNew"] = true
  77. c.Data["Webhook"] = models.Webhook{HookEvent: &models.HookEvent{}}
  78. orCtx, err := getOrgRepoCtx(c)
  79. if err != nil {
  80. c.Handle(500, "getOrgRepoCtx", err)
  81. return
  82. }
  83. c.Data["HookType"] = checkHookType(c)
  84. if c.Written() {
  85. return
  86. }
  87. c.Data["BaseLink"] = orCtx.Link
  88. c.HTML(200, orCtx.NewTemplate)
  89. }
  90. func ParseHookEvent(f form.Webhook) *models.HookEvent {
  91. return &models.HookEvent{
  92. PushOnly: f.PushOnly(),
  93. SendEverything: f.SendEverything(),
  94. ChooseEvents: f.ChooseEvents(),
  95. HookEvents: models.HookEvents{
  96. Create: f.Create,
  97. Delete: f.Delete,
  98. Fork: f.Fork,
  99. Push: f.Push,
  100. Issues: f.Issues,
  101. IssueComment: f.IssueComment,
  102. PullRequest: f.PullRequest,
  103. Release: f.Release,
  104. },
  105. }
  106. }
  107. func WebHooksNewPost(c *context.Context, f form.NewWebhook) {
  108. c.Data["Title"] = c.Tr("repo.settings.add_webhook")
  109. c.Data["PageIsSettingsHooks"] = true
  110. c.Data["PageIsSettingsHooksNew"] = true
  111. c.Data["Webhook"] = models.Webhook{HookEvent: &models.HookEvent{}}
  112. c.Data["HookType"] = "gitote"
  113. orCtx, err := getOrgRepoCtx(c)
  114. if err != nil {
  115. c.Handle(500, "getOrgRepoCtx", err)
  116. return
  117. }
  118. c.Data["BaseLink"] = orCtx.Link
  119. if c.HasError() {
  120. c.HTML(200, orCtx.NewTemplate)
  121. return
  122. }
  123. contentType := models.JSON
  124. if models.HookContentType(f.ContentType) == models.FORM {
  125. contentType = models.FORM
  126. }
  127. w := &models.Webhook{
  128. RepoID: orCtx.RepoID,
  129. URL: f.PayloadURL,
  130. ContentType: contentType,
  131. Secret: f.Secret,
  132. HookEvent: ParseHookEvent(f.Webhook),
  133. IsActive: f.Active,
  134. HookTaskType: models.GITOTE,
  135. OrgID: orCtx.OrgID,
  136. }
  137. if err := w.UpdateEvent(); err != nil {
  138. c.Handle(500, "UpdateEvent", err)
  139. return
  140. } else if err := models.CreateWebhook(w); err != nil {
  141. c.Handle(500, "CreateWebhook", err)
  142. return
  143. }
  144. c.Flash.Success(c.Tr("repo.settings.add_hook_success"))
  145. c.Redirect(orCtx.Link + "/settings/hooks")
  146. }
  147. func SlackHooksNewPost(c *context.Context, f form.NewSlackHook) {
  148. c.Data["Title"] = c.Tr("repo.settings")
  149. c.Data["PageIsSettingsHooks"] = true
  150. c.Data["PageIsSettingsHooksNew"] = true
  151. c.Data["Webhook"] = models.Webhook{HookEvent: &models.HookEvent{}}
  152. orCtx, err := getOrgRepoCtx(c)
  153. if err != nil {
  154. c.Handle(500, "getOrgRepoCtx", err)
  155. return
  156. }
  157. if c.HasError() {
  158. c.HTML(200, orCtx.NewTemplate)
  159. return
  160. }
  161. meta, err := jsoniter.Marshal(&models.SlackMeta{
  162. Channel: f.Channel,
  163. Username: f.Username,
  164. IconURL: f.IconURL,
  165. Color: f.Color,
  166. })
  167. if err != nil {
  168. c.Handle(500, "Marshal", err)
  169. return
  170. }
  171. w := &models.Webhook{
  172. RepoID: orCtx.RepoID,
  173. URL: f.PayloadURL,
  174. ContentType: models.JSON,
  175. HookEvent: ParseHookEvent(f.Webhook),
  176. IsActive: f.Active,
  177. HookTaskType: models.SLACK,
  178. Meta: string(meta),
  179. OrgID: orCtx.OrgID,
  180. }
  181. if err := w.UpdateEvent(); err != nil {
  182. c.Handle(500, "UpdateEvent", err)
  183. return
  184. } else if err := models.CreateWebhook(w); err != nil {
  185. c.Handle(500, "CreateWebhook", err)
  186. return
  187. }
  188. c.Flash.Success(c.Tr("repo.settings.add_hook_success"))
  189. c.Redirect(orCtx.Link + "/settings/hooks")
  190. }
  191. // FIXME: merge logic to Slack
  192. func DiscordHooksNewPost(c *context.Context, f form.NewDiscordHook) {
  193. c.Data["Title"] = c.Tr("repo.settings")
  194. c.Data["PageIsSettingsHooks"] = true
  195. c.Data["PageIsSettingsHooksNew"] = true
  196. c.Data["Webhook"] = models.Webhook{HookEvent: &models.HookEvent{}}
  197. orCtx, err := getOrgRepoCtx(c)
  198. if err != nil {
  199. c.Handle(500, "getOrgRepoCtx", err)
  200. return
  201. }
  202. if c.HasError() {
  203. c.HTML(200, orCtx.NewTemplate)
  204. return
  205. }
  206. meta, err := jsoniter.Marshal(&models.SlackMeta{
  207. Username: f.Username,
  208. IconURL: f.IconURL,
  209. Color: f.Color,
  210. })
  211. if err != nil {
  212. c.Handle(500, "Marshal", err)
  213. return
  214. }
  215. w := &models.Webhook{
  216. RepoID: orCtx.RepoID,
  217. URL: f.PayloadURL,
  218. ContentType: models.JSON,
  219. HookEvent: ParseHookEvent(f.Webhook),
  220. IsActive: f.Active,
  221. HookTaskType: models.DISCORD,
  222. Meta: string(meta),
  223. OrgID: orCtx.OrgID,
  224. }
  225. if err := w.UpdateEvent(); err != nil {
  226. c.Handle(500, "UpdateEvent", err)
  227. return
  228. } else if err := models.CreateWebhook(w); err != nil {
  229. c.Handle(500, "CreateWebhook", err)
  230. return
  231. }
  232. c.Flash.Success(c.Tr("repo.settings.add_hook_success"))
  233. c.Redirect(orCtx.Link + "/settings/hooks")
  234. }
  235. func checkWebhook(c *context.Context) (*OrgRepoCtx, *models.Webhook) {
  236. c.Data["RequireHighlightJS"] = true
  237. orCtx, err := getOrgRepoCtx(c)
  238. if err != nil {
  239. c.Handle(500, "getOrgRepoCtx", err)
  240. return nil, nil
  241. }
  242. c.Data["BaseLink"] = orCtx.Link
  243. var w *models.Webhook
  244. if orCtx.RepoID > 0 {
  245. w, err = models.GetWebhookOfRepoByID(c.Repo.Repository.ID, c.ParamsInt64(":id"))
  246. } else {
  247. w, err = models.GetWebhookByOrgID(c.Org.Organization.ID, c.ParamsInt64(":id"))
  248. }
  249. if err != nil {
  250. c.NotFoundOrServerError("GetWebhookOfRepoByID/GetWebhookByOrgID", errors.IsWebhookNotExist, err)
  251. return nil, nil
  252. }
  253. switch w.HookTaskType {
  254. case models.SLACK:
  255. c.Data["SlackHook"] = w.GetSlackHook()
  256. c.Data["HookType"] = "slack"
  257. case models.DISCORD:
  258. c.Data["SlackHook"] = w.GetSlackHook()
  259. c.Data["HookType"] = "discord"
  260. default:
  261. c.Data["HookType"] = "gitote"
  262. }
  263. c.Data["History"], err = w.History(1)
  264. if err != nil {
  265. c.Handle(500, "History", err)
  266. }
  267. return orCtx, w
  268. }
  269. func WebHooksEdit(c *context.Context) {
  270. c.Data["Title"] = c.Tr("repo.settings.update_webhook")
  271. c.Data["PageIsSettingsHooks"] = true
  272. c.Data["PageIsSettingsHooksEdit"] = true
  273. orCtx, w := checkWebhook(c)
  274. if c.Written() {
  275. return
  276. }
  277. c.Data["Webhook"] = w
  278. c.HTML(200, orCtx.NewTemplate)
  279. }
  280. func WebHooksEditPost(c *context.Context, f form.NewWebhook) {
  281. c.Data["Title"] = c.Tr("repo.settings.update_webhook")
  282. c.Data["PageIsSettingsHooks"] = true
  283. c.Data["PageIsSettingsHooksEdit"] = true
  284. orCtx, w := checkWebhook(c)
  285. if c.Written() {
  286. return
  287. }
  288. c.Data["Webhook"] = w
  289. if c.HasError() {
  290. c.HTML(200, orCtx.NewTemplate)
  291. return
  292. }
  293. contentType := models.JSON
  294. if models.HookContentType(f.ContentType) == models.FORM {
  295. contentType = models.FORM
  296. }
  297. w.URL = f.PayloadURL
  298. w.ContentType = contentType
  299. w.Secret = f.Secret
  300. w.HookEvent = ParseHookEvent(f.Webhook)
  301. w.IsActive = f.Active
  302. if err := w.UpdateEvent(); err != nil {
  303. c.Handle(500, "UpdateEvent", err)
  304. return
  305. } else if err := models.UpdateWebhook(w); err != nil {
  306. c.Handle(500, "WebHooksEditPost", err)
  307. return
  308. }
  309. c.Flash.Success(c.Tr("repo.settings.update_hook_success"))
  310. c.Redirect(fmt.Sprintf("%s/settings/hooks/%d", orCtx.Link, w.ID))
  311. }
  312. func SlackHooksEditPost(c *context.Context, f form.NewSlackHook) {
  313. c.Data["Title"] = c.Tr("repo.settings")
  314. c.Data["PageIsSettingsHooks"] = true
  315. c.Data["PageIsSettingsHooksEdit"] = true
  316. orCtx, w := checkWebhook(c)
  317. if c.Written() {
  318. return
  319. }
  320. c.Data["Webhook"] = w
  321. if c.HasError() {
  322. c.HTML(200, orCtx.NewTemplate)
  323. return
  324. }
  325. meta, err := jsoniter.Marshal(&models.SlackMeta{
  326. Channel: f.Channel,
  327. Username: f.Username,
  328. IconURL: f.IconURL,
  329. Color: f.Color,
  330. })
  331. if err != nil {
  332. c.Handle(500, "Marshal", err)
  333. return
  334. }
  335. w.URL = f.PayloadURL
  336. w.Meta = string(meta)
  337. w.HookEvent = ParseHookEvent(f.Webhook)
  338. w.IsActive = f.Active
  339. if err := w.UpdateEvent(); err != nil {
  340. c.Handle(500, "UpdateEvent", err)
  341. return
  342. } else if err := models.UpdateWebhook(w); err != nil {
  343. c.Handle(500, "UpdateWebhook", err)
  344. return
  345. }
  346. c.Flash.Success(c.Tr("repo.settings.update_hook_success"))
  347. c.Redirect(fmt.Sprintf("%s/settings/hooks/%d", orCtx.Link, w.ID))
  348. }
  349. // FIXME: merge logic to Slack
  350. func DiscordHooksEditPost(c *context.Context, f form.NewDiscordHook) {
  351. c.Data["Title"] = c.Tr("repo.settings")
  352. c.Data["PageIsSettingsHooks"] = true
  353. c.Data["PageIsSettingsHooksEdit"] = true
  354. orCtx, w := checkWebhook(c)
  355. if c.Written() {
  356. return
  357. }
  358. c.Data["Webhook"] = w
  359. if c.HasError() {
  360. c.HTML(200, orCtx.NewTemplate)
  361. return
  362. }
  363. meta, err := jsoniter.Marshal(&models.SlackMeta{
  364. Username: f.Username,
  365. IconURL: f.IconURL,
  366. Color: f.Color,
  367. })
  368. if err != nil {
  369. c.Handle(500, "Marshal", err)
  370. return
  371. }
  372. w.URL = f.PayloadURL
  373. w.Meta = string(meta)
  374. w.HookEvent = ParseHookEvent(f.Webhook)
  375. w.IsActive = f.Active
  376. if err := w.UpdateEvent(); err != nil {
  377. c.Handle(500, "UpdateEvent", err)
  378. return
  379. } else if err := models.UpdateWebhook(w); err != nil {
  380. c.Handle(500, "UpdateWebhook", err)
  381. return
  382. }
  383. c.Flash.Success(c.Tr("repo.settings.update_hook_success"))
  384. c.Redirect(fmt.Sprintf("%s/settings/hooks/%d", orCtx.Link, w.ID))
  385. }
  386. func TestWebhook(c *context.Context) {
  387. var authorUsername, committerUsername string
  388. // Grab latest commit or fake one if it's empty repository.
  389. commit := c.Repo.Commit
  390. if commit == nil {
  391. ghost := models.NewGhostUser()
  392. commit = &git.Commit{
  393. ID: git.MustIDFromString(git.EMPTY_SHA),
  394. Author: ghost.NewGitSig(),
  395. Committer: ghost.NewGitSig(),
  396. CommitMessage: "This is a fake commit",
  397. }
  398. authorUsername = ghost.Name
  399. committerUsername = ghost.Name
  400. } else {
  401. // Try to match email with a real user.
  402. author, err := models.GetUserByEmail(commit.Author.Email)
  403. if err == nil {
  404. authorUsername = author.Name
  405. } else if !errors.IsUserNotExist(err) {
  406. c.Handle(500, "GetUserByEmail.(author)", err)
  407. return
  408. }
  409. committer, err := models.GetUserByEmail(commit.Committer.Email)
  410. if err == nil {
  411. committerUsername = committer.Name
  412. } else if !errors.IsUserNotExist(err) {
  413. c.Handle(500, "GetUserByEmail.(committer)", err)
  414. return
  415. }
  416. }
  417. fileStatus, err := commit.FileStatus()
  418. if err != nil {
  419. c.Handle(500, "FileStatus", err)
  420. return
  421. }
  422. apiUser := c.User.APIFormat()
  423. p := &api.PushPayload{
  424. Ref: git.BRANCH_PREFIX + c.Repo.Repository.DefaultBranch,
  425. Before: commit.ID.String(),
  426. After: commit.ID.String(),
  427. Commits: []*api.PayloadCommit{
  428. {
  429. ID: commit.ID.String(),
  430. Message: commit.Message(),
  431. URL: c.Repo.Repository.HTMLURL() + "/commit/" + commit.ID.String(),
  432. Author: &api.PayloadUser{
  433. Name: commit.Author.Name,
  434. Email: commit.Author.Email,
  435. UserName: authorUsername,
  436. },
  437. Committer: &api.PayloadUser{
  438. Name: commit.Committer.Name,
  439. Email: commit.Committer.Email,
  440. UserName: committerUsername,
  441. },
  442. Added: fileStatus.Added,
  443. Removed: fileStatus.Removed,
  444. Modified: fileStatus.Modified,
  445. },
  446. },
  447. Repo: c.Repo.Repository.APIFormat(nil),
  448. Pusher: apiUser,
  449. Sender: apiUser,
  450. }
  451. if err := models.TestWebhook(c.Repo.Repository, models.HOOK_EVENT_PUSH, p, c.ParamsInt64("id")); err != nil {
  452. c.Handle(500, "TestWebhook", err)
  453. } else {
  454. c.Flash.Info(c.Tr("repo.settings.webhook.test_delivery_success"))
  455. c.Status(200)
  456. }
  457. }
  458. func RedeliveryWebhook(c *context.Context) {
  459. webhook, err := models.GetWebhookOfRepoByID(c.Repo.Repository.ID, c.ParamsInt64(":id"))
  460. if err != nil {
  461. c.NotFoundOrServerError("GetWebhookOfRepoByID/GetWebhookByOrgID", errors.IsWebhookNotExist, err)
  462. return
  463. }
  464. hookTask, err := models.GetHookTaskOfWebhookByUUID(webhook.ID, c.Query("uuid"))
  465. if err != nil {
  466. c.NotFoundOrServerError("GetHookTaskOfWebhookByUUID/GetWebhookByOrgID", errors.IsHookTaskNotExist, err)
  467. return
  468. }
  469. hookTask.IsDelivered = false
  470. if err = models.UpdateHookTask(hookTask); err != nil {
  471. c.Handle(500, "UpdateHookTask", err)
  472. } else {
  473. go models.HookQueue.Add(c.Repo.Repository.ID)
  474. c.Flash.Info(c.Tr("repo.settings.webhook.redelivery_success", hookTask.UUID))
  475. c.Status(200)
  476. }
  477. }
  478. func DeleteWebhook(c *context.Context) {
  479. if err := models.DeleteWebhookOfRepoByID(c.Repo.Repository.ID, c.QueryInt64("id")); err != nil {
  480. c.Flash.Error("DeleteWebhookByRepoID: " + err.Error())
  481. } else {
  482. c.Flash.Success(c.Tr("repo.settings.webhook_deletion_success"))
  483. }
  484. c.JSON(200, map[string]interface{}{
  485. "redirect": c.Repo.RepoLink + "/settings/hooks",
  486. })
  487. }