Skip to content

Commit 98f7506

Browse files
authored
Merge branch 'main' into dev/hezi/fix-run-name
2 parents c9043c7 + d89eed9 commit 98f7506

File tree

4 files changed

+150
-55
lines changed

4 files changed

+150
-55
lines changed

options/locale/TRANSLATORS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ Piotr Orzechowski <piotr AT orzechowski DOT tech>
6666
Richard Bukovansky <richard DOT bukovansky AT gmail DOT com>
6767
Robert Nuske <robert DOT nuske AT web DOT de>
6868
Robin Hübner <profan AT prfn DOT se>
69+
Ryo Hanafusa <ryo7gumi AT gmail DOT com>
6970
SeongJae Park <sj38 DOT park AT gmail DOT com>
7071
Thiago Avelino <thiago AT avelino DOT xxx>
7172
Thomas Fanninger <gogs DOT thomas AT fanninger DOT at>

routers/api/v1/utils/hook.go

Lines changed: 46 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import (
1515
"code.gitea.io/gitea/modules/setting"
1616
api "code.gitea.io/gitea/modules/structs"
1717
"code.gitea.io/gitea/modules/util"
18+
"code.gitea.io/gitea/modules/validation"
1819
webhook_module "code.gitea.io/gitea/modules/webhook"
1920
"code.gitea.io/gitea/services/context"
2021
webhook_service "code.gitea.io/gitea/services/webhook"
@@ -92,6 +93,10 @@ func checkCreateHookOption(ctx *context.APIContext, form *api.CreateHookOption)
9293
ctx.APIError(http.StatusUnprocessableEntity, "Invalid content type")
9394
return false
9495
}
96+
if !validation.IsValidURL(form.Config["url"]) {
97+
ctx.APIError(http.StatusUnprocessableEntity, "Invalid url")
98+
return false
99+
}
95100
return true
96101
}
97102

@@ -154,6 +159,41 @@ func pullHook(events []string, event string) bool {
154159
return util.SliceContainsString(events, event, true) || util.SliceContainsString(events, string(webhook_module.HookEventPullRequest), true)
155160
}
156161

162+
func updateHookEvents(events []string) webhook_module.HookEvents {
163+
if len(events) == 0 {
164+
events = []string{"push"}
165+
}
166+
hookEvents := make(webhook_module.HookEvents)
167+
hookEvents[webhook_module.HookEventCreate] = util.SliceContainsString(events, string(webhook_module.HookEventCreate), true)
168+
hookEvents[webhook_module.HookEventPush] = util.SliceContainsString(events, string(webhook_module.HookEventPush), true)
169+
hookEvents[webhook_module.HookEventDelete] = util.SliceContainsString(events, string(webhook_module.HookEventDelete), true)
170+
hookEvents[webhook_module.HookEventFork] = util.SliceContainsString(events, string(webhook_module.HookEventFork), true)
171+
hookEvents[webhook_module.HookEventRepository] = util.SliceContainsString(events, string(webhook_module.HookEventRepository), true)
172+
hookEvents[webhook_module.HookEventWiki] = util.SliceContainsString(events, string(webhook_module.HookEventWiki), true)
173+
hookEvents[webhook_module.HookEventRelease] = util.SliceContainsString(events, string(webhook_module.HookEventRelease), true)
174+
hookEvents[webhook_module.HookEventPackage] = util.SliceContainsString(events, string(webhook_module.HookEventPackage), true)
175+
hookEvents[webhook_module.HookEventStatus] = util.SliceContainsString(events, string(webhook_module.HookEventStatus), true)
176+
hookEvents[webhook_module.HookEventWorkflowJob] = util.SliceContainsString(events, string(webhook_module.HookEventWorkflowJob), true)
177+
178+
// Issues
179+
hookEvents[webhook_module.HookEventIssues] = issuesHook(events, "issues_only")
180+
hookEvents[webhook_module.HookEventIssueAssign] = issuesHook(events, string(webhook_module.HookEventIssueAssign))
181+
hookEvents[webhook_module.HookEventIssueLabel] = issuesHook(events, string(webhook_module.HookEventIssueLabel))
182+
hookEvents[webhook_module.HookEventIssueMilestone] = issuesHook(events, string(webhook_module.HookEventIssueMilestone))
183+
hookEvents[webhook_module.HookEventIssueComment] = issuesHook(events, string(webhook_module.HookEventIssueComment))
184+
185+
// Pull requests
186+
hookEvents[webhook_module.HookEventPullRequest] = pullHook(events, "pull_request_only")
187+
hookEvents[webhook_module.HookEventPullRequestAssign] = pullHook(events, string(webhook_module.HookEventPullRequestAssign))
188+
hookEvents[webhook_module.HookEventPullRequestLabel] = pullHook(events, string(webhook_module.HookEventPullRequestLabel))
189+
hookEvents[webhook_module.HookEventPullRequestMilestone] = pullHook(events, string(webhook_module.HookEventPullRequestMilestone))
190+
hookEvents[webhook_module.HookEventPullRequestComment] = pullHook(events, string(webhook_module.HookEventPullRequestComment))
191+
hookEvents[webhook_module.HookEventPullRequestReview] = pullHook(events, "pull_request_review")
192+
hookEvents[webhook_module.HookEventPullRequestReviewRequest] = pullHook(events, string(webhook_module.HookEventPullRequestReviewRequest))
193+
hookEvents[webhook_module.HookEventPullRequestSync] = pullHook(events, string(webhook_module.HookEventPullRequestSync))
194+
return hookEvents
195+
}
196+
157197
// addHook add the hook specified by `form`, `ownerID` and `repoID`. If there is
158198
// an error, write to `ctx` accordingly. Return (webhook, ok)
159199
func addHook(ctx *context.APIContext, form *api.CreateHookOption, ownerID, repoID int64) (*webhook.Webhook, bool) {
@@ -162,9 +202,6 @@ func addHook(ctx *context.APIContext, form *api.CreateHookOption, ownerID, repoI
162202
return nil, false
163203
}
164204

165-
if len(form.Events) == 0 {
166-
form.Events = []string{"push"}
167-
}
168205
if form.Config["is_system_webhook"] != "" {
169206
sw, err := strconv.ParseBool(form.Config["is_system_webhook"])
170207
if err != nil {
@@ -183,31 +220,7 @@ func addHook(ctx *context.APIContext, form *api.CreateHookOption, ownerID, repoI
183220
IsSystemWebhook: isSystemWebhook,
184221
HookEvent: &webhook_module.HookEvent{
185222
ChooseEvents: true,
186-
HookEvents: webhook_module.HookEvents{
187-
webhook_module.HookEventCreate: util.SliceContainsString(form.Events, string(webhook_module.HookEventCreate), true),
188-
webhook_module.HookEventDelete: util.SliceContainsString(form.Events, string(webhook_module.HookEventDelete), true),
189-
webhook_module.HookEventFork: util.SliceContainsString(form.Events, string(webhook_module.HookEventFork), true),
190-
webhook_module.HookEventIssues: issuesHook(form.Events, "issues_only"),
191-
webhook_module.HookEventIssueAssign: issuesHook(form.Events, string(webhook_module.HookEventIssueAssign)),
192-
webhook_module.HookEventIssueLabel: issuesHook(form.Events, string(webhook_module.HookEventIssueLabel)),
193-
webhook_module.HookEventIssueMilestone: issuesHook(form.Events, string(webhook_module.HookEventIssueMilestone)),
194-
webhook_module.HookEventIssueComment: issuesHook(form.Events, string(webhook_module.HookEventIssueComment)),
195-
webhook_module.HookEventPush: util.SliceContainsString(form.Events, string(webhook_module.HookEventPush), true),
196-
webhook_module.HookEventPullRequest: pullHook(form.Events, "pull_request_only"),
197-
webhook_module.HookEventPullRequestAssign: pullHook(form.Events, string(webhook_module.HookEventPullRequestAssign)),
198-
webhook_module.HookEventPullRequestLabel: pullHook(form.Events, string(webhook_module.HookEventPullRequestLabel)),
199-
webhook_module.HookEventPullRequestMilestone: pullHook(form.Events, string(webhook_module.HookEventPullRequestMilestone)),
200-
webhook_module.HookEventPullRequestComment: pullHook(form.Events, string(webhook_module.HookEventPullRequestComment)),
201-
webhook_module.HookEventPullRequestReview: pullHook(form.Events, "pull_request_review"),
202-
webhook_module.HookEventPullRequestReviewRequest: pullHook(form.Events, string(webhook_module.HookEventPullRequestReviewRequest)),
203-
webhook_module.HookEventPullRequestSync: pullHook(form.Events, string(webhook_module.HookEventPullRequestSync)),
204-
webhook_module.HookEventWiki: util.SliceContainsString(form.Events, string(webhook_module.HookEventWiki), true),
205-
webhook_module.HookEventRepository: util.SliceContainsString(form.Events, string(webhook_module.HookEventRepository), true),
206-
webhook_module.HookEventRelease: util.SliceContainsString(form.Events, string(webhook_module.HookEventRelease), true),
207-
webhook_module.HookEventPackage: util.SliceContainsString(form.Events, string(webhook_module.HookEventPackage), true),
208-
webhook_module.HookEventStatus: util.SliceContainsString(form.Events, string(webhook_module.HookEventStatus), true),
209-
webhook_module.HookEventWorkflowJob: util.SliceContainsString(form.Events, string(webhook_module.HookEventWorkflowJob), true),
210-
},
223+
HookEvents: updateHookEvents(form.Events),
211224
BranchFilter: form.BranchFilter,
212225
},
213226
IsActive: form.Active,
@@ -324,6 +337,10 @@ func EditRepoHook(ctx *context.APIContext, form *api.EditHookOption, hookID int6
324337
func editHook(ctx *context.APIContext, form *api.EditHookOption, w *webhook.Webhook) bool {
325338
if form.Config != nil {
326339
if url, ok := form.Config["url"]; ok {
340+
if !validation.IsValidURL(url) {
341+
ctx.APIError(http.StatusUnprocessableEntity, "Invalid url")
342+
return false
343+
}
327344
w.URL = url
328345
}
329346
if ct, ok := form.Config["content_type"]; ok {
@@ -352,19 +369,10 @@ func editHook(ctx *context.APIContext, form *api.EditHookOption, w *webhook.Webh
352369
}
353370

354371
// Update events
355-
if len(form.Events) == 0 {
356-
form.Events = []string{"push"}
357-
}
372+
w.HookEvents = updateHookEvents(form.Events)
358373
w.PushOnly = false
359374
w.SendEverything = false
360375
w.ChooseEvents = true
361-
w.HookEvents[webhook_module.HookEventCreate] = util.SliceContainsString(form.Events, string(webhook_module.HookEventCreate), true)
362-
w.HookEvents[webhook_module.HookEventPush] = util.SliceContainsString(form.Events, string(webhook_module.HookEventPush), true)
363-
w.HookEvents[webhook_module.HookEventDelete] = util.SliceContainsString(form.Events, string(webhook_module.HookEventDelete), true)
364-
w.HookEvents[webhook_module.HookEventFork] = util.SliceContainsString(form.Events, string(webhook_module.HookEventFork), true)
365-
w.HookEvents[webhook_module.HookEventRepository] = util.SliceContainsString(form.Events, string(webhook_module.HookEventRepository), true)
366-
w.HookEvents[webhook_module.HookEventWiki] = util.SliceContainsString(form.Events, string(webhook_module.HookEventWiki), true)
367-
w.HookEvents[webhook_module.HookEventRelease] = util.SliceContainsString(form.Events, string(webhook_module.HookEventRelease), true)
368376
w.BranchFilter = form.BranchFilter
369377

370378
err := w.SetHeaderAuthorization(form.AuthorizationHeader)
@@ -373,23 +381,6 @@ func editHook(ctx *context.APIContext, form *api.EditHookOption, w *webhook.Webh
373381
return false
374382
}
375383

376-
// Issues
377-
w.HookEvents[webhook_module.HookEventIssues] = issuesHook(form.Events, "issues_only")
378-
w.HookEvents[webhook_module.HookEventIssueAssign] = issuesHook(form.Events, string(webhook_module.HookEventIssueAssign))
379-
w.HookEvents[webhook_module.HookEventIssueLabel] = issuesHook(form.Events, string(webhook_module.HookEventIssueLabel))
380-
w.HookEvents[webhook_module.HookEventIssueMilestone] = issuesHook(form.Events, string(webhook_module.HookEventIssueMilestone))
381-
w.HookEvents[webhook_module.HookEventIssueComment] = issuesHook(form.Events, string(webhook_module.HookEventIssueComment))
382-
383-
// Pull requests
384-
w.HookEvents[webhook_module.HookEventPullRequest] = pullHook(form.Events, "pull_request_only")
385-
w.HookEvents[webhook_module.HookEventPullRequestAssign] = pullHook(form.Events, string(webhook_module.HookEventPullRequestAssign))
386-
w.HookEvents[webhook_module.HookEventPullRequestLabel] = pullHook(form.Events, string(webhook_module.HookEventPullRequestLabel))
387-
w.HookEvents[webhook_module.HookEventPullRequestMilestone] = pullHook(form.Events, string(webhook_module.HookEventPullRequestMilestone))
388-
w.HookEvents[webhook_module.HookEventPullRequestComment] = pullHook(form.Events, string(webhook_module.HookEventPullRequestComment))
389-
w.HookEvents[webhook_module.HookEventPullRequestReview] = pullHook(form.Events, "pull_request_review")
390-
w.HookEvents[webhook_module.HookEventPullRequestReviewRequest] = pullHook(form.Events, string(webhook_module.HookEventPullRequestReviewRequest))
391-
w.HookEvents[webhook_module.HookEventPullRequestSync] = pullHook(form.Events, string(webhook_module.HookEventPullRequestSync))
392-
393384
if err := w.UpdateEvent(); err != nil {
394385
ctx.APIErrorInternal(err)
395386
return false

routers/api/v1/utils/hook_test.go

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
// Copyright 2025 The Gitea Authors. All rights reserved.
2+
// SPDX-License-Identifier: MIT
3+
4+
package utils
5+
6+
import (
7+
"net/http"
8+
"testing"
9+
10+
"code.gitea.io/gitea/models/unittest"
11+
"code.gitea.io/gitea/modules/structs"
12+
"code.gitea.io/gitea/services/contexttest"
13+
14+
"github.com/stretchr/testify/assert"
15+
)
16+
17+
func TestTestHookValidation(t *testing.T) {
18+
unittest.PrepareTestEnv(t)
19+
20+
t.Run("Test Validation", func(t *testing.T) {
21+
ctx, _ := contexttest.MockAPIContext(t, "user2/repo1/hooks")
22+
contexttest.LoadRepo(t, ctx, 1)
23+
contexttest.LoadRepoCommit(t, ctx)
24+
contexttest.LoadUser(t, ctx, 2)
25+
26+
checkCreateHookOption(ctx, &structs.CreateHookOption{
27+
Type: "gitea",
28+
Config: map[string]string{
29+
"content_type": "json",
30+
"url": "https://example.com/webhook",
31+
},
32+
})
33+
assert.Equal(t, 0, ctx.Resp.WrittenStatus()) // not written yet
34+
})
35+
36+
t.Run("Test Validation with invalid URL", func(t *testing.T) {
37+
ctx, _ := contexttest.MockAPIContext(t, "user2/repo1/hooks")
38+
contexttest.LoadRepo(t, ctx, 1)
39+
contexttest.LoadRepoCommit(t, ctx)
40+
contexttest.LoadUser(t, ctx, 2)
41+
42+
checkCreateHookOption(ctx, &structs.CreateHookOption{
43+
Type: "gitea",
44+
Config: map[string]string{
45+
"content_type": "json",
46+
"url": "example.com/webhook",
47+
},
48+
})
49+
assert.Equal(t, http.StatusUnprocessableEntity, ctx.Resp.WrittenStatus())
50+
})
51+
52+
t.Run("Test Validation with invalid webhook type", func(t *testing.T) {
53+
ctx, _ := contexttest.MockAPIContext(t, "user2/repo1/hooks")
54+
contexttest.LoadRepo(t, ctx, 1)
55+
contexttest.LoadRepoCommit(t, ctx)
56+
contexttest.LoadUser(t, ctx, 2)
57+
58+
checkCreateHookOption(ctx, &structs.CreateHookOption{
59+
Type: "unknown",
60+
Config: map[string]string{
61+
"content_type": "json",
62+
"url": "example.com/webhook",
63+
},
64+
})
65+
assert.Equal(t, http.StatusUnprocessableEntity, ctx.Resp.WrittenStatus())
66+
})
67+
68+
t.Run("Test Validation with empty content type", func(t *testing.T) {
69+
ctx, _ := contexttest.MockAPIContext(t, "user2/repo1/hooks")
70+
contexttest.LoadRepo(t, ctx, 1)
71+
contexttest.LoadRepoCommit(t, ctx)
72+
contexttest.LoadUser(t, ctx, 2)
73+
74+
checkCreateHookOption(ctx, &structs.CreateHookOption{
75+
Type: "unknown",
76+
Config: map[string]string{
77+
"url": "https://example.com/webhook",
78+
},
79+
})
80+
assert.Equal(t, http.StatusUnprocessableEntity, ctx.Resp.WrittenStatus())
81+
})
82+
}

routers/api/v1/utils/main_test.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
// Copyright 2018 The Gitea Authors. All rights reserved.
2+
// SPDX-License-Identifier: MIT
3+
4+
package utils
5+
6+
import (
7+
"testing"
8+
9+
"code.gitea.io/gitea/models/unittest"
10+
"code.gitea.io/gitea/modules/setting"
11+
webhook_service "code.gitea.io/gitea/services/webhook"
12+
)
13+
14+
func TestMain(m *testing.M) {
15+
unittest.MainTest(m, &unittest.TestOptions{
16+
SetUp: func() error {
17+
setting.LoadQueueSettings()
18+
return webhook_service.Init()
19+
},
20+
})
21+
}

0 commit comments

Comments
 (0)