gitea源码

deliver_test.go 7.9KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295
  1. // Copyright 2019 The Gitea Authors. All rights reserved.
  2. // SPDX-License-Identifier: MIT
  3. package webhook
  4. import (
  5. "io"
  6. "net/http"
  7. "net/http/httptest"
  8. "net/url"
  9. "strings"
  10. "testing"
  11. "time"
  12. "code.gitea.io/gitea/models/unittest"
  13. webhook_model "code.gitea.io/gitea/models/webhook"
  14. "code.gitea.io/gitea/modules/hostmatcher"
  15. "code.gitea.io/gitea/modules/setting"
  16. "code.gitea.io/gitea/modules/util"
  17. webhook_module "code.gitea.io/gitea/modules/webhook"
  18. "github.com/stretchr/testify/assert"
  19. "github.com/stretchr/testify/require"
  20. )
  21. func TestWebhookProxy(t *testing.T) {
  22. oldWebhook := setting.Webhook
  23. t.Cleanup(func() {
  24. setting.Webhook = oldWebhook
  25. })
  26. setting.Webhook.ProxyURL = "http://localhost:8080"
  27. setting.Webhook.ProxyURLFixed, _ = url.Parse(setting.Webhook.ProxyURL)
  28. setting.Webhook.ProxyHosts = []string{"*.discordapp.com", "discordapp.com"}
  29. allowedHostMatcher := hostmatcher.ParseHostMatchList("webhook.ALLOWED_HOST_LIST", "discordapp.com,s.discordapp.com")
  30. tests := []struct {
  31. req string
  32. want string
  33. wantErr bool
  34. }{
  35. {
  36. req: "https://discordapp.com/api/webhooks/xxxxxxxxx/xxxxxxxxxxxxxxxxxxx",
  37. want: "http://localhost:8080",
  38. wantErr: false,
  39. },
  40. {
  41. req: "http://s.discordapp.com/assets/xxxxxx",
  42. want: "http://localhost:8080",
  43. wantErr: false,
  44. },
  45. {
  46. req: "http://github.com/a/b",
  47. want: "",
  48. wantErr: false,
  49. },
  50. {
  51. req: "http://www.discordapp.com/assets/xxxxxx",
  52. want: "",
  53. wantErr: true,
  54. },
  55. }
  56. for _, tt := range tests {
  57. t.Run(tt.req, func(t *testing.T) {
  58. req, err := http.NewRequest(http.MethodPost, tt.req, nil)
  59. require.NoError(t, err)
  60. u, err := webhookProxy(allowedHostMatcher)(req)
  61. if tt.wantErr {
  62. assert.Error(t, err)
  63. return
  64. }
  65. assert.NoError(t, err)
  66. got := ""
  67. if u != nil {
  68. got = u.String()
  69. }
  70. assert.Equal(t, tt.want, got)
  71. })
  72. }
  73. }
  74. func TestWebhookDeliverAuthorizationHeader(t *testing.T) {
  75. assert.NoError(t, unittest.PrepareTestDatabase())
  76. done := make(chan struct{}, 1)
  77. s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  78. assert.Equal(t, "/webhook", r.URL.Path)
  79. assert.Equal(t, "Bearer s3cr3t-t0ken", r.Header.Get("Authorization"))
  80. w.WriteHeader(http.StatusOK)
  81. done <- struct{}{}
  82. }))
  83. t.Cleanup(s.Close)
  84. hook := &webhook_model.Webhook{
  85. RepoID: 3,
  86. URL: s.URL + "/webhook",
  87. ContentType: webhook_model.ContentTypeJSON,
  88. IsActive: true,
  89. Type: webhook_module.GITEA,
  90. }
  91. err := hook.SetHeaderAuthorization("Bearer s3cr3t-t0ken")
  92. assert.NoError(t, err)
  93. assert.NoError(t, webhook_model.CreateWebhook(t.Context(), hook))
  94. hookTask := &webhook_model.HookTask{
  95. HookID: hook.ID,
  96. EventType: webhook_module.HookEventPush,
  97. PayloadVersion: 2,
  98. }
  99. hookTask, err = webhook_model.CreateHookTask(t.Context(), hookTask)
  100. assert.NoError(t, err)
  101. assert.NotNil(t, hookTask)
  102. assert.NoError(t, Deliver(t.Context(), hookTask))
  103. select {
  104. case <-done:
  105. case <-time.After(5 * time.Second):
  106. t.Fatal("waited to long for request to happen")
  107. }
  108. assert.True(t, hookTask.IsSucceed)
  109. assert.Equal(t, "******", hookTask.RequestInfo.Headers["Authorization"])
  110. }
  111. func TestWebhookDeliverHookTask(t *testing.T) {
  112. assert.NoError(t, unittest.PrepareTestDatabase())
  113. done := make(chan struct{}, 1)
  114. s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  115. assert.Equal(t, "PUT", r.Method)
  116. switch r.URL.Path {
  117. case "/webhook/66d222a5d6349e1311f551e50722d837e30fce98":
  118. // Version 1
  119. assert.Equal(t, "push", r.Header.Get("X-GitHub-Event"))
  120. assert.Empty(t, r.Header.Get("Content-Type"))
  121. body, err := io.ReadAll(r.Body)
  122. assert.NoError(t, err)
  123. assert.Equal(t, `{"data": 42}`, string(body))
  124. case "/webhook/6db5dc1e282529a8c162c7fe93dd2667494eeb51":
  125. // Version 2
  126. assert.Equal(t, "push", r.Header.Get("X-GitHub-Event"))
  127. assert.Equal(t, "application/json", r.Header.Get("Content-Type"))
  128. body, err := io.ReadAll(r.Body)
  129. assert.NoError(t, err)
  130. assert.Len(t, body, 2147)
  131. default:
  132. w.WriteHeader(http.StatusNotFound)
  133. t.Fatalf("unexpected url path %s", r.URL.Path)
  134. return
  135. }
  136. w.WriteHeader(http.StatusOK)
  137. done <- struct{}{}
  138. }))
  139. t.Cleanup(s.Close)
  140. hook := &webhook_model.Webhook{
  141. RepoID: 3,
  142. IsActive: true,
  143. Type: webhook_module.MATRIX,
  144. URL: s.URL + "/webhook",
  145. HTTPMethod: "PUT",
  146. ContentType: webhook_model.ContentTypeJSON,
  147. Meta: `{"message_type":0}`, // text
  148. }
  149. assert.NoError(t, webhook_model.CreateWebhook(t.Context(), hook))
  150. t.Run("Version 1", func(t *testing.T) {
  151. hookTask := &webhook_model.HookTask{
  152. HookID: hook.ID,
  153. EventType: webhook_module.HookEventPush,
  154. PayloadContent: `{"data": 42}`,
  155. PayloadVersion: 1,
  156. }
  157. hookTask, err := webhook_model.CreateHookTask(t.Context(), hookTask)
  158. assert.NoError(t, err)
  159. assert.NotNil(t, hookTask)
  160. assert.NoError(t, Deliver(t.Context(), hookTask))
  161. select {
  162. case <-done:
  163. case <-time.After(5 * time.Second):
  164. t.Fatal("waited to long for request to happen")
  165. }
  166. assert.True(t, hookTask.IsSucceed)
  167. })
  168. t.Run("Version 2", func(t *testing.T) {
  169. p := pushTestPayload()
  170. data, err := p.JSONPayload()
  171. assert.NoError(t, err)
  172. hookTask := &webhook_model.HookTask{
  173. HookID: hook.ID,
  174. EventType: webhook_module.HookEventPush,
  175. PayloadContent: string(data),
  176. PayloadVersion: 2,
  177. }
  178. hookTask, err = webhook_model.CreateHookTask(t.Context(), hookTask)
  179. assert.NoError(t, err)
  180. assert.NotNil(t, hookTask)
  181. assert.NoError(t, Deliver(t.Context(), hookTask))
  182. select {
  183. case <-done:
  184. case <-time.After(5 * time.Second):
  185. t.Fatal("waited to long for request to happen")
  186. }
  187. assert.True(t, hookTask.IsSucceed)
  188. })
  189. }
  190. func TestWebhookDeliverSpecificTypes(t *testing.T) {
  191. assert.NoError(t, unittest.PrepareTestDatabase())
  192. type hookCase struct {
  193. gotBody chan []byte
  194. httpMethod string // default to POST
  195. }
  196. cases := map[string]*hookCase{
  197. webhook_module.SLACK: {},
  198. webhook_module.DISCORD: {},
  199. webhook_module.DINGTALK: {},
  200. webhook_module.TELEGRAM: {},
  201. webhook_module.MSTEAMS: {},
  202. webhook_module.FEISHU: {},
  203. webhook_module.MATRIX: {httpMethod: "PUT"},
  204. webhook_module.WECHATWORK: {},
  205. webhook_module.PACKAGIST: {},
  206. }
  207. s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  208. typ := strings.Split(r.URL.Path, "/")[1] // URL: "/{webhook_type}/other-path"
  209. assert.Equal(t, "application/json", r.Header.Get("Content-Type"), r.URL.Path)
  210. assert.Equal(t, util.IfZero(cases[typ].httpMethod, "POST"), r.Method, "webhook test request %q", r.URL.Path)
  211. body, _ := io.ReadAll(r.Body) // read request and send it back to the test by testcase's chan
  212. cases[typ].gotBody <- body
  213. w.WriteHeader(http.StatusNoContent)
  214. }))
  215. t.Cleanup(s.Close)
  216. p := pushTestPayload()
  217. data, err := p.JSONPayload()
  218. assert.NoError(t, err)
  219. for typ := range cases {
  220. cases[typ].gotBody = make(chan []byte, 1)
  221. t.Run(typ, func(t *testing.T) {
  222. t.Parallel()
  223. hook := &webhook_model.Webhook{
  224. RepoID: 3,
  225. IsActive: true,
  226. Type: typ,
  227. URL: s.URL + "/" + typ,
  228. Meta: "{}",
  229. }
  230. assert.NoError(t, webhook_model.CreateWebhook(t.Context(), hook))
  231. hookTask := &webhook_model.HookTask{
  232. HookID: hook.ID,
  233. EventType: webhook_module.HookEventPush,
  234. PayloadContent: string(data),
  235. PayloadVersion: 2,
  236. }
  237. hookTask, err := webhook_model.CreateHookTask(t.Context(), hookTask)
  238. assert.NoError(t, err)
  239. assert.NotNil(t, hookTask)
  240. assert.NoError(t, Deliver(t.Context(), hookTask))
  241. select {
  242. case gotBody := <-cases[typ].gotBody:
  243. assert.NotEqual(t, string(data), string(gotBody), "request body must be different from the event payload")
  244. assert.Equal(t, hookTask.RequestInfo.Body, string(gotBody), "delivered webhook payload doesn't match saved request")
  245. case <-time.After(5 * time.Second):
  246. t.Fatal("waited to long for request to happen")
  247. }
  248. assert.True(t, hookTask.IsSucceed)
  249. })
  250. }
  251. }