Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor Webhook + Add X-Hub-Signature #16176

Merged
merged 10 commits into from
Jun 27, 2021
2 changes: 2 additions & 0 deletions models/migrations/migrations.go
Original file line number Diff line number Diff line change
@@ -317,6 +317,8 @@ var migrations = []Migration{
NewMigration("Add issue resource index table", addIssueResourceIndexTable),
// v183 -> v184
NewMigration("Create PushMirror table", createPushMirrorTable),
// v184 -> v185
NewMigration("Drop unneeded webhook related columns", dropWebhookColumns),
}

// GetCurrentDBVersion returns the current db version
46 changes: 46 additions & 0 deletions models/migrations/v184.go
Original file line number Diff line number Diff line change
@@ -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 {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

don't know why this columns should be dropped? webhook maybe changed after hook_task finished. So these columns will keep the original information when requesting.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Webhook:
Signature and IsSSL are never used

HookTask:
Typ see #16176 (comment)
URL moved to HookRequest
Signature calculated on delivery. Is saved indirect in the Header map in HookRequest
HTTPMethod moved to HookRequest
ContentType of the Webhook is used on delivery. If the Webhook gets changed before sending the HookTask will use the current/correct settings. The original value is not available after a change but it's not visible on the UI anyway.
IsSSL is never used

return err
}

return sess.Commit()
}
52 changes: 23 additions & 29 deletions models/webhook.go
Original file line number Diff line number Diff line change
@@ -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,17 +142,15 @@ 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'"`
Meta string `xorm:"TEXT"` // store hook-specific attributes
LastStatus HookStatus // Last delivery status
IsActive bool `xorm:"INDEX"`
Type HookType `xorm:"VARCHAR(16) 'type'"`
Meta string `xorm:"TEXT"` // store hook-specific attributes
LastStatus HookStatus // Last delivery status

CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"`
UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"`
@@ -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,7 +633,9 @@ func (h HookEventType) Event() string {

// HookRequest represents hook task request information.
type HookRequest struct {
Headers map[string]string `json:"headers"`
URL string `json:"url"`
HTTPMethod string `json:"http_method"`
Headers map[string]string `json:"headers"`
}

// HookResponse represents hook task response information.
@@ -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:"-"`
14 changes: 0 additions & 14 deletions models/webhook_test.go
Original file line number Diff line number Diff line change
@@ -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(),
55 changes: 0 additions & 55 deletions modules/structs/hook.go
Original file line number Diff line number Diff line change
@@ -62,7 +62,6 @@ type EditHookOption struct {

// Payloader payload is some part of one hook
type Payloader interface {
SetSecret(string)
JSONPayload() ([]byte, error)
}

@@ -124,19 +123,13 @@ var (

// CreatePayload FIXME
type CreatePayload struct {
Secret string `json:"secret"`
Sha string `json:"sha"`
Ref string `json:"ref"`
RefType string `json:"ref_type"`
Repo *Repository `json:"repository"`
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,19 +174,13 @@ 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"`
Repo *Repository `json:"repository"`
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
2 changes: 1 addition & 1 deletion routers/api/v1/utils/hook.go
Original file line number Diff line number Diff line change
@@ -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"]
2 changes: 1 addition & 1 deletion routers/web/repo/webhook.go
Original file line number Diff line number Diff line change
@@ -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
61 changes: 41 additions & 20 deletions services/webhook/deliver.go
Original file line number Diff line number Diff line change
@@ -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 {
3 changes: 0 additions & 3 deletions services/webhook/dingtalk.go
Original file line number Diff line number Diff line change
@@ -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
3 changes: 0 additions & 3 deletions services/webhook/discord.go
Original file line number Diff line number Diff line change
@@ -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
3 changes: 0 additions & 3 deletions services/webhook/feishu.go
Original file line number Diff line number Diff line change
@@ -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
9 changes: 3 additions & 6 deletions services/webhook/matrix.go
Original file line number Diff line number Diff line change
@@ -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
}
4 changes: 3 additions & 1 deletion services/webhook/matrix_test.go
Original file line number Diff line number Diff line change
@@ -82,6 +82,8 @@ func TestMatrixPullRequestPayload(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",
@@ -143,7 +145,7 @@ func TestMatrixHookRequest(t *testing.T) {
]
}`

req, err := getMatrixHookRequest(h)
req, err := getMatrixHookRequest(w, h)
require.NoError(t, err)
require.NotNil(t, req)

3 changes: 0 additions & 3 deletions services/webhook/msteams.go
Original file line number Diff line number Diff line change
@@ -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
3 changes: 0 additions & 3 deletions services/webhook/slack.go
Original file line number Diff line number Diff line change
@@ -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
3 changes: 0 additions & 3 deletions services/webhook/telegram.go
Original file line number Diff line number Diff line change
@@ -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"
40 changes: 8 additions & 32 deletions services/webhook/webhook.go
Original file line number Diff line number Diff line change
@@ -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,15 +57,15 @@ 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
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,
RepoID: repo.ID,
HookID: w.ID,
Payloader: payloader,
EventType: event,
}); err != nil {
return fmt.Errorf("CreateHookTask: %v", err)
}
4 changes: 2 additions & 2 deletions templates/repo/settings/webhook/history.tmpl
Original file line number Diff line number Diff line change
@@ -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>