mirror of
https://github.com/go-gitea/gitea.git
synced 2026-10-01 21:44:57 +00:00
1b1274486c
Introduces gitproxy module which spawns a small forward proxy as scanner for git calls Replaces hostmatcher with matchlist which supports port rules Deprecates ALLOWED_DOMAINS/BLOCKED_DOMAINS and ALLOW_LOCALNETWORKS settings in migration in favor of full names we have in security configs. Removes `external` preset in favor of lax/strict modes, strict mode requiring explicit ports if they aren't standard http/s ones. Breaking changes: - `external` preset no longer works as deny rule. To enforce that, use `strict` mode and allow ranges to connect to - Wildcards are no longer accepted in IP addresses - `*` is no longer allowed as entry in lists - domain rules now use curl like syntax `*.example.com` matching subdomains but not `example.com`, `example.com` matching itself and all subdomains. `example.*` is not a valid rule - In the default `lax` mode, `[security] ALLOWED_HOST_LIST` no longer restricts public hosts, set `EGRESS_MODE = strict` to keep an exclusive list. A startup warning flags this - Invalid list entries are logged at startup, invalid `BLOCKED_HOST_LIST`/`BLOCKED_DOMAINS` entries stop it Docs: https://gitea.com/gitea/docs/pulls/557 Signed-off-by: wxiaoguang <wxiaoguang@gmail.com> Co-authored-by: silverwind <me@silverwind.io> Co-authored-by: bircni <bircni@icloud.com> Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
283 lines
8.6 KiB
Go
283 lines
8.6 KiB
Go
// Copyright 2019 The Gitea Authors. All rights reserved.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package webhook
|
|
|
|
import (
|
|
"io"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"gitea.dev/models/unittest"
|
|
webhook_model "gitea.dev/models/webhook"
|
|
"gitea.dev/modules/util"
|
|
webhook_module "gitea.dev/modules/webhook"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
)
|
|
|
|
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(http.StatusOK)
|
|
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(t.Context(), hook))
|
|
|
|
hookTask := &webhook_model.HookTask{
|
|
HookID: hook.ID,
|
|
EventType: webhook_module.HookEventPush,
|
|
PayloadVersion: 2,
|
|
}
|
|
|
|
hookTask, err = webhook_model.CreateHookTask(t.Context(), hookTask)
|
|
assert.NoError(t, err)
|
|
assert.NotNil(t, hookTask)
|
|
|
|
assert.NoError(t, Deliver(t.Context(), 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)
|
|
version2Body := `{
|
|
"body": "[[test/repo](http://localhost:3000/test/repo)] user1 pushed 2 commits to [test](http://localhost:3000/test/repo/src/branch/test):\n[2020558](http://localhost:3000/test/repo/commit/2020558fe2e34debb818a514715839cabd25e778): commit message - user1\n[2020558](http://localhost:3000/test/repo/commit/2020558fe2e34debb818a514715839cabd25e778): commit message - user1",
|
|
"msgtype": "",
|
|
"format": "org.matrix.custom.html",
|
|
"formatted_body": "[<a href=\"http://localhost:3000/test/repo\">test/repo</a>] user1 pushed 2 commits to <a href=\"http://localhost:3000/test/repo/src/branch/test\">test</a>:<br><a href=\"http://localhost:3000/test/repo/commit/2020558fe2e34debb818a514715839cabd25e778\">2020558</a>: commit message - user1<br><a href=\"http://localhost:3000/test/repo/commit/2020558fe2e34debb818a514715839cabd25e778\">2020558</a>: commit message - user1",
|
|
"io.gitea.commits": [
|
|
{
|
|
"id": "2020558fe2e34debb818a514715839cabd25e778",
|
|
"message": "commit message",
|
|
"url": "http://localhost:3000/test/repo/commit/2020558fe2e34debb818a514715839cabd25e778",
|
|
"author": {
|
|
"name": "user1",
|
|
"email": "user1@localhost",
|
|
"username": "user1"
|
|
},
|
|
"committer": {
|
|
"name": "user1",
|
|
"email": "user1@localhost",
|
|
"username": "user1"
|
|
},
|
|
"verification": null,
|
|
"timestamp": "0001-01-01T00:00:00Z",
|
|
"added": null,
|
|
"removed": null,
|
|
"modified": null
|
|
},
|
|
{
|
|
"id": "2020558fe2e34debb818a514715839cabd25e778",
|
|
"message": "commit message",
|
|
"url": "http://localhost:3000/test/repo/commit/2020558fe2e34debb818a514715839cabd25e778",
|
|
"author": {
|
|
"name": "user1",
|
|
"email": "user1@localhost",
|
|
"username": "user1"
|
|
},
|
|
"committer": {
|
|
"name": "user1",
|
|
"email": "user1@localhost",
|
|
"username": "user1"
|
|
},
|
|
"verification": null,
|
|
"timestamp": "0001-01-01T00:00:00Z",
|
|
"added": null,
|
|
"removed": null,
|
|
"modified": null
|
|
}
|
|
]
|
|
}`
|
|
|
|
testVersion := 0
|
|
s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
assert.Equal(t, "PUT", r.Method)
|
|
assert.True(t, strings.HasPrefix(r.URL.Path, "/webhook/"))
|
|
assert.Len(t, r.URL.Path, len("/webhook/")+40) // +40 for txnID, a unique ID from payload's sha1 hash
|
|
switch testVersion {
|
|
case 1: // Version 1
|
|
assert.Equal(t, "push", r.Header.Get("X-GitHub-Event"))
|
|
assert.Empty(t, r.Header.Get("Content-Type"))
|
|
body, err := io.ReadAll(r.Body)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, `{"data": 42}`, string(body))
|
|
|
|
case 2: // 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.JSONEq(t, version2Body, string(body))
|
|
|
|
default:
|
|
w.WriteHeader(http.StatusNotFound)
|
|
t.Fatalf("unexpected url path %s", r.URL.Path)
|
|
return
|
|
}
|
|
w.WriteHeader(http.StatusOK)
|
|
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(t.Context(), hook))
|
|
|
|
t.Run("Version 1", func(t *testing.T) {
|
|
testVersion = 1
|
|
hookTask := &webhook_model.HookTask{
|
|
HookID: hook.ID,
|
|
EventType: webhook_module.HookEventPush,
|
|
PayloadContent: `{"data": 42}`,
|
|
PayloadVersion: 1,
|
|
}
|
|
|
|
hookTask, err := webhook_model.CreateHookTask(t.Context(), hookTask)
|
|
assert.NoError(t, err)
|
|
assert.NotNil(t, hookTask)
|
|
|
|
assert.NoError(t, Deliver(t.Context(), 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)
|
|
|
|
testVersion = 2
|
|
hookTask := &webhook_model.HookTask{
|
|
HookID: hook.ID,
|
|
EventType: webhook_module.HookEventPush,
|
|
PayloadContent: string(data),
|
|
PayloadVersion: 2,
|
|
}
|
|
|
|
hookTask, err = webhook_model.CreateHookTask(t.Context(), hookTask)
|
|
assert.NoError(t, err)
|
|
assert.NotNil(t, hookTask)
|
|
|
|
assert.NoError(t, Deliver(t.Context(), 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(t.Context(), hook))
|
|
|
|
hookTask := &webhook_model.HookTask{
|
|
HookID: hook.ID,
|
|
EventType: webhook_module.HookEventPush,
|
|
PayloadContent: string(data),
|
|
PayloadVersion: 2,
|
|
}
|
|
|
|
hookTask, err := webhook_model.CreateHookTask(t.Context(), hookTask)
|
|
assert.NoError(t, err)
|
|
assert.NotNil(t, hookTask)
|
|
|
|
assert.NoError(t, Deliver(t.Context(), 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)
|
|
})
|
|
}
|
|
}
|