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.

620 lines
15 KiB

10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
9 years ago
10 years ago
9 years ago
9 years ago
9 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
9 years ago
10 years ago
10 years ago
10 years ago
  1. // Copyright 2014 The Gogs 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 models
  5. import (
  6. "crypto/tls"
  7. "encoding/json"
  8. "fmt"
  9. "io/ioutil"
  10. "strings"
  11. "time"
  12. "github.com/go-xorm/xorm"
  13. gouuid "github.com/satori/go.uuid"
  14. api "github.com/gogits/go-gogs-client"
  15. "github.com/go-gitea/gitea/modules/httplib"
  16. "github.com/go-gitea/gitea/modules/log"
  17. "github.com/go-gitea/gitea/modules/setting"
  18. "github.com/go-gitea/gitea/modules/sync"
  19. )
  20. var HookQueue = sync.NewUniqueQueue(setting.Webhook.QueueLength)
  21. type HookContentType int
  22. const (
  23. JSON HookContentType = iota + 1
  24. FORM
  25. )
  26. var hookContentTypes = map[string]HookContentType{
  27. "json": JSON,
  28. "form": FORM,
  29. }
  30. // ToHookContentType returns HookContentType by given name.
  31. func ToHookContentType(name string) HookContentType {
  32. return hookContentTypes[name]
  33. }
  34. func (t HookContentType) Name() string {
  35. switch t {
  36. case JSON:
  37. return "json"
  38. case FORM:
  39. return "form"
  40. }
  41. return ""
  42. }
  43. // IsValidHookContentType returns true if given name is a valid hook content type.
  44. func IsValidHookContentType(name string) bool {
  45. _, ok := hookContentTypes[name]
  46. return ok
  47. }
  48. type HookEvents struct {
  49. Create bool `json:"create"`
  50. Push bool `json:"push"`
  51. PullRequest bool `json:"pull_request"`
  52. }
  53. // HookEvent represents events that will delivery hook.
  54. type HookEvent struct {
  55. PushOnly bool `json:"push_only"`
  56. SendEverything bool `json:"send_everything"`
  57. ChooseEvents bool `json:"choose_events"`
  58. HookEvents `json:"events"`
  59. }
  60. type HookStatus int
  61. const (
  62. HOOK_STATUS_NONE = iota
  63. HOOK_STATUS_SUCCEED
  64. HOOK_STATUS_FAILED
  65. )
  66. // Webhook represents a web hook object.
  67. type Webhook struct {
  68. ID int64 `xorm:"pk autoincr"`
  69. RepoID int64
  70. OrgID int64
  71. URL string `xorm:"url TEXT"`
  72. ContentType HookContentType
  73. Secret string `xorm:"TEXT"`
  74. Events string `xorm:"TEXT"`
  75. *HookEvent `xorm:"-"`
  76. IsSSL bool `xorm:"is_ssl"`
  77. IsActive bool
  78. HookTaskType HookTaskType
  79. Meta string `xorm:"TEXT"` // store hook-specific attributes
  80. LastStatus HookStatus // Last delivery status
  81. Created time.Time `xorm:"-"`
  82. CreatedUnix int64
  83. Updated time.Time `xorm:"-"`
  84. UpdatedUnix int64
  85. }
  86. func (w *Webhook) BeforeInsert() {
  87. w.CreatedUnix = time.Now().Unix()
  88. w.UpdatedUnix = w.CreatedUnix
  89. }
  90. func (w *Webhook) BeforeUpdate() {
  91. w.UpdatedUnix = time.Now().Unix()
  92. }
  93. func (w *Webhook) AfterSet(colName string, _ xorm.Cell) {
  94. var err error
  95. switch colName {
  96. case "events":
  97. w.HookEvent = &HookEvent{}
  98. if err = json.Unmarshal([]byte(w.Events), w.HookEvent); err != nil {
  99. log.Error(3, "Unmarshal[%d]: %v", w.ID, err)
  100. }
  101. case "created_unix":
  102. w.Created = time.Unix(w.CreatedUnix, 0).Local()
  103. case "updated_unix":
  104. w.Updated = time.Unix(w.UpdatedUnix, 0).Local()
  105. }
  106. }
  107. func (w *Webhook) GetSlackHook() *SlackMeta {
  108. s := &SlackMeta{}
  109. if err := json.Unmarshal([]byte(w.Meta), s); err != nil {
  110. log.Error(4, "webhook.GetSlackHook(%d): %v", w.ID, err)
  111. }
  112. return s
  113. }
  114. // History returns history of webhook by given conditions.
  115. func (w *Webhook) History(page int) ([]*HookTask, error) {
  116. return HookTasks(w.ID, page)
  117. }
  118. // UpdateEvent handles conversion from HookEvent to Events.
  119. func (w *Webhook) UpdateEvent() error {
  120. data, err := json.Marshal(w.HookEvent)
  121. w.Events = string(data)
  122. return err
  123. }
  124. // HasCreateEvent returns true if hook enabled create event.
  125. func (w *Webhook) HasCreateEvent() bool {
  126. return w.SendEverything ||
  127. (w.ChooseEvents && w.HookEvents.Create)
  128. }
  129. // HasPushEvent returns true if hook enabled push event.
  130. func (w *Webhook) HasPushEvent() bool {
  131. return w.PushOnly || w.SendEverything ||
  132. (w.ChooseEvents && w.HookEvents.Push)
  133. }
  134. // HasPullRequestEvent returns true if hook enabled pull request event.
  135. func (w *Webhook) HasPullRequestEvent() bool {
  136. return w.SendEverything ||
  137. (w.ChooseEvents && w.HookEvents.PullRequest)
  138. }
  139. func (w *Webhook) EventsArray() []string {
  140. events := make([]string, 0, 3)
  141. if w.HasCreateEvent() {
  142. events = append(events, "create")
  143. }
  144. if w.HasPushEvent() {
  145. events = append(events, "push")
  146. }
  147. if w.HasPullRequestEvent() {
  148. events = append(events, "pull_request")
  149. }
  150. return events
  151. }
  152. // CreateWebhook creates a new web hook.
  153. func CreateWebhook(w *Webhook) error {
  154. _, err := x.Insert(w)
  155. return err
  156. }
  157. // getWebhook uses argument bean as query condition,
  158. // ID must be specified and do not assign unnecessary fields.
  159. func getWebhook(bean *Webhook) (*Webhook, error) {
  160. has, err := x.Get(bean)
  161. if err != nil {
  162. return nil, err
  163. } else if !has {
  164. return nil, ErrWebhookNotExist{bean.ID}
  165. }
  166. return bean, nil
  167. }
  168. // GetWebhookByRepoID returns webhook of repository by given ID.
  169. func GetWebhookByRepoID(repoID, id int64) (*Webhook, error) {
  170. return getWebhook(&Webhook{
  171. ID: id,
  172. RepoID: repoID,
  173. })
  174. }
  175. // GetWebhookByOrgID returns webhook of organization by given ID.
  176. func GetWebhookByOrgID(orgID, id int64) (*Webhook, error) {
  177. return getWebhook(&Webhook{
  178. ID: id,
  179. OrgID: orgID,
  180. })
  181. }
  182. // GetActiveWebhooksByRepoID returns all active webhooks of repository.
  183. func GetActiveWebhooksByRepoID(repoID int64) ([]*Webhook, error) {
  184. webhooks := make([]*Webhook, 0, 5)
  185. return webhooks, x.Find(&webhooks, &Webhook{
  186. RepoID: repoID,
  187. IsActive: true,
  188. })
  189. }
  190. // GetWebhooksByRepoID returns all webhooks of a repository.
  191. func GetWebhooksByRepoID(repoID int64) ([]*Webhook, error) {
  192. webhooks := make([]*Webhook, 0, 5)
  193. return webhooks, x.Find(&webhooks, &Webhook{RepoID: repoID})
  194. }
  195. // UpdateWebhook updates information of webhook.
  196. func UpdateWebhook(w *Webhook) error {
  197. _, err := x.Id(w.ID).AllCols().Update(w)
  198. return err
  199. }
  200. // deleteWebhook uses argument bean as query condition,
  201. // ID must be specified and do not assign unnecessary fields.
  202. func deleteWebhook(bean *Webhook) (err error) {
  203. sess := x.NewSession()
  204. defer sessionRelease(sess)
  205. if err = sess.Begin(); err != nil {
  206. return err
  207. }
  208. if _, err = sess.Delete(bean); err != nil {
  209. return err
  210. } else if _, err = sess.Delete(&HookTask{HookID: bean.ID}); err != nil {
  211. return err
  212. }
  213. return sess.Commit()
  214. }
  215. // DeleteWebhookByRepoID deletes webhook of repository by given ID.
  216. func DeleteWebhookByRepoID(repoID, id int64) error {
  217. return deleteWebhook(&Webhook{
  218. ID: id,
  219. RepoID: repoID,
  220. })
  221. }
  222. // DeleteWebhookByOrgID deletes webhook of organization by given ID.
  223. func DeleteWebhookByOrgID(orgID, id int64) error {
  224. return deleteWebhook(&Webhook{
  225. ID: id,
  226. OrgID: orgID,
  227. })
  228. }
  229. // GetWebhooksByOrgID returns all webhooks for an organization.
  230. func GetWebhooksByOrgID(orgID int64) (ws []*Webhook, err error) {
  231. err = x.Find(&ws, &Webhook{OrgID: orgID})
  232. return ws, err
  233. }
  234. // GetActiveWebhooksByOrgID returns all active webhooks for an organization.
  235. func GetActiveWebhooksByOrgID(orgID int64) (ws []*Webhook, err error) {
  236. err = x.Where("org_id=?", orgID).And("is_active=?", true).Find(&ws)
  237. return ws, err
  238. }
  239. // ___ ___ __ ___________ __
  240. // / | \ ____ ____ | | _\__ ___/____ _____| | __
  241. // / ~ \/ _ \ / _ \| |/ / | | \__ \ / ___/ |/ /
  242. // \ Y ( <_> | <_> ) < | | / __ \_\___ \| <
  243. // \___|_ / \____/ \____/|__|_ \ |____| (____ /____ >__|_ \
  244. // \/ \/ \/ \/ \/
  245. type HookTaskType int
  246. const (
  247. GOGS HookTaskType = iota + 1
  248. SLACK
  249. )
  250. var hookTaskTypes = map[string]HookTaskType{
  251. "gogs": GOGS,
  252. "slack": SLACK,
  253. }
  254. // ToHookTaskType returns HookTaskType by given name.
  255. func ToHookTaskType(name string) HookTaskType {
  256. return hookTaskTypes[name]
  257. }
  258. func (t HookTaskType) Name() string {
  259. switch t {
  260. case GOGS:
  261. return "gogs"
  262. case SLACK:
  263. return "slack"
  264. }
  265. return ""
  266. }
  267. // IsValidHookTaskType returns true if given name is a valid hook task type.
  268. func IsValidHookTaskType(name string) bool {
  269. _, ok := hookTaskTypes[name]
  270. return ok
  271. }
  272. type HookEventType string
  273. const (
  274. HOOK_EVENT_CREATE HookEventType = "create"
  275. HOOK_EVENT_PUSH HookEventType = "push"
  276. HOOK_EVENT_PULL_REQUEST HookEventType = "pull_request"
  277. )
  278. // HookRequest represents hook task request information.
  279. type HookRequest struct {
  280. Headers map[string]string `json:"headers"`
  281. }
  282. // HookResponse represents hook task response information.
  283. type HookResponse struct {
  284. Status int `json:"status"`
  285. Headers map[string]string `json:"headers"`
  286. Body string `json:"body"`
  287. }
  288. // HookTask represents a hook task.
  289. type HookTask struct {
  290. ID int64 `xorm:"pk autoincr"`
  291. RepoID int64 `xorm:"INDEX"`
  292. HookID int64
  293. UUID string
  294. Type HookTaskType
  295. URL string `xorm:"TEXT"`
  296. api.Payloader `xorm:"-"`
  297. PayloadContent string `xorm:"TEXT"`
  298. ContentType HookContentType
  299. EventType HookEventType
  300. IsSSL bool
  301. IsDelivered bool
  302. Delivered int64
  303. DeliveredString string `xorm:"-"`
  304. // History info.
  305. IsSucceed bool
  306. RequestContent string `xorm:"TEXT"`
  307. RequestInfo *HookRequest `xorm:"-"`
  308. ResponseContent string `xorm:"TEXT"`
  309. ResponseInfo *HookResponse `xorm:"-"`
  310. }
  311. func (t *HookTask) BeforeUpdate() {
  312. if t.RequestInfo != nil {
  313. t.RequestContent = t.SimpleMarshalJSON(t.RequestInfo)
  314. }
  315. if t.ResponseInfo != nil {
  316. t.ResponseContent = t.SimpleMarshalJSON(t.ResponseInfo)
  317. }
  318. }
  319. func (t *HookTask) AfterSet(colName string, _ xorm.Cell) {
  320. var err error
  321. switch colName {
  322. case "delivered":
  323. t.DeliveredString = time.Unix(0, t.Delivered).Format("2006-01-02 15:04:05 MST")
  324. case "request_content":
  325. if len(t.RequestContent) == 0 {
  326. return
  327. }
  328. t.RequestInfo = &HookRequest{}
  329. if err = json.Unmarshal([]byte(t.RequestContent), t.RequestInfo); err != nil {
  330. log.Error(3, "Unmarshal[%d]: %v", t.ID, err)
  331. }
  332. case "response_content":
  333. if len(t.ResponseContent) == 0 {
  334. return
  335. }
  336. t.ResponseInfo = &HookResponse{}
  337. if err = json.Unmarshal([]byte(t.ResponseContent), t.ResponseInfo); err != nil {
  338. log.Error(3, "Unmarshal [%d]: %v", t.ID, err)
  339. }
  340. }
  341. }
  342. func (t *HookTask) SimpleMarshalJSON(v interface{}) string {
  343. p, err := json.Marshal(v)
  344. if err != nil {
  345. log.Error(3, "Marshal [%d]: %v", t.ID, err)
  346. }
  347. return string(p)
  348. }
  349. // HookTasks returns a list of hook tasks by given conditions.
  350. func HookTasks(hookID int64, page int) ([]*HookTask, error) {
  351. tasks := make([]*HookTask, 0, setting.Webhook.PagingNum)
  352. return tasks, x.Limit(setting.Webhook.PagingNum, (page-1)*setting.Webhook.PagingNum).Where("hook_id=?", hookID).Desc("id").Find(&tasks)
  353. }
  354. // CreateHookTask creates a new hook task,
  355. // it handles conversion from Payload to PayloadContent.
  356. func CreateHookTask(t *HookTask) error {
  357. data, err := t.Payloader.JSONPayload()
  358. if err != nil {
  359. return err
  360. }
  361. t.UUID = gouuid.NewV4().String()
  362. t.PayloadContent = string(data)
  363. _, err = x.Insert(t)
  364. return err
  365. }
  366. // UpdateHookTask updates information of hook task.
  367. func UpdateHookTask(t *HookTask) error {
  368. _, err := x.Id(t.ID).AllCols().Update(t)
  369. return err
  370. }
  371. // PrepareWebhooks adds new webhooks to task queue for given payload.
  372. func PrepareWebhooks(repo *Repository, event HookEventType, p api.Payloader) error {
  373. ws, err := GetActiveWebhooksByRepoID(repo.ID)
  374. if err != nil {
  375. return fmt.Errorf("GetActiveWebhooksByRepoID: %v", err)
  376. }
  377. // check if repo belongs to org and append additional webhooks
  378. if repo.MustOwner().IsOrganization() {
  379. // get hooks for org
  380. orgws, err := GetActiveWebhooksByOrgID(repo.OwnerID)
  381. if err != nil {
  382. return fmt.Errorf("GetActiveWebhooksByOrgID: %v", err)
  383. }
  384. ws = append(ws, orgws...)
  385. }
  386. if len(ws) == 0 {
  387. return nil
  388. }
  389. var payloader api.Payloader
  390. for _, w := range ws {
  391. switch event {
  392. case HOOK_EVENT_CREATE:
  393. if !w.HasCreateEvent() {
  394. continue
  395. }
  396. case HOOK_EVENT_PUSH:
  397. if !w.HasPushEvent() {
  398. continue
  399. }
  400. case HOOK_EVENT_PULL_REQUEST:
  401. if !w.HasPullRequestEvent() {
  402. continue
  403. }
  404. }
  405. // Use separate objects so modifcations won't be made on payload on non-Gogs type hooks.
  406. switch w.HookTaskType {
  407. case SLACK:
  408. payloader, err = GetSlackPayload(p, event, w.Meta)
  409. if err != nil {
  410. return fmt.Errorf("GetSlackPayload: %v", err)
  411. }
  412. default:
  413. p.SetSecret(w.Secret)
  414. payloader = p
  415. }
  416. if err = CreateHookTask(&HookTask{
  417. RepoID: repo.ID,
  418. HookID: w.ID,
  419. Type: w.HookTaskType,
  420. URL: w.URL,
  421. Payloader: payloader,
  422. ContentType: w.ContentType,
  423. EventType: event,
  424. IsSSL: w.IsSSL,
  425. }); err != nil {
  426. return fmt.Errorf("CreateHookTask: %v", err)
  427. }
  428. }
  429. return nil
  430. }
  431. func (t *HookTask) deliver() {
  432. t.IsDelivered = true
  433. timeout := time.Duration(setting.Webhook.DeliverTimeout) * time.Second
  434. req := httplib.Post(t.URL).SetTimeout(timeout, timeout).
  435. Header("X-Gogs-Delivery", t.UUID).
  436. Header("X-Gogs-Event", string(t.EventType)).
  437. SetTLSClientConfig(&tls.Config{InsecureSkipVerify: setting.Webhook.SkipTLSVerify})
  438. switch t.ContentType {
  439. case JSON:
  440. req = req.Header("Content-Type", "application/json").Body(t.PayloadContent)
  441. case FORM:
  442. req.Param("payload", t.PayloadContent)
  443. }
  444. // Record delivery information.
  445. t.RequestInfo = &HookRequest{
  446. Headers: map[string]string{},
  447. }
  448. for k, vals := range req.Headers() {
  449. t.RequestInfo.Headers[k] = strings.Join(vals, ",")
  450. }
  451. t.ResponseInfo = &HookResponse{
  452. Headers: map[string]string{},
  453. }
  454. defer func() {
  455. t.Delivered = time.Now().UnixNano()
  456. if t.IsSucceed {
  457. log.Trace("Hook delivered: %s", t.UUID)
  458. } else {
  459. log.Trace("Hook delivery failed: %s", t.UUID)
  460. }
  461. // Update webhook last delivery status.
  462. w, err := GetWebhookByRepoID(t.RepoID, t.HookID)
  463. if err != nil {
  464. log.Error(5, "GetWebhookByID: %v", err)
  465. return
  466. }
  467. if t.IsSucceed {
  468. w.LastStatus = HOOK_STATUS_SUCCEED
  469. } else {
  470. w.LastStatus = HOOK_STATUS_FAILED
  471. }
  472. if err = UpdateWebhook(w); err != nil {
  473. log.Error(5, "UpdateWebhook: %v", err)
  474. return
  475. }
  476. }()
  477. resp, err := req.Response()
  478. if err != nil {
  479. t.ResponseInfo.Body = fmt.Sprintf("Delivery: %v", err)
  480. return
  481. }
  482. defer resp.Body.Close()
  483. // Status code is 20x can be seen as succeed.
  484. t.IsSucceed = resp.StatusCode/100 == 2
  485. t.ResponseInfo.Status = resp.StatusCode
  486. for k, vals := range resp.Header {
  487. t.ResponseInfo.Headers[k] = strings.Join(vals, ",")
  488. }
  489. p, err := ioutil.ReadAll(resp.Body)
  490. if err != nil {
  491. t.ResponseInfo.Body = fmt.Sprintf("read body: %s", err)
  492. return
  493. }
  494. t.ResponseInfo.Body = string(p)
  495. }
  496. // DeliverHooks checks and delivers undelivered hooks.
  497. // TODO: shoot more hooks at same time.
  498. func DeliverHooks() {
  499. tasks := make([]*HookTask, 0, 10)
  500. x.Where("is_delivered=?", false).Iterate(new(HookTask),
  501. func(idx int, bean interface{}) error {
  502. t := bean.(*HookTask)
  503. t.deliver()
  504. tasks = append(tasks, t)
  505. return nil
  506. })
  507. // Update hook task status.
  508. for _, t := range tasks {
  509. if err := UpdateHookTask(t); err != nil {
  510. log.Error(4, "UpdateHookTask [%d]: %v", t.ID, err)
  511. }
  512. }
  513. // Start listening on new hook requests.
  514. for repoID := range HookQueue.Queue() {
  515. log.Trace("DeliverHooks [repo_id: %v]", repoID)
  516. HookQueue.Remove(repoID)
  517. tasks = make([]*HookTask, 0, 5)
  518. if err := x.Where("repo_id=? AND is_delivered=?", repoID, false).Find(&tasks); err != nil {
  519. log.Error(4, "Get repository [%d] hook tasks: %v", repoID, err)
  520. continue
  521. }
  522. for _, t := range tasks {
  523. t.deliver()
  524. if err := UpdateHookTask(t); err != nil {
  525. log.Error(4, "UpdateHookTask [%d]: %v", t.ID, err)
  526. continue
  527. }
  528. }
  529. }
  530. }
  531. func InitDeliverHooks() {
  532. go DeliverHooks()
  533. }