Add GET requests to webhook (#6771)

* Add GET requests to webhook

* make fmt

* Handle invalid http method on webhook

* Uppercase http method in webhook

* Rename v85.go to v86.go

* make fmt
release/v1.15
Simon 2019-05-05 20:09:02 +02:00 committed by techknowlogick
parent 55a8e12d85
commit a2a006a5d5
8 changed files with 71 additions and 15 deletions

View File

@ -225,6 +225,8 @@ var migrations = []Migration{
NewMigration("add table to store original imported gpg keys", addGPGKeyImport), NewMigration("add table to store original imported gpg keys", addGPGKeyImport),
// v85 -> v86 // v85 -> v86
NewMigration("hash application token", hashAppToken), NewMigration("hash application token", hashAppToken),
// v86 -> v87
NewMigration("add http method to webhook", addHTTPMethodToWebhook),
} }
// Migrate database to current version // Migrate database to current version

17
models/migrations/v86.go Normal file
View File

@ -0,0 +1,17 @@
// Copyright 2019 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 (
"github.com/go-xorm/xorm"
)
func addHTTPMethodToWebhook(x *xorm.Engine) error {
type Webhook struct {
HTTPMethod string `xorm:"http_method DEFAULT 'POST'"`
}
return x.Sync2(new(Webhook))
}

View File

@ -13,6 +13,7 @@ import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"net/http"
"strings" "strings"
"time" "time"
@ -105,6 +106,7 @@ type Webhook struct {
OrgID int64 `xorm:"INDEX"` OrgID int64 `xorm:"INDEX"`
URL string `xorm:"url TEXT"` URL string `xorm:"url TEXT"`
Signature string `xorm:"TEXT"` Signature string `xorm:"TEXT"`
HTTPMethod string `xorm:"http_method"`
ContentType HookContentType ContentType HookContentType
Secret string `xorm:"TEXT"` Secret string `xorm:"TEXT"`
Events string `xorm:"TEXT"` Events string `xorm:"TEXT"`
@ -553,6 +555,7 @@ type HookTask struct {
Signature string `xorm:"TEXT"` Signature string `xorm:"TEXT"`
api.Payloader `xorm:"-"` api.Payloader `xorm:"-"`
PayloadContent string `xorm:"TEXT"` PayloadContent string `xorm:"TEXT"`
HTTPMethod string `xorm:"http_method"`
ContentType HookContentType ContentType HookContentType
EventType HookEventType EventType HookEventType
IsSSL bool IsSSL bool
@ -707,6 +710,7 @@ func prepareWebhook(e Engine, w *Webhook, repo *Repository, event HookEventType,
URL: w.URL, URL: w.URL,
Signature: signature, Signature: signature,
Payloader: payloader, Payloader: payloader,
HTTPMethod: w.HTTPMethod,
ContentType: w.ContentType, ContentType: w.ContentType,
EventType: event, EventType: event,
IsSSL: w.IsSSL, IsSSL: w.IsSSL,
@ -751,9 +755,32 @@ func prepareWebhooks(e Engine, repo *Repository, event HookEventType, p api.Payl
func (t *HookTask) deliver() { func (t *HookTask) deliver() {
t.IsDelivered = true t.IsDelivered = true
t.RequestInfo = &HookRequest{
Headers: map[string]string{},
}
t.ResponseInfo = &HookResponse{
Headers: map[string]string{},
}
timeout := time.Duration(setting.Webhook.DeliverTimeout) * time.Second timeout := time.Duration(setting.Webhook.DeliverTimeout) * time.Second
req := httplib.Post(t.URL).SetTimeout(timeout, timeout).
var req *httplib.Request
if t.HTTPMethod == http.MethodPost {
req = httplib.Post(t.URL)
switch t.ContentType {
case ContentTypeJSON:
req = req.Header("Content-Type", "application/json").Body(t.PayloadContent)
case ContentTypeForm:
req.Param("payload", t.PayloadContent)
}
} else if t.HTTPMethod == http.MethodGet {
req = httplib.Get(t.URL).Param("payload", t.PayloadContent)
} else {
t.ResponseInfo.Body = fmt.Sprintf("Invalid http method: %v", t.HTTPMethod)
return
}
req = req.SetTimeout(timeout, timeout).
Header("X-Gitea-Delivery", t.UUID). Header("X-Gitea-Delivery", t.UUID).
Header("X-Gitea-Event", string(t.EventType)). Header("X-Gitea-Event", string(t.EventType)).
Header("X-Gitea-Signature", t.Signature). Header("X-Gitea-Signature", t.Signature).
@ -764,25 +791,11 @@ func (t *HookTask) deliver() {
HeaderWithSensitiveCase("X-GitHub-Event", string(t.EventType)). HeaderWithSensitiveCase("X-GitHub-Event", string(t.EventType)).
SetTLSClientConfig(&tls.Config{InsecureSkipVerify: setting.Webhook.SkipTLSVerify}) SetTLSClientConfig(&tls.Config{InsecureSkipVerify: setting.Webhook.SkipTLSVerify})
switch t.ContentType {
case ContentTypeJSON:
req = req.Header("Content-Type", "application/json").Body(t.PayloadContent)
case ContentTypeForm:
req.Param("payload", t.PayloadContent)
}
// Record delivery information. // Record delivery information.
t.RequestInfo = &HookRequest{
Headers: map[string]string{},
}
for k, vals := range req.Headers() { for k, vals := range req.Headers() {
t.RequestInfo.Headers[k] = strings.Join(vals, ",") t.RequestInfo.Headers[k] = strings.Join(vals, ",")
} }
t.ResponseInfo = &HookResponse{
Headers: map[string]string{},
}
defer func() { defer func() {
t.Delivered = time.Now().UnixNano() t.Delivered = time.Now().UnixNano()
if t.IsSucceed { if t.IsSucceed {

View File

@ -196,6 +196,7 @@ func (f WebhookForm) ChooseEvents() bool {
// NewWebhookForm form for creating web hook // NewWebhookForm form for creating web hook
type NewWebhookForm struct { type NewWebhookForm struct {
PayloadURL string `binding:"Required;ValidUrl"` PayloadURL string `binding:"Required;ValidUrl"`
HTTPMethod string `binding:"Required;In(POST,GET)"`
ContentType int `binding:"Required"` ContentType int `binding:"Required"`
Secret string Secret string
WebhookForm WebhookForm

View File

@ -1192,6 +1192,7 @@ settings.githook_content = Hook Content
settings.update_githook = Update Hook settings.update_githook = Update Hook
settings.add_webhook_desc = Gitea will send <code>POST</code> requests with a specified content type to the target URL. Read more in the <a target="_blank" rel="noopener noreferrer" href="%s">webhooks guide</a>. settings.add_webhook_desc = Gitea will send <code>POST</code> requests with a specified content type to the target URL. Read more in the <a target="_blank" rel="noopener noreferrer" href="%s">webhooks guide</a>.
settings.payload_url = Target URL settings.payload_url = Target URL
settings.http_method = HTTP Method
settings.content_type = POST Content Type settings.content_type = POST Content Type
settings.secret = Secret settings.secret = Secret
settings.slack_username = Username settings.slack_username = Username

View File

@ -1430,6 +1430,15 @@ function initWebhook() {
} }
}); });
var updateContentType = function () {
var visible = $('#http_method').val() === 'POST';
$('#content_type').parent().parent()[visible ? 'show' : 'hide']();
};
updateContentType();
$('#http_method').change(function () {
updateContentType();
});
// Test delivery // Test delivery
$('#test-delivery').click(function () { $('#test-delivery').click(function () {
var $this = $(this); var $this = $(this);

View File

@ -176,6 +176,7 @@ func WebHooksNewPost(ctx *context.Context, form auth.NewWebhookForm) {
w := &models.Webhook{ w := &models.Webhook{
RepoID: orCtx.RepoID, RepoID: orCtx.RepoID,
URL: form.PayloadURL, URL: form.PayloadURL,
HTTPMethod: form.HTTPMethod,
ContentType: contentType, ContentType: contentType,
Secret: form.Secret, Secret: form.Secret,
HookEvent: ParseHookEvent(form.WebhookForm), HookEvent: ParseHookEvent(form.WebhookForm),

View File

@ -6,6 +6,18 @@
<label for="payload_url">{{.i18n.Tr "repo.settings.payload_url"}}</label> <label for="payload_url">{{.i18n.Tr "repo.settings.payload_url"}}</label>
<input id="payload_url" name="payload_url" type="url" value="{{.Webhook.URL}}" autofocus required> <input id="payload_url" name="payload_url" type="url" value="{{.Webhook.URL}}" autofocus required>
</div> </div>
<div class="field">
<label>{{.i18n.Tr "repo.settings.http_method"}}</label>
<div class="ui selection dropdown">
<input type="hidden" id="http_method" name="http_method" value="{{if .Webhook.HTTPMethod}}{{.Webhook.HTTPMethod}}{{else}}POST{{end}}">
<div class="default text"></div>
<i class="dropdown icon"></i>
<div class="menu">
<div class="item" data-value="POST">POST</div>
<div class="item" data-value="GET">GET</div>
</div>
</div>
</div>
<div class="field"> <div class="field">
<label>{{.i18n.Tr "repo.settings.content_type"}}</label> <label>{{.i18n.Tr "repo.settings.content_type"}}</label>
<div class="ui selection dropdown"> <div class="ui selection dropdown">