mirror of
				https://github.com/go-gitea/gitea.git
				synced 2025-10-31 21:28:11 +09:00 
			
		
		
		
	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:
		| @@ -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 New Issue
	
	Block a user