Skip to content

Commit 4ad42a4

Browse files
authored
[REFACTOR] webhook shared code to prevent import cycles
(cherry picked from commit 04a398a)
1 parent be41258 commit 4ad42a4

File tree

17 files changed

+232
-211
lines changed

17 files changed

+232
-211
lines changed

routers/web/repo/setting/webhook.go

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,7 @@ func WebhookNew(ctx *context.Context) {
148148
}
149149

150150
// ParseHookEvent convert web form content to webhook.HookEvent
151-
func ParseHookEvent(form forms.WebhookForm) *webhook_module.HookEvent {
151+
func ParseHookEvent(form forms.WebhookCoreForm) *webhook_module.HookEvent {
152152
return &webhook_module.HookEvent{
153153
PushOnly: form.PushOnly(),
154154
SendEverything: form.SendEverything(),
@@ -188,7 +188,7 @@ func WebhookCreate(ctx *context.Context) {
188188
return
189189
}
190190

191-
fields := handler.FormFields(func(form any) {
191+
fields := handler.UnmarshalForm(func(form any) {
192192
errs := binding.Bind(ctx.Req, form)
193193
middleware.Validate(errs, ctx.Data, form, ctx.Locale) // error checked below in ctx.HasError
194194
})
@@ -215,10 +215,10 @@ func WebhookCreate(ctx *context.Context) {
215215
w.URL = fields.URL
216216
w.ContentType = fields.ContentType
217217
w.Secret = fields.Secret
218-
w.HookEvent = ParseHookEvent(fields.WebhookForm)
219-
w.IsActive = fields.WebhookForm.Active
218+
w.HookEvent = ParseHookEvent(fields.WebhookCoreForm)
219+
w.IsActive = fields.Active
220220
w.HTTPMethod = fields.HTTPMethod
221-
err := w.SetHeaderAuthorization(fields.WebhookForm.AuthorizationHeader)
221+
err := w.SetHeaderAuthorization(fields.AuthorizationHeader)
222222
if err != nil {
223223
ctx.ServerError("SetHeaderAuthorization", err)
224224
return
@@ -245,14 +245,14 @@ func WebhookCreate(ctx *context.Context) {
245245
HTTPMethod: fields.HTTPMethod,
246246
ContentType: fields.ContentType,
247247
Secret: fields.Secret,
248-
HookEvent: ParseHookEvent(fields.WebhookForm),
249-
IsActive: fields.WebhookForm.Active,
248+
HookEvent: ParseHookEvent(fields.WebhookCoreForm),
249+
IsActive: fields.Active,
250250
Type: hookType,
251251
Meta: string(meta),
252252
OwnerID: orCtx.OwnerID,
253253
IsSystemWebhook: orCtx.IsSystemWebhook,
254254
}
255-
err = w.SetHeaderAuthorization(fields.WebhookForm.AuthorizationHeader)
255+
err = w.SetHeaderAuthorization(fields.AuthorizationHeader)
256256
if err != nil {
257257
ctx.ServerError("SetHeaderAuthorization", err)
258258
return
@@ -286,7 +286,7 @@ func WebhookUpdate(ctx *context.Context) {
286286
return
287287
}
288288

289-
fields := handler.FormFields(func(form any) {
289+
fields := handler.UnmarshalForm(func(form any) {
290290
errs := binding.Bind(ctx.Req, form)
291291
middleware.Validate(errs, ctx.Data, form, ctx.Locale) // error checked below in ctx.HasError
292292
})
@@ -295,11 +295,11 @@ func WebhookUpdate(ctx *context.Context) {
295295
w.URL = fields.URL
296296
w.ContentType = fields.ContentType
297297
w.Secret = fields.Secret
298-
w.HookEvent = ParseHookEvent(fields.WebhookForm)
299-
w.IsActive = fields.WebhookForm.Active
298+
w.HookEvent = ParseHookEvent(fields.WebhookCoreForm)
299+
w.IsActive = fields.Active
300300
w.HTTPMethod = fields.HTTPMethod
301301

302-
err := w.SetHeaderAuthorization(fields.WebhookForm.AuthorizationHeader)
302+
err := w.SetHeaderAuthorization(fields.AuthorizationHeader)
303303
if err != nil {
304304
ctx.ServerError("SetHeaderAuthorization", err)
305305
return

services/forms/repo_form.go

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212
"code.gitea.io/gitea/models"
1313
issues_model "code.gitea.io/gitea/models/issues"
1414
project_model "code.gitea.io/gitea/models/project"
15+
webhook_model "code.gitea.io/gitea/models/webhook"
1516
"code.gitea.io/gitea/modules/setting"
1617
"code.gitea.io/gitea/modules/structs"
1718
"code.gitea.io/gitea/modules/web/middleware"
@@ -235,8 +236,8 @@ func (f *ProtectBranchForm) Validate(req *http.Request, errs binding.Errors) bin
235236
// \__/\ / \___ >___ /___| /\____/ \____/|__|_ \
236237
// \/ \/ \/ \/ \/
237238

238-
// WebhookForm form for changing web hook
239-
type WebhookForm struct {
239+
// WebhookCoreForm form for changing web hook (common to all webhook types)
240+
type WebhookCoreForm struct {
240241
Events string
241242
Create bool
242243
Delete bool
@@ -265,20 +266,30 @@ type WebhookForm struct {
265266
}
266267

267268
// PushOnly if the hook will be triggered when push
268-
func (f WebhookForm) PushOnly() bool {
269+
func (f WebhookCoreForm) PushOnly() bool {
269270
return f.Events == "push_only"
270271
}
271272

272273
// SendEverything if the hook will be triggered any event
273-
func (f WebhookForm) SendEverything() bool {
274+
func (f WebhookCoreForm) SendEverything() bool {
274275
return f.Events == "send_everything"
275276
}
276277

277278
// ChooseEvents if the hook will be triggered choose events
278-
func (f WebhookForm) ChooseEvents() bool {
279+
func (f WebhookCoreForm) ChooseEvents() bool {
279280
return f.Events == "choose_events"
280281
}
281282

283+
// WebhookForm form for changing web hook (specific handling depending on the webhook type)
284+
type WebhookForm struct {
285+
WebhookCoreForm
286+
URL string
287+
ContentType webhook_model.HookContentType
288+
Secret string
289+
HTTPMethod string
290+
Metadata any
291+
}
292+
282293
// .___
283294
// | | ______ ________ __ ____
284295
// | |/ ___// ___/ | \_/ __ \

services/webhook/default.go

Lines changed: 12 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,8 @@ package webhook
55

66
import (
77
"context"
8-
"crypto/hmac"
9-
"crypto/sha1"
10-
"crypto/sha256"
11-
"encoding/hex"
128
"fmt"
139
"html/template"
14-
"io"
1510
"net/http"
1611
"net/url"
1712
"strings"
@@ -21,6 +16,7 @@ import (
2116
"code.gitea.io/gitea/modules/svg"
2217
webhook_module "code.gitea.io/gitea/modules/webhook"
2318
"code.gitea.io/gitea/services/forms"
19+
"code.gitea.io/gitea/services/webhook/shared"
2420
)
2521

2622
var _ Handler = defaultHandler{}
@@ -39,16 +35,16 @@ func (dh defaultHandler) Type() webhook_module.HookType {
3935
func (dh defaultHandler) Icon(size int) template.HTML {
4036
if dh.forgejo {
4137
// forgejo.svg is not in web_src/svg/, so svg.RenderHTML does not work
42-
return imgIcon("forgejo.svg", size)
38+
return shared.ImgIcon("forgejo.svg", size)
4339
}
4440
return svg.RenderHTML("gitea-gitea", size, "img")
4541
}
4642

4743
func (defaultHandler) Metadata(*webhook_model.Webhook) any { return nil }
4844

49-
func (defaultHandler) FormFields(bind func(any)) FormFields {
45+
func (defaultHandler) UnmarshalForm(bind func(any)) forms.WebhookForm {
5046
var form struct {
51-
forms.WebhookForm
47+
forms.WebhookCoreForm
5248
PayloadURL string `binding:"Required;ValidUrl"`
5349
HTTPMethod string `binding:"Required;In(POST,GET)"`
5450
ContentType int `binding:"Required"`
@@ -60,13 +56,13 @@ func (defaultHandler) FormFields(bind func(any)) FormFields {
6056
if webhook_model.HookContentType(form.ContentType) == webhook_model.ContentTypeForm {
6157
contentType = webhook_model.ContentTypeForm
6258
}
63-
return FormFields{
64-
WebhookForm: form.WebhookForm,
65-
URL: form.PayloadURL,
66-
ContentType: contentType,
67-
Secret: form.Secret,
68-
HTTPMethod: form.HTTPMethod,
69-
Metadata: nil,
59+
return forms.WebhookForm{
60+
WebhookCoreForm: form.WebhookCoreForm,
61+
URL: form.PayloadURL,
62+
ContentType: contentType,
63+
Secret: form.Secret,
64+
HTTPMethod: form.HTTPMethod,
65+
Metadata: nil,
7066
}
7167
}
7268

@@ -130,42 +126,5 @@ func (defaultHandler) NewRequest(ctx context.Context, w *webhook_model.Webhook,
130126
}
131127

132128
body = []byte(t.PayloadContent)
133-
return req, body, addDefaultHeaders(req, []byte(w.Secret), t, body)
134-
}
135-
136-
func addDefaultHeaders(req *http.Request, secret []byte, t *webhook_model.HookTask, payloadContent []byte) error {
137-
var signatureSHA1 string
138-
var signatureSHA256 string
139-
if len(secret) > 0 {
140-
sig1 := hmac.New(sha1.New, secret)
141-
sig256 := hmac.New(sha256.New, secret)
142-
_, err := io.MultiWriter(sig1, sig256).Write(payloadContent)
143-
if err != nil {
144-
// this error should never happen, since the hashes are writing to []byte and always return a nil error.
145-
return fmt.Errorf("prepareWebhooks.sigWrite: %w", err)
146-
}
147-
signatureSHA1 = hex.EncodeToString(sig1.Sum(nil))
148-
signatureSHA256 = hex.EncodeToString(sig256.Sum(nil))
149-
}
150-
151-
event := t.EventType.Event()
152-
eventType := string(t.EventType)
153-
req.Header.Add("X-Forgejo-Delivery", t.UUID)
154-
req.Header.Add("X-Forgejo-Event", event)
155-
req.Header.Add("X-Forgejo-Event-Type", eventType)
156-
req.Header.Add("X-Forgejo-Signature", signatureSHA256)
157-
req.Header.Add("X-Gitea-Delivery", t.UUID)
158-
req.Header.Add("X-Gitea-Event", event)
159-
req.Header.Add("X-Gitea-Event-Type", eventType)
160-
req.Header.Add("X-Gitea-Signature", signatureSHA256)
161-
req.Header.Add("X-Gogs-Delivery", t.UUID)
162-
req.Header.Add("X-Gogs-Event", event)
163-
req.Header.Add("X-Gogs-Event-Type", eventType)
164-
req.Header.Add("X-Gogs-Signature", signatureSHA256)
165-
req.Header.Add("X-Hub-Signature", "sha1="+signatureSHA1)
166-
req.Header.Add("X-Hub-Signature-256", "sha256="+signatureSHA256)
167-
req.Header["X-GitHub-Delivery"] = []string{t.UUID}
168-
req.Header["X-GitHub-Event"] = []string{event}
169-
req.Header["X-GitHub-Event-Type"] = []string{eventType}
170-
return nil
129+
return req, body, shared.AddDefaultHeaders(req, []byte(w.Secret), t, body)
171130
}

services/webhook/dingtalk.go

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -17,28 +17,29 @@ import (
1717
"code.gitea.io/gitea/modules/util"
1818
webhook_module "code.gitea.io/gitea/modules/webhook"
1919
"code.gitea.io/gitea/services/forms"
20+
"code.gitea.io/gitea/services/webhook/shared"
2021
)
2122

2223
type dingtalkHandler struct{}
2324

2425
func (dingtalkHandler) Type() webhook_module.HookType { return webhook_module.DINGTALK }
2526
func (dingtalkHandler) Metadata(*webhook_model.Webhook) any { return nil }
26-
func (dingtalkHandler) Icon(size int) template.HTML { return imgIcon("dingtalk.ico", size) }
27+
func (dingtalkHandler) Icon(size int) template.HTML { return shared.ImgIcon("dingtalk.ico", size) }
2728

28-
func (dingtalkHandler) FormFields(bind func(any)) FormFields {
29+
func (dingtalkHandler) UnmarshalForm(bind func(any)) forms.WebhookForm {
2930
var form struct {
30-
forms.WebhookForm
31+
forms.WebhookCoreForm
3132
PayloadURL string `binding:"Required;ValidUrl"`
3233
}
3334
bind(&form)
3435

35-
return FormFields{
36-
WebhookForm: form.WebhookForm,
37-
URL: form.PayloadURL,
38-
ContentType: webhook_model.ContentTypeJSON,
39-
Secret: "",
40-
HTTPMethod: http.MethodPost,
41-
Metadata: nil,
36+
return forms.WebhookForm{
37+
WebhookCoreForm: form.WebhookCoreForm,
38+
URL: form.PayloadURL,
39+
ContentType: webhook_model.ContentTypeJSON,
40+
Secret: "",
41+
HTTPMethod: http.MethodPost,
42+
Metadata: nil,
4243
}
4344
}
4445

@@ -225,8 +226,8 @@ func createDingtalkPayload(title, text, singleTitle, singleURL string) DingtalkP
225226

226227
type dingtalkConvertor struct{}
227228

228-
var _ payloadConvertor[DingtalkPayload] = dingtalkConvertor{}
229+
var _ shared.PayloadConvertor[DingtalkPayload] = dingtalkConvertor{}
229230

230231
func (dingtalkHandler) NewRequest(ctx context.Context, w *webhook_model.Webhook, t *webhook_model.HookTask) (*http.Request, []byte, error) {
231-
return newJSONRequest(dingtalkConvertor{}, w, t, true)
232+
return shared.NewJSONRequest(dingtalkConvertor{}, w, t, true)
232233
}

services/webhook/discord.go

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -22,28 +22,29 @@ import (
2222
"code.gitea.io/gitea/modules/util"
2323
webhook_module "code.gitea.io/gitea/modules/webhook"
2424
"code.gitea.io/gitea/services/forms"
25+
"code.gitea.io/gitea/services/webhook/shared"
2526
)
2627

2728
type discordHandler struct{}
2829

2930
func (discordHandler) Type() webhook_module.HookType { return webhook_module.DISCORD }
30-
func (discordHandler) Icon(size int) template.HTML { return imgIcon("discord.png", size) }
31+
func (discordHandler) Icon(size int) template.HTML { return shared.ImgIcon("discord.png", size) }
3132

32-
func (discordHandler) FormFields(bind func(any)) FormFields {
33+
func (discordHandler) UnmarshalForm(bind func(any)) forms.WebhookForm {
3334
var form struct {
34-
forms.WebhookForm
35+
forms.WebhookCoreForm
3536
PayloadURL string `binding:"Required;ValidUrl"`
3637
Username string
3738
IconURL string
3839
}
3940
bind(&form)
4041

41-
return FormFields{
42-
WebhookForm: form.WebhookForm,
43-
URL: form.PayloadURL,
44-
ContentType: webhook_model.ContentTypeJSON,
45-
Secret: "",
46-
HTTPMethod: http.MethodPost,
42+
return forms.WebhookForm{
43+
WebhookCoreForm: form.WebhookCoreForm,
44+
URL: form.PayloadURL,
45+
ContentType: webhook_model.ContentTypeJSON,
46+
Secret: "",
47+
HTTPMethod: http.MethodPost,
4748
Metadata: &DiscordMeta{
4849
Username: form.Username,
4950
IconURL: form.IconURL,
@@ -287,7 +288,7 @@ type discordConvertor struct {
287288
AvatarURL string
288289
}
289290

290-
var _ payloadConvertor[DiscordPayload] = discordConvertor{}
291+
var _ shared.PayloadConvertor[DiscordPayload] = discordConvertor{}
291292

292293
func (discordHandler) NewRequest(ctx context.Context, w *webhook_model.Webhook, t *webhook_model.HookTask) (*http.Request, []byte, error) {
293294
meta := &DiscordMeta{}
@@ -298,7 +299,7 @@ func (discordHandler) NewRequest(ctx context.Context, w *webhook_model.Webhook,
298299
Username: meta.Username,
299300
AvatarURL: meta.IconURL,
300301
}
301-
return newJSONRequest(sc, w, t, true)
302+
return shared.NewJSONRequest(sc, w, t, true)
302303
}
303304

304305
func parseHookPullRequestEventType(event webhook_module.HookEventType) (string, error) {

services/webhook/feishu.go

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -15,27 +15,28 @@ import (
1515
api "code.gitea.io/gitea/modules/structs"
1616
webhook_module "code.gitea.io/gitea/modules/webhook"
1717
"code.gitea.io/gitea/services/forms"
18+
"code.gitea.io/gitea/services/webhook/shared"
1819
)
1920

2021
type feishuHandler struct{}
2122

2223
func (feishuHandler) Type() webhook_module.HookType { return webhook_module.FEISHU }
23-
func (feishuHandler) Icon(size int) template.HTML { return imgIcon("feishu.png", size) }
24+
func (feishuHandler) Icon(size int) template.HTML { return shared.ImgIcon("feishu.png", size) }
2425

25-
func (feishuHandler) FormFields(bind func(any)) FormFields {
26+
func (feishuHandler) UnmarshalForm(bind func(any)) forms.WebhookForm {
2627
var form struct {
27-
forms.WebhookForm
28+
forms.WebhookCoreForm
2829
PayloadURL string `binding:"Required;ValidUrl"`
2930
}
3031
bind(&form)
3132

32-
return FormFields{
33-
WebhookForm: form.WebhookForm,
34-
URL: form.PayloadURL,
35-
ContentType: webhook_model.ContentTypeJSON,
36-
Secret: "",
37-
HTTPMethod: http.MethodPost,
38-
Metadata: nil,
33+
return forms.WebhookForm{
34+
WebhookCoreForm: form.WebhookCoreForm,
35+
URL: form.PayloadURL,
36+
ContentType: webhook_model.ContentTypeJSON,
37+
Secret: "",
38+
HTTPMethod: http.MethodPost,
39+
Metadata: nil,
3940
}
4041
}
4142

@@ -192,8 +193,8 @@ func (fc feishuConvertor) Package(p *api.PackagePayload) (FeishuPayload, error)
192193

193194
type feishuConvertor struct{}
194195

195-
var _ payloadConvertor[FeishuPayload] = feishuConvertor{}
196+
var _ shared.PayloadConvertor[FeishuPayload] = feishuConvertor{}
196197

197198
func (feishuHandler) NewRequest(ctx context.Context, w *webhook_model.Webhook, t *webhook_model.HookTask) (*http.Request, []byte, error) {
198-
return newJSONRequest(feishuConvertor{}, w, t, true)
199+
return shared.NewJSONRequest(feishuConvertor{}, w, t, true)
199200
}

0 commit comments

Comments
 (0)