本站源代码
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.

993 lines
26KB

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