Don't use legacy method to send Matrix Webhook (#12348)
* Don't use legacy send for messages * Add migrations to ensure Matrix webhooks use PUT * Set HTTP method to PUT as default * Fix sql condition.. Signed-off-by: Till Faelligen <tfaelligen@gmail.com> * Rename getTxnID -> getMatrixTxnID * Use local variable instead of constant value Co-authored-by: techknowlogick <techknowlogick@gitea.io>
This commit is contained in:
parent
f6d5303e02
commit
bf60146444
6 changed files with 87 additions and 8 deletions
|
@ -220,6 +220,8 @@ var migrations = []Migration{
|
||||||
NewMigration("Ensure Repository.IsArchived is not null", setIsArchivedToFalse),
|
NewMigration("Ensure Repository.IsArchived is not null", setIsArchivedToFalse),
|
||||||
// v143 -> v144
|
// v143 -> v144
|
||||||
NewMigration("recalculate Stars number for all user", recalculateStars),
|
NewMigration("recalculate Stars number for all user", recalculateStars),
|
||||||
|
// v144 -> v145
|
||||||
|
NewMigration("update Matrix Webhook http method to 'PUT'", updateMatrixWebhookHTTPMethod),
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetCurrentDBVersion returns the current db version
|
// GetCurrentDBVersion returns the current db version
|
||||||
|
|
25
models/migrations/v144.go
Normal file
25
models/migrations/v144.go
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
// Copyright 2020 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 (
|
||||||
|
"code.gitea.io/gitea/modules/log"
|
||||||
|
"xorm.io/builder"
|
||||||
|
"xorm.io/xorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
func updateMatrixWebhookHTTPMethod(x *xorm.Engine) error {
|
||||||
|
var matrixHookTaskType = 9 // value comes from the models package
|
||||||
|
type Webhook struct {
|
||||||
|
HTTPMethod string
|
||||||
|
}
|
||||||
|
|
||||||
|
cond := builder.Eq{"hook_task_type": matrixHookTaskType}.And(builder.Neq{"http_method": "PUT"})
|
||||||
|
count, err := x.Where(cond).Cols("http_method").Update(&Webhook{HTTPMethod: "PUT"})
|
||||||
|
if err == nil {
|
||||||
|
log.Debug("Updated %d Matrix webhooks with http_method 'PUT'", count)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
|
@ -77,17 +77,20 @@ func Deliver(t *models.HookTask) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
case http.MethodPut:
|
||||||
|
switch t.Type {
|
||||||
|
case models.MATRIX:
|
||||||
|
req, err = getMatrixHookRequest(t)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("Invalid http method for webhook: [%d] %v", t.ID, t.HTTPMethod)
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("Invalid http method for webhook: [%d] %v", t.ID, t.HTTPMethod)
|
return fmt.Errorf("Invalid http method for webhook: [%d] %v", t.ID, t.HTTPMethod)
|
||||||
}
|
}
|
||||||
|
|
||||||
if t.Type == models.MATRIX {
|
|
||||||
req, err = getMatrixHookRequest(t)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
req.Header.Add("X-Gitea-Delivery", t.UUID)
|
req.Header.Add("X-Gitea-Delivery", t.UUID)
|
||||||
req.Header.Add("X-Gitea-Event", t.EventType.Event())
|
req.Header.Add("X-Gitea-Event", t.EventType.Event())
|
||||||
req.Header.Add("X-Gitea-Signature", t.Signature)
|
req.Header.Add("X-Gitea-Signature", t.Signature)
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
package webhook
|
package webhook
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/sha1"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
@ -291,7 +292,14 @@ func getMatrixHookRequest(t *models.HookTask) (*http.Request, error) {
|
||||||
}
|
}
|
||||||
t.PayloadContent = string(payload)
|
t.PayloadContent = string(payload)
|
||||||
|
|
||||||
req, err := http.NewRequest("POST", t.URL, strings.NewReader(string(payload)))
|
txnID, err := getMatrixTxnID(payload)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("getMatrixHookRequest: unable to hash payload: %+v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
t.URL = fmt.Sprintf("%s/%s", t.URL, txnID)
|
||||||
|
|
||||||
|
req, err := http.NewRequest(t.HTTPMethod, t.URL, strings.NewReader(string(payload)))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -301,3 +309,14 @@ func getMatrixHookRequest(t *models.HookTask) (*http.Request, error) {
|
||||||
|
|
||||||
return req, nil
|
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
|
||||||
|
}
|
||||||
|
|
|
@ -154,3 +154,32 @@ func TestMatrixHookRequest(t *testing.T) {
|
||||||
assert.Equal(t, "Bearer dummy_access_token", req.Header.Get("Authorization"))
|
assert.Equal(t, "Bearer dummy_access_token", req.Header.Get("Authorization"))
|
||||||
assert.Equal(t, wantPayloadContent, h.PayloadContent)
|
assert.Equal(t, wantPayloadContent, h.PayloadContent)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Test_getTxnID(t *testing.T) {
|
||||||
|
type args struct {
|
||||||
|
payload []byte
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
args args
|
||||||
|
want string
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "dummy payload",
|
||||||
|
args: args{payload: []byte("Hello World")},
|
||||||
|
want: "0a4d55a8d778e5022fab701977c5d840bbc486d0",
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
got, err := getMatrixTxnID(tt.args.payload)
|
||||||
|
if (err != nil) != tt.wantErr {
|
||||||
|
t.Errorf("getMatrixTxnID() error = %v, wantErr %v", err, tt.wantErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
assert.Equal(t, tt.want, got)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -454,6 +454,7 @@ func MatrixHooksNewPost(ctx *context.Context, form auth.NewMatrixHookForm) {
|
||||||
RepoID: orCtx.RepoID,
|
RepoID: orCtx.RepoID,
|
||||||
URL: fmt.Sprintf("%s/_matrix/client/r0/rooms/%s/send/m.room.message", form.HomeserverURL, form.RoomID),
|
URL: fmt.Sprintf("%s/_matrix/client/r0/rooms/%s/send/m.room.message", form.HomeserverURL, form.RoomID),
|
||||||
ContentType: models.ContentTypeJSON,
|
ContentType: models.ContentTypeJSON,
|
||||||
|
HTTPMethod: "PUT",
|
||||||
HookEvent: ParseHookEvent(form.WebhookForm),
|
HookEvent: ParseHookEvent(form.WebhookForm),
|
||||||
IsActive: form.Active,
|
IsActive: form.Active,
|
||||||
HookTaskType: models.MATRIX,
|
HookTaskType: models.MATRIX,
|
||||||
|
|
Reference in a new issue