* 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 fmttags/v1.21.12.1
| @@ -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 | ||||
| @@ -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)) | |||||
| } | |||||
| @@ -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 { | ||||
| @@ -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 | ||||
| @@ -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 | ||||
| @@ -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); | ||||
| @@ -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), | ||||
| @@ -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"> | ||||