mirror of
				https://gitee.com/gitea/gitea
				synced 2025-11-04 16:40:24 +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:
		@@ -323,6 +323,8 @@ var migrations = []Migration{
 | 
			
		||||
	NewMigration("Add new table repo_archiver", addRepoArchiver),
 | 
			
		||||
	// v186 -> v187
 | 
			
		||||
	NewMigration("Create protected tag table", createProtectedTagTable),
 | 
			
		||||
	// v187 -> v188
 | 
			
		||||
	NewMigration("Drop unneeded webhook related columns", dropWebhookColumns),
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetCurrentDBVersion returns the current db version
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										46
									
								
								models/migrations/v187.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								models/migrations/v187.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,46 @@
 | 
			
		||||
// Copyright 2021 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 (
 | 
			
		||||
	"xorm.io/xorm"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func dropWebhookColumns(x *xorm.Engine) error {
 | 
			
		||||
	// Make sure the columns exist before dropping them
 | 
			
		||||
	type Webhook struct {
 | 
			
		||||
		Signature string `xorm:"TEXT"`
 | 
			
		||||
		IsSSL     bool   `xorm:"is_ssl"`
 | 
			
		||||
	}
 | 
			
		||||
	if err := x.Sync2(new(Webhook)); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	type HookTask struct {
 | 
			
		||||
		Typ         string `xorm:"VARCHAR(16) index"`
 | 
			
		||||
		URL         string `xorm:"TEXT"`
 | 
			
		||||
		Signature   string `xorm:"TEXT"`
 | 
			
		||||
		HTTPMethod  string `xorm:"http_method"`
 | 
			
		||||
		ContentType int
 | 
			
		||||
		IsSSL       bool
 | 
			
		||||
	}
 | 
			
		||||
	if err := x.Sync2(new(HookTask)); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	sess := x.NewSession()
 | 
			
		||||
	defer sess.Close()
 | 
			
		||||
	if err := sess.Begin(); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	if err := dropTableColumns(sess, "webhook", "signature", "is_ssl"); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	if err := dropTableColumns(sess, "hook_task", "typ", "url", "signature", "http_method", "content_type", "is_ssl"); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return sess.Commit()
 | 
			
		||||
}
 | 
			
		||||
@@ -109,6 +109,22 @@ type HookEvent struct {
 | 
			
		||||
	HookEvents `json:"events"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// HookType is the type of a webhook
 | 
			
		||||
type HookType = string
 | 
			
		||||
 | 
			
		||||
// Types of webhooks
 | 
			
		||||
const (
 | 
			
		||||
	GITEA    HookType = "gitea"
 | 
			
		||||
	GOGS     HookType = "gogs"
 | 
			
		||||
	SLACK    HookType = "slack"
 | 
			
		||||
	DISCORD  HookType = "discord"
 | 
			
		||||
	DINGTALK HookType = "dingtalk"
 | 
			
		||||
	TELEGRAM HookType = "telegram"
 | 
			
		||||
	MSTEAMS  HookType = "msteams"
 | 
			
		||||
	FEISHU   HookType = "feishu"
 | 
			
		||||
	MATRIX   HookType = "matrix"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// HookStatus is the status of a web hook
 | 
			
		||||
type HookStatus int
 | 
			
		||||
 | 
			
		||||
@@ -126,15 +142,13 @@ type Webhook struct {
 | 
			
		||||
	OrgID           int64 `xorm:"INDEX"`
 | 
			
		||||
	IsSystemWebhook bool
 | 
			
		||||
	URL             string `xorm:"url TEXT"`
 | 
			
		||||
	Signature       string `xorm:"TEXT"`
 | 
			
		||||
	HTTPMethod      string `xorm:"http_method"`
 | 
			
		||||
	ContentType     HookContentType
 | 
			
		||||
	Secret          string `xorm:"TEXT"`
 | 
			
		||||
	Events          string `xorm:"TEXT"`
 | 
			
		||||
	*HookEvent      `xorm:"-"`
 | 
			
		||||
	IsSSL           bool         `xorm:"is_ssl"`
 | 
			
		||||
	IsActive        bool       `xorm:"INDEX"`
 | 
			
		||||
	Type            HookTaskType `xorm:"VARCHAR(16) 'type'"`
 | 
			
		||||
	Type            HookType   `xorm:"VARCHAR(16) 'type'"`
 | 
			
		||||
	Meta            string     `xorm:"TEXT"` // store hook-specific attributes
 | 
			
		||||
	LastStatus      HookStatus // Last delivery status
 | 
			
		||||
 | 
			
		||||
@@ -558,22 +572,6 @@ func copyDefaultWebhooksToRepo(e Engine, repoID int64) error {
 | 
			
		||||
//  \___|_  / \____/ \____/|__|_ \ |____|  (____  /____  >__|_ \
 | 
			
		||||
//        \/                    \/              \/     \/     \/
 | 
			
		||||
 | 
			
		||||
// HookTaskType is the type of an hook task
 | 
			
		||||
type HookTaskType = string
 | 
			
		||||
 | 
			
		||||
// Types of hook tasks
 | 
			
		||||
const (
 | 
			
		||||
	GITEA    HookTaskType = "gitea"
 | 
			
		||||
	GOGS     HookTaskType = "gogs"
 | 
			
		||||
	SLACK    HookTaskType = "slack"
 | 
			
		||||
	DISCORD  HookTaskType = "discord"
 | 
			
		||||
	DINGTALK HookTaskType = "dingtalk"
 | 
			
		||||
	TELEGRAM HookTaskType = "telegram"
 | 
			
		||||
	MSTEAMS  HookTaskType = "msteams"
 | 
			
		||||
	FEISHU   HookTaskType = "feishu"
 | 
			
		||||
	MATRIX   HookTaskType = "matrix"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// HookEventType is the type of an hook event
 | 
			
		||||
type HookEventType string
 | 
			
		||||
 | 
			
		||||
@@ -635,6 +633,8 @@ func (h HookEventType) Event() string {
 | 
			
		||||
 | 
			
		||||
// HookRequest represents hook task request information.
 | 
			
		||||
type HookRequest struct {
 | 
			
		||||
	URL        string            `json:"url"`
 | 
			
		||||
	HTTPMethod string            `json:"http_method"`
 | 
			
		||||
	Headers    map[string]string `json:"headers"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -651,15 +651,9 @@ type HookTask struct {
 | 
			
		||||
	RepoID          int64 `xorm:"INDEX"`
 | 
			
		||||
	HookID          int64
 | 
			
		||||
	UUID            string
 | 
			
		||||
	Typ             HookTaskType `xorm:"VARCHAR(16) index"`
 | 
			
		||||
	URL             string       `xorm:"TEXT"`
 | 
			
		||||
	Signature       string       `xorm:"TEXT"`
 | 
			
		||||
	api.Payloader   `xorm:"-"`
 | 
			
		||||
	PayloadContent  string `xorm:"TEXT"`
 | 
			
		||||
	HTTPMethod      string `xorm:"http_method"`
 | 
			
		||||
	ContentType     HookContentType
 | 
			
		||||
	EventType       HookEventType
 | 
			
		||||
	IsSSL           bool
 | 
			
		||||
	IsDelivered     bool
 | 
			
		||||
	Delivered       int64
 | 
			
		||||
	DeliveredString string `xorm:"-"`
 | 
			
		||||
 
 | 
			
		||||
@@ -207,8 +207,6 @@ func TestCreateHookTask(t *testing.T) {
 | 
			
		||||
	hookTask := &HookTask{
 | 
			
		||||
		RepoID:    3,
 | 
			
		||||
		HookID:    3,
 | 
			
		||||
		Typ:       GITEA,
 | 
			
		||||
		URL:       "http://www.example.com/unit_test",
 | 
			
		||||
		Payloader: &api.PushPayload{},
 | 
			
		||||
	}
 | 
			
		||||
	AssertNotExistsBean(t, hookTask)
 | 
			
		||||
@@ -233,8 +231,6 @@ func TestCleanupHookTaskTable_PerWebhook_DeletesDelivered(t *testing.T) {
 | 
			
		||||
	hookTask := &HookTask{
 | 
			
		||||
		RepoID:      3,
 | 
			
		||||
		HookID:      3,
 | 
			
		||||
		Typ:         GITEA,
 | 
			
		||||
		URL:         "http://www.example.com/unit_test",
 | 
			
		||||
		Payloader:   &api.PushPayload{},
 | 
			
		||||
		IsDelivered: true,
 | 
			
		||||
		Delivered:   time.Now().UnixNano(),
 | 
			
		||||
@@ -252,8 +248,6 @@ func TestCleanupHookTaskTable_PerWebhook_LeavesUndelivered(t *testing.T) {
 | 
			
		||||
	hookTask := &HookTask{
 | 
			
		||||
		RepoID:      2,
 | 
			
		||||
		HookID:      4,
 | 
			
		||||
		Typ:         GITEA,
 | 
			
		||||
		URL:         "http://www.example.com/unit_test",
 | 
			
		||||
		Payloader:   &api.PushPayload{},
 | 
			
		||||
		IsDelivered: false,
 | 
			
		||||
	}
 | 
			
		||||
@@ -270,8 +264,6 @@ func TestCleanupHookTaskTable_PerWebhook_LeavesMostRecentTask(t *testing.T) {
 | 
			
		||||
	hookTask := &HookTask{
 | 
			
		||||
		RepoID:      2,
 | 
			
		||||
		HookID:      4,
 | 
			
		||||
		Typ:         GITEA,
 | 
			
		||||
		URL:         "http://www.example.com/unit_test",
 | 
			
		||||
		Payloader:   &api.PushPayload{},
 | 
			
		||||
		IsDelivered: true,
 | 
			
		||||
		Delivered:   time.Now().UnixNano(),
 | 
			
		||||
@@ -289,8 +281,6 @@ func TestCleanupHookTaskTable_OlderThan_DeletesDelivered(t *testing.T) {
 | 
			
		||||
	hookTask := &HookTask{
 | 
			
		||||
		RepoID:      3,
 | 
			
		||||
		HookID:      3,
 | 
			
		||||
		Typ:         GITEA,
 | 
			
		||||
		URL:         "http://www.example.com/unit_test",
 | 
			
		||||
		Payloader:   &api.PushPayload{},
 | 
			
		||||
		IsDelivered: true,
 | 
			
		||||
		Delivered:   time.Now().AddDate(0, 0, -8).UnixNano(),
 | 
			
		||||
@@ -308,8 +298,6 @@ func TestCleanupHookTaskTable_OlderThan_LeavesUndelivered(t *testing.T) {
 | 
			
		||||
	hookTask := &HookTask{
 | 
			
		||||
		RepoID:      2,
 | 
			
		||||
		HookID:      4,
 | 
			
		||||
		Typ:         GITEA,
 | 
			
		||||
		URL:         "http://www.example.com/unit_test",
 | 
			
		||||
		Payloader:   &api.PushPayload{},
 | 
			
		||||
		IsDelivered: false,
 | 
			
		||||
	}
 | 
			
		||||
@@ -326,8 +314,6 @@ func TestCleanupHookTaskTable_OlderThan_LeavesTaskEarlierThanAgeToDelete(t *test
 | 
			
		||||
	hookTask := &HookTask{
 | 
			
		||||
		RepoID:      2,
 | 
			
		||||
		HookID:      4,
 | 
			
		||||
		Typ:         GITEA,
 | 
			
		||||
		URL:         "http://www.example.com/unit_test",
 | 
			
		||||
		Payloader:   &api.PushPayload{},
 | 
			
		||||
		IsDelivered: true,
 | 
			
		||||
		Delivered:   time.Now().AddDate(0, 0, -6).UnixNano(),
 | 
			
		||||
 
 | 
			
		||||
@@ -62,7 +62,6 @@ type EditHookOption struct {
 | 
			
		||||
 | 
			
		||||
// Payloader payload is some part of one hook
 | 
			
		||||
type Payloader interface {
 | 
			
		||||
	SetSecret(string)
 | 
			
		||||
	JSONPayload() ([]byte, error)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -124,7 +123,6 @@ var (
 | 
			
		||||
 | 
			
		||||
// CreatePayload FIXME
 | 
			
		||||
type CreatePayload struct {
 | 
			
		||||
	Secret  string      `json:"secret"`
 | 
			
		||||
	Sha     string      `json:"sha"`
 | 
			
		||||
	Ref     string      `json:"ref"`
 | 
			
		||||
	RefType string      `json:"ref_type"`
 | 
			
		||||
@@ -132,11 +130,6 @@ type CreatePayload struct {
 | 
			
		||||
	Sender  *User       `json:"sender"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// SetSecret modifies the secret of the CreatePayload
 | 
			
		||||
func (p *CreatePayload) SetSecret(secret string) {
 | 
			
		||||
	p.Secret = secret
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// JSONPayload return payload information
 | 
			
		||||
func (p *CreatePayload) JSONPayload() ([]byte, error) {
 | 
			
		||||
	json := jsoniter.ConfigCompatibleWithStandardLibrary
 | 
			
		||||
@@ -181,7 +174,6 @@ const (
 | 
			
		||||
 | 
			
		||||
// DeletePayload represents delete payload
 | 
			
		||||
type DeletePayload struct {
 | 
			
		||||
	Secret     string      `json:"secret"`
 | 
			
		||||
	Ref        string      `json:"ref"`
 | 
			
		||||
	RefType    string      `json:"ref_type"`
 | 
			
		||||
	PusherType PusherType  `json:"pusher_type"`
 | 
			
		||||
@@ -189,11 +181,6 @@ type DeletePayload struct {
 | 
			
		||||
	Sender     *User       `json:"sender"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// SetSecret modifies the secret of the DeletePayload
 | 
			
		||||
func (p *DeletePayload) SetSecret(secret string) {
 | 
			
		||||
	p.Secret = secret
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// JSONPayload implements Payload
 | 
			
		||||
func (p *DeletePayload) JSONPayload() ([]byte, error) {
 | 
			
		||||
	json := jsoniter.ConfigCompatibleWithStandardLibrary
 | 
			
		||||
@@ -209,17 +196,11 @@ func (p *DeletePayload) JSONPayload() ([]byte, error) {
 | 
			
		||||
 | 
			
		||||
// ForkPayload represents fork payload
 | 
			
		||||
type ForkPayload struct {
 | 
			
		||||
	Secret string      `json:"secret"`
 | 
			
		||||
	Forkee *Repository `json:"forkee"`
 | 
			
		||||
	Repo   *Repository `json:"repository"`
 | 
			
		||||
	Sender *User       `json:"sender"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// SetSecret modifies the secret of the ForkPayload
 | 
			
		||||
func (p *ForkPayload) SetSecret(secret string) {
 | 
			
		||||
	p.Secret = secret
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// JSONPayload implements Payload
 | 
			
		||||
func (p *ForkPayload) JSONPayload() ([]byte, error) {
 | 
			
		||||
	json := jsoniter.ConfigCompatibleWithStandardLibrary
 | 
			
		||||
@@ -238,7 +219,6 @@ const (
 | 
			
		||||
 | 
			
		||||
// IssueCommentPayload represents a payload information of issue comment event.
 | 
			
		||||
type IssueCommentPayload struct {
 | 
			
		||||
	Secret     string                 `json:"secret"`
 | 
			
		||||
	Action     HookIssueCommentAction `json:"action"`
 | 
			
		||||
	Issue      *Issue                 `json:"issue"`
 | 
			
		||||
	Comment    *Comment               `json:"comment"`
 | 
			
		||||
@@ -248,11 +228,6 @@ type IssueCommentPayload struct {
 | 
			
		||||
	IsPull     bool                   `json:"is_pull"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// SetSecret modifies the secret of the IssueCommentPayload
 | 
			
		||||
func (p *IssueCommentPayload) SetSecret(secret string) {
 | 
			
		||||
	p.Secret = secret
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// JSONPayload implements Payload
 | 
			
		||||
func (p *IssueCommentPayload) JSONPayload() ([]byte, error) {
 | 
			
		||||
	json := jsoniter.ConfigCompatibleWithStandardLibrary
 | 
			
		||||
@@ -278,18 +253,12 @@ const (
 | 
			
		||||
 | 
			
		||||
// ReleasePayload represents a payload information of release event.
 | 
			
		||||
type ReleasePayload struct {
 | 
			
		||||
	Secret     string            `json:"secret"`
 | 
			
		||||
	Action     HookReleaseAction `json:"action"`
 | 
			
		||||
	Release    *Release          `json:"release"`
 | 
			
		||||
	Repository *Repository       `json:"repository"`
 | 
			
		||||
	Sender     *User             `json:"sender"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// SetSecret modifies the secret of the ReleasePayload
 | 
			
		||||
func (p *ReleasePayload) SetSecret(secret string) {
 | 
			
		||||
	p.Secret = secret
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// JSONPayload implements Payload
 | 
			
		||||
func (p *ReleasePayload) JSONPayload() ([]byte, error) {
 | 
			
		||||
	json := jsoniter.ConfigCompatibleWithStandardLibrary
 | 
			
		||||
@@ -305,7 +274,6 @@ func (p *ReleasePayload) JSONPayload() ([]byte, error) {
 | 
			
		||||
 | 
			
		||||
// PushPayload represents a payload information of push event.
 | 
			
		||||
type PushPayload struct {
 | 
			
		||||
	Secret     string           `json:"secret"`
 | 
			
		||||
	Ref        string           `json:"ref"`
 | 
			
		||||
	Before     string           `json:"before"`
 | 
			
		||||
	After      string           `json:"after"`
 | 
			
		||||
@@ -317,11 +285,6 @@ type PushPayload struct {
 | 
			
		||||
	Sender     *User            `json:"sender"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// SetSecret modifies the secret of the PushPayload
 | 
			
		||||
func (p *PushPayload) SetSecret(secret string) {
 | 
			
		||||
	p.Secret = secret
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// JSONPayload FIXME
 | 
			
		||||
func (p *PushPayload) JSONPayload() ([]byte, error) {
 | 
			
		||||
	json := jsoniter.ConfigCompatibleWithStandardLibrary
 | 
			
		||||
@@ -389,7 +352,6 @@ const (
 | 
			
		||||
 | 
			
		||||
// IssuePayload represents the payload information that is sent along with an issue event.
 | 
			
		||||
type IssuePayload struct {
 | 
			
		||||
	Secret     string          `json:"secret"`
 | 
			
		||||
	Action     HookIssueAction `json:"action"`
 | 
			
		||||
	Index      int64           `json:"number"`
 | 
			
		||||
	Changes    *ChangesPayload `json:"changes,omitempty"`
 | 
			
		||||
@@ -398,11 +360,6 @@ type IssuePayload struct {
 | 
			
		||||
	Sender     *User           `json:"sender"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// SetSecret modifies the secret of the IssuePayload.
 | 
			
		||||
func (p *IssuePayload) SetSecret(secret string) {
 | 
			
		||||
	p.Secret = secret
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// JSONPayload encodes the IssuePayload to JSON, with an indentation of two spaces.
 | 
			
		||||
func (p *IssuePayload) JSONPayload() ([]byte, error) {
 | 
			
		||||
	json := jsoniter.ConfigCompatibleWithStandardLibrary
 | 
			
		||||
@@ -430,7 +387,6 @@ type ChangesPayload struct {
 | 
			
		||||
 | 
			
		||||
// PullRequestPayload represents a payload information of pull request event.
 | 
			
		||||
type PullRequestPayload struct {
 | 
			
		||||
	Secret      string          `json:"secret"`
 | 
			
		||||
	Action      HookIssueAction `json:"action"`
 | 
			
		||||
	Index       int64           `json:"number"`
 | 
			
		||||
	Changes     *ChangesPayload `json:"changes,omitempty"`
 | 
			
		||||
@@ -440,11 +396,6 @@ type PullRequestPayload struct {
 | 
			
		||||
	Review      *ReviewPayload  `json:"review"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// SetSecret modifies the secret of the PullRequestPayload.
 | 
			
		||||
func (p *PullRequestPayload) SetSecret(secret string) {
 | 
			
		||||
	p.Secret = secret
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// JSONPayload FIXME
 | 
			
		||||
func (p *PullRequestPayload) JSONPayload() ([]byte, error) {
 | 
			
		||||
	json := jsoniter.ConfigCompatibleWithStandardLibrary
 | 
			
		||||
@@ -476,18 +427,12 @@ const (
 | 
			
		||||
 | 
			
		||||
// RepositoryPayload payload for repository webhooks
 | 
			
		||||
type RepositoryPayload struct {
 | 
			
		||||
	Secret       string         `json:"secret"`
 | 
			
		||||
	Action       HookRepoAction `json:"action"`
 | 
			
		||||
	Repository   *Repository    `json:"repository"`
 | 
			
		||||
	Organization *User          `json:"organization"`
 | 
			
		||||
	Sender       *User          `json:"sender"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// SetSecret modifies the secret of the RepositoryPayload
 | 
			
		||||
func (p *RepositoryPayload) SetSecret(secret string) {
 | 
			
		||||
	p.Secret = secret
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// JSONPayload JSON representation of the payload
 | 
			
		||||
func (p *RepositoryPayload) JSONPayload() ([]byte, error) {
 | 
			
		||||
	json := jsoniter.ConfigCompatibleWithStandardLibrary
 | 
			
		||||
 
 | 
			
		||||
@@ -133,7 +133,7 @@ func addHook(ctx *context.APIContext, form *api.CreateHookOption, orgID, repoID
 | 
			
		||||
			BranchFilter: form.BranchFilter,
 | 
			
		||||
		},
 | 
			
		||||
		IsActive: form.Active,
 | 
			
		||||
		Type:     models.HookTaskType(form.Type),
 | 
			
		||||
		Type:     models.HookType(form.Type),
 | 
			
		||||
	}
 | 
			
		||||
	if w.Type == models.SLACK {
 | 
			
		||||
		channel, ok := form.Config["channel"]
 | 
			
		||||
 
 | 
			
		||||
@@ -239,7 +239,7 @@ func GogsHooksNewPost(ctx *context.Context) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// newGogsWebhookPost response for creating gogs hook
 | 
			
		||||
func newGogsWebhookPost(ctx *context.Context, form forms.NewGogshookForm, kind models.HookTaskType) {
 | 
			
		||||
func newGogsWebhookPost(ctx *context.Context, form forms.NewGogshookForm, kind models.HookType) {
 | 
			
		||||
	ctx.Data["Title"] = ctx.Tr("repo.settings.add_webhook")
 | 
			
		||||
	ctx.Data["PageIsSettingsHooks"] = true
 | 
			
		||||
	ctx.Data["PageIsSettingsHooksNew"] = true
 | 
			
		||||
 
 | 
			
		||||
@@ -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,30 +87,47 @@ 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{
 | 
			
		||||
		URL:        req.URL.String(),
 | 
			
		||||
		HTTPMethod: req.Method,
 | 
			
		||||
		Headers:    map[string]string{},
 | 
			
		||||
	}
 | 
			
		||||
	for k, vals := range req.Header {
 | 
			
		||||
@@ -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 {
 | 
			
		||||
 
 | 
			
		||||
@@ -25,9 +25,6 @@ var (
 | 
			
		||||
	_ PayloadConvertor = &DingtalkPayload{}
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// SetSecret sets the dingtalk secret
 | 
			
		||||
func (d *DingtalkPayload) SetSecret(_ string) {}
 | 
			
		||||
 | 
			
		||||
// JSONPayload Marshals the DingtalkPayload to json
 | 
			
		||||
func (d *DingtalkPayload) JSONPayload() ([]byte, error) {
 | 
			
		||||
	json := jsoniter.ConfigCompatibleWithStandardLibrary
 | 
			
		||||
 
 | 
			
		||||
@@ -97,9 +97,6 @@ var (
 | 
			
		||||
	redColor         = color("ff3232")
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// SetSecret sets the discord secret
 | 
			
		||||
func (d *DiscordPayload) SetSecret(_ string) {}
 | 
			
		||||
 | 
			
		||||
// JSONPayload Marshals the DiscordPayload to json
 | 
			
		||||
func (d *DiscordPayload) JSONPayload() ([]byte, error) {
 | 
			
		||||
	json := jsoniter.ConfigCompatibleWithStandardLibrary
 | 
			
		||||
 
 | 
			
		||||
@@ -35,9 +35,6 @@ func newFeishuTextPayload(text string) *FeishuPayload {
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// SetSecret sets the Feishu secret
 | 
			
		||||
func (f *FeishuPayload) SetSecret(_ string) {}
 | 
			
		||||
 | 
			
		||||
// JSONPayload Marshals the FeishuPayload to json
 | 
			
		||||
func (f *FeishuPayload) JSONPayload() ([]byte, error) {
 | 
			
		||||
	json := jsoniter.ConfigCompatibleWithStandardLibrary
 | 
			
		||||
 
 | 
			
		||||
@@ -76,9 +76,6 @@ type MatrixPayloadSafe struct {
 | 
			
		||||
	Commits       []*api.PayloadCommit `json:"io.gitea.commits,omitempty"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// SetSecret sets the Matrix secret
 | 
			
		||||
func (m *MatrixPayloadUnsafe) SetSecret(_ string) {}
 | 
			
		||||
 | 
			
		||||
// JSONPayload Marshals the MatrixPayloadUnsafe to json
 | 
			
		||||
func (m *MatrixPayloadUnsafe) JSONPayload() ([]byte, error) {
 | 
			
		||||
	json := jsoniter.ConfigCompatibleWithStandardLibrary
 | 
			
		||||
@@ -263,7 +260,7 @@ func getMessageBody(htmlText string) string {
 | 
			
		||||
 | 
			
		||||
// getMatrixHookRequest creates a new request which contains an Authorization header.
 | 
			
		||||
// The access_token is removed from t.PayloadContent
 | 
			
		||||
func getMatrixHookRequest(t *models.HookTask) (*http.Request, error) {
 | 
			
		||||
func getMatrixHookRequest(w *models.Webhook, t *models.HookTask) (*http.Request, error) {
 | 
			
		||||
	payloadunsafe := MatrixPayloadUnsafe{}
 | 
			
		||||
	json := jsoniter.ConfigCompatibleWithStandardLibrary
 | 
			
		||||
	if err := json.Unmarshal([]byte(t.PayloadContent), &payloadunsafe); err != nil {
 | 
			
		||||
@@ -288,9 +285,9 @@ func getMatrixHookRequest(t *models.HookTask) (*http.Request, error) {
 | 
			
		||||
		return nil, fmt.Errorf("getMatrixHookRequest: unable to hash payload: %+v", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	t.URL = fmt.Sprintf("%s/%s", t.URL, txnID)
 | 
			
		||||
	url := fmt.Sprintf("%s/%s", w.URL, txnID)
 | 
			
		||||
 | 
			
		||||
	req, err := http.NewRequest(t.HTTPMethod, t.URL, strings.NewReader(string(payload)))
 | 
			
		||||
	req, err := http.NewRequest(w.HTTPMethod, url, strings.NewReader(string(payload)))
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 
 | 
			
		||||
@@ -184,6 +184,8 @@ func TestMatrixJSONPayload(t *testing.T) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestMatrixHookRequest(t *testing.T) {
 | 
			
		||||
	w := &models.Webhook{}
 | 
			
		||||
 | 
			
		||||
	h := &models.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",
 | 
			
		||||
@@ -245,7 +247,7 @@ func TestMatrixHookRequest(t *testing.T) {
 | 
			
		||||
  ]
 | 
			
		||||
}`
 | 
			
		||||
 | 
			
		||||
	req, err := getMatrixHookRequest(h)
 | 
			
		||||
	req, err := getMatrixHookRequest(w, h)
 | 
			
		||||
	require.NoError(t, err)
 | 
			
		||||
	require.NotNil(t, req)
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -55,9 +55,6 @@ type (
 | 
			
		||||
	}
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// SetSecret sets the MSTeams secret
 | 
			
		||||
func (m *MSTeamsPayload) SetSecret(_ string) {}
 | 
			
		||||
 | 
			
		||||
// JSONPayload Marshals the MSTeamsPayload to json
 | 
			
		||||
func (m *MSTeamsPayload) JSONPayload() ([]byte, error) {
 | 
			
		||||
	json := jsoniter.ConfigCompatibleWithStandardLibrary
 | 
			
		||||
 
 | 
			
		||||
@@ -56,9 +56,6 @@ type SlackAttachment struct {
 | 
			
		||||
	Text      string `json:"text"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// SetSecret sets the slack secret
 | 
			
		||||
func (s *SlackPayload) SetSecret(_ string) {}
 | 
			
		||||
 | 
			
		||||
// JSONPayload Marshals the SlackPayload to json
 | 
			
		||||
func (s *SlackPayload) JSONPayload() ([]byte, error) {
 | 
			
		||||
	json := jsoniter.ConfigCompatibleWithStandardLibrary
 | 
			
		||||
 
 | 
			
		||||
@@ -45,9 +45,6 @@ var (
 | 
			
		||||
	_ PayloadConvertor = &TelegramPayload{}
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// SetSecret sets the telegram secret
 | 
			
		||||
func (t *TelegramPayload) SetSecret(_ string) {}
 | 
			
		||||
 | 
			
		||||
// JSONPayload Marshals the TelegramPayload to json
 | 
			
		||||
func (t *TelegramPayload) JSONPayload() ([]byte, error) {
 | 
			
		||||
	t.ParseMode = "HTML"
 | 
			
		||||
 
 | 
			
		||||
@@ -5,9 +5,6 @@
 | 
			
		||||
package webhook
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"crypto/hmac"
 | 
			
		||||
	"crypto/sha256"
 | 
			
		||||
	"encoding/hex"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
@@ -21,12 +18,12 @@ import (
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type webhook struct {
 | 
			
		||||
	name           models.HookTaskType
 | 
			
		||||
	name           models.HookType
 | 
			
		||||
	payloadCreator func(p api.Payloader, event models.HookEventType, meta string) (api.Payloader, error)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var (
 | 
			
		||||
	webhooks = map[models.HookTaskType]*webhook{
 | 
			
		||||
	webhooks = map[models.HookType]*webhook{
 | 
			
		||||
		models.SLACK: {
 | 
			
		||||
			name:           models.SLACK,
 | 
			
		||||
			payloadCreator: GetSlackPayload,
 | 
			
		||||
@@ -60,7 +57,7 @@ var (
 | 
			
		||||
 | 
			
		||||
// RegisterWebhook registers a webhook
 | 
			
		||||
func RegisterWebhook(name string, webhook *webhook) {
 | 
			
		||||
	webhooks[models.HookTaskType(name)] = webhook
 | 
			
		||||
	webhooks[models.HookType(name)] = webhook
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// IsValidHookTaskType returns true if a webhook registered
 | 
			
		||||
@@ -68,7 +65,7 @@ func IsValidHookTaskType(name string) bool {
 | 
			
		||||
	if name == models.GITEA || name == models.GOGS {
 | 
			
		||||
		return true
 | 
			
		||||
	}
 | 
			
		||||
	_, ok := webhooks[models.HookTaskType(name)]
 | 
			
		||||
	_, ok := webhooks[models.HookType(name)]
 | 
			
		||||
	return ok
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -161,35 +158,14 @@ func prepareWebhook(w *models.Webhook, repo *models.Repository, event models.Hoo
 | 
			
		||||
			return fmt.Errorf("create payload for %s[%s]: %v", w.Type, event, err)
 | 
			
		||||
		}
 | 
			
		||||
	} else {
 | 
			
		||||
		p.SetSecret(w.Secret)
 | 
			
		||||
		payloader = p
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var signature string
 | 
			
		||||
	if len(w.Secret) > 0 {
 | 
			
		||||
		data, err := payloader.JSONPayload()
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			log.Error("prepareWebhooks.JSONPayload: %v", err)
 | 
			
		||||
		}
 | 
			
		||||
		sig := hmac.New(sha256.New, []byte(w.Secret))
 | 
			
		||||
		_, err = sig.Write(data)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			log.Error("prepareWebhooks.sigWrite: %v", err)
 | 
			
		||||
		}
 | 
			
		||||
		signature = hex.EncodeToString(sig.Sum(nil))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err = models.CreateHookTask(&models.HookTask{
 | 
			
		||||
		RepoID:    repo.ID,
 | 
			
		||||
		HookID:    w.ID,
 | 
			
		||||
		Typ:         w.Type,
 | 
			
		||||
		URL:         w.URL,
 | 
			
		||||
		Signature:   signature,
 | 
			
		||||
		Payloader: payloader,
 | 
			
		||||
		HTTPMethod:  w.HTTPMethod,
 | 
			
		||||
		ContentType: w.ContentType,
 | 
			
		||||
		EventType: event,
 | 
			
		||||
		IsSSL:       w.IsSSL,
 | 
			
		||||
	}); err != nil {
 | 
			
		||||
		return fmt.Errorf("CreateHookTask: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
 
 | 
			
		||||
@@ -44,8 +44,8 @@
 | 
			
		||||
						<div class="ui bottom attached tab segment active" data-tab="request-{{.ID}}">
 | 
			
		||||
							{{if .RequestInfo}}
 | 
			
		||||
								<h5>{{$.i18n.Tr "repo.settings.webhook.headers"}}</h5>
 | 
			
		||||
								<pre class="webhook-info"><strong>Request URL:</strong> {{.URL}}
 | 
			
		||||
<strong>Request method:</strong> {{if .HTTPMethod}}{{.HTTPMethod}}{{else}}POST{{end}}
 | 
			
		||||
								<pre class="webhook-info"><strong>Request URL:</strong> {{.RequestInfo.URL}}
 | 
			
		||||
<strong>Request method:</strong> {{if .RequestInfo.HTTPMethod}}{{.RequestInfo.HTTPMethod}}{{else}}POST{{end}}
 | 
			
		||||
{{ range $key, $val := .RequestInfo.Headers }}<strong>{{$key}}:</strong> {{$val}}
 | 
			
		||||
{{end}}</pre>
 | 
			
		||||
								<h5>{{$.i18n.Tr "repo.settings.webhook.payload"}}</h5>
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user