mirror of
				https://gitee.com/gitea/gitea
				synced 2025-11-04 08:30:25 +08:00 
			
		
		
		
	Refactor Webhook + Add X-Hub-Signature (#16176)
This PR removes multiple unneeded fields from the `HookTask` struct and adds the two headers `X-Hub-Signature` and `X-Hub-Signature-256`. ## ⚠️ BREAKING ⚠️ * The `Secret` field is no longer passed as part of the payload. * "Breaking" change (or fix?): The webhook history shows the real called url and not the url registered in the webhook (`deliver.go`@129). Close #16115 Fixes #7788 Fixes #11755 Co-authored-by: zeripath <art27@cantab.net>
This commit is contained in:
		@@ -6,8 +6,13 @@ package webhook
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"context"
 | 
			
		||||
	"crypto/hmac"
 | 
			
		||||
	"crypto/sha1"
 | 
			
		||||
	"crypto/sha256"
 | 
			
		||||
	"crypto/tls"
 | 
			
		||||
	"encoding/hex"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"io"
 | 
			
		||||
	"io/ioutil"
 | 
			
		||||
	"net"
 | 
			
		||||
	"net/http"
 | 
			
		||||
@@ -26,27 +31,32 @@ import (
 | 
			
		||||
 | 
			
		||||
// Deliver deliver hook task
 | 
			
		||||
func Deliver(t *models.HookTask) error {
 | 
			
		||||
	w, err := models.GetWebhookByID(t.HookID)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	defer func() {
 | 
			
		||||
		err := recover()
 | 
			
		||||
		if err == nil {
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
		// There was a panic whilst delivering a hook...
 | 
			
		||||
		log.Error("PANIC whilst trying to deliver webhook[%d] for repo[%d] to %s Panic: %v\nStacktrace: %s", t.ID, t.RepoID, t.URL, err, log.Stack(2))
 | 
			
		||||
		log.Error("PANIC whilst trying to deliver webhook[%d] for repo[%d] to %s Panic: %v\nStacktrace: %s", t.ID, t.RepoID, w.URL, err, log.Stack(2))
 | 
			
		||||
	}()
 | 
			
		||||
 | 
			
		||||
	t.IsDelivered = true
 | 
			
		||||
 | 
			
		||||
	var req *http.Request
 | 
			
		||||
	var err error
 | 
			
		||||
 | 
			
		||||
	switch t.HTTPMethod {
 | 
			
		||||
	switch w.HTTPMethod {
 | 
			
		||||
	case "":
 | 
			
		||||
		log.Info("HTTP Method for webhook %d empty, setting to POST as default", t.ID)
 | 
			
		||||
		fallthrough
 | 
			
		||||
	case http.MethodPost:
 | 
			
		||||
		switch t.ContentType {
 | 
			
		||||
		switch w.ContentType {
 | 
			
		||||
		case models.ContentTypeJSON:
 | 
			
		||||
			req, err = http.NewRequest("POST", t.URL, strings.NewReader(t.PayloadContent))
 | 
			
		||||
			req, err = http.NewRequest("POST", w.URL, strings.NewReader(t.PayloadContent))
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
@@ -57,16 +67,15 @@ func Deliver(t *models.HookTask) error {
 | 
			
		||||
				"payload": []string{t.PayloadContent},
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			req, err = http.NewRequest("POST", t.URL, strings.NewReader(forms.Encode()))
 | 
			
		||||
			req, err = http.NewRequest("POST", w.URL, strings.NewReader(forms.Encode()))
 | 
			
		||||
			if err != nil {
 | 
			
		||||
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
 | 
			
		||||
		}
 | 
			
		||||
	case http.MethodGet:
 | 
			
		||||
		u, err := url.Parse(t.URL)
 | 
			
		||||
		u, err := url.Parse(w.URL)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
@@ -78,31 +87,48 @@ func Deliver(t *models.HookTask) error {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
	case http.MethodPut:
 | 
			
		||||
		switch t.Typ {
 | 
			
		||||
		switch w.Type {
 | 
			
		||||
		case models.MATRIX:
 | 
			
		||||
			req, err = getMatrixHookRequest(t)
 | 
			
		||||
			req, err = getMatrixHookRequest(w, t)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
		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, w.HTTPMethod)
 | 
			
		||||
		}
 | 
			
		||||
	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, w.HTTPMethod)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var signatureSHA1 string
 | 
			
		||||
	var signatureSHA256 string
 | 
			
		||||
	if len(w.Secret) > 0 {
 | 
			
		||||
		sig1 := hmac.New(sha1.New, []byte(w.Secret))
 | 
			
		||||
		sig256 := hmac.New(sha256.New, []byte(w.Secret))
 | 
			
		||||
		_, err = io.MultiWriter(sig1, sig256).Write([]byte(t.PayloadContent))
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			log.Error("prepareWebhooks.sigWrite: %v", err)
 | 
			
		||||
		}
 | 
			
		||||
		signatureSHA1 = hex.EncodeToString(sig1.Sum(nil))
 | 
			
		||||
		signatureSHA256 = hex.EncodeToString(sig256.Sum(nil))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	req.Header.Add("X-Gitea-Delivery", t.UUID)
 | 
			
		||||
	req.Header.Add("X-Gitea-Event", t.EventType.Event())
 | 
			
		||||
	req.Header.Add("X-Gitea-Signature", t.Signature)
 | 
			
		||||
	req.Header.Add("X-Gitea-Signature", signatureSHA256)
 | 
			
		||||
	req.Header.Add("X-Gogs-Delivery", t.UUID)
 | 
			
		||||
	req.Header.Add("X-Gogs-Event", t.EventType.Event())
 | 
			
		||||
	req.Header.Add("X-Gogs-Signature", t.Signature)
 | 
			
		||||
	req.Header.Add("X-Gogs-Signature", signatureSHA256)
 | 
			
		||||
	req.Header.Add("X-Hub-Signature", "sha1="+signatureSHA1)
 | 
			
		||||
	req.Header.Add("X-Hub-Signature-256", "sha256="+signatureSHA256)
 | 
			
		||||
	req.Header["X-GitHub-Delivery"] = []string{t.UUID}
 | 
			
		||||
	req.Header["X-GitHub-Event"] = []string{t.EventType.Event()}
 | 
			
		||||
 | 
			
		||||
	// Record delivery information.
 | 
			
		||||
	t.RequestInfo = &models.HookRequest{
 | 
			
		||||
		Headers: map[string]string{},
 | 
			
		||||
		URL:        req.URL.String(),
 | 
			
		||||
		HTTPMethod: req.Method,
 | 
			
		||||
		Headers:    map[string]string{},
 | 
			
		||||
	}
 | 
			
		||||
	for k, vals := range req.Header {
 | 
			
		||||
		t.RequestInfo.Headers[k] = strings.Join(vals, ",")
 | 
			
		||||
@@ -125,11 +151,6 @@ func Deliver(t *models.HookTask) error {
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// Update webhook last delivery status.
 | 
			
		||||
		w, err := models.GetWebhookByID(t.HookID)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			log.Error("GetWebhookByID: %v", err)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
		if t.IsSucceed {
 | 
			
		||||
			w.LastStatus = models.HookStatusSucceed
 | 
			
		||||
		} else {
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user