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

webhook.go 23 kB

11 years ago
11 years ago
8 years ago
8 years ago
8 years ago
11 years ago
11 years ago
11 years ago
9 years ago
9 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
8 years ago
9 years ago
11 years ago
11 years ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850
  1. // Copyright 2014 The Gogs Authors. All rights reserved.
  2. // Copyright 2017 The Gitea Authors. All rights reserved.
  3. // Use of this source code is governed by a MIT-style
  4. // license that can be found in the LICENSE file.
  5. package models
  6. import (
  7. "crypto/hmac"
  8. "crypto/sha256"
  9. "crypto/tls"
  10. "encoding/hex"
  11. "encoding/json"
  12. "fmt"
  13. "io/ioutil"
  14. "strings"
  15. "time"
  16. "code.gitea.io/gitea/modules/httplib"
  17. "code.gitea.io/gitea/modules/log"
  18. "code.gitea.io/gitea/modules/setting"
  19. "code.gitea.io/gitea/modules/sync"
  20. "code.gitea.io/gitea/modules/util"
  21. api "code.gitea.io/sdk/gitea"
  22. "github.com/Unknwon/com"
  23. gouuid "github.com/satori/go.uuid"
  24. )
  25. // HookQueue is a global queue of web hooks
  26. var HookQueue = sync.NewUniqueQueue(setting.Webhook.QueueLength)
  27. // HookContentType is the content type of a web hook
  28. type HookContentType int
  29. const (
  30. // ContentTypeJSON is a JSON payload for web hooks
  31. ContentTypeJSON HookContentType = iota + 1
  32. // ContentTypeForm is an url-encoded form payload for web hook
  33. ContentTypeForm
  34. )
  35. var hookContentTypes = map[string]HookContentType{
  36. "json": ContentTypeJSON,
  37. "form": ContentTypeForm,
  38. }
  39. // ToHookContentType returns HookContentType by given name.
  40. func ToHookContentType(name string) HookContentType {
  41. return hookContentTypes[name]
  42. }
  43. // Name returns the name of a given web hook's content type
  44. func (t HookContentType) Name() string {
  45. switch t {
  46. case ContentTypeJSON:
  47. return "json"
  48. case ContentTypeForm:
  49. return "form"
  50. }
  51. return ""
  52. }
  53. // IsValidHookContentType returns true if given name is a valid hook content type.
  54. func IsValidHookContentType(name string) bool {
  55. _, ok := hookContentTypes[name]
  56. return ok
  57. }
  58. // HookEvents is a set of web hook events
  59. type HookEvents struct {
  60. Create bool `json:"create"`
  61. Delete bool `json:"delete"`
  62. Fork bool `json:"fork"`
  63. Issues bool `json:"issues"`
  64. IssueComment bool `json:"issue_comment"`
  65. Push bool `json:"push"`
  66. PullRequest bool `json:"pull_request"`
  67. Repository bool `json:"repository"`
  68. Release bool `json:"release"`
  69. }
  70. // HookEvent represents events that will delivery hook.
  71. type HookEvent struct {
  72. PushOnly bool `json:"push_only"`
  73. SendEverything bool `json:"send_everything"`
  74. ChooseEvents bool `json:"choose_events"`
  75. HookEvents `json:"events"`
  76. }
  77. // HookStatus is the status of a web hook
  78. type HookStatus int
  79. // Possible statuses of a web hook
  80. const (
  81. HookStatusNone = iota
  82. HookStatusSucceed
  83. HookStatusFail
  84. )
  85. // Webhook represents a web hook object.
  86. type Webhook struct {
  87. ID int64 `xorm:"pk autoincr"`
  88. RepoID int64 `xorm:"INDEX"`
  89. OrgID int64 `xorm:"INDEX"`
  90. URL string `xorm:"url TEXT"`
  91. Signature string `xorm:"TEXT"`
  92. ContentType HookContentType
  93. Secret string `xorm:"TEXT"`
  94. Events string `xorm:"TEXT"`
  95. *HookEvent `xorm:"-"`
  96. IsSSL bool `xorm:"is_ssl"`
  97. IsActive bool `xorm:"INDEX"`
  98. HookTaskType HookTaskType
  99. Meta string `xorm:"TEXT"` // store hook-specific attributes
  100. LastStatus HookStatus // Last delivery status
  101. CreatedUnix util.TimeStamp `xorm:"INDEX created"`
  102. UpdatedUnix util.TimeStamp `xorm:"INDEX updated"`
  103. }
  104. // AfterLoad updates the webhook object upon setting a column
  105. func (w *Webhook) AfterLoad() {
  106. w.HookEvent = &HookEvent{}
  107. if err := json.Unmarshal([]byte(w.Events), w.HookEvent); err != nil {
  108. log.Error(3, "Unmarshal[%d]: %v", w.ID, err)
  109. }
  110. }
  111. // GetSlackHook returns slack metadata
  112. func (w *Webhook) GetSlackHook() *SlackMeta {
  113. s := &SlackMeta{}
  114. if err := json.Unmarshal([]byte(w.Meta), s); err != nil {
  115. log.Error(4, "webhook.GetSlackHook(%d): %v", w.ID, err)
  116. }
  117. return s
  118. }
  119. // GetDiscordHook returns discord metadata
  120. func (w *Webhook) GetDiscordHook() *DiscordMeta {
  121. s := &DiscordMeta{}
  122. if err := json.Unmarshal([]byte(w.Meta), s); err != nil {
  123. log.Error(4, "webhook.GetDiscordHook(%d): %v", w.ID, err)
  124. }
  125. return s
  126. }
  127. // History returns history of webhook by given conditions.
  128. func (w *Webhook) History(page int) ([]*HookTask, error) {
  129. return HookTasks(w.ID, page)
  130. }
  131. // UpdateEvent handles conversion from HookEvent to Events.
  132. func (w *Webhook) UpdateEvent() error {
  133. data, err := json.Marshal(w.HookEvent)
  134. w.Events = string(data)
  135. return err
  136. }
  137. // HasCreateEvent returns true if hook enabled create event.
  138. func (w *Webhook) HasCreateEvent() bool {
  139. return w.SendEverything ||
  140. (w.ChooseEvents && w.HookEvents.Create)
  141. }
  142. // HasDeleteEvent returns true if hook enabled delete event.
  143. func (w *Webhook) HasDeleteEvent() bool {
  144. return w.SendEverything ||
  145. (w.ChooseEvents && w.HookEvents.Delete)
  146. }
  147. // HasForkEvent returns true if hook enabled fork event.
  148. func (w *Webhook) HasForkEvent() bool {
  149. return w.SendEverything ||
  150. (w.ChooseEvents && w.HookEvents.Fork)
  151. }
  152. // HasIssuesEvent returns true if hook enabled issues event.
  153. func (w *Webhook) HasIssuesEvent() bool {
  154. return w.SendEverything ||
  155. (w.ChooseEvents && w.HookEvents.Issues)
  156. }
  157. // HasIssueCommentEvent returns true if hook enabled issue_comment event.
  158. func (w *Webhook) HasIssueCommentEvent() bool {
  159. return w.SendEverything ||
  160. (w.ChooseEvents && w.HookEvents.IssueComment)
  161. }
  162. // HasPushEvent returns true if hook enabled push event.
  163. func (w *Webhook) HasPushEvent() bool {
  164. return w.PushOnly || w.SendEverything ||
  165. (w.ChooseEvents && w.HookEvents.Push)
  166. }
  167. // HasPullRequestEvent returns true if hook enabled pull request event.
  168. func (w *Webhook) HasPullRequestEvent() bool {
  169. return w.SendEverything ||
  170. (w.ChooseEvents && w.HookEvents.PullRequest)
  171. }
  172. // HasReleaseEvent returns if hook enabled release event.
  173. func (w *Webhook) HasReleaseEvent() bool {
  174. return w.SendEverything ||
  175. (w.ChooseEvents && w.HookEvents.Release)
  176. }
  177. // HasRepositoryEvent returns if hook enabled repository event.
  178. func (w *Webhook) HasRepositoryEvent() bool {
  179. return w.SendEverything ||
  180. (w.ChooseEvents && w.HookEvents.Repository)
  181. }
  182. func (w *Webhook) eventCheckers() []struct {
  183. has func() bool
  184. typ HookEventType
  185. } {
  186. return []struct {
  187. has func() bool
  188. typ HookEventType
  189. }{
  190. {w.HasCreateEvent, HookEventCreate},
  191. {w.HasDeleteEvent, HookEventDelete},
  192. {w.HasForkEvent, HookEventFork},
  193. {w.HasPushEvent, HookEventPush},
  194. {w.HasIssuesEvent, HookEventIssues},
  195. {w.HasIssueCommentEvent, HookEventIssueComment},
  196. {w.HasPullRequestEvent, HookEventPullRequest},
  197. {w.HasRepositoryEvent, HookEventRepository},
  198. {w.HasReleaseEvent, HookEventRelease},
  199. }
  200. }
  201. // EventsArray returns an array of hook events
  202. func (w *Webhook) EventsArray() []string {
  203. events := make([]string, 0, 7)
  204. for _, c := range w.eventCheckers() {
  205. if c.has() {
  206. events = append(events, string(c.typ))
  207. }
  208. }
  209. return events
  210. }
  211. // CreateWebhook creates a new web hook.
  212. func CreateWebhook(w *Webhook) error {
  213. return createWebhook(x, w)
  214. }
  215. func createWebhook(e Engine, w *Webhook) error {
  216. _, err := e.Insert(w)
  217. return err
  218. }
  219. // getWebhook uses argument bean as query condition,
  220. // ID must be specified and do not assign unnecessary fields.
  221. func getWebhook(bean *Webhook) (*Webhook, error) {
  222. has, err := x.Get(bean)
  223. if err != nil {
  224. return nil, err
  225. } else if !has {
  226. return nil, ErrWebhookNotExist{bean.ID}
  227. }
  228. return bean, nil
  229. }
  230. // GetWebhookByID returns webhook of repository by given ID.
  231. func GetWebhookByID(id int64) (*Webhook, error) {
  232. return getWebhook(&Webhook{
  233. ID: id,
  234. })
  235. }
  236. // GetWebhookByRepoID returns webhook of repository by given ID.
  237. func GetWebhookByRepoID(repoID, id int64) (*Webhook, error) {
  238. return getWebhook(&Webhook{
  239. ID: id,
  240. RepoID: repoID,
  241. })
  242. }
  243. // GetWebhookByOrgID returns webhook of organization by given ID.
  244. func GetWebhookByOrgID(orgID, id int64) (*Webhook, error) {
  245. return getWebhook(&Webhook{
  246. ID: id,
  247. OrgID: orgID,
  248. })
  249. }
  250. // GetActiveWebhooksByRepoID returns all active webhooks of repository.
  251. func GetActiveWebhooksByRepoID(repoID int64) ([]*Webhook, error) {
  252. return getActiveWebhooksByRepoID(x, repoID)
  253. }
  254. func getActiveWebhooksByRepoID(e Engine, repoID int64) ([]*Webhook, error) {
  255. webhooks := make([]*Webhook, 0, 5)
  256. return webhooks, e.Where("is_active=?", true).
  257. Find(&webhooks, &Webhook{RepoID: repoID})
  258. }
  259. // GetWebhooksByRepoID returns all webhooks of a repository.
  260. func GetWebhooksByRepoID(repoID int64) ([]*Webhook, error) {
  261. webhooks := make([]*Webhook, 0, 5)
  262. return webhooks, x.Find(&webhooks, &Webhook{RepoID: repoID})
  263. }
  264. // GetActiveWebhooksByOrgID returns all active webhooks for an organization.
  265. func GetActiveWebhooksByOrgID(orgID int64) (ws []*Webhook, err error) {
  266. return getActiveWebhooksByOrgID(x, orgID)
  267. }
  268. func getActiveWebhooksByOrgID(e Engine, orgID int64) (ws []*Webhook, err error) {
  269. err = e.
  270. Where("org_id=?", orgID).
  271. And("is_active=?", true).
  272. Find(&ws)
  273. return ws, err
  274. }
  275. // GetWebhooksByOrgID returns all webhooks for an organization.
  276. func GetWebhooksByOrgID(orgID int64) (ws []*Webhook, err error) {
  277. err = x.Find(&ws, &Webhook{OrgID: orgID})
  278. return ws, err
  279. }
  280. // GetDefaultWebhook returns admin-default webhook by given ID.
  281. func GetDefaultWebhook(id int64) (*Webhook, error) {
  282. webhook := &Webhook{ID: id}
  283. has, err := x.
  284. Where("repo_id=? AND org_id=?", 0, 0).
  285. Get(webhook)
  286. if err != nil {
  287. return nil, err
  288. } else if !has {
  289. return nil, ErrWebhookNotExist{id}
  290. }
  291. return webhook, nil
  292. }
  293. // GetDefaultWebhooks returns all admin-default webhooks.
  294. func GetDefaultWebhooks() ([]*Webhook, error) {
  295. return getDefaultWebhooks(x)
  296. }
  297. func getDefaultWebhooks(e Engine) ([]*Webhook, error) {
  298. webhooks := make([]*Webhook, 0, 5)
  299. return webhooks, e.
  300. Where("repo_id=? AND org_id=?", 0, 0).
  301. Find(&webhooks)
  302. }
  303. // UpdateWebhook updates information of webhook.
  304. func UpdateWebhook(w *Webhook) error {
  305. _, err := x.ID(w.ID).AllCols().Update(w)
  306. return err
  307. }
  308. // UpdateWebhookLastStatus updates last status of webhook.
  309. func UpdateWebhookLastStatus(w *Webhook) error {
  310. _, err := x.ID(w.ID).Cols("last_status").Update(w)
  311. return err
  312. }
  313. // deleteWebhook uses argument bean as query condition,
  314. // ID must be specified and do not assign unnecessary fields.
  315. func deleteWebhook(bean *Webhook) (err error) {
  316. sess := x.NewSession()
  317. defer sess.Close()
  318. if err = sess.Begin(); err != nil {
  319. return err
  320. }
  321. if count, err := sess.Delete(bean); err != nil {
  322. return err
  323. } else if count == 0 {
  324. return ErrWebhookNotExist{ID: bean.ID}
  325. } else if _, err = sess.Delete(&HookTask{HookID: bean.ID}); err != nil {
  326. return err
  327. }
  328. return sess.Commit()
  329. }
  330. // DeleteWebhookByRepoID deletes webhook of repository by given ID.
  331. func DeleteWebhookByRepoID(repoID, id int64) error {
  332. return deleteWebhook(&Webhook{
  333. ID: id,
  334. RepoID: repoID,
  335. })
  336. }
  337. // DeleteWebhookByOrgID deletes webhook of organization by given ID.
  338. func DeleteWebhookByOrgID(orgID, id int64) error {
  339. return deleteWebhook(&Webhook{
  340. ID: id,
  341. OrgID: orgID,
  342. })
  343. }
  344. // DeleteDefaultWebhook deletes an admin-default webhook by given ID.
  345. func DeleteDefaultWebhook(id int64) error {
  346. sess := x.NewSession()
  347. defer sess.Close()
  348. if err := sess.Begin(); err != nil {
  349. return err
  350. }
  351. count, err := sess.
  352. Where("repo_id=? AND org_id=?", 0, 0).
  353. Delete(&Webhook{ID: id})
  354. if err != nil {
  355. return err
  356. } else if count == 0 {
  357. return ErrWebhookNotExist{ID: id}
  358. }
  359. if _, err := sess.Delete(&HookTask{HookID: id}); err != nil {
  360. return err
  361. }
  362. return sess.Commit()
  363. }
  364. // copyDefaultWebhooksToRepo creates copies of the default webhooks in a new repo
  365. func copyDefaultWebhooksToRepo(e Engine, repoID int64) error {
  366. ws, err := getDefaultWebhooks(e)
  367. if err != nil {
  368. return fmt.Errorf("GetDefaultWebhooks: %v", err)
  369. }
  370. for _, w := range ws {
  371. w.ID = 0
  372. w.RepoID = repoID
  373. if err := createWebhook(e, w); err != nil {
  374. return fmt.Errorf("CreateWebhook: %v", err)
  375. }
  376. }
  377. return nil
  378. }
  379. // ___ ___ __ ___________ __
  380. // / | \ ____ ____ | | _\__ ___/____ _____| | __
  381. // / ~ \/ _ \ / _ \| |/ / | | \__ \ / ___/ |/ /
  382. // \ Y ( <_> | <_> ) < | | / __ \_\___ \| <
  383. // \___|_ / \____/ \____/|__|_ \ |____| (____ /____ >__|_ \
  384. // \/ \/ \/ \/ \/
  385. // HookTaskType is the type of an hook task
  386. type HookTaskType int
  387. // Types of hook tasks
  388. const (
  389. GOGS HookTaskType = iota + 1
  390. SLACK
  391. GITEA
  392. DISCORD
  393. DINGTALK
  394. )
  395. var hookTaskTypes = map[string]HookTaskType{
  396. "gitea": GITEA,
  397. "gogs": GOGS,
  398. "slack": SLACK,
  399. "discord": DISCORD,
  400. "dingtalk": DINGTALK,
  401. }
  402. // ToHookTaskType returns HookTaskType by given name.
  403. func ToHookTaskType(name string) HookTaskType {
  404. return hookTaskTypes[name]
  405. }
  406. // Name returns the name of an hook task type
  407. func (t HookTaskType) Name() string {
  408. switch t {
  409. case GITEA:
  410. return "gitea"
  411. case GOGS:
  412. return "gogs"
  413. case SLACK:
  414. return "slack"
  415. case DISCORD:
  416. return "discord"
  417. case DINGTALK:
  418. return "dingtalk"
  419. }
  420. return ""
  421. }
  422. // IsValidHookTaskType returns true if given name is a valid hook task type.
  423. func IsValidHookTaskType(name string) bool {
  424. _, ok := hookTaskTypes[name]
  425. return ok
  426. }
  427. // HookEventType is the type of an hook event
  428. type HookEventType string
  429. // Types of hook events
  430. const (
  431. HookEventCreate HookEventType = "create"
  432. HookEventDelete HookEventType = "delete"
  433. HookEventFork HookEventType = "fork"
  434. HookEventPush HookEventType = "push"
  435. HookEventIssues HookEventType = "issues"
  436. HookEventIssueComment HookEventType = "issue_comment"
  437. HookEventPullRequest HookEventType = "pull_request"
  438. HookEventRepository HookEventType = "repository"
  439. HookEventRelease HookEventType = "release"
  440. HookEventPullRequestApproved HookEventType = "pull_request_approved"
  441. HookEventPullRequestRejected HookEventType = "pull_request_rejected"
  442. HookEventPullRequestComment HookEventType = "pull_request_comment"
  443. )
  444. // HookRequest represents hook task request information.
  445. type HookRequest struct {
  446. Headers map[string]string `json:"headers"`
  447. }
  448. // HookResponse represents hook task response information.
  449. type HookResponse struct {
  450. Status int `json:"status"`
  451. Headers map[string]string `json:"headers"`
  452. Body string `json:"body"`
  453. }
  454. // HookTask represents a hook task.
  455. type HookTask struct {
  456. ID int64 `xorm:"pk autoincr"`
  457. RepoID int64 `xorm:"INDEX"`
  458. HookID int64
  459. UUID string
  460. Type HookTaskType
  461. URL string `xorm:"TEXT"`
  462. Signature string `xorm:"TEXT"`
  463. api.Payloader `xorm:"-"`
  464. PayloadContent string `xorm:"TEXT"`
  465. ContentType HookContentType
  466. EventType HookEventType
  467. IsSSL bool
  468. IsDelivered bool
  469. Delivered int64
  470. DeliveredString string `xorm:"-"`
  471. // History info.
  472. IsSucceed bool
  473. RequestContent string `xorm:"TEXT"`
  474. RequestInfo *HookRequest `xorm:"-"`
  475. ResponseContent string `xorm:"TEXT"`
  476. ResponseInfo *HookResponse `xorm:"-"`
  477. }
  478. // BeforeUpdate will be invoked by XORM before updating a record
  479. // representing this object
  480. func (t *HookTask) BeforeUpdate() {
  481. if t.RequestInfo != nil {
  482. t.RequestContent = t.simpleMarshalJSON(t.RequestInfo)
  483. }
  484. if t.ResponseInfo != nil {
  485. t.ResponseContent = t.simpleMarshalJSON(t.ResponseInfo)
  486. }
  487. }
  488. // AfterLoad updates the webhook object upon setting a column
  489. func (t *HookTask) AfterLoad() {
  490. t.DeliveredString = time.Unix(0, t.Delivered).Format("2006-01-02 15:04:05 MST")
  491. if len(t.RequestContent) == 0 {
  492. return
  493. }
  494. t.RequestInfo = &HookRequest{}
  495. if err := json.Unmarshal([]byte(t.RequestContent), t.RequestInfo); err != nil {
  496. log.Error(3, "Unmarshal RequestContent[%d]: %v", t.ID, err)
  497. }
  498. if len(t.ResponseContent) > 0 {
  499. t.ResponseInfo = &HookResponse{}
  500. if err := json.Unmarshal([]byte(t.ResponseContent), t.ResponseInfo); err != nil {
  501. log.Error(3, "Unmarshal ResponseContent[%d]: %v", t.ID, err)
  502. }
  503. }
  504. }
  505. func (t *HookTask) simpleMarshalJSON(v interface{}) string {
  506. p, err := json.Marshal(v)
  507. if err != nil {
  508. log.Error(3, "Marshal [%d]: %v", t.ID, err)
  509. }
  510. return string(p)
  511. }
  512. // HookTasks returns a list of hook tasks by given conditions.
  513. func HookTasks(hookID int64, page int) ([]*HookTask, error) {
  514. tasks := make([]*HookTask, 0, setting.Webhook.PagingNum)
  515. return tasks, x.
  516. Limit(setting.Webhook.PagingNum, (page-1)*setting.Webhook.PagingNum).
  517. Where("hook_id=?", hookID).
  518. Desc("id").
  519. Find(&tasks)
  520. }
  521. // CreateHookTask creates a new hook task,
  522. // it handles conversion from Payload to PayloadContent.
  523. func CreateHookTask(t *HookTask) error {
  524. return createHookTask(x, t)
  525. }
  526. func createHookTask(e Engine, t *HookTask) error {
  527. data, err := t.Payloader.JSONPayload()
  528. if err != nil {
  529. return err
  530. }
  531. t.UUID = gouuid.NewV4().String()
  532. t.PayloadContent = string(data)
  533. _, err = e.Insert(t)
  534. return err
  535. }
  536. // UpdateHookTask updates information of hook task.
  537. func UpdateHookTask(t *HookTask) error {
  538. _, err := x.ID(t.ID).AllCols().Update(t)
  539. return err
  540. }
  541. // PrepareWebhook adds special webhook to task queue for given payload.
  542. func PrepareWebhook(w *Webhook, repo *Repository, event HookEventType, p api.Payloader) error {
  543. return prepareWebhook(x, w, repo, event, p)
  544. }
  545. func prepareWebhook(e Engine, w *Webhook, repo *Repository, event HookEventType, p api.Payloader) error {
  546. for _, e := range w.eventCheckers() {
  547. if event == e.typ {
  548. if !e.has() {
  549. return nil
  550. }
  551. }
  552. }
  553. var payloader api.Payloader
  554. var err error
  555. // Use separate objects so modifications won't be made on payload on non-Gogs/Gitea type hooks.
  556. switch w.HookTaskType {
  557. case SLACK:
  558. payloader, err = GetSlackPayload(p, event, w.Meta)
  559. if err != nil {
  560. return fmt.Errorf("GetSlackPayload: %v", err)
  561. }
  562. case DISCORD:
  563. payloader, err = GetDiscordPayload(p, event, w.Meta)
  564. if err != nil {
  565. return fmt.Errorf("GetDiscordPayload: %v", err)
  566. }
  567. case DINGTALK:
  568. payloader, err = GetDingtalkPayload(p, event, w.Meta)
  569. if err != nil {
  570. return fmt.Errorf("GetDingtalkPayload: %v", err)
  571. }
  572. default:
  573. p.SetSecret(w.Secret)
  574. payloader = p
  575. }
  576. var signature string
  577. if len(w.Secret) > 0 {
  578. data, err := payloader.JSONPayload()
  579. if err != nil {
  580. log.Error(2, "prepareWebhooks.JSONPayload: %v", err)
  581. }
  582. sig := hmac.New(sha256.New, []byte(w.Secret))
  583. sig.Write(data)
  584. signature = hex.EncodeToString(sig.Sum(nil))
  585. }
  586. if err = createHookTask(e, &HookTask{
  587. RepoID: repo.ID,
  588. HookID: w.ID,
  589. Type: w.HookTaskType,
  590. URL: w.URL,
  591. Signature: signature,
  592. Payloader: payloader,
  593. ContentType: w.ContentType,
  594. EventType: event,
  595. IsSSL: w.IsSSL,
  596. }); err != nil {
  597. return fmt.Errorf("CreateHookTask: %v", err)
  598. }
  599. return nil
  600. }
  601. // PrepareWebhooks adds new webhooks to task queue for given payload.
  602. func PrepareWebhooks(repo *Repository, event HookEventType, p api.Payloader) error {
  603. return prepareWebhooks(x, repo, event, p)
  604. }
  605. func prepareWebhooks(e Engine, repo *Repository, event HookEventType, p api.Payloader) error {
  606. ws, err := getActiveWebhooksByRepoID(e, repo.ID)
  607. if err != nil {
  608. return fmt.Errorf("GetActiveWebhooksByRepoID: %v", err)
  609. }
  610. // check if repo belongs to org and append additional webhooks
  611. if repo.mustOwner(e).IsOrganization() {
  612. // get hooks for org
  613. orgHooks, err := getActiveWebhooksByOrgID(e, repo.OwnerID)
  614. if err != nil {
  615. return fmt.Errorf("GetActiveWebhooksByOrgID: %v", err)
  616. }
  617. ws = append(ws, orgHooks...)
  618. }
  619. if len(ws) == 0 {
  620. return nil
  621. }
  622. for _, w := range ws {
  623. if err = prepareWebhook(e, w, repo, event, p); err != nil {
  624. return err
  625. }
  626. }
  627. return nil
  628. }
  629. func (t *HookTask) deliver() {
  630. t.IsDelivered = true
  631. timeout := time.Duration(setting.Webhook.DeliverTimeout) * time.Second
  632. req := httplib.Post(t.URL).SetTimeout(timeout, timeout).
  633. Header("X-Gitea-Delivery", t.UUID).
  634. Header("X-Gitea-Event", string(t.EventType)).
  635. Header("X-Gitea-Signature", t.Signature).
  636. Header("X-Gogs-Delivery", t.UUID).
  637. Header("X-Gogs-Event", string(t.EventType)).
  638. Header("X-Gogs-Signature", t.Signature).
  639. HeaderWithSensitiveCase("X-GitHub-Delivery", t.UUID).
  640. HeaderWithSensitiveCase("X-GitHub-Event", string(t.EventType)).
  641. SetTLSClientConfig(&tls.Config{InsecureSkipVerify: setting.Webhook.SkipTLSVerify})
  642. switch t.ContentType {
  643. case ContentTypeJSON:
  644. req = req.Header("Content-Type", "application/json").Body(t.PayloadContent)
  645. case ContentTypeForm:
  646. req.Param("payload", t.PayloadContent)
  647. }
  648. // Record delivery information.
  649. t.RequestInfo = &HookRequest{
  650. Headers: map[string]string{},
  651. }
  652. for k, vals := range req.Headers() {
  653. t.RequestInfo.Headers[k] = strings.Join(vals, ",")
  654. }
  655. t.ResponseInfo = &HookResponse{
  656. Headers: map[string]string{},
  657. }
  658. defer func() {
  659. t.Delivered = time.Now().UnixNano()
  660. if t.IsSucceed {
  661. log.Trace("Hook delivered: %s", t.UUID)
  662. } else {
  663. log.Trace("Hook delivery failed: %s", t.UUID)
  664. }
  665. if err := UpdateHookTask(t); err != nil {
  666. log.Error(4, "UpdateHookTask [%d]: %v", t.ID, err)
  667. }
  668. // Update webhook last delivery status.
  669. w, err := GetWebhookByID(t.HookID)
  670. if err != nil {
  671. log.Error(5, "GetWebhookByID: %v", err)
  672. return
  673. }
  674. if t.IsSucceed {
  675. w.LastStatus = HookStatusSucceed
  676. } else {
  677. w.LastStatus = HookStatusFail
  678. }
  679. if err = UpdateWebhookLastStatus(w); err != nil {
  680. log.Error(5, "UpdateWebhookLastStatus: %v", err)
  681. return
  682. }
  683. }()
  684. resp, err := req.Response()
  685. if err != nil {
  686. t.ResponseInfo.Body = fmt.Sprintf("Delivery: %v", err)
  687. return
  688. }
  689. defer resp.Body.Close()
  690. // Status code is 20x can be seen as succeed.
  691. t.IsSucceed = resp.StatusCode/100 == 2
  692. t.ResponseInfo.Status = resp.StatusCode
  693. for k, vals := range resp.Header {
  694. t.ResponseInfo.Headers[k] = strings.Join(vals, ",")
  695. }
  696. p, err := ioutil.ReadAll(resp.Body)
  697. if err != nil {
  698. t.ResponseInfo.Body = fmt.Sprintf("read body: %s", err)
  699. return
  700. }
  701. t.ResponseInfo.Body = string(p)
  702. }
  703. // DeliverHooks checks and delivers undelivered hooks.
  704. // TODO: shoot more hooks at same time.
  705. func DeliverHooks() {
  706. tasks := make([]*HookTask, 0, 10)
  707. err := x.Where("is_delivered=?", false).Find(&tasks)
  708. if err != nil {
  709. log.Error(4, "DeliverHooks: %v", err)
  710. return
  711. }
  712. // Update hook task status.
  713. for _, t := range tasks {
  714. t.deliver()
  715. }
  716. // Start listening on new hook requests.
  717. for repoIDStr := range HookQueue.Queue() {
  718. log.Trace("DeliverHooks [repo_id: %v]", repoIDStr)
  719. HookQueue.Remove(repoIDStr)
  720. repoID, err := com.StrTo(repoIDStr).Int64()
  721. if err != nil {
  722. log.Error(4, "Invalid repo ID: %s", repoIDStr)
  723. continue
  724. }
  725. tasks = make([]*HookTask, 0, 5)
  726. if err := x.Where("repo_id=? AND is_delivered=?", repoID, false).Find(&tasks); err != nil {
  727. log.Error(4, "Get repository [%s] hook tasks: %v", repoID, err)
  728. continue
  729. }
  730. for _, t := range tasks {
  731. t.deliver()
  732. }
  733. }
  734. }
  735. // InitDeliverHooks starts the hooks delivery thread
  736. func InitDeliverHooks() {
  737. go DeliverHooks()
  738. }