123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297 |
- // Copyright 2019 The Gitea Authors. All rights reserved.
- // SPDX-License-Identifier: MIT
-
- package webhook
-
- import (
- "context"
- "io"
- "net/http"
- "net/http/httptest"
- "net/url"
- "strings"
- "testing"
- "time"
-
- "code.gitea.io/gitea/models/db"
- "code.gitea.io/gitea/models/unittest"
- webhook_model "code.gitea.io/gitea/models/webhook"
- "code.gitea.io/gitea/modules/hostmatcher"
- "code.gitea.io/gitea/modules/setting"
- "code.gitea.io/gitea/modules/util"
- webhook_module "code.gitea.io/gitea/modules/webhook"
-
- "github.com/stretchr/testify/assert"
- "github.com/stretchr/testify/require"
- )
-
- func TestWebhookProxy(t *testing.T) {
- oldWebhook := setting.Webhook
- t.Cleanup(func() {
- setting.Webhook = oldWebhook
- })
-
- setting.Webhook.ProxyURL = "http://localhost:8080"
- setting.Webhook.ProxyURLFixed, _ = url.Parse(setting.Webhook.ProxyURL)
- setting.Webhook.ProxyHosts = []string{"*.discordapp.com", "discordapp.com"}
-
- allowedHostMatcher := hostmatcher.ParseHostMatchList("webhook.ALLOWED_HOST_LIST", "discordapp.com,s.discordapp.com")
-
- tests := []struct {
- req string
- want string
- wantErr bool
- }{
- {
- req: "https://discordapp.com/api/webhooks/xxxxxxxxx/xxxxxxxxxxxxxxxxxxx",
- want: "http://localhost:8080",
- wantErr: false,
- },
- {
- req: "http://s.discordapp.com/assets/xxxxxx",
- want: "http://localhost:8080",
- wantErr: false,
- },
- {
- req: "http://github.com/a/b",
- want: "",
- wantErr: false,
- },
- {
- req: "http://www.discordapp.com/assets/xxxxxx",
- want: "",
- wantErr: true,
- },
- }
- for _, tt := range tests {
- t.Run(tt.req, func(t *testing.T) {
- req, err := http.NewRequest("POST", tt.req, nil)
- require.NoError(t, err)
-
- u, err := webhookProxy(allowedHostMatcher)(req)
- if tt.wantErr {
- assert.Error(t, err)
- return
- }
-
- assert.NoError(t, err)
-
- got := ""
- if u != nil {
- got = u.String()
- }
- assert.Equal(t, tt.want, got)
- })
- }
- }
-
- func TestWebhookDeliverAuthorizationHeader(t *testing.T) {
- assert.NoError(t, unittest.PrepareTestDatabase())
-
- done := make(chan struct{}, 1)
- s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- assert.Equal(t, "/webhook", r.URL.Path)
- assert.Equal(t, "Bearer s3cr3t-t0ken", r.Header.Get("Authorization"))
- w.WriteHeader(200)
- done <- struct{}{}
- }))
- t.Cleanup(s.Close)
-
- hook := &webhook_model.Webhook{
- RepoID: 3,
- URL: s.URL + "/webhook",
- ContentType: webhook_model.ContentTypeJSON,
- IsActive: true,
- Type: webhook_module.GITEA,
- }
- err := hook.SetHeaderAuthorization("Bearer s3cr3t-t0ken")
- assert.NoError(t, err)
- assert.NoError(t, webhook_model.CreateWebhook(db.DefaultContext, hook))
- db.GetEngine(db.DefaultContext).NoAutoTime().DB().Logger.ShowSQL(true)
-
- hookTask := &webhook_model.HookTask{
- HookID: hook.ID,
- EventType: webhook_module.HookEventPush,
- PayloadVersion: 2,
- }
-
- hookTask, err = webhook_model.CreateHookTask(db.DefaultContext, hookTask)
- assert.NoError(t, err)
- assert.NotNil(t, hookTask)
-
- assert.NoError(t, Deliver(context.Background(), hookTask))
- select {
- case <-done:
- case <-time.After(5 * time.Second):
- t.Fatal("waited to long for request to happen")
- }
-
- assert.True(t, hookTask.IsSucceed)
- assert.Equal(t, "******", hookTask.RequestInfo.Headers["Authorization"])
- }
-
- func TestWebhookDeliverHookTask(t *testing.T) {
- assert.NoError(t, unittest.PrepareTestDatabase())
-
- done := make(chan struct{}, 1)
- s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- assert.Equal(t, "PUT", r.Method)
- switch r.URL.Path {
- case "/webhook/66d222a5d6349e1311f551e50722d837e30fce98":
- // Version 1
- assert.Equal(t, "push", r.Header.Get("X-GitHub-Event"))
- assert.Equal(t, "", r.Header.Get("Content-Type"))
- body, err := io.ReadAll(r.Body)
- assert.NoError(t, err)
- assert.Equal(t, `{"data": 42}`, string(body))
-
- case "/webhook/6db5dc1e282529a8c162c7fe93dd2667494eeb51":
- // Version 2
- assert.Equal(t, "push", r.Header.Get("X-GitHub-Event"))
- assert.Equal(t, "application/json", r.Header.Get("Content-Type"))
- body, err := io.ReadAll(r.Body)
- assert.NoError(t, err)
- assert.Len(t, body, 2147)
-
- default:
- w.WriteHeader(404)
- t.Fatalf("unexpected url path %s", r.URL.Path)
- return
- }
- w.WriteHeader(200)
- done <- struct{}{}
- }))
- t.Cleanup(s.Close)
-
- hook := &webhook_model.Webhook{
- RepoID: 3,
- IsActive: true,
- Type: webhook_module.MATRIX,
- URL: s.URL + "/webhook",
- HTTPMethod: "PUT",
- ContentType: webhook_model.ContentTypeJSON,
- Meta: `{"message_type":0}`, // text
- }
- assert.NoError(t, webhook_model.CreateWebhook(db.DefaultContext, hook))
-
- t.Run("Version 1", func(t *testing.T) {
- hookTask := &webhook_model.HookTask{
- HookID: hook.ID,
- EventType: webhook_module.HookEventPush,
- PayloadContent: `{"data": 42}`,
- PayloadVersion: 1,
- }
-
- hookTask, err := webhook_model.CreateHookTask(db.DefaultContext, hookTask)
- assert.NoError(t, err)
- assert.NotNil(t, hookTask)
-
- assert.NoError(t, Deliver(context.Background(), hookTask))
- select {
- case <-done:
- case <-time.After(5 * time.Second):
- t.Fatal("waited to long for request to happen")
- }
-
- assert.True(t, hookTask.IsSucceed)
- })
-
- t.Run("Version 2", func(t *testing.T) {
- p := pushTestPayload()
- data, err := p.JSONPayload()
- assert.NoError(t, err)
-
- hookTask := &webhook_model.HookTask{
- HookID: hook.ID,
- EventType: webhook_module.HookEventPush,
- PayloadContent: string(data),
- PayloadVersion: 2,
- }
-
- hookTask, err = webhook_model.CreateHookTask(db.DefaultContext, hookTask)
- assert.NoError(t, err)
- assert.NotNil(t, hookTask)
-
- assert.NoError(t, Deliver(context.Background(), hookTask))
- select {
- case <-done:
- case <-time.After(5 * time.Second):
- t.Fatal("waited to long for request to happen")
- }
-
- assert.True(t, hookTask.IsSucceed)
- })
- }
-
- func TestWebhookDeliverSpecificTypes(t *testing.T) {
- assert.NoError(t, unittest.PrepareTestDatabase())
-
- type hookCase struct {
- gotBody chan []byte
- httpMethod string // default to POST
- }
-
- cases := map[string]*hookCase{
- webhook_module.SLACK: {},
- webhook_module.DISCORD: {},
- webhook_module.DINGTALK: {},
- webhook_module.TELEGRAM: {},
- webhook_module.MSTEAMS: {},
- webhook_module.FEISHU: {},
- webhook_module.MATRIX: {httpMethod: "PUT"},
- webhook_module.WECHATWORK: {},
- webhook_module.PACKAGIST: {},
- }
-
- s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- typ := strings.Split(r.URL.Path, "/")[1] // URL: "/{webhook_type}/other-path"
- assert.Equal(t, "application/json", r.Header.Get("Content-Type"), r.URL.Path)
- assert.Equal(t, util.IfZero(cases[typ].httpMethod, "POST"), r.Method, "webhook test request %q", r.URL.Path)
- body, _ := io.ReadAll(r.Body) // read request and send it back to the test by testcase's chan
- cases[typ].gotBody <- body
- w.WriteHeader(http.StatusNoContent)
- }))
- t.Cleanup(s.Close)
-
- p := pushTestPayload()
- data, err := p.JSONPayload()
- assert.NoError(t, err)
-
- for typ := range cases {
- cases[typ].gotBody = make(chan []byte, 1)
- t.Run(typ, func(t *testing.T) {
- t.Parallel()
- hook := &webhook_model.Webhook{
- RepoID: 3,
- IsActive: true,
- Type: typ,
- URL: s.URL + "/" + typ,
- Meta: "{}",
- }
- assert.NoError(t, webhook_model.CreateWebhook(db.DefaultContext, hook))
-
- hookTask := &webhook_model.HookTask{
- HookID: hook.ID,
- EventType: webhook_module.HookEventPush,
- PayloadContent: string(data),
- PayloadVersion: 2,
- }
-
- hookTask, err := webhook_model.CreateHookTask(db.DefaultContext, hookTask)
- assert.NoError(t, err)
- assert.NotNil(t, hookTask)
-
- assert.NoError(t, Deliver(context.Background(), hookTask))
-
- select {
- case gotBody := <-cases[typ].gotBody:
- assert.NotEqual(t, string(data), string(gotBody), "request body must be different from the event payload")
- assert.Equal(t, hookTask.RequestInfo.Body, string(gotBody), "delivered webhook payload doesn't match saved request")
- case <-time.After(5 * time.Second):
- t.Fatal("waited to long for request to happen")
- }
-
- assert.True(t, hookTask.IsSucceed)
- })
- }
- }
|