From 9462de6e942af8c050a60b695fd5bf964c4f79af Mon Sep 17 00:00:00 2001 From: oliverpool Date: Mon, 26 Sep 2022 14:58:33 +0200 Subject: [PATCH 01/15] drone glitch - retrigger From ed56a84f1d82ba9b166de7006c6c29e75869ded9 Mon Sep 17 00:00:00 2001 From: oliverpool Date: Thu, 20 Oct 2022 11:54:26 +0200 Subject: [PATCH 02/15] webhook: add Authorization Header --- docs/content/doc/features/webhooks.en-us.md | 4 + .../expected_webhook.yml | 9 + .../hook_task.yml | 6 + .../webhook.yml | 34 +++ models/migrations/migrations.go | 2 + models/migrations/v229.go | 204 ++++++++++++++++++ models/migrations/v229_test.go | 115 ++++++++++ models/webhook/webhook.go | 28 +++ modules/convert/convert.go | 26 ++- modules/structs/hook.go | 29 +-- options/locale/locale_en-US.ini | 3 +- routers/api/v1/org/hook.go | 14 +- routers/api/v1/repo/hook.go | 13 +- routers/api/v1/utils/hook.go | 52 ++++- routers/web/repo/webhook.go | 12 +- services/forms/repo_form.go | 2 +- services/webhook/deliver.go | 17 +- services/webhook/deliver_test.go | 41 ++++ services/webhook/main_test.go | 4 + services/webhook/matrix.go | 127 ++++------- services/webhook/matrix_test.go | 190 +++++----------- templates/repo/settings/webhook/matrix.tmpl | 4 - templates/repo/settings/webhook/settings.tmpl | 9 + templates/swagger/v1_json.tmpl | 12 ++ tests/integration/api_repo_hook_test.go | 47 ++++ 25 files changed, 741 insertions(+), 263 deletions(-) create mode 100644 models/migrations/fixtures/Test_addHeaderAuthorizationEncryptedColWebhook/expected_webhook.yml create mode 100644 models/migrations/fixtures/Test_addHeaderAuthorizationEncryptedColWebhook/hook_task.yml create mode 100644 models/migrations/fixtures/Test_addHeaderAuthorizationEncryptedColWebhook/webhook.yml create mode 100644 models/migrations/v229.go create mode 100644 models/migrations/v229_test.go create mode 100644 tests/integration/api_repo_hook_test.go diff --git a/docs/content/doc/features/webhooks.en-us.md b/docs/content/doc/features/webhooks.en-us.md index 2dba7b7f83c74..ecbe13c3adb21 100644 --- a/docs/content/doc/features/webhooks.en-us.md +++ b/docs/content/doc/features/webhooks.en-us.md @@ -188,3 +188,7 @@ if (json_last_error() !== JSON_ERROR_NONE) { ``` There is a Test Delivery button in the webhook settings that allows to test the configuration as well as a list of the most Recent Deliveries. + +### Authorization header + +**With 1.19**, Gitea hooks can be configured to send an [authorization header](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Authorization) to the webhook target. diff --git a/models/migrations/fixtures/Test_addHeaderAuthorizationEncryptedColWebhook/expected_webhook.yml b/models/migrations/fixtures/Test_addHeaderAuthorizationEncryptedColWebhook/expected_webhook.yml new file mode 100644 index 0000000000000..f6239998c3459 --- /dev/null +++ b/models/migrations/fixtures/Test_addHeaderAuthorizationEncryptedColWebhook/expected_webhook.yml @@ -0,0 +1,9 @@ +# for matrix, the access_token has been moved to "header_authorization" +- + id: 1 + meta: '{"homeserver_url":"https://matrix.example.com","room_id":"roomID","message_type":1}' + header_authorization: "Bearer s3cr3t" +- + id: 2 + meta: '' + header_authorization: "" diff --git a/models/migrations/fixtures/Test_addHeaderAuthorizationEncryptedColWebhook/hook_task.yml b/models/migrations/fixtures/Test_addHeaderAuthorizationEncryptedColWebhook/hook_task.yml new file mode 100644 index 0000000000000..258089a97131c --- /dev/null +++ b/models/migrations/fixtures/Test_addHeaderAuthorizationEncryptedColWebhook/hook_task.yml @@ -0,0 +1,6 @@ +# unsafe payload +- id: 1 + payload_content: '{"homeserver_url":"https://matrix.example.com","room_id":"roomID","access_token":"s3cr3t","message_type":1}' +# safe payload +- id: 2 + payload_content: '{"homeserver_url":"https://matrix.example.com","room_id":"roomID","message_type":1}' diff --git a/models/migrations/fixtures/Test_addHeaderAuthorizationEncryptedColWebhook/webhook.yml b/models/migrations/fixtures/Test_addHeaderAuthorizationEncryptedColWebhook/webhook.yml new file mode 100644 index 0000000000000..f0212e61a51aa --- /dev/null +++ b/models/migrations/fixtures/Test_addHeaderAuthorizationEncryptedColWebhook/webhook.yml @@ -0,0 +1,34 @@ +# matrix webhook +- id: 1 + repo_id: 1 + org_id: 0 + is_system_webhook: 0 + url: https://matrix.example.com/_matrix/client/r0/rooms/roomID/send/m.room.message + http_method: PUT + content_type: 1 + secret: '' + events: '{"push_only":true,"send_everything":false,"choose_events":false,"branch_filter":"*","events":{"create":false,"delete":false,"fork":false,"issues":false,"issue_assign":false,"issue_label":false,"issue_milestone":false,"issue_comment":false,"push":false,"pull_request":false,"pull_request_assign":false,"pull_request_label":false,"pull_request_milestone":false,"pull_request_comment":false,"pull_request_review":false,"pull_request_sync":false,"repository":false,"release":false,"package":false}}' + is_active: 1 + type: matrix + meta: '{"homeserver_url":"https://matrix.example.com","room_id":"roomID","access_token":"s3cr3t","message_type":1}' + last_status: 0 + header_authorization_encrypted: '' + created_unix: 1661677165 + updated_unix: 1661677165 +# gitea webhook +- id: 2 + repo_id: 1 + org_id: 0 + is_system_webhook: 0 + url: https://gitea.example.com + http_method: POST + content_type: 1 + secret: '' + events: '{"push_only":true,"send_everything":false,"choose_events":false,"branch_filter":"*","events":{"create":false,"delete":false,"fork":false,"issues":false,"issue_assign":false,"issue_label":false,"issue_milestone":false,"issue_comment":false,"push":false,"pull_request":false,"pull_request_assign":false,"pull_request_label":false,"pull_request_milestone":false,"pull_request_comment":false,"pull_request_review":false,"pull_request_sync":false,"repository":false,"release":false,"package":false}}' + is_active: 1 + type: gitea + meta: '' + last_status: 0 + header_authorization_encrypted: '' + created_unix: 1661677348 + updated_unix: 1661677348 diff --git a/models/migrations/migrations.go b/models/migrations/migrations.go index 46ef052829b6d..b6c64bfaf2e70 100644 --- a/models/migrations/migrations.go +++ b/models/migrations/migrations.go @@ -419,6 +419,8 @@ var migrations = []Migration{ NewMigration("Create key/value table for system settings", createSystemSettingsTable), // v228 -> v229 NewMigration("Add TeamInvite table", addTeamInviteTable), + // v229 -> v230 + NewMigration("Add header_authorization_encrypted column to webhook table", addHeaderAuthorizationEncryptedColWebhook), } // GetCurrentDBVersion returns the current db version diff --git a/models/migrations/v229.go b/models/migrations/v229.go new file mode 100644 index 0000000000000..60711dd367f74 --- /dev/null +++ b/models/migrations/v229.go @@ -0,0 +1,204 @@ +// Copyright 2022 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package migrations + +import ( + "fmt" + + "code.gitea.io/gitea/models/webhook" + "code.gitea.io/gitea/modules/json" + "code.gitea.io/gitea/modules/secret" + "code.gitea.io/gitea/modules/setting" + api "code.gitea.io/gitea/modules/structs" + "code.gitea.io/gitea/modules/timeutil" + + "xorm.io/builder" + "xorm.io/xorm" +) + +func batchProcess[T any](x *xorm.Engine, query func() *xorm.Session, buf []T, process func(*xorm.Session, T) error) error { + size := cap(buf) + start := 0 + for { + err := query().Limit(size, start).Find(&buf) + if err != nil { + return err + } + if len(buf) == 0 { + return nil + } + + err = func() error { + sess := x.NewSession() + defer sess.Close() + if err := sess.Begin(); err != nil { + return fmt.Errorf("unable to allow start session. Error: %w", err) + } + for _, record := range buf { + if err := process(sess, record); err != nil { + return err + } + } + return sess.Commit() + }() + if err != nil { + return err + } + + if len(buf) < size { + return nil + } + start += size + buf = buf[:0] + } +} + +func addHeaderAuthorizationEncryptedColWebhook(x *xorm.Engine) error { + // Add the column to the table + type Webhook struct { + ID int64 `xorm:"pk autoincr"` + RepoID int64 `xorm:"INDEX"` // An ID of 0 indicates either a default or system webhook + OrgID int64 `xorm:"INDEX"` + IsSystemWebhook bool + URL string `xorm:"url TEXT"` + HTTPMethod string `xorm:"http_method"` + ContentType webhook.HookContentType + Secret string `xorm:"TEXT"` + Events string `xorm:"TEXT"` + *webhook.HookEvent `xorm:"-"` + IsActive bool `xorm:"INDEX"` + Type webhook.HookType `xorm:"VARCHAR(16) 'type'"` + Meta string `xorm:"TEXT"` // store hook-specific attributes + LastStatus webhook.HookStatus // Last delivery status + + // HeaderAuthorizationEncrypted should be accessed using HeaderAuthorization() and SetHeaderAuthorization() + HeaderAuthorizationEncrypted string `xorm:"TEXT"` + + CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"` + UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"` + } + err := x.Sync2(new(Webhook)) + if err != nil { + return err + } + + // Migrate the matrix webhooks + + type MatrixMeta struct { + HomeserverURL string `json:"homeserver_url"` + Room string `json:"room_id"` + MessageType int `json:"message_type"` + } + type MatrixMetaWithAccessToken struct { + MatrixMeta + AccessToken string `json:"access_token"` + } + + err = batchProcess(x, + func() *xorm.Session { return x.Where("type=?", "matrix").OrderBy("id") }, + make([]*Webhook, 0, 50), + func(sess *xorm.Session, hook *Webhook) error { + // retrieve token from meta + var withToken MatrixMetaWithAccessToken + err := json.Unmarshal([]byte(hook.Meta), &withToken) + if err != nil { + return fmt.Errorf("unable to unmarshal matrix meta for webhook[%d]: %w", hook.ID, err) + } + if withToken.AccessToken == "" { + return nil + } + + // encrypt token + authorization := "Bearer " + withToken.AccessToken + hook.HeaderAuthorizationEncrypted, err = secret.EncryptSecret(setting.SecretKey, authorization) + if err != nil { + return fmt.Errorf("unable to encrypt access token for webhook[%d]: %w", hook.ID, err) + } + + // remove token from meta + withoutToken, err := json.Marshal(withToken.MatrixMeta) + if err != nil { + return fmt.Errorf("unable to marshal matrix meta for webhook[%d]: %w", hook.ID, err) + } + hook.Meta = string(withoutToken) + + // save in database + count, err := sess.ID(hook.ID).Cols("meta", "header_authorization_encrypted").Update(hook) + if count != 1 || err != nil { + return fmt.Errorf("unable to update header_authorization_encrypted for webhook[%d]: %d,%w", hook.ID, count, err) + } + return nil + }) + if err != nil { + return err + } + + // Remove access_token from HookTask + + type HookTask struct { + ID int64 `xorm:"pk autoincr"` + RepoID int64 `xorm:"INDEX"` + HookID int64 + UUID string + PayloadContent string `xorm:"LONGTEXT"` + EventType string + IsDelivered bool + Delivered int64 + DeliveredString string `xorm:"-"` + + // History info. + IsSucceed bool + RequestContent string `xorm:"LONGTEXT"` + // RequestInfo *HookRequest `xorm:"-"` + ResponseContent string `xorm:"LONGTEXT"` + // ResponseInfo *HookResponse `xorm:"-"` + } + + type MatrixPayloadSafe struct { + Body string `json:"body"` + MsgType string `json:"msgtype"` + Format string `json:"format"` + FormattedBody string `json:"formatted_body"` + Commits []*api.PayloadCommit `json:"io.gitea.commits,omitempty"` + } + type MatrixPayloadUnsafe struct { + MatrixPayloadSafe + AccessToken string `json:"access_token"` + } + + err = batchProcess(x, + func() *xorm.Session { return x.Where(builder.Like{"payload_content", "access_token"}).OrderBy("id") }, + make([]*HookTask, 0, 50), + func(sess *xorm.Session, hookTask *HookTask) error { + // retrieve token from payload_content + var withToken MatrixPayloadUnsafe + err := json.Unmarshal([]byte(hookTask.PayloadContent), &withToken) + if err != nil { + return fmt.Errorf("unable to unmarshal payload_content for hook_task[%d]: %w", hookTask.ID, err) + } + if withToken.AccessToken == "" { + return nil + } + + // remove token from payload_content + withoutToken, err := json.Marshal(withToken.MatrixPayloadSafe) + if err != nil { + return fmt.Errorf("unable to marshal payload_content for hook_task[%d]: %w", hookTask.ID, err) + } + hookTask.PayloadContent = string(withoutToken) + + // save in database + count, err := sess.ID(hookTask.ID).Cols("payload_content").Update(hookTask) + if count != 1 || err != nil { + return fmt.Errorf("unable to update payload_content for hook_task[%d]: %d,%w", hookTask.ID, count, err) + } + return nil + }) + if err != nil { + return err + } + + return nil +} diff --git a/models/migrations/v229_test.go b/models/migrations/v229_test.go new file mode 100644 index 0000000000000..a1198b4123508 --- /dev/null +++ b/models/migrations/v229_test.go @@ -0,0 +1,115 @@ +// Copyright 2022 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package migrations + +import ( + "testing" + + "code.gitea.io/gitea/models/webhook" + "code.gitea.io/gitea/modules/json" + "code.gitea.io/gitea/modules/secret" + "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/modules/timeutil" + + "github.com/stretchr/testify/assert" +) + +func Test_addHeaderAuthorizationEncryptedColWebhook(t *testing.T) { + // Create Webhook table + type Webhook struct { + ID int64 `xorm:"pk autoincr"` + RepoID int64 `xorm:"INDEX"` // An ID of 0 indicates either a default or system webhook + OrgID int64 `xorm:"INDEX"` + IsSystemWebhook bool + URL string `xorm:"url TEXT"` + HTTPMethod string `xorm:"http_method"` + ContentType webhook.HookContentType + Secret string `xorm:"TEXT"` + Events string `xorm:"TEXT"` + *webhook.HookEvent `xorm:"-"` + IsActive bool `xorm:"INDEX"` + Type webhook.HookType `xorm:"VARCHAR(16) 'type'"` + Meta string `xorm:"TEXT"` // store hook-specific attributes + LastStatus webhook.HookStatus // Last delivery status + + // HeaderAuthorizationEncrypted should be accessed using HeaderAuthorization() and SetHeaderAuthorization() + HeaderAuthorizationEncrypted string `xorm:"TEXT"` + + CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"` + UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"` + } + + type ExpectedWebhook struct { + ID int64 `xorm:"pk autoincr"` + Meta string + HeaderAuthorization string + } + + type HookTask struct { + ID int64 `xorm:"pk autoincr"` + RepoID int64 `xorm:"INDEX"` + HookID int64 + UUID string + PayloadContent string `xorm:"LONGTEXT"` + EventType string + IsDelivered bool + Delivered int64 + DeliveredString string `xorm:"-"` + + // History info. + IsSucceed bool + RequestContent string `xorm:"LONGTEXT"` + // RequestInfo *HookRequest `xorm:"-"` + ResponseContent string `xorm:"LONGTEXT"` + // ResponseInfo *HookResponse `xorm:"-"` + } + + // Prepare and load the testing database + x, deferable := prepareTestEnv(t, 0, new(Webhook), new(ExpectedWebhook), new(HookTask)) + defer deferable() + if x == nil || t.Failed() { + return + } + + if err := addHeaderAuthorizationEncryptedColWebhook(x); err != nil { + assert.NoError(t, err) + return + } + + expected := []ExpectedWebhook{} + if err := x.Table("expected_webhook").Asc("id").Find(&expected); !assert.NoError(t, err) { + return + } + + got := []Webhook{} + if err := x.Table("webhook").Select("id, meta, header_authorization_encrypted").Asc("id").Find(&got); !assert.NoError(t, err) { + return + } + + for i, e := range expected { + assert.Equal(t, e.Meta, got[i].Meta) + + if e.HeaderAuthorization == "" { + assert.Equal(t, "", got[i].HeaderAuthorizationEncrypted) + } else { + cipherhex := got[i].HeaderAuthorizationEncrypted + cleartext, err := secret.DecryptSecret(setting.SecretKey, cipherhex) + assert.NoError(t, err) + assert.Equal(t, e.HeaderAuthorization, cleartext) + } + } + + // ensure that no hook_task has some remaining "access_token" + hookTasks := []HookTask{} + if err := x.Table("hook_task").Select("id, payload_content").Asc("id").Find(&hookTasks); !assert.NoError(t, err) { + return + } + for _, h := range hookTasks { + var m map[string]interface{} + err := json.Unmarshal([]byte(h.PayloadContent), &m) + assert.NoError(t, err) + assert.Nil(t, m["access_token"]) + } +} diff --git a/models/webhook/webhook.go b/models/webhook/webhook.go index 83200a3d1c217..74c11f9577f1c 100644 --- a/models/webhook/webhook.go +++ b/models/webhook/webhook.go @@ -13,6 +13,8 @@ import ( "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/json" "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/secret" + "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/timeutil" "code.gitea.io/gitea/modules/util" @@ -201,6 +203,9 @@ type Webhook struct { Meta string `xorm:"TEXT"` // store hook-specific attributes LastStatus HookStatus // Last delivery status + // HeaderAuthorizationEncrypted should be accessed using HeaderAuthorization() and SetHeaderAuthorization() + HeaderAuthorizationEncrypted string `xorm:"TEXT"` + CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"` UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"` } @@ -407,6 +412,29 @@ func (w *Webhook) EventsArray() []string { return events } +// HeaderAuthorization returns the decrypted Authorization header. +// Not on the reference (*w), to be accessible on WebhooksNew. +func (w Webhook) HeaderAuthorization() (string, error) { + if w.HeaderAuthorizationEncrypted == "" { + return "", nil + } + return secret.DecryptSecret(setting.SecretKey, w.HeaderAuthorizationEncrypted) +} + +// SetHeaderAuthorization encrypts and sets the Authorization header. +func (w *Webhook) SetHeaderAuthorization(cleartext string) error { + if cleartext == "" { + w.HeaderAuthorizationEncrypted = "" + return nil + } + ciphertext, err := secret.EncryptSecret(setting.SecretKey, cleartext) + if err != nil { + return err + } + w.HeaderAuthorizationEncrypted = ciphertext + return nil +} + // CreateWebhook creates a new web hook. func CreateWebhook(ctx context.Context, w *Webhook) error { w.Type = strings.TrimSpace(w.Type) diff --git a/modules/convert/convert.go b/modules/convert/convert.go index 187c67fa7680b..7b9fa4d99ac7c 100644 --- a/modules/convert/convert.go +++ b/modules/convert/convert.go @@ -243,7 +243,7 @@ func ToGPGKeyEmail(email *user_model.EmailAddress) *api.GPGKeyEmail { } // ToHook convert models.Webhook to api.Hook -func ToHook(repoLink string, w *webhook.Webhook) *api.Hook { +func ToHook(repoLink string, w *webhook.Webhook) (*api.Hook, error) { config := map[string]string{ "url": w.URL, "content_type": w.ContentType.Name(), @@ -256,16 +256,22 @@ func ToHook(repoLink string, w *webhook.Webhook) *api.Hook { config["color"] = s.Color } - return &api.Hook{ - ID: w.ID, - Type: w.Type, - URL: fmt.Sprintf("%s/settings/hooks/%d", repoLink, w.ID), - Active: w.IsActive, - Config: config, - Events: w.EventsArray(), - Updated: w.UpdatedUnix.AsTime(), - Created: w.CreatedUnix.AsTime(), + authorizationHeader, err := w.HeaderAuthorization() + if err != nil { + return nil, err } + + return &api.Hook{ + ID: w.ID, + Type: w.Type, + URL: fmt.Sprintf("%s/settings/hooks/%d", repoLink, w.ID), + Active: w.IsActive, + Config: config, + Events: w.EventsArray(), + AuthorizationHeader: authorizationHeader, + Updated: w.UpdatedUnix.AsTime(), + Created: w.CreatedUnix.AsTime(), + }, nil } // ToGitHook convert git.Hook to api.GitHook diff --git a/modules/structs/hook.go b/modules/structs/hook.go index 8321a15a8f1e8..f0600a192d962 100644 --- a/modules/structs/hook.go +++ b/modules/structs/hook.go @@ -18,12 +18,13 @@ var ErrInvalidReceiveHook = errors.New("Invalid JSON payload received over webho // Hook a hook is a web hook when one repository changed type Hook struct { - ID int64 `json:"id"` - Type string `json:"type"` - URL string `json:"-"` - Config map[string]string `json:"config"` - Events []string `json:"events"` - Active bool `json:"active"` + ID int64 `json:"id"` + Type string `json:"type"` + URL string `json:"-"` + Config map[string]string `json:"config"` + Events []string `json:"events"` + AuthorizationHeader string `json:"authorization_header"` + Active bool `json:"active"` // swagger:strfmt date-time Updated time.Time `json:"updated_at"` // swagger:strfmt date-time @@ -43,19 +44,21 @@ type CreateHookOption struct { // enum: dingtalk,discord,gitea,gogs,msteams,slack,telegram,feishu,wechatwork,packagist Type string `json:"type" binding:"Required"` // required: true - Config CreateHookOptionConfig `json:"config" binding:"Required"` - Events []string `json:"events"` - BranchFilter string `json:"branch_filter" binding:"GlobPattern"` + Config CreateHookOptionConfig `json:"config" binding:"Required"` + Events []string `json:"events"` + BranchFilter string `json:"branch_filter" binding:"GlobPattern"` + AuthorizationHeader string `json:"authorization_header"` // default: false Active bool `json:"active"` } // EditHookOption options when modify one hook type EditHookOption struct { - Config map[string]string `json:"config"` - Events []string `json:"events"` - BranchFilter string `json:"branch_filter" binding:"GlobPattern"` - Active *bool `json:"active"` + Config map[string]string `json:"config"` + Events []string `json:"events"` + BranchFilter string `json:"branch_filter" binding:"GlobPattern"` + AuthorizationHeader string `json:"authorization_header"` + Active *bool `json:"active"` } // Payloader payload is some part of one hook diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index a35c6a668af4a..87f714638a03d 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -2008,6 +2008,8 @@ settings.event_package = Package settings.event_package_desc = Package created or deleted in a repository. settings.branch_filter = Branch filter settings.branch_filter_desc = Branch whitelist for push, branch creation and branch deletion events, specified as glob pattern. If empty or *, events for all branches are reported. See github.com/gobwas/glob documentation for syntax. Examples: master, {master,release*}. +settings.authorization_header = Authorization Header +settings.authorization_header_desc = Will be included as authorization header for requests when present. Examples: %s. settings.active = Active settings.active_helper = Information about triggered events will be sent to this webhook URL. settings.add_hook_success = The webhook has been added. @@ -2122,7 +2124,6 @@ settings.bot_token = Bot Token settings.chat_id = Chat ID settings.matrix.homeserver_url = Homeserver URL settings.matrix.room_id = Room ID -settings.matrix.access_token = Access Token settings.matrix.message_type = Message Type settings.archive.button = Archive Repo settings.archive.header = Archive This Repo diff --git a/routers/api/v1/org/hook.go b/routers/api/v1/org/hook.go index 2ddb2b2d2ba94..f8ea5a876cf81 100644 --- a/routers/api/v1/org/hook.go +++ b/routers/api/v1/org/hook.go @@ -59,7 +59,11 @@ func ListHooks(ctx *context.APIContext) { hooks := make([]*api.Hook, len(orgHooks)) for i, hook := range orgHooks { - hooks[i] = convert.ToHook(ctx.Org.Organization.AsUser().HomeLink(), hook) + hooks[i], err = convert.ToHook(ctx.Org.Organization.AsUser().HomeLink(), hook) + if err != nil { + ctx.InternalServerError(err) + return + } } ctx.SetTotalCountHeader(count) @@ -95,7 +99,13 @@ func GetHook(ctx *context.APIContext) { if err != nil { return } - ctx.JSON(http.StatusOK, convert.ToHook(org.AsUser().HomeLink(), hook)) + + apiHook, err := convert.ToHook(org.AsUser().HomeLink(), hook) + if err != nil { + ctx.InternalServerError(err) + return + } + ctx.JSON(http.StatusOK, apiHook) } // CreateHook create a hook for an organization diff --git a/routers/api/v1/repo/hook.go b/routers/api/v1/repo/hook.go index 86361817cb464..8333e50c5e397 100644 --- a/routers/api/v1/repo/hook.go +++ b/routers/api/v1/repo/hook.go @@ -69,7 +69,11 @@ func ListHooks(ctx *context.APIContext) { apiHooks := make([]*api.Hook, len(hooks)) for i := range hooks { - apiHooks[i] = convert.ToHook(ctx.Repo.RepoLink, hooks[i]) + apiHooks[i], err = convert.ToHook(ctx.Repo.RepoLink, hooks[i]) + if err != nil { + ctx.InternalServerError(err) + return + } } ctx.SetTotalCountHeader(count) @@ -112,7 +116,12 @@ func GetHook(ctx *context.APIContext) { if err != nil { return } - ctx.JSON(http.StatusOK, convert.ToHook(repo.RepoLink, hook)) + apiHook, err := convert.ToHook(repo.RepoLink, hook) + if err != nil { + ctx.InternalServerError(err) + return + } + ctx.JSON(http.StatusOK, apiHook) } // TestHook tests a hook diff --git a/routers/api/v1/utils/hook.go b/routers/api/v1/utils/hook.go index 7e4dfca9adf20..aa922f4f5b152 100644 --- a/routers/api/v1/utils/hook.go +++ b/routers/api/v1/utils/hook.go @@ -72,18 +72,39 @@ func CheckCreateHookOption(ctx *context.APIContext, form *api.CreateHookOption) func AddOrgHook(ctx *context.APIContext, form *api.CreateHookOption) { org := ctx.Org.Organization hook, ok := addHook(ctx, form, org.ID, 0) - if ok { - ctx.JSON(http.StatusCreated, convert.ToHook(org.AsUser().HomeLink(), hook)) + if !ok { + return + } + apiHook, ok := toAPIHook(ctx, org.AsUser().HomeLink(), hook) + if !ok { + return } + ctx.JSON(http.StatusCreated, apiHook) } // AddRepoHook add a hook to a repo. Writes to `ctx` accordingly func AddRepoHook(ctx *context.APIContext, form *api.CreateHookOption) { repo := ctx.Repo hook, ok := addHook(ctx, form, 0, repo.Repository.ID) - if ok { - ctx.JSON(http.StatusCreated, convert.ToHook(repo.RepoLink, hook)) + if !ok { + return + } + apiHook, ok := toAPIHook(ctx, repo.RepoLink, hook) + if !ok { + return + } + ctx.JSON(http.StatusCreated, apiHook) +} + +// toAPIHook converts the hook to its API representation. +// If there is an error, write to `ctx` accordingly. Return (hook, ok) +func toAPIHook(ctx *context.APIContext, repoLink string, hook *webhook.Webhook) (*api.Hook, bool) { + apiHook, err := convert.ToHook(repoLink, hook) + if err != nil { + ctx.Error(http.StatusInternalServerError, "ToHook", err) + return nil, false } + return apiHook, true } func issuesHook(events []string, event string) bool { @@ -135,6 +156,11 @@ func addHook(ctx *context.APIContext, form *api.CreateHookOption, orgID, repoID IsActive: form.Active, Type: form.Type, } + err := w.SetHeaderAuthorization(form.AuthorizationHeader) + if err != nil { + ctx.Error(http.StatusInternalServerError, "SetHeaderAuthorization", err) + return nil, false + } if w.Type == webhook.SLACK { channel, ok := form.Config["channel"] if !ok { @@ -185,7 +211,11 @@ func EditOrgHook(ctx *context.APIContext, form *api.EditHookOption, hookID int64 if err != nil { return } - ctx.JSON(http.StatusOK, convert.ToHook(org.AsUser().HomeLink(), updated)) + apiHook, ok := toAPIHook(ctx, org.AsUser().HomeLink(), updated) + if !ok { + return + } + ctx.JSON(http.StatusOK, apiHook) } // EditRepoHook edit webhook `w` according to `form`. Writes to `ctx` accordingly @@ -202,7 +232,11 @@ func EditRepoHook(ctx *context.APIContext, form *api.EditHookOption, hookID int6 if err != nil { return } - ctx.JSON(http.StatusOK, convert.ToHook(repo.RepoLink, updated)) + apiHook, ok := toAPIHook(ctx, repo.RepoLink, updated) + if !ok { + return + } + ctx.JSON(http.StatusOK, apiHook) } // editHook edit the webhook `w` according to `form`. If an error occurs, write @@ -254,6 +288,12 @@ func editHook(ctx *context.APIContext, form *api.EditHookOption, w *webhook.Webh w.Release = util.IsStringInSlice(string(webhook.HookEventRelease), form.Events, true) w.BranchFilter = form.BranchFilter + err := w.SetHeaderAuthorization(form.AuthorizationHeader) + if err != nil { + ctx.Error(http.StatusInternalServerError, "SetHeaderAuthorization", err) + return false + } + // Issues w.Issues = issuesHook(form.Events, "issues_only") w.IssueAssign = issuesHook(form.Events, string(webhook.HookEventIssueAssign)) diff --git a/routers/web/repo/webhook.go b/routers/web/repo/webhook.go index 425198ce24422..2569a2833c2fb 100644 --- a/routers/web/repo/webhook.go +++ b/routers/web/repo/webhook.go @@ -239,6 +239,11 @@ func createWebhook(ctx *context.Context, params webhookParams) { OrgID: orCtx.OrgID, IsSystemWebhook: orCtx.IsSystemWebhook, } + err = w.SetHeaderAuthorization(params.WebhookForm.AuthorizationHeader) + if err != nil { + ctx.ServerError("SetHeaderAuthorization", err) + return + } if err := w.UpdateEvent(); err != nil { ctx.ServerError("UpdateEvent", err) return @@ -285,6 +290,12 @@ func editWebhook(ctx *context.Context, params webhookParams) { w.HTTPMethod = params.HTTPMethod w.Meta = string(meta) + err = w.SetHeaderAuthorization(params.WebhookForm.AuthorizationHeader) + if err != nil { + ctx.ServerError("SetHeaderAuthorization", err) + return + } + if err := w.UpdateEvent(); err != nil { ctx.ServerError("UpdateEvent", err) return @@ -445,7 +456,6 @@ func matrixHookParams(ctx *context.Context) webhookParams { Meta: &webhook_service.MatrixMeta{ HomeserverURL: form.HomeserverURL, Room: form.RoomID, - AccessToken: form.AccessToken, MessageType: form.MessageType, }, } diff --git a/services/forms/repo_form.go b/services/forms/repo_form.go index c1e9cb3197c0b..64f47aadd5cae 100644 --- a/services/forms/repo_form.go +++ b/services/forms/repo_form.go @@ -247,6 +247,7 @@ type WebhookForm struct { Package bool Active bool BranchFilter string `binding:"GlobPattern"` + AuthorizationHeader string } // PushOnly if the hook will be triggered when push @@ -359,7 +360,6 @@ func (f *NewTelegramHookForm) Validate(req *http.Request, errs binding.Errors) b type NewMatrixHookForm struct { HomeserverURL string `binding:"Required;ValidUrl"` RoomID string `binding:"Required"` - AccessToken string `binding:"Required"` MessageType int WebhookForm } diff --git a/services/webhook/deliver.go b/services/webhook/deliver.go index 77744473f1ce3..562d229872ef6 100644 --- a/services/webhook/deliver.go +++ b/services/webhook/deliver.go @@ -91,7 +91,12 @@ func Deliver(ctx context.Context, t *webhook_model.HookTask) error { case http.MethodPut: switch w.Type { case webhook_model.MATRIX: - req, err = getMatrixHookRequest(w, t) + txnID, err := getMatrixTxnID([]byte(t.PayloadContent)) + if err != nil { + return err + } + url := fmt.Sprintf("%s/%s", w.URL, url.PathEscape(txnID)) + req, err = http.NewRequest("PUT", url, strings.NewReader(t.PayloadContent)) if err != nil { return err } @@ -131,6 +136,16 @@ func Deliver(ctx context.Context, t *webhook_model.HookTask) error { req.Header["X-GitHub-Event"] = []string{event} req.Header["X-GitHub-Event-Type"] = []string{eventType} + // Add Authorization Header + authorization, err := w.HeaderAuthorization() + if err != nil { + log.Error("Webhook could not get Authorization header [%d]: %v", w.ID, err) + return err + } + if authorization != "" { + req.Header["Authorization"] = []string{authorization} + } + // Record delivery information. t.RequestInfo = &webhook_model.HookRequest{ URL: req.URL.String(), diff --git a/services/webhook/deliver_test.go b/services/webhook/deliver_test.go index 8d1d587c38fb7..ec79ae60279de 100644 --- a/services/webhook/deliver_test.go +++ b/services/webhook/deliver_test.go @@ -5,10 +5,16 @@ package webhook import ( + "context" "net/http" + "net/http/httptest" "net/url" "testing" + "time" + "code.gitea.io/gitea/models/db" + "code.gitea.io/gitea/models/unittest" + webhook_model "code.gitea.io/gitea/models/webhook" "code.gitea.io/gitea/modules/setting" "github.com/stretchr/testify/assert" @@ -38,3 +44,38 @@ func TestWebhookProxy(t *testing.T) { } } } + +func TestWebhookDeliverAuthorizationHeader(t *testing.T) { + assert.NoError(t, unittest.PrepareTestDatabase()) + + done := make(chan struct{}, 1) + s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, "/webhook", r.URL.Path) + assert.Equal(t, "Bearer s3cr3t-t0ken", r.Header.Get("Authorization")) + w.WriteHeader(200) + done <- struct{}{} + })) + t.Cleanup(s.Close) + + hook := &webhook_model.Webhook{ + RepoID: 3, + URL: s.URL + "/webhook", + ContentType: webhook_model.ContentTypeJSON, + IsActive: true, + Type: webhook_model.GITEA, + } + err := hook.SetHeaderAuthorization("Bearer s3cr3t-t0ken") + assert.NoError(t, err) + assert.NoError(t, webhook_model.CreateWebhook(db.DefaultContext, hook)) + + hookTask := &webhook_model.HookTask{RepoID: hook.RepoID, HookID: hook.ID, EventType: webhook_model.HookEventPush} + + assert.NoError(t, Deliver(context.Background(), hookTask)) + select { + case <-done: + case <-time.After(5 * time.Second): + t.Fatal("waited to long for request to happen") + } + + assert.True(t, hookTask.IsSucceed) +} diff --git a/services/webhook/main_test.go b/services/webhook/main_test.go index 1dc2e1bd83fb3..5ddb6cf1f3384 100644 --- a/services/webhook/main_test.go +++ b/services/webhook/main_test.go @@ -9,6 +9,7 @@ import ( "testing" "code.gitea.io/gitea/models/unittest" + "code.gitea.io/gitea/modules/hostmatcher" "code.gitea.io/gitea/modules/setting" _ "code.gitea.io/gitea/models" @@ -17,6 +18,9 @@ import ( func TestMain(m *testing.M) { setting.LoadForTest() setting.NewQueueService() + + // for tests, allow only loopback IPs + setting.Webhook.AllowedHostList = hostmatcher.MatchBuiltinLoopback unittest.MainTest(m, &unittest.TestOptions{ GiteaRootPath: filepath.Join("..", ".."), SetUp: Init, diff --git a/services/webhook/matrix.go b/services/webhook/matrix.go index 6bbae70422750..7ff8b1e638386 100644 --- a/services/webhook/matrix.go +++ b/services/webhook/matrix.go @@ -6,10 +6,10 @@ package webhook import ( "crypto/sha1" + "encoding/hex" "errors" "fmt" "html" - "net/http" "net/url" "regexp" "strings" @@ -29,7 +29,6 @@ const matrixPayloadSizeLimit = 1024 * 64 type MatrixMeta struct { HomeserverURL string `json:"homeserver_url"` Room string `json:"room_id"` - AccessToken string `json:"access_token"` MessageType int `json:"message_type"` } @@ -47,27 +46,10 @@ func GetMatrixHook(w *webhook_model.Webhook) *MatrixMeta { return s } -// MatrixPayloadUnsafe contains the (unsafe) payload for a Matrix room -type MatrixPayloadUnsafe struct { - MatrixPayloadSafe - AccessToken string `json:"access_token"` -} - -var _ PayloadConvertor = &MatrixPayloadUnsafe{} - -// safePayload "converts" a unsafe payload to a safe payload -func (m *MatrixPayloadUnsafe) safePayload() *MatrixPayloadSafe { - return &MatrixPayloadSafe{ - Body: m.Body, - MsgType: m.MsgType, - Format: m.Format, - FormattedBody: m.FormattedBody, - Commits: m.Commits, - } -} +var _ PayloadConvertor = &MatrixPayload{} -// MatrixPayloadSafe contains (safe) payload for a Matrix room -type MatrixPayloadSafe struct { +// MatrixPayload contains payload for a Matrix room +type MatrixPayload struct { Body string `json:"body"` MsgType string `json:"msgtype"` Format string `json:"format"` @@ -75,8 +57,8 @@ type MatrixPayloadSafe struct { Commits []*api.PayloadCommit `json:"io.gitea.commits,omitempty"` } -// JSONPayload Marshals the MatrixPayloadUnsafe to json -func (m *MatrixPayloadUnsafe) JSONPayload() ([]byte, error) { +// JSONPayload Marshals the MatrixPayload to json +func (m *MatrixPayload) JSONPayload() ([]byte, error) { data, err := json.MarshalIndent(m, "", " ") if err != nil { return []byte{}, err @@ -103,62 +85,62 @@ func MatrixLinkToRef(repoURL, ref string) string { } // Create implements PayloadConvertor Create method -func (m *MatrixPayloadUnsafe) Create(p *api.CreatePayload) (api.Payloader, error) { +func (m *MatrixPayload) Create(p *api.CreatePayload) (api.Payloader, error) { repoLink := MatrixLinkFormatter(p.Repo.HTMLURL, p.Repo.FullName) refLink := MatrixLinkToRef(p.Repo.HTMLURL, p.Ref) text := fmt.Sprintf("[%s:%s] %s created by %s", repoLink, refLink, p.RefType, p.Sender.UserName) - return getMatrixPayloadUnsafe(text, nil, m.AccessToken, m.MsgType), nil + return getMatrixPayload(text, nil, m.MsgType), nil } // Delete composes Matrix payload for delete a branch or tag. -func (m *MatrixPayloadUnsafe) Delete(p *api.DeletePayload) (api.Payloader, error) { +func (m *MatrixPayload) Delete(p *api.DeletePayload) (api.Payloader, error) { refName := git.RefEndName(p.Ref) repoLink := MatrixLinkFormatter(p.Repo.HTMLURL, p.Repo.FullName) text := fmt.Sprintf("[%s:%s] %s deleted by %s", repoLink, refName, p.RefType, p.Sender.UserName) - return getMatrixPayloadUnsafe(text, nil, m.AccessToken, m.MsgType), nil + return getMatrixPayload(text, nil, m.MsgType), nil } // Fork composes Matrix payload for forked by a repository. -func (m *MatrixPayloadUnsafe) Fork(p *api.ForkPayload) (api.Payloader, error) { +func (m *MatrixPayload) Fork(p *api.ForkPayload) (api.Payloader, error) { baseLink := MatrixLinkFormatter(p.Forkee.HTMLURL, p.Forkee.FullName) forkLink := MatrixLinkFormatter(p.Repo.HTMLURL, p.Repo.FullName) text := fmt.Sprintf("%s is forked to %s", baseLink, forkLink) - return getMatrixPayloadUnsafe(text, nil, m.AccessToken, m.MsgType), nil + return getMatrixPayload(text, nil, m.MsgType), nil } // Issue implements PayloadConvertor Issue method -func (m *MatrixPayloadUnsafe) Issue(p *api.IssuePayload) (api.Payloader, error) { +func (m *MatrixPayload) Issue(p *api.IssuePayload) (api.Payloader, error) { text, _, _, _ := getIssuesPayloadInfo(p, MatrixLinkFormatter, true) - return getMatrixPayloadUnsafe(text, nil, m.AccessToken, m.MsgType), nil + return getMatrixPayload(text, nil, m.MsgType), nil } // IssueComment implements PayloadConvertor IssueComment method -func (m *MatrixPayloadUnsafe) IssueComment(p *api.IssueCommentPayload) (api.Payloader, error) { +func (m *MatrixPayload) IssueComment(p *api.IssueCommentPayload) (api.Payloader, error) { text, _, _ := getIssueCommentPayloadInfo(p, MatrixLinkFormatter, true) - return getMatrixPayloadUnsafe(text, nil, m.AccessToken, m.MsgType), nil + return getMatrixPayload(text, nil, m.MsgType), nil } // Wiki implements PayloadConvertor Wiki method -func (m *MatrixPayloadUnsafe) Wiki(p *api.WikiPayload) (api.Payloader, error) { +func (m *MatrixPayload) Wiki(p *api.WikiPayload) (api.Payloader, error) { text, _, _ := getWikiPayloadInfo(p, MatrixLinkFormatter, true) - return getMatrixPayloadUnsafe(text, nil, m.AccessToken, m.MsgType), nil + return getMatrixPayload(text, nil, m.MsgType), nil } // Release implements PayloadConvertor Release method -func (m *MatrixPayloadUnsafe) Release(p *api.ReleasePayload) (api.Payloader, error) { +func (m *MatrixPayload) Release(p *api.ReleasePayload) (api.Payloader, error) { text, _ := getReleasePayloadInfo(p, MatrixLinkFormatter, true) - return getMatrixPayloadUnsafe(text, nil, m.AccessToken, m.MsgType), nil + return getMatrixPayload(text, nil, m.MsgType), nil } // Push implements PayloadConvertor Push method -func (m *MatrixPayloadUnsafe) Push(p *api.PushPayload) (api.Payloader, error) { +func (m *MatrixPayload) Push(p *api.PushPayload) (api.Payloader, error) { var commitDesc string if p.TotalCommits == 1 { @@ -181,18 +163,18 @@ func (m *MatrixPayloadUnsafe) Push(p *api.PushPayload) (api.Payloader, error) { } - return getMatrixPayloadUnsafe(text, p.Commits, m.AccessToken, m.MsgType), nil + return getMatrixPayload(text, p.Commits, m.MsgType), nil } // PullRequest implements PayloadConvertor PullRequest method -func (m *MatrixPayloadUnsafe) PullRequest(p *api.PullRequestPayload) (api.Payloader, error) { +func (m *MatrixPayload) PullRequest(p *api.PullRequestPayload) (api.Payloader, error) { text, _, _, _ := getPullRequestPayloadInfo(p, MatrixLinkFormatter, true) - return getMatrixPayloadUnsafe(text, nil, m.AccessToken, m.MsgType), nil + return getMatrixPayload(text, nil, m.MsgType), nil } // Review implements PayloadConvertor Review method -func (m *MatrixPayloadUnsafe) Review(p *api.PullRequestPayload, event webhook_model.HookEventType) (api.Payloader, error) { +func (m *MatrixPayload) Review(p *api.PullRequestPayload, event webhook_model.HookEventType) (api.Payloader, error) { senderLink := MatrixLinkFormatter(setting.AppURL+url.PathEscape(p.Sender.UserName), p.Sender.UserName) title := fmt.Sprintf("#%d %s", p.Index, p.PullRequest.Title) titleLink := MatrixLinkFormatter(p.PullRequest.URL, title) @@ -209,11 +191,11 @@ func (m *MatrixPayloadUnsafe) Review(p *api.PullRequestPayload, event webhook_mo text = fmt.Sprintf("[%s] Pull request review %s: %s by %s", repoLink, action, titleLink, senderLink) } - return getMatrixPayloadUnsafe(text, nil, m.AccessToken, m.MsgType), nil + return getMatrixPayload(text, nil, m.MsgType), nil } // Repository implements PayloadConvertor Repository method -func (m *MatrixPayloadUnsafe) Repository(p *api.RepositoryPayload) (api.Payloader, error) { +func (m *MatrixPayload) Repository(p *api.RepositoryPayload) (api.Payloader, error) { senderLink := MatrixLinkFormatter(setting.AppURL+p.Sender.UserName, p.Sender.UserName) repoLink := MatrixLinkFormatter(p.Repository.HTMLURL, p.Repository.FullName) var text string @@ -225,27 +207,25 @@ func (m *MatrixPayloadUnsafe) Repository(p *api.RepositoryPayload) (api.Payloade text = fmt.Sprintf("[%s] Repository deleted by %s", repoLink, senderLink) } - return getMatrixPayloadUnsafe(text, nil, m.AccessToken, m.MsgType), nil + return getMatrixPayload(text, nil, m.MsgType), nil } -// GetMatrixPayload converts a Matrix webhook into a MatrixPayloadUnsafe +// GetMatrixPayload converts a Matrix webhook into a MatrixPayload func GetMatrixPayload(p api.Payloader, event webhook_model.HookEventType, meta string) (api.Payloader, error) { - s := new(MatrixPayloadUnsafe) + s := new(MatrixPayload) matrix := &MatrixMeta{} if err := json.Unmarshal([]byte(meta), &matrix); err != nil { return s, errors.New("GetMatrixPayload meta json:" + err.Error()) } - s.AccessToken = matrix.AccessToken s.MsgType = messageTypeText[matrix.MessageType] return convertPayloader(s, p, event) } -func getMatrixPayloadUnsafe(text string, commits []*api.PayloadCommit, accessToken, msgType string) *MatrixPayloadUnsafe { - p := MatrixPayloadUnsafe{} - p.AccessToken = accessToken +func getMatrixPayload(text string, commits []*api.PayloadCommit, msgType string) *MatrixPayload { + p := MatrixPayload{} p.FormattedBody = text p.Body = getMessageBody(text) p.Format = "org.matrix.custom.html" @@ -262,52 +242,17 @@ func getMessageBody(htmlText string) string { return htmlText } -// getMatrixHookRequest creates a new request which contains an Authorization header. -// The access_token is removed from t.PayloadContent -func getMatrixHookRequest(w *webhook_model.Webhook, t *webhook_model.HookTask) (*http.Request, error) { - payloadunsafe := MatrixPayloadUnsafe{} - if err := json.Unmarshal([]byte(t.PayloadContent), &payloadunsafe); err != nil { - log.Error("Matrix Hook delivery failed: %v", err) - return nil, err - } - - payloadsafe := payloadunsafe.safePayload() - - var payload []byte - var err error - if payload, err = json.MarshalIndent(payloadsafe, "", " "); err != nil { - return nil, err - } +// getMatrixTxnID computes the transaction ID to ensure idempotency +func getMatrixTxnID(payload []byte) (string, error) { if len(payload) >= matrixPayloadSizeLimit { - return nil, fmt.Errorf("getMatrixHookRequest: payload size %d > %d", len(payload), matrixPayloadSizeLimit) + return "", fmt.Errorf("getMatrixTxnID: payload size %d > %d", len(payload), matrixPayloadSizeLimit) } - t.PayloadContent = string(payload) - txnID, err := getMatrixTxnID(payload) - if err != nil { - return nil, fmt.Errorf("getMatrixHookRequest: unable to hash payload: %+v", err) - } - - url := fmt.Sprintf("%s/%s", w.URL, url.PathEscape(txnID)) - - req, err := http.NewRequest(w.HTTPMethod, url, strings.NewReader(string(payload))) - if err != nil { - return nil, err - } - - req.Header.Set("Content-Type", "application/json") - req.Header.Add("Authorization", "Bearer "+payloadunsafe.AccessToken) - - return req, nil -} - -// getMatrixTxnID creates a txnID based on the payload to ensure idempotency -func getMatrixTxnID(payload []byte) (string, error) { h := sha1.New() _, err := h.Write(payload) if err != nil { return "", err } - return fmt.Sprintf("%x", h.Sum(nil)), nil + return hex.EncodeToString(h.Sum(nil)), nil } diff --git a/services/webhook/matrix_test.go b/services/webhook/matrix_test.go index 624986ee9b95a..bbcdef3567780 100644 --- a/services/webhook/matrix_test.go +++ b/services/webhook/matrix_test.go @@ -18,275 +18,203 @@ func TestMatrixPayload(t *testing.T) { t.Run("Create", func(t *testing.T) { p := createTestPayload() - d := new(MatrixPayloadUnsafe) + d := new(MatrixPayload) pl, err := d.Create(p) require.NoError(t, err) require.NotNil(t, pl) - require.IsType(t, &MatrixPayloadUnsafe{}, pl) + require.IsType(t, &MatrixPayload{}, pl) - assert.Equal(t, "[[test/repo](http://localhost:3000/test/repo):[test](http://localhost:3000/test/repo/src/branch/test)] branch created by user1", pl.(*MatrixPayloadUnsafe).Body) - assert.Equal(t, `[test/repo:test] branch created by user1`, pl.(*MatrixPayloadUnsafe).FormattedBody) + assert.Equal(t, "[[test/repo](http://localhost:3000/test/repo):[test](http://localhost:3000/test/repo/src/branch/test)] branch created by user1", pl.(*MatrixPayload).Body) + assert.Equal(t, `[test/repo:test] branch created by user1`, pl.(*MatrixPayload).FormattedBody) }) t.Run("Delete", func(t *testing.T) { p := deleteTestPayload() - d := new(MatrixPayloadUnsafe) + d := new(MatrixPayload) pl, err := d.Delete(p) require.NoError(t, err) require.NotNil(t, pl) - require.IsType(t, &MatrixPayloadUnsafe{}, pl) + require.IsType(t, &MatrixPayload{}, pl) - assert.Equal(t, "[[test/repo](http://localhost:3000/test/repo):test] branch deleted by user1", pl.(*MatrixPayloadUnsafe).Body) - assert.Equal(t, `[test/repo:test] branch deleted by user1`, pl.(*MatrixPayloadUnsafe).FormattedBody) + assert.Equal(t, "[[test/repo](http://localhost:3000/test/repo):test] branch deleted by user1", pl.(*MatrixPayload).Body) + assert.Equal(t, `[test/repo:test] branch deleted by user1`, pl.(*MatrixPayload).FormattedBody) }) t.Run("Fork", func(t *testing.T) { p := forkTestPayload() - d := new(MatrixPayloadUnsafe) + d := new(MatrixPayload) pl, err := d.Fork(p) require.NoError(t, err) require.NotNil(t, pl) - require.IsType(t, &MatrixPayloadUnsafe{}, pl) + require.IsType(t, &MatrixPayload{}, pl) - assert.Equal(t, "[test/repo2](http://localhost:3000/test/repo2) is forked to [test/repo](http://localhost:3000/test/repo)", pl.(*MatrixPayloadUnsafe).Body) - assert.Equal(t, `test/repo2 is forked to test/repo`, pl.(*MatrixPayloadUnsafe).FormattedBody) + assert.Equal(t, "[test/repo2](http://localhost:3000/test/repo2) is forked to [test/repo](http://localhost:3000/test/repo)", pl.(*MatrixPayload).Body) + assert.Equal(t, `test/repo2 is forked to test/repo`, pl.(*MatrixPayload).FormattedBody) }) t.Run("Push", func(t *testing.T) { p := pushTestPayload() - d := new(MatrixPayloadUnsafe) + d := new(MatrixPayload) pl, err := d.Push(p) require.NoError(t, err) require.NotNil(t, pl) - require.IsType(t, &MatrixPayloadUnsafe{}, pl) + require.IsType(t, &MatrixPayload{}, pl) - assert.Equal(t, "[[test/repo](http://localhost:3000/test/repo)] user1 pushed 2 commits to [test](http://localhost:3000/test/repo/src/branch/test):\n[2020558](http://localhost:3000/test/repo/commit/2020558fe2e34debb818a514715839cabd25e778): commit message - user1\n[2020558](http://localhost:3000/test/repo/commit/2020558fe2e34debb818a514715839cabd25e778): commit message - user1", pl.(*MatrixPayloadUnsafe).Body) - assert.Equal(t, `[test/repo] user1 pushed 2 commits to test:
2020558: commit message - user1
2020558: commit message - user1`, pl.(*MatrixPayloadUnsafe).FormattedBody) + assert.Equal(t, "[[test/repo](http://localhost:3000/test/repo)] user1 pushed 2 commits to [test](http://localhost:3000/test/repo/src/branch/test):\n[2020558](http://localhost:3000/test/repo/commit/2020558fe2e34debb818a514715839cabd25e778): commit message - user1\n[2020558](http://localhost:3000/test/repo/commit/2020558fe2e34debb818a514715839cabd25e778): commit message - user1", pl.(*MatrixPayload).Body) + assert.Equal(t, `[test/repo] user1 pushed 2 commits to test:
2020558: commit message - user1
2020558: commit message - user1`, pl.(*MatrixPayload).FormattedBody) }) t.Run("Issue", func(t *testing.T) { p := issueTestPayload() - d := new(MatrixPayloadUnsafe) + d := new(MatrixPayload) p.Action = api.HookIssueOpened pl, err := d.Issue(p) require.NoError(t, err) require.NotNil(t, pl) - require.IsType(t, &MatrixPayloadUnsafe{}, pl) + require.IsType(t, &MatrixPayload{}, pl) - assert.Equal(t, "[[test/repo](http://localhost:3000/test/repo)] Issue opened: [#2 crash](http://localhost:3000/test/repo/issues/2) by [user1](https://try.gitea.io/user1)", pl.(*MatrixPayloadUnsafe).Body) - assert.Equal(t, `[test/repo] Issue opened: #2 crash by user1`, pl.(*MatrixPayloadUnsafe).FormattedBody) + assert.Equal(t, "[[test/repo](http://localhost:3000/test/repo)] Issue opened: [#2 crash](http://localhost:3000/test/repo/issues/2) by [user1](https://try.gitea.io/user1)", pl.(*MatrixPayload).Body) + assert.Equal(t, `[test/repo] Issue opened: #2 crash by user1`, pl.(*MatrixPayload).FormattedBody) p.Action = api.HookIssueClosed pl, err = d.Issue(p) require.NoError(t, err) require.NotNil(t, pl) - require.IsType(t, &MatrixPayloadUnsafe{}, pl) + require.IsType(t, &MatrixPayload{}, pl) - assert.Equal(t, "[[test/repo](http://localhost:3000/test/repo)] Issue closed: [#2 crash](http://localhost:3000/test/repo/issues/2) by [user1](https://try.gitea.io/user1)", pl.(*MatrixPayloadUnsafe).Body) - assert.Equal(t, `[test/repo] Issue closed: #2 crash by user1`, pl.(*MatrixPayloadUnsafe).FormattedBody) + assert.Equal(t, "[[test/repo](http://localhost:3000/test/repo)] Issue closed: [#2 crash](http://localhost:3000/test/repo/issues/2) by [user1](https://try.gitea.io/user1)", pl.(*MatrixPayload).Body) + assert.Equal(t, `[test/repo] Issue closed: #2 crash by user1`, pl.(*MatrixPayload).FormattedBody) }) t.Run("IssueComment", func(t *testing.T) { p := issueCommentTestPayload() - d := new(MatrixPayloadUnsafe) + d := new(MatrixPayload) pl, err := d.IssueComment(p) require.NoError(t, err) require.NotNil(t, pl) - require.IsType(t, &MatrixPayloadUnsafe{}, pl) + require.IsType(t, &MatrixPayload{}, pl) - assert.Equal(t, "[[test/repo](http://localhost:3000/test/repo)] New comment on issue [#2 crash](http://localhost:3000/test/repo/issues/2) by [user1](https://try.gitea.io/user1)", pl.(*MatrixPayloadUnsafe).Body) - assert.Equal(t, `[test/repo] New comment on issue #2 crash by user1`, pl.(*MatrixPayloadUnsafe).FormattedBody) + assert.Equal(t, "[[test/repo](http://localhost:3000/test/repo)] New comment on issue [#2 crash](http://localhost:3000/test/repo/issues/2) by [user1](https://try.gitea.io/user1)", pl.(*MatrixPayload).Body) + assert.Equal(t, `[test/repo] New comment on issue #2 crash by user1`, pl.(*MatrixPayload).FormattedBody) }) t.Run("PullRequest", func(t *testing.T) { p := pullRequestTestPayload() - d := new(MatrixPayloadUnsafe) + d := new(MatrixPayload) pl, err := d.PullRequest(p) require.NoError(t, err) require.NotNil(t, pl) - require.IsType(t, &MatrixPayloadUnsafe{}, pl) + require.IsType(t, &MatrixPayload{}, pl) - assert.Equal(t, "[[test/repo](http://localhost:3000/test/repo)] Pull request opened: [#12 Fix bug](http://localhost:3000/test/repo/pulls/12) by [user1](https://try.gitea.io/user1)", pl.(*MatrixPayloadUnsafe).Body) - assert.Equal(t, `[test/repo] Pull request opened: #12 Fix bug by user1`, pl.(*MatrixPayloadUnsafe).FormattedBody) + assert.Equal(t, "[[test/repo](http://localhost:3000/test/repo)] Pull request opened: [#12 Fix bug](http://localhost:3000/test/repo/pulls/12) by [user1](https://try.gitea.io/user1)", pl.(*MatrixPayload).Body) + assert.Equal(t, `[test/repo] Pull request opened: #12 Fix bug by user1`, pl.(*MatrixPayload).FormattedBody) }) t.Run("PullRequestComment", func(t *testing.T) { p := pullRequestCommentTestPayload() - d := new(MatrixPayloadUnsafe) + d := new(MatrixPayload) pl, err := d.IssueComment(p) require.NoError(t, err) require.NotNil(t, pl) - require.IsType(t, &MatrixPayloadUnsafe{}, pl) + require.IsType(t, &MatrixPayload{}, pl) - assert.Equal(t, "[[test/repo](http://localhost:3000/test/repo)] New comment on pull request [#12 Fix bug](http://localhost:3000/test/repo/pulls/12) by [user1](https://try.gitea.io/user1)", pl.(*MatrixPayloadUnsafe).Body) - assert.Equal(t, `[test/repo] New comment on pull request #12 Fix bug by user1`, pl.(*MatrixPayloadUnsafe).FormattedBody) + assert.Equal(t, "[[test/repo](http://localhost:3000/test/repo)] New comment on pull request [#12 Fix bug](http://localhost:3000/test/repo/pulls/12) by [user1](https://try.gitea.io/user1)", pl.(*MatrixPayload).Body) + assert.Equal(t, `[test/repo] New comment on pull request #12 Fix bug by user1`, pl.(*MatrixPayload).FormattedBody) }) t.Run("Review", func(t *testing.T) { p := pullRequestTestPayload() p.Action = api.HookIssueReviewed - d := new(MatrixPayloadUnsafe) + d := new(MatrixPayload) pl, err := d.Review(p, webhook_model.HookEventPullRequestReviewApproved) require.NoError(t, err) require.NotNil(t, pl) - require.IsType(t, &MatrixPayloadUnsafe{}, pl) + require.IsType(t, &MatrixPayload{}, pl) - assert.Equal(t, "[[test/repo](http://localhost:3000/test/repo)] Pull request review approved: [#12 Fix bug](http://localhost:3000/test/repo/pulls/12) by [user1](https://try.gitea.io/user1)", pl.(*MatrixPayloadUnsafe).Body) - assert.Equal(t, `[test/repo] Pull request review approved: #12 Fix bug by user1`, pl.(*MatrixPayloadUnsafe).FormattedBody) + assert.Equal(t, "[[test/repo](http://localhost:3000/test/repo)] Pull request review approved: [#12 Fix bug](http://localhost:3000/test/repo/pulls/12) by [user1](https://try.gitea.io/user1)", pl.(*MatrixPayload).Body) + assert.Equal(t, `[test/repo] Pull request review approved: #12 Fix bug by user1`, pl.(*MatrixPayload).FormattedBody) }) t.Run("Repository", func(t *testing.T) { p := repositoryTestPayload() - d := new(MatrixPayloadUnsafe) + d := new(MatrixPayload) pl, err := d.Repository(p) require.NoError(t, err) require.NotNil(t, pl) - require.IsType(t, &MatrixPayloadUnsafe{}, pl) + require.IsType(t, &MatrixPayload{}, pl) - assert.Equal(t, `[[test/repo](http://localhost:3000/test/repo)] Repository created by [user1](https://try.gitea.io/user1)`, pl.(*MatrixPayloadUnsafe).Body) - assert.Equal(t, `[test/repo] Repository created by user1`, pl.(*MatrixPayloadUnsafe).FormattedBody) + assert.Equal(t, `[[test/repo](http://localhost:3000/test/repo)] Repository created by [user1](https://try.gitea.io/user1)`, pl.(*MatrixPayload).Body) + assert.Equal(t, `[test/repo] Repository created by user1`, pl.(*MatrixPayload).FormattedBody) }) t.Run("Wiki", func(t *testing.T) { p := wikiTestPayload() - d := new(MatrixPayloadUnsafe) + d := new(MatrixPayload) p.Action = api.HookWikiCreated pl, err := d.Wiki(p) require.NoError(t, err) require.NotNil(t, pl) - require.IsType(t, &MatrixPayloadUnsafe{}, pl) + require.IsType(t, &MatrixPayload{}, pl) - assert.Equal(t, "[[test/repo](http://localhost:3000/test/repo)] New wiki page '[index](http://localhost:3000/test/repo/wiki/index)' (Wiki change comment) by [user1](https://try.gitea.io/user1)", pl.(*MatrixPayloadUnsafe).Body) - assert.Equal(t, `[test/repo] New wiki page 'index' (Wiki change comment) by user1`, pl.(*MatrixPayloadUnsafe).FormattedBody) + assert.Equal(t, "[[test/repo](http://localhost:3000/test/repo)] New wiki page '[index](http://localhost:3000/test/repo/wiki/index)' (Wiki change comment) by [user1](https://try.gitea.io/user1)", pl.(*MatrixPayload).Body) + assert.Equal(t, `[test/repo] New wiki page 'index' (Wiki change comment) by user1`, pl.(*MatrixPayload).FormattedBody) p.Action = api.HookWikiEdited pl, err = d.Wiki(p) require.NoError(t, err) require.NotNil(t, pl) - require.IsType(t, &MatrixPayloadUnsafe{}, pl) + require.IsType(t, &MatrixPayload{}, pl) - assert.Equal(t, "[[test/repo](http://localhost:3000/test/repo)] Wiki page '[index](http://localhost:3000/test/repo/wiki/index)' edited (Wiki change comment) by [user1](https://try.gitea.io/user1)", pl.(*MatrixPayloadUnsafe).Body) - assert.Equal(t, `[test/repo] Wiki page 'index' edited (Wiki change comment) by user1`, pl.(*MatrixPayloadUnsafe).FormattedBody) + assert.Equal(t, "[[test/repo](http://localhost:3000/test/repo)] Wiki page '[index](http://localhost:3000/test/repo/wiki/index)' edited (Wiki change comment) by [user1](https://try.gitea.io/user1)", pl.(*MatrixPayload).Body) + assert.Equal(t, `[test/repo] Wiki page 'index' edited (Wiki change comment) by user1`, pl.(*MatrixPayload).FormattedBody) p.Action = api.HookWikiDeleted pl, err = d.Wiki(p) require.NoError(t, err) require.NotNil(t, pl) - require.IsType(t, &MatrixPayloadUnsafe{}, pl) + require.IsType(t, &MatrixPayload{}, pl) - assert.Equal(t, "[[test/repo](http://localhost:3000/test/repo)] Wiki page '[index](http://localhost:3000/test/repo/wiki/index)' deleted by [user1](https://try.gitea.io/user1)", pl.(*MatrixPayloadUnsafe).Body) - assert.Equal(t, `[test/repo] Wiki page 'index' deleted by user1`, pl.(*MatrixPayloadUnsafe).FormattedBody) + assert.Equal(t, "[[test/repo](http://localhost:3000/test/repo)] Wiki page '[index](http://localhost:3000/test/repo/wiki/index)' deleted by [user1](https://try.gitea.io/user1)", pl.(*MatrixPayload).Body) + assert.Equal(t, `[test/repo] Wiki page 'index' deleted by user1`, pl.(*MatrixPayload).FormattedBody) }) t.Run("Release", func(t *testing.T) { p := pullReleaseTestPayload() - d := new(MatrixPayloadUnsafe) + d := new(MatrixPayload) pl, err := d.Release(p) require.NoError(t, err) require.NotNil(t, pl) - require.IsType(t, &MatrixPayloadUnsafe{}, pl) + require.IsType(t, &MatrixPayload{}, pl) - assert.Equal(t, "[[test/repo](http://localhost:3000/test/repo)] Release created: [v1.0](http://localhost:3000/test/repo/releases/tag/v1.0) by [user1](https://try.gitea.io/user1)", pl.(*MatrixPayloadUnsafe).Body) - assert.Equal(t, `[test/repo] Release created: v1.0 by user1`, pl.(*MatrixPayloadUnsafe).FormattedBody) + assert.Equal(t, "[[test/repo](http://localhost:3000/test/repo)] Release created: [v1.0](http://localhost:3000/test/repo/releases/tag/v1.0) by [user1](https://try.gitea.io/user1)", pl.(*MatrixPayload).Body) + assert.Equal(t, `[test/repo] Release created: v1.0 by user1`, pl.(*MatrixPayload).FormattedBody) }) } func TestMatrixJSONPayload(t *testing.T) { p := pushTestPayload() - pl, err := new(MatrixPayloadUnsafe).Push(p) + pl, err := new(MatrixPayload).Push(p) require.NoError(t, err) require.NotNil(t, pl) - require.IsType(t, &MatrixPayloadUnsafe{}, pl) + require.IsType(t, &MatrixPayload{}, pl) json, err := pl.JSONPayload() require.NoError(t, err) assert.NotEmpty(t, json) } -func TestMatrixHookRequest(t *testing.T) { - w := &webhook_model.Webhook{} - - h := &webhook_model.HookTask{ - PayloadContent: `{ - "body": "[[user1/test](http://localhost:3000/user1/test)] user1 pushed 1 commit to [master](http://localhost:3000/user1/test/src/branch/master):\n[5175ef2](http://localhost:3000/user1/test/commit/5175ef26201c58b035a3404b3fe02b4e8d436eee): Merge pull request 'Change readme.md' (#2) from add-matrix-webhook into master\n\nReviewed-on: http://localhost:3000/user1/test/pulls/2\n - user1", - "msgtype": "m.notice", - "format": "org.matrix.custom.html", - "formatted_body": "[\u003ca href=\"http://localhost:3000/user1/test\"\u003euser1/test\u003c/a\u003e] user1 pushed 1 commit to \u003ca href=\"http://localhost:3000/user1/test/src/branch/master\"\u003emaster\u003c/a\u003e:\u003cbr\u003e\u003ca href=\"http://localhost:3000/user1/test/commit/5175ef26201c58b035a3404b3fe02b4e8d436eee\"\u003e5175ef2\u003c/a\u003e: Merge pull request 'Change readme.md' (#2) from add-matrix-webhook into master\n\nReviewed-on: http://localhost:3000/user1/test/pulls/2\n - user1", - "io.gitea.commits": [ - { - "id": "5175ef26201c58b035a3404b3fe02b4e8d436eee", - "message": "Merge pull request 'Change readme.md' (#2) from add-matrix-webhook into master\n\nReviewed-on: http://localhost:3000/user1/test/pulls/2\n", - "url": "http://localhost:3000/user1/test/commit/5175ef26201c58b035a3404b3fe02b4e8d436eee", - "author": { - "name": "user1", - "email": "user@mail.com", - "username": "" - }, - "committer": { - "name": "user1", - "email": "user@mail.com", - "username": "" - }, - "verification": null, - "timestamp": "0001-01-01T00:00:00Z", - "added": null, - "removed": null, - "modified": null - } - ], - "access_token": "dummy_access_token" -}`, - } - - wantPayloadContent := `{ - "body": "[[user1/test](http://localhost:3000/user1/test)] user1 pushed 1 commit to [master](http://localhost:3000/user1/test/src/branch/master):\n[5175ef2](http://localhost:3000/user1/test/commit/5175ef26201c58b035a3404b3fe02b4e8d436eee): Merge pull request 'Change readme.md' (#2) from add-matrix-webhook into master\n\nReviewed-on: http://localhost:3000/user1/test/pulls/2\n - user1", - "msgtype": "m.notice", - "format": "org.matrix.custom.html", - "formatted_body": "[\u003ca href=\"http://localhost:3000/user1/test\"\u003euser1/test\u003c/a\u003e] user1 pushed 1 commit to \u003ca href=\"http://localhost:3000/user1/test/src/branch/master\"\u003emaster\u003c/a\u003e:\u003cbr\u003e\u003ca href=\"http://localhost:3000/user1/test/commit/5175ef26201c58b035a3404b3fe02b4e8d436eee\"\u003e5175ef2\u003c/a\u003e: Merge pull request 'Change readme.md' (#2) from add-matrix-webhook into master\n\nReviewed-on: http://localhost:3000/user1/test/pulls/2\n - user1", - "io.gitea.commits": [ - { - "id": "5175ef26201c58b035a3404b3fe02b4e8d436eee", - "message": "Merge pull request 'Change readme.md' (#2) from add-matrix-webhook into master\n\nReviewed-on: http://localhost:3000/user1/test/pulls/2\n", - "url": "http://localhost:3000/user1/test/commit/5175ef26201c58b035a3404b3fe02b4e8d436eee", - "author": { - "name": "user1", - "email": "user@mail.com", - "username": "" - }, - "committer": { - "name": "user1", - "email": "user@mail.com", - "username": "" - }, - "verification": null, - "timestamp": "0001-01-01T00:00:00Z", - "added": null, - "removed": null, - "modified": null - } - ] -}` - - req, err := getMatrixHookRequest(w, h) - require.NoError(t, err) - require.NotNil(t, req) - - assert.Equal(t, "Bearer dummy_access_token", req.Header.Get("Authorization")) - assert.Equal(t, wantPayloadContent, h.PayloadContent) -} - func Test_getTxnID(t *testing.T) { type args struct { payload []byte diff --git a/templates/repo/settings/webhook/matrix.tmpl b/templates/repo/settings/webhook/matrix.tmpl index 8edab870cf0ce..d3ab5588bff28 100644 --- a/templates/repo/settings/webhook/matrix.tmpl +++ b/templates/repo/settings/webhook/matrix.tmpl @@ -10,10 +10,6 @@ -
- - -
+ +
+ + + {{if ne .HookType "matrix"}} + {{.locale.Tr "repo.settings.authorization_header_desc" "Bearer token123456, Basic YWxhZGRpbjpvcGVuc2VzYW1l" | Str2html}} + {{end}} +
+
diff --git a/templates/swagger/v1_json.tmpl b/templates/swagger/v1_json.tmpl index 728e88b734aa5..2fecdcf765479 100644 --- a/templates/swagger/v1_json.tmpl +++ b/templates/swagger/v1_json.tmpl @@ -14448,6 +14448,10 @@ "default": false, "x-go-name": "Active" }, + "authorization_header": { + "type": "string", + "x-go-name": "AuthorizationHeader" + }, "branch_filter": { "type": "string", "x-go-name": "BranchFilter" @@ -15433,6 +15437,10 @@ "type": "boolean", "x-go-name": "Active" }, + "authorization_header": { + "type": "string", + "x-go-name": "AuthorizationHeader" + }, "branch_filter": { "type": "string", "x-go-name": "BranchFilter" @@ -16540,6 +16548,10 @@ "type": "boolean", "x-go-name": "Active" }, + "authorization_header": { + "type": "string", + "x-go-name": "AuthorizationHeader" + }, "config": { "type": "object", "additionalProperties": { diff --git a/tests/integration/api_repo_hook_test.go b/tests/integration/api_repo_hook_test.go new file mode 100644 index 0000000000000..e503834e188c9 --- /dev/null +++ b/tests/integration/api_repo_hook_test.go @@ -0,0 +1,47 @@ +// Copyright 2022 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package integration + +import ( + "fmt" + "net/http" + "testing" + + repo_model "code.gitea.io/gitea/models/repo" + "code.gitea.io/gitea/models/unittest" + user_model "code.gitea.io/gitea/models/user" + api "code.gitea.io/gitea/modules/structs" + "code.gitea.io/gitea/tests" + + "github.com/stretchr/testify/assert" +) + +func TestAPICreateHook(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 37}) + owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) + + // user1 is an admin user + session := loginUser(t, "user1") + token := getTokenForLoggedInUser(t, session) + completeURL := func(lastSegment string) string { + return fmt.Sprintf("/api/v1/repos/%s/%s/%s?token=%s", owner.Name, repo.Name, lastSegment, token) + } + req := NewRequestWithJSON(t, "POST", completeURL("hooks"), api.CreateHookOption{ + Type: "gitea", + Config: api.CreateHookOptionConfig{ + "content_type": "json", + "url": "http://example.com/", + }, + AuthorizationHeader: "Bearer s3cr3t", + }) + resp := MakeRequest(t, req, http.StatusCreated) + + var apiHook *api.Hook + DecodeJSON(t, resp, &apiHook) + assert.Equal(t, "http://example.com/", apiHook.Config["url"]) + assert.Equal(t, "Bearer s3cr3t", apiHook.AuthorizationHeader) +} From 79bb608b426785571c96556e5c836fd442751ee9 Mon Sep 17 00:00:00 2001 From: oliverpool Date: Fri, 21 Oct 2022 08:54:29 +0200 Subject: [PATCH 03/15] add matrix help text comment --- templates/repo/settings/webhook/settings.tmpl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/repo/settings/webhook/settings.tmpl b/templates/repo/settings/webhook/settings.tmpl index 61beb75609cdf..42bc144b6f706 100644 --- a/templates/repo/settings/webhook/settings.tmpl +++ b/templates/repo/settings/webhook/settings.tmpl @@ -252,7 +252,7 @@
- {{if ne .HookType "matrix"}} + {{if ne .HookType "matrix"}}{{/* Matrix doesn't make the authorization optional but it is implied by the help string, should be changed.*/}} {{.locale.Tr "repo.settings.authorization_header_desc" "Bearer token123456, Basic YWxhZGRpbjpvcGVuc2VzYW1l" | Str2html}} {{end}}
From 223db4c09e1a3fdfecb8adcec7165e8b9fd4d505 Mon Sep 17 00:00:00 2001 From: oliverpool Date: Mon, 24 Oct 2022 08:39:55 +0200 Subject: [PATCH 04/15] rename migration --- models/migrations/migrations.go | 3 ++- models/migrations/{v229.go => v230.go} | 0 models/migrations/{v229_test.go => v230_test.go} | 0 3 files changed, 2 insertions(+), 1 deletion(-) rename models/migrations/{v229.go => v230.go} (100%) rename models/migrations/{v229_test.go => v230_test.go} (100%) diff --git a/models/migrations/migrations.go b/models/migrations/migrations.go index b6c64bfaf2e70..91d078d02ffa2 100644 --- a/models/migrations/migrations.go +++ b/models/migrations/migrations.go @@ -419,7 +419,8 @@ var migrations = []Migration{ NewMigration("Create key/value table for system settings", createSystemSettingsTable), // v228 -> v229 NewMigration("Add TeamInvite table", addTeamInviteTable), - // v229 -> v230 + + // v230 -> v231 NewMigration("Add header_authorization_encrypted column to webhook table", addHeaderAuthorizationEncryptedColWebhook), } diff --git a/models/migrations/v229.go b/models/migrations/v230.go similarity index 100% rename from models/migrations/v229.go rename to models/migrations/v230.go diff --git a/models/migrations/v229_test.go b/models/migrations/v230_test.go similarity index 100% rename from models/migrations/v229_test.go rename to models/migrations/v230_test.go From a61f722134ea4551291463604fc4df7584a6b775 Mon Sep 17 00:00:00 2001 From: oliverpool Date: Mon, 24 Oct 2022 08:52:18 +0200 Subject: [PATCH 05/15] remove unused RepoID --- services/webhook/deliver_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/webhook/deliver_test.go b/services/webhook/deliver_test.go index ec79ae60279de..83ca7d61786d3 100644 --- a/services/webhook/deliver_test.go +++ b/services/webhook/deliver_test.go @@ -68,7 +68,7 @@ func TestWebhookDeliverAuthorizationHeader(t *testing.T) { assert.NoError(t, err) assert.NoError(t, webhook_model.CreateWebhook(db.DefaultContext, hook)) - hookTask := &webhook_model.HookTask{RepoID: hook.RepoID, HookID: hook.ID, EventType: webhook_model.HookEventPush} + hookTask := &webhook_model.HookTask{HookID: hook.ID, EventType: webhook_model.HookEventPush} assert.NoError(t, Deliver(context.Background(), hookTask)) select { From 91d2709c4dd74efb8bc30a6a55f04051d5ab2b5d Mon Sep 17 00:00:00 2001 From: oliverpool Date: Mon, 24 Oct 2022 09:39:39 +0200 Subject: [PATCH 06/15] remove unused HookTask fields --- models/migrations/v230.go | 18 ++---------------- 1 file changed, 2 insertions(+), 16 deletions(-) diff --git a/models/migrations/v230.go b/models/migrations/v230.go index 60711dd367f74..941f85ab5c061 100644 --- a/models/migrations/v230.go +++ b/models/migrations/v230.go @@ -138,22 +138,8 @@ func addHeaderAuthorizationEncryptedColWebhook(x *xorm.Engine) error { // Remove access_token from HookTask type HookTask struct { - ID int64 `xorm:"pk autoincr"` - RepoID int64 `xorm:"INDEX"` - HookID int64 - UUID string - PayloadContent string `xorm:"LONGTEXT"` - EventType string - IsDelivered bool - Delivered int64 - DeliveredString string `xorm:"-"` - - // History info. - IsSucceed bool - RequestContent string `xorm:"LONGTEXT"` - // RequestInfo *HookRequest `xorm:"-"` - ResponseContent string `xorm:"LONGTEXT"` - // ResponseInfo *HookResponse `xorm:"-"` + ID int64 `xorm:"pk autoincr"` + PayloadContent string `xorm:"LONGTEXT"` } type MatrixPayloadSafe struct { From 8a4aacd0a56eabcbc7daea2a4e41c81a0677b183 Mon Sep 17 00:00:00 2001 From: oliverpool Date: Mon, 24 Oct 2022 10:16:18 +0200 Subject: [PATCH 07/15] remove unused Webhook fields --- models/migrations/v230.go | 21 ++------------------- 1 file changed, 2 insertions(+), 19 deletions(-) diff --git a/models/migrations/v230.go b/models/migrations/v230.go index 941f85ab5c061..651430d516b12 100644 --- a/models/migrations/v230.go +++ b/models/migrations/v230.go @@ -7,12 +7,10 @@ package migrations import ( "fmt" - "code.gitea.io/gitea/models/webhook" "code.gitea.io/gitea/modules/json" "code.gitea.io/gitea/modules/secret" "code.gitea.io/gitea/modules/setting" api "code.gitea.io/gitea/modules/structs" - "code.gitea.io/gitea/modules/timeutil" "xorm.io/builder" "xorm.io/xorm" @@ -58,26 +56,11 @@ func batchProcess[T any](x *xorm.Engine, query func() *xorm.Session, buf []T, pr func addHeaderAuthorizationEncryptedColWebhook(x *xorm.Engine) error { // Add the column to the table type Webhook struct { - ID int64 `xorm:"pk autoincr"` - RepoID int64 `xorm:"INDEX"` // An ID of 0 indicates either a default or system webhook - OrgID int64 `xorm:"INDEX"` - IsSystemWebhook bool - URL string `xorm:"url TEXT"` - HTTPMethod string `xorm:"http_method"` - ContentType webhook.HookContentType - Secret string `xorm:"TEXT"` - Events string `xorm:"TEXT"` - *webhook.HookEvent `xorm:"-"` - IsActive bool `xorm:"INDEX"` - Type webhook.HookType `xorm:"VARCHAR(16) 'type'"` - Meta string `xorm:"TEXT"` // store hook-specific attributes - LastStatus webhook.HookStatus // Last delivery status + ID int64 `xorm:"pk autoincr"` + Meta string `xorm:"TEXT"` // store hook-specific attributes // HeaderAuthorizationEncrypted should be accessed using HeaderAuthorization() and SetHeaderAuthorization() HeaderAuthorizationEncrypted string `xorm:"TEXT"` - - CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"` - UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"` } err := x.Sync2(new(Webhook)) if err != nil { From 6dc80761e894bcfbb6f7ae197bcda6f169f9d154 Mon Sep 17 00:00:00 2001 From: oliverpool Date: Mon, 24 Oct 2022 10:58:39 +0200 Subject: [PATCH 08/15] rename migration --- models/migrations/{v230.go => v231.go} | 0 models/migrations/{v230_test.go => v231_test.go} | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename models/migrations/{v230.go => v231.go} (100%) rename models/migrations/{v230_test.go => v231_test.go} (100%) diff --git a/models/migrations/v230.go b/models/migrations/v231.go similarity index 100% rename from models/migrations/v230.go rename to models/migrations/v231.go diff --git a/models/migrations/v230_test.go b/models/migrations/v231_test.go similarity index 100% rename from models/migrations/v230_test.go rename to models/migrations/v231_test.go From e75ebebb589905a33ebda5170e04e68989755ed4 Mon Sep 17 00:00:00 2001 From: oliverpool Date: Mon, 24 Oct 2022 10:59:30 +0200 Subject: [PATCH 09/15] use x.Sync --- models/migrations/v231.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/models/migrations/v231.go b/models/migrations/v231.go index 651430d516b12..b29517f706186 100644 --- a/models/migrations/v231.go +++ b/models/migrations/v231.go @@ -62,7 +62,7 @@ func addHeaderAuthorizationEncryptedColWebhook(x *xorm.Engine) error { // HeaderAuthorizationEncrypted should be accessed using HeaderAuthorization() and SetHeaderAuthorization() HeaderAuthorizationEncrypted string `xorm:"TEXT"` } - err := x.Sync2(new(Webhook)) + err := x.Sync(new(Webhook)) if err != nil { return err } From 05e6713555988a9e2926fd0d20e404cb47dc4149 Mon Sep 17 00:00:00 2001 From: oliverpool Date: Mon, 24 Oct 2022 11:27:18 +0200 Subject: [PATCH 10/15] filter on hook_id for migration --- .../hook_task.yml | 2 + .../webhook.yml | 24 ----------- models/migrations/v231.go | 16 ++++++-- models/migrations/v231_test.go | 40 +++---------------- 4 files changed, 20 insertions(+), 62 deletions(-) diff --git a/models/migrations/fixtures/Test_addHeaderAuthorizationEncryptedColWebhook/hook_task.yml b/models/migrations/fixtures/Test_addHeaderAuthorizationEncryptedColWebhook/hook_task.yml index 258089a97131c..8f61d6e70b2d6 100644 --- a/models/migrations/fixtures/Test_addHeaderAuthorizationEncryptedColWebhook/hook_task.yml +++ b/models/migrations/fixtures/Test_addHeaderAuthorizationEncryptedColWebhook/hook_task.yml @@ -1,6 +1,8 @@ # unsafe payload - id: 1 + hook_id: 1 payload_content: '{"homeserver_url":"https://matrix.example.com","room_id":"roomID","access_token":"s3cr3t","message_type":1}' # safe payload - id: 2 + hook_id: 2 payload_content: '{"homeserver_url":"https://matrix.example.com","room_id":"roomID","message_type":1}' diff --git a/models/migrations/fixtures/Test_addHeaderAuthorizationEncryptedColWebhook/webhook.yml b/models/migrations/fixtures/Test_addHeaderAuthorizationEncryptedColWebhook/webhook.yml index f0212e61a51aa..ec6f9bffaef2a 100644 --- a/models/migrations/fixtures/Test_addHeaderAuthorizationEncryptedColWebhook/webhook.yml +++ b/models/migrations/fixtures/Test_addHeaderAuthorizationEncryptedColWebhook/webhook.yml @@ -1,34 +1,10 @@ # matrix webhook - id: 1 - repo_id: 1 - org_id: 0 - is_system_webhook: 0 - url: https://matrix.example.com/_matrix/client/r0/rooms/roomID/send/m.room.message - http_method: PUT - content_type: 1 - secret: '' - events: '{"push_only":true,"send_everything":false,"choose_events":false,"branch_filter":"*","events":{"create":false,"delete":false,"fork":false,"issues":false,"issue_assign":false,"issue_label":false,"issue_milestone":false,"issue_comment":false,"push":false,"pull_request":false,"pull_request_assign":false,"pull_request_label":false,"pull_request_milestone":false,"pull_request_comment":false,"pull_request_review":false,"pull_request_sync":false,"repository":false,"release":false,"package":false}}' - is_active: 1 type: matrix meta: '{"homeserver_url":"https://matrix.example.com","room_id":"roomID","access_token":"s3cr3t","message_type":1}' - last_status: 0 header_authorization_encrypted: '' - created_unix: 1661677165 - updated_unix: 1661677165 # gitea webhook - id: 2 - repo_id: 1 - org_id: 0 - is_system_webhook: 0 - url: https://gitea.example.com - http_method: POST - content_type: 1 - secret: '' - events: '{"push_only":true,"send_everything":false,"choose_events":false,"branch_filter":"*","events":{"create":false,"delete":false,"fork":false,"issues":false,"issue_assign":false,"issue_label":false,"issue_milestone":false,"issue_comment":false,"push":false,"pull_request":false,"pull_request_assign":false,"pull_request_label":false,"pull_request_milestone":false,"pull_request_comment":false,"pull_request_review":false,"pull_request_sync":false,"repository":false,"release":false,"package":false}}' - is_active: 1 type: gitea meta: '' - last_status: 0 header_authorization_encrypted: '' - created_unix: 1661677348 - updated_unix: 1661677348 diff --git a/models/migrations/v231.go b/models/migrations/v231.go index b29517f706186..f4b7864c01607 100644 --- a/models/migrations/v231.go +++ b/models/migrations/v231.go @@ -7,6 +7,7 @@ package migrations import ( "fmt" + "code.gitea.io/gitea/models/webhook" "code.gitea.io/gitea/modules/json" "code.gitea.io/gitea/modules/secret" "code.gitea.io/gitea/modules/setting" @@ -56,8 +57,9 @@ func batchProcess[T any](x *xorm.Engine, query func() *xorm.Session, buf []T, pr func addHeaderAuthorizationEncryptedColWebhook(x *xorm.Engine) error { // Add the column to the table type Webhook struct { - ID int64 `xorm:"pk autoincr"` - Meta string `xorm:"TEXT"` // store hook-specific attributes + ID int64 `xorm:"pk autoincr"` + Type webhook.HookType `xorm:"VARCHAR(16) 'type'"` + Meta string `xorm:"TEXT"` // store hook-specific attributes // HeaderAuthorizationEncrypted should be accessed using HeaderAuthorization() and SetHeaderAuthorization() HeaderAuthorizationEncrypted string `xorm:"TEXT"` @@ -121,7 +123,8 @@ func addHeaderAuthorizationEncryptedColWebhook(x *xorm.Engine) error { // Remove access_token from HookTask type HookTask struct { - ID int64 `xorm:"pk autoincr"` + ID int64 `xorm:"pk autoincr"` + HookID int64 PayloadContent string `xorm:"LONGTEXT"` } @@ -138,7 +141,12 @@ func addHeaderAuthorizationEncryptedColWebhook(x *xorm.Engine) error { } err = batchProcess(x, - func() *xorm.Session { return x.Where(builder.Like{"payload_content", "access_token"}).OrderBy("id") }, + func() *xorm.Session { + return x.Where(builder.And( + builder.In("hook_id", builder.Select("id").From("webhook").Where(builder.Eq{"type": "matrix"})), + builder.Like{"payload_content", "access_token"}, + )).OrderBy("id") + }, make([]*HookTask, 0, 50), func(sess *xorm.Session, hookTask *HookTask) error { // retrieve token from payload_content diff --git a/models/migrations/v231_test.go b/models/migrations/v231_test.go index a1198b4123508..d293c91a73806 100644 --- a/models/migrations/v231_test.go +++ b/models/migrations/v231_test.go @@ -11,7 +11,6 @@ import ( "code.gitea.io/gitea/modules/json" "code.gitea.io/gitea/modules/secret" "code.gitea.io/gitea/modules/setting" - "code.gitea.io/gitea/modules/timeutil" "github.com/stretchr/testify/assert" ) @@ -19,26 +18,12 @@ import ( func Test_addHeaderAuthorizationEncryptedColWebhook(t *testing.T) { // Create Webhook table type Webhook struct { - ID int64 `xorm:"pk autoincr"` - RepoID int64 `xorm:"INDEX"` // An ID of 0 indicates either a default or system webhook - OrgID int64 `xorm:"INDEX"` - IsSystemWebhook bool - URL string `xorm:"url TEXT"` - HTTPMethod string `xorm:"http_method"` - ContentType webhook.HookContentType - Secret string `xorm:"TEXT"` - Events string `xorm:"TEXT"` - *webhook.HookEvent `xorm:"-"` - IsActive bool `xorm:"INDEX"` - Type webhook.HookType `xorm:"VARCHAR(16) 'type'"` - Meta string `xorm:"TEXT"` // store hook-specific attributes - LastStatus webhook.HookStatus // Last delivery status + ID int64 `xorm:"pk autoincr"` + Type webhook.HookType `xorm:"VARCHAR(16) 'type'"` + Meta string `xorm:"TEXT"` // store hook-specific attributes // HeaderAuthorizationEncrypted should be accessed using HeaderAuthorization() and SetHeaderAuthorization() HeaderAuthorizationEncrypted string `xorm:"TEXT"` - - CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"` - UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"` } type ExpectedWebhook struct { @@ -48,22 +33,9 @@ func Test_addHeaderAuthorizationEncryptedColWebhook(t *testing.T) { } type HookTask struct { - ID int64 `xorm:"pk autoincr"` - RepoID int64 `xorm:"INDEX"` - HookID int64 - UUID string - PayloadContent string `xorm:"LONGTEXT"` - EventType string - IsDelivered bool - Delivered int64 - DeliveredString string `xorm:"-"` - - // History info. - IsSucceed bool - RequestContent string `xorm:"LONGTEXT"` - // RequestInfo *HookRequest `xorm:"-"` - ResponseContent string `xorm:"LONGTEXT"` - // ResponseInfo *HookResponse `xorm:"-"` + ID int64 `xorm:"pk autoincr"` + HookID int64 + PayloadContent string `xorm:"LONGTEXT"` } // Prepare and load the testing database From 5a4c564cde21b046bb28642787f4758eba483321 Mon Sep 17 00:00:00 2001 From: oliverpool <3864879+oliverpool@users.noreply.github.com> Date: Mon, 24 Oct 2022 16:44:28 +0200 Subject: [PATCH 11/15] Apply fmt suggestions from gusted Co-authored-by: Gusted --- models/migrations/v231.go | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/models/migrations/v231.go b/models/migrations/v231.go index f4b7864c01607..d36cec6e39709 100644 --- a/models/migrations/v231.go +++ b/models/migrations/v231.go @@ -89,7 +89,7 @@ func addHeaderAuthorizationEncryptedColWebhook(x *xorm.Engine) error { var withToken MatrixMetaWithAccessToken err := json.Unmarshal([]byte(hook.Meta), &withToken) if err != nil { - return fmt.Errorf("unable to unmarshal matrix meta for webhook[%d]: %w", hook.ID, err) + return fmt.Errorf("unable to unmarshal matrix meta for webhook[id=%d]: %w", hook.ID, err) } if withToken.AccessToken == "" { return nil @@ -99,20 +99,20 @@ func addHeaderAuthorizationEncryptedColWebhook(x *xorm.Engine) error { authorization := "Bearer " + withToken.AccessToken hook.HeaderAuthorizationEncrypted, err = secret.EncryptSecret(setting.SecretKey, authorization) if err != nil { - return fmt.Errorf("unable to encrypt access token for webhook[%d]: %w", hook.ID, err) + return fmt.Errorf("unable to encrypt access token for webhook[id=%d]: %w", hook.ID, err) } // remove token from meta withoutToken, err := json.Marshal(withToken.MatrixMeta) if err != nil { - return fmt.Errorf("unable to marshal matrix meta for webhook[%d]: %w", hook.ID, err) + return fmt.Errorf("unable to marshal matrix meta for webhook[id=%d]: %w", hook.ID, err) } hook.Meta = string(withoutToken) // save in database count, err := sess.ID(hook.ID).Cols("meta", "header_authorization_encrypted").Update(hook) if count != 1 || err != nil { - return fmt.Errorf("unable to update header_authorization_encrypted for webhook[%d]: %d,%w", hook.ID, count, err) + return fmt.Errorf("unable to update header_authorization_encrypted for webhook[id=%d]: %d,%w", hook.ID, count, err) } return nil }) @@ -153,7 +153,7 @@ func addHeaderAuthorizationEncryptedColWebhook(x *xorm.Engine) error { var withToken MatrixPayloadUnsafe err := json.Unmarshal([]byte(hookTask.PayloadContent), &withToken) if err != nil { - return fmt.Errorf("unable to unmarshal payload_content for hook_task[%d]: %w", hookTask.ID, err) + return fmt.Errorf("unable to unmarshal payload_content for hook_task[id=%d]: %w", hookTask.ID, err) } if withToken.AccessToken == "" { return nil @@ -162,14 +162,14 @@ func addHeaderAuthorizationEncryptedColWebhook(x *xorm.Engine) error { // remove token from payload_content withoutToken, err := json.Marshal(withToken.MatrixPayloadSafe) if err != nil { - return fmt.Errorf("unable to marshal payload_content for hook_task[%d]: %w", hookTask.ID, err) + return fmt.Errorf("unable to marshal payload_content for hook_task[id=%d]: %w", hookTask.ID, err) } hookTask.PayloadContent = string(withoutToken) // save in database count, err := sess.ID(hookTask.ID).Cols("payload_content").Update(hookTask) if count != 1 || err != nil { - return fmt.Errorf("unable to update payload_content for hook_task[%d]: %d,%w", hookTask.ID, count, err) + return fmt.Errorf("unable to update payload_content for hook_task[id=%d]: %d,%w", hookTask.ID, count, err) } return nil }) From 69a22a4dcd1af246795e24bd71ff755bc649d88d Mon Sep 17 00:00:00 2001 From: oliverpool Date: Mon, 24 Oct 2022 16:45:26 +0200 Subject: [PATCH 12/15] reorder func arguments --- docs/content/doc/features/webhooks.en-us.md | 2 +- models/migrations/v231.go | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/content/doc/features/webhooks.en-us.md b/docs/content/doc/features/webhooks.en-us.md index ecbe13c3adb21..2643036662010 100644 --- a/docs/content/doc/features/webhooks.en-us.md +++ b/docs/content/doc/features/webhooks.en-us.md @@ -191,4 +191,4 @@ There is a Test Delivery button in the webhook settings that allows to test the ### Authorization header -**With 1.19**, Gitea hooks can be configured to send an [authorization header](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Authorization) to the webhook target. +**With 1.18**, Gitea hooks can be configured to send an [authorization header](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Authorization) to the webhook target. diff --git a/models/migrations/v231.go b/models/migrations/v231.go index d36cec6e39709..a6c4838619347 100644 --- a/models/migrations/v231.go +++ b/models/migrations/v231.go @@ -17,7 +17,7 @@ import ( "xorm.io/xorm" ) -func batchProcess[T any](x *xorm.Engine, query func() *xorm.Session, buf []T, process func(*xorm.Session, T) error) error { +func batchProcess[T any](x *xorm.Engine, buf []T, query func() *xorm.Session, process func(*xorm.Session, T) error) error { size := cap(buf) start := 0 for { @@ -82,8 +82,8 @@ func addHeaderAuthorizationEncryptedColWebhook(x *xorm.Engine) error { } err = batchProcess(x, - func() *xorm.Session { return x.Where("type=?", "matrix").OrderBy("id") }, make([]*Webhook, 0, 50), + func() *xorm.Session { return x.Where("type=?", "matrix").OrderBy("id") }, func(sess *xorm.Session, hook *Webhook) error { // retrieve token from meta var withToken MatrixMetaWithAccessToken @@ -141,13 +141,13 @@ func addHeaderAuthorizationEncryptedColWebhook(x *xorm.Engine) error { } err = batchProcess(x, + make([]*HookTask, 0, 50), func() *xorm.Session { return x.Where(builder.And( builder.In("hook_id", builder.Select("id").From("webhook").Where(builder.Eq{"type": "matrix"})), builder.Like{"payload_content", "access_token"}, )).OrderBy("id") }, - make([]*HookTask, 0, 50), func(sess *xorm.Session, hookTask *HookTask) error { // retrieve token from payload_content var withToken MatrixPayloadUnsafe From 7bf090a374be665b17fdf87390203a5c8c4f01cc Mon Sep 17 00:00:00 2001 From: oliverpool Date: Mon, 24 Oct 2022 16:50:36 +0200 Subject: [PATCH 13/15] fix limit,start bug --- models/migrations/v231.go | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/models/migrations/v231.go b/models/migrations/v231.go index a6c4838619347..204c551518794 100644 --- a/models/migrations/v231.go +++ b/models/migrations/v231.go @@ -17,11 +17,11 @@ import ( "xorm.io/xorm" ) -func batchProcess[T any](x *xorm.Engine, buf []T, query func() *xorm.Session, process func(*xorm.Session, T) error) error { +func batchProcess[T any](x *xorm.Engine, buf []T, query func(limit, start int) *xorm.Session, process func(*xorm.Session, T) error) error { size := cap(buf) start := 0 for { - err := query().Limit(size, start).Find(&buf) + err := query(size, start).Find(&buf) if err != nil { return err } @@ -83,7 +83,9 @@ func addHeaderAuthorizationEncryptedColWebhook(x *xorm.Engine) error { err = batchProcess(x, make([]*Webhook, 0, 50), - func() *xorm.Session { return x.Where("type=?", "matrix").OrderBy("id") }, + func(limit, start int) *xorm.Session { + return x.Where("type=?", "matrix").OrderBy("id").Limit(limit, start) + }, func(sess *xorm.Session, hook *Webhook) error { // retrieve token from meta var withToken MatrixMetaWithAccessToken @@ -142,11 +144,11 @@ func addHeaderAuthorizationEncryptedColWebhook(x *xorm.Engine) error { err = batchProcess(x, make([]*HookTask, 0, 50), - func() *xorm.Session { + func(limit, start int) *xorm.Session { return x.Where(builder.And( builder.In("hook_id", builder.Select("id").From("webhook").Where(builder.Eq{"type": "matrix"})), builder.Like{"payload_content", "access_token"}, - )).OrderBy("id") + )).OrderBy("id").Limit(limit, 0) // ignore the provided "start", since other payload were already converted and don't contain 'payload_content' anymore }, func(sess *xorm.Session, hookTask *HookTask) error { // retrieve token from payload_content From a66e44d31d50b2d3863ee028c9cc79a87408b6ce Mon Sep 17 00:00:00 2001 From: oliverpool <3864879+oliverpool@users.noreply.github.com> Date: Wed, 26 Oct 2022 10:40:16 +0200 Subject: [PATCH 14/15] Didn't make it into 1.18 :confused: Co-authored-by: delvh --- docs/content/doc/features/webhooks.en-us.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/content/doc/features/webhooks.en-us.md b/docs/content/doc/features/webhooks.en-us.md index 2643036662010..ecbe13c3adb21 100644 --- a/docs/content/doc/features/webhooks.en-us.md +++ b/docs/content/doc/features/webhooks.en-us.md @@ -191,4 +191,4 @@ There is a Test Delivery button in the webhook settings that allows to test the ### Authorization header -**With 1.18**, Gitea hooks can be configured to send an [authorization header](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Authorization) to the webhook target. +**With 1.19**, Gitea hooks can be configured to send an [authorization header](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Authorization) to the webhook target. From 4933fe90225fdf43bd051f9b9a01128e0dcd823c Mon Sep 17 00:00:00 2001 From: oliverpool Date: Thu, 3 Nov 2022 18:36:48 +0100 Subject: [PATCH 15/15] fix migration import --- models/migrations/migrations.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/models/migrations/migrations.go b/models/migrations/migrations.go index 30ebd6a8b946b..5f5ec8fdd7136 100644 --- a/models/migrations/migrations.go +++ b/models/migrations/migrations.go @@ -436,7 +436,7 @@ var migrations = []Migration{ // v232 -> v233 NewMigration("Alter package_version.metadata_json to LONGTEXT", v1_19.AlterPackageVersionMetadataToLongText), // v233 -> v234 - NewMigration("Add header_authorization_encrypted column to webhook table", addHeaderAuthorizationEncryptedColWebhook), + NewMigration("Add header_authorization_encrypted column to webhook table", v1_19.AddHeaderAuthorizationEncryptedColWebhook), } // GetCurrentDBVersion returns the current db version