From e9e1fab1e6ba8e1e3b7bfce5a4844e835f9ffc40 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Fri, 16 May 2025 14:59:06 -0700 Subject: [PATCH] Fix url validation in webhook add/edit API --- routers/api/v1/utils/hook.go | 9 ++++ routers/api/v1/utils/hook_test.go | 82 +++++++++++++++++++++++++++++++ routers/api/v1/utils/main_test.go | 21 ++++++++ 3 files changed, 112 insertions(+) create mode 100644 routers/api/v1/utils/hook_test.go create mode 100644 routers/api/v1/utils/main_test.go diff --git a/routers/api/v1/utils/hook.go b/routers/api/v1/utils/hook.go index 532d157e35df2..b6e650bdc066c 100644 --- a/routers/api/v1/utils/hook.go +++ b/routers/api/v1/utils/hook.go @@ -15,6 +15,7 @@ import ( "code.gitea.io/gitea/modules/setting" api "code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/util" + "code.gitea.io/gitea/modules/validation" webhook_module "code.gitea.io/gitea/modules/webhook" "code.gitea.io/gitea/services/context" webhook_service "code.gitea.io/gitea/services/webhook" @@ -92,6 +93,10 @@ func checkCreateHookOption(ctx *context.APIContext, form *api.CreateHookOption) ctx.APIError(http.StatusUnprocessableEntity, "Invalid content type") return false } + if !validation.IsValidURL(form.Config["url"]) { + ctx.APIError(http.StatusUnprocessableEntity, "Invalid url") + return false + } return true } @@ -324,6 +329,10 @@ func EditRepoHook(ctx *context.APIContext, form *api.EditHookOption, hookID int6 func editHook(ctx *context.APIContext, form *api.EditHookOption, w *webhook.Webhook) bool { if form.Config != nil { if url, ok := form.Config["url"]; ok { + if !validation.IsValidURL(url) { + ctx.APIError(http.StatusUnprocessableEntity, "Invalid url") + return false + } w.URL = url } if ct, ok := form.Config["content_type"]; ok { diff --git a/routers/api/v1/utils/hook_test.go b/routers/api/v1/utils/hook_test.go new file mode 100644 index 0000000000000..e5e8ce07cecfe --- /dev/null +++ b/routers/api/v1/utils/hook_test.go @@ -0,0 +1,82 @@ +// Copyright 2025 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package utils + +import ( + "net/http" + "testing" + + "code.gitea.io/gitea/models/unittest" + "code.gitea.io/gitea/modules/structs" + "code.gitea.io/gitea/services/contexttest" + + "github.com/stretchr/testify/assert" +) + +func TestTestHookValidation(t *testing.T) { + unittest.PrepareTestEnv(t) + + t.Run("Test Validation", func(t *testing.T) { + ctx, _ := contexttest.MockAPIContext(t, "user2/repo1/hooks") + contexttest.LoadRepo(t, ctx, 1) + contexttest.LoadRepoCommit(t, ctx) + contexttest.LoadUser(t, ctx, 2) + + checkCreateHookOption(ctx, &structs.CreateHookOption{ + Type: "gitea", + Config: map[string]string{ + "content_type": "json", + "url": "https://example.com/webhook", + }, + }) + assert.Equal(t, 0, ctx.Resp.WrittenStatus()) // not written yet + }) + + t.Run("Test Validation with invalid URL", func(t *testing.T) { + ctx, _ := contexttest.MockAPIContext(t, "user2/repo1/hooks") + contexttest.LoadRepo(t, ctx, 1) + contexttest.LoadRepoCommit(t, ctx) + contexttest.LoadUser(t, ctx, 2) + + checkCreateHookOption(ctx, &structs.CreateHookOption{ + Type: "gitea", + Config: map[string]string{ + "content_type": "json", + "url": "example.com/webhook", + }, + }) + assert.Equal(t, http.StatusUnprocessableEntity, ctx.Resp.WrittenStatus()) + }) + + t.Run("Test Validation with invalid webhook type", func(t *testing.T) { + ctx, _ := contexttest.MockAPIContext(t, "user2/repo1/hooks") + contexttest.LoadRepo(t, ctx, 1) + contexttest.LoadRepoCommit(t, ctx) + contexttest.LoadUser(t, ctx, 2) + + checkCreateHookOption(ctx, &structs.CreateHookOption{ + Type: "unknown", + Config: map[string]string{ + "content_type": "json", + "url": "example.com/webhook", + }, + }) + assert.Equal(t, http.StatusUnprocessableEntity, ctx.Resp.WrittenStatus()) + }) + + t.Run("Test Validation with empty content type", func(t *testing.T) { + ctx, _ := contexttest.MockAPIContext(t, "user2/repo1/hooks") + contexttest.LoadRepo(t, ctx, 1) + contexttest.LoadRepoCommit(t, ctx) + contexttest.LoadUser(t, ctx, 2) + + checkCreateHookOption(ctx, &structs.CreateHookOption{ + Type: "unknown", + Config: map[string]string{ + "url": "https://example.com/webhook", + }, + }) + assert.Equal(t, http.StatusUnprocessableEntity, ctx.Resp.WrittenStatus()) + }) +} diff --git a/routers/api/v1/utils/main_test.go b/routers/api/v1/utils/main_test.go new file mode 100644 index 0000000000000..4eace1f3694af --- /dev/null +++ b/routers/api/v1/utils/main_test.go @@ -0,0 +1,21 @@ +// Copyright 2018 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package utils + +import ( + "testing" + + "code.gitea.io/gitea/models/unittest" + "code.gitea.io/gitea/modules/setting" + webhook_service "code.gitea.io/gitea/services/webhook" +) + +func TestMain(m *testing.M) { + unittest.MainTest(m, &unittest.TestOptions{ + SetUp: func() error { + setting.LoadQueueSettings() + return webhook_service.Init() + }, + }) +}