Skip to content

Commit 7df46a9

Browse files
author
Earl Warren
committed
Merge pull request '[v7.0/forgejo] [FEAT] sourcehut webhooks' (go-gitea#3065) from bp-v7.0/forgejo-ed9dd0e-04a398a into v7.0/forgejo
Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/3065 Reviewed-by: Earl Warren <[email protected]>
2 parents be41258 + 82b92c3 commit 7df46a9

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

43 files changed

+1122
-224
lines changed

modules/structs/repo.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,16 @@ type Repository struct {
115115
RepoTransfer *RepoTransfer `json:"repo_transfer"`
116116
}
117117

118+
// GetName implements the gitrepo.Repository interface
119+
func (r Repository) GetName() string {
120+
return r.Name
121+
}
122+
123+
// GetOwnerName implements the gitrepo.Repository interface
124+
func (r Repository) GetOwnerName() string {
125+
return r.Owner.UserName
126+
}
127+
118128
// CreateRepoOption options when creating repository
119129
// swagger:model
120130
type CreateRepoOption struct {

modules/webhook/type.go

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -73,18 +73,19 @@ type HookType = string
7373

7474
// Types of webhooks
7575
const (
76-
FORGEJO HookType = "forgejo"
77-
GITEA HookType = "gitea"
78-
GOGS HookType = "gogs"
79-
SLACK HookType = "slack"
80-
DISCORD HookType = "discord"
81-
DINGTALK HookType = "dingtalk"
82-
TELEGRAM HookType = "telegram"
83-
MSTEAMS HookType = "msteams"
84-
FEISHU HookType = "feishu"
85-
MATRIX HookType = "matrix"
86-
WECHATWORK HookType = "wechatwork"
87-
PACKAGIST HookType = "packagist"
76+
FORGEJO HookType = "forgejo"
77+
GITEA HookType = "gitea"
78+
GOGS HookType = "gogs"
79+
SLACK HookType = "slack"
80+
DISCORD HookType = "discord"
81+
DINGTALK HookType = "dingtalk"
82+
TELEGRAM HookType = "telegram"
83+
MSTEAMS HookType = "msteams"
84+
FEISHU HookType = "feishu"
85+
MATRIX HookType = "matrix"
86+
WECHATWORK HookType = "wechatwork"
87+
PACKAGIST HookType = "packagist"
88+
SOURCEHUT_BUILDS HookType = "sourcehut_builds" //nolint:revive
8889
)
8990

9091
// HookStatus is the status of a web hook

options/locale/locale_en-US.ini

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -640,6 +640,8 @@ target_branch_not_exist = Target branch does not exist.
640640
641641
admin_cannot_delete_self = You cannot delete yourself when you are an admin. Please remove your admin privileges first.
642642
643+
required_prefix = Input must start with "%s"
644+
643645
[user]
644646
change_avatar = Change your avatar…
645647
joined_on = Joined on %s
@@ -2269,6 +2271,7 @@ settings.delete_team_tip = This team has access to all repositories and can't be
22692271
settings.remove_team_success = The team's access to the repository has been removed.
22702272
settings.add_webhook = Add webhook
22712273
settings.add_webhook.invalid_channel_name = Webhook channel name cannot be empty and cannot contain only a # character.
2274+
settings.add_webhook.invalid_path = Path must not contain a part that is "." or ".." or the empty string. It cannot start or end with a slash.
22722275
settings.hooks_desc = Webhooks automatically make HTTP POST requests to a server when certain Forgejo events trigger. Read more in the <a target="_blank" rel="noopener noreferrer" href="%s">webhooks guide</a>.
22732276
settings.webhook_deletion = Remove webhook
22742277
settings.webhook_deletion_desc = Removing a webhook deletes its settings and delivery history. Continue?
@@ -2384,6 +2387,12 @@ settings.web_hook_name_packagist = Packagist
23842387
settings.packagist_username = Packagist username
23852388
settings.packagist_api_token = API token
23862389
settings.packagist_package_url = Packagist package URL
2390+
settings.web_hook_name_sourcehut_builds = SourceHut Builds
2391+
settings.sourcehut_builds.manifest_path = Build manifest path
2392+
settings.sourcehut_builds.graphql_url = GraphQL URL (e.g. https://builds.sr.ht/query)
2393+
settings.sourcehut_builds.visibility = Job visibility
2394+
settings.sourcehut_builds.secrets = Secrets
2395+
settings.sourcehut_builds.secrets_helper = Give the job access to the build secrets (requires the SECRETS:RO grant)
23872396
settings.deploy_keys = Deploy keys
23882397
settings.add_deploy_key = Add deploy key
23892398
settings.deploy_key_desc = Deploy keys have read-only pull access to the repository.

public/assets/img/sourcehut.svg

Lines changed: 7 additions & 0 deletions
Loading

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
}

0 commit comments

Comments
 (0)