fix: url validation in webhook add/edit API (#7932)
Cherry-pick from 972381097c (see #7909).
Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/7932
Reviewed-by: Gusted <gusted@noreply.codeberg.org>
Co-authored-by: Antonin Delpeuch <antonin@delpeuch.eu>
Co-committed-by: Antonin Delpeuch <antonin@delpeuch.eu>
	
	
This commit is contained in:
		
					parent
					
						
							
								b2a3966e64
							
						
					
				
			
			
				commit
				
					
						2b30c83a0c
					
				
			
		
					 3 changed files with 116 additions and 0 deletions
				
			
		| 
						 | 
					@ -16,6 +16,7 @@ import (
 | 
				
			||||||
	"forgejo.org/modules/setting"
 | 
						"forgejo.org/modules/setting"
 | 
				
			||||||
	api "forgejo.org/modules/structs"
 | 
						api "forgejo.org/modules/structs"
 | 
				
			||||||
	"forgejo.org/modules/util"
 | 
						"forgejo.org/modules/util"
 | 
				
			||||||
 | 
						"forgejo.org/modules/validation"
 | 
				
			||||||
	webhook_module "forgejo.org/modules/webhook"
 | 
						webhook_module "forgejo.org/modules/webhook"
 | 
				
			||||||
	"forgejo.org/services/context"
 | 
						"forgejo.org/services/context"
 | 
				
			||||||
	webhook_service "forgejo.org/services/webhook"
 | 
						webhook_service "forgejo.org/services/webhook"
 | 
				
			||||||
| 
						 | 
					@ -93,6 +94,10 @@ func checkCreateHookOption(ctx *context.APIContext, form *api.CreateHookOption)
 | 
				
			||||||
		ctx.Error(http.StatusUnprocessableEntity, "", "Invalid content type")
 | 
							ctx.Error(http.StatusUnprocessableEntity, "", "Invalid content type")
 | 
				
			||||||
		return false
 | 
							return false
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
						if !validation.IsValidURL(form.Config["url"]) {
 | 
				
			||||||
 | 
							ctx.Error(http.StatusUnprocessableEntity, "", "Invalid url")
 | 
				
			||||||
 | 
							return false
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
	return true
 | 
						return true
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -322,6 +327,10 @@ func EditRepoHook(ctx *context.APIContext, form *api.EditHookOption, hookID int6
 | 
				
			||||||
func editHook(ctx *context.APIContext, form *api.EditHookOption, w *webhook.Webhook) bool {
 | 
					func editHook(ctx *context.APIContext, form *api.EditHookOption, w *webhook.Webhook) bool {
 | 
				
			||||||
	if form.Config != nil {
 | 
						if form.Config != nil {
 | 
				
			||||||
		if url, ok := form.Config["url"]; ok {
 | 
							if url, ok := form.Config["url"]; ok {
 | 
				
			||||||
 | 
								if !validation.IsValidURL(url) {
 | 
				
			||||||
 | 
									ctx.Error(http.StatusUnprocessableEntity, "", "Invalid url")
 | 
				
			||||||
 | 
									return false
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
			w.URL = url
 | 
								w.URL = url
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		if ct, ok := form.Config["content_type"]; ok {
 | 
							if ct, ok := form.Config["content_type"]; ok {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										86
									
								
								routers/api/v1/utils/hook_test.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										86
									
								
								routers/api/v1/utils/hook_test.go
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,86 @@
 | 
				
			||||||
 | 
					// Copyright 2025 The Gitea Authors. All rights reserved.
 | 
				
			||||||
 | 
					// SPDX-License-Identifier: MIT
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					package utils
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"net/http"
 | 
				
			||||||
 | 
						"testing"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"forgejo.org/models/unittest"
 | 
				
			||||||
 | 
						"forgejo.org/modules/structs"
 | 
				
			||||||
 | 
						"forgejo.org/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.LoadGitRepo(t, ctx)
 | 
				
			||||||
 | 
							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.LoadGitRepo(t, ctx)
 | 
				
			||||||
 | 
							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.LoadGitRepo(t, ctx)
 | 
				
			||||||
 | 
							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.LoadGitRepo(t, ctx)
 | 
				
			||||||
 | 
							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())
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										21
									
								
								routers/api/v1/utils/main_test.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								routers/api/v1/utils/main_test.go
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,21 @@
 | 
				
			||||||
 | 
					// Copyright 2018 The Gitea Authors. All rights reserved.
 | 
				
			||||||
 | 
					// SPDX-License-Identifier: MIT
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					package utils
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"testing"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"forgejo.org/models/unittest"
 | 
				
			||||||
 | 
						"forgejo.org/modules/setting"
 | 
				
			||||||
 | 
						webhook_service "forgejo.org/services/webhook"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func TestMain(m *testing.M) {
 | 
				
			||||||
 | 
						unittest.MainTest(m, &unittest.TestOptions{
 | 
				
			||||||
 | 
							SetUp: func() error {
 | 
				
			||||||
 | 
								setting.LoadQueueSettings()
 | 
				
			||||||
 | 
								return webhook_service.Init()
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue