2014-05-06 04:52:25 +04:00
// Copyright 2014 The Gogs Authors. All rights reserved.
2017-05-29 10:17:15 +03:00
// Copyright 2017 The Gitea Authors. All rights reserved.
2014-05-06 04:52:25 +04:00
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package models
import (
2019-03-26 03:08:55 +03:00
"crypto/hmac"
"crypto/sha256"
2015-02-11 05:06:59 +03:00
"crypto/tls"
2019-03-26 03:08:55 +03:00
"encoding/hex"
2014-05-06 04:52:25 +04:00
"encoding/json"
2015-08-27 18:06:14 +03:00
"fmt"
2014-08-24 16:59:47 +04:00
"io/ioutil"
2019-05-21 10:20:17 +03:00
"net"
2019-05-05 21:09:02 +03:00
"net/http"
2019-05-21 10:20:17 +03:00
"net/url"
2015-08-27 18:06:14 +03:00
"strings"
2014-06-08 12:45:34 +04:00
"time"
2014-05-06 04:52:25 +04:00
2019-09-09 08:48:21 +03:00
"code.gitea.io/gitea/modules/git"
2016-11-10 19:24:48 +03:00
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
2019-05-11 13:21:34 +03:00
api "code.gitea.io/gitea/modules/structs"
2016-11-10 19:24:48 +03:00
"code.gitea.io/gitea/modules/sync"
2019-08-15 17:46:21 +03:00
"code.gitea.io/gitea/modules/timeutil"
2019-09-09 08:48:21 +03:00
"github.com/gobwas/glob"
2017-08-28 08:06:45 +03:00
gouuid "github.com/satori/go.uuid"
2019-08-23 19:40:30 +03:00
"github.com/unknwon/com"
2014-05-06 04:52:25 +04:00
)
2016-11-22 09:42:52 +03:00
// HookQueue is a global queue of web hooks
2016-08-31 01:50:30 +03:00
var HookQueue = sync . NewUniqueQueue ( setting . Webhook . QueueLength )
2016-11-22 09:42:52 +03:00
// HookContentType is the content type of a web hook
2014-06-08 12:45:34 +04:00
type HookContentType int
2014-05-06 04:52:25 +04:00
const (
2016-11-22 09:42:52 +03:00
// ContentTypeJSON is a JSON payload for web hooks
2016-11-07 23:58:22 +03:00
ContentTypeJSON HookContentType = iota + 1
2016-11-22 09:42:52 +03:00
// ContentTypeForm is an url-encoded form payload for web hook
2016-11-07 19:53:22 +03:00
ContentTypeForm
2014-05-06 04:52:25 +04:00
)
2014-11-13 20:57:00 +03:00
var hookContentTypes = map [ string ] HookContentType {
2016-11-07 23:58:22 +03:00
"json" : ContentTypeJSON ,
2016-11-07 19:53:22 +03:00
"form" : ContentTypeForm ,
2014-11-13 20:57:00 +03:00
}
// ToHookContentType returns HookContentType by given name.
func ToHookContentType ( name string ) HookContentType {
return hookContentTypes [ name ]
}
2016-11-22 09:42:52 +03:00
// Name returns the name of a given web hook's content type
2014-11-13 10:32:18 +03:00
func ( t HookContentType ) Name ( ) string {
switch t {
2016-11-07 23:58:22 +03:00
case ContentTypeJSON :
2014-11-13 10:32:18 +03:00
return "json"
2016-11-07 19:53:22 +03:00
case ContentTypeForm :
2014-11-13 10:32:18 +03:00
return "form"
}
return ""
}
2014-11-13 20:57:00 +03:00
// IsValidHookContentType returns true if given name is a valid hook content type.
func IsValidHookContentType ( name string ) bool {
_ , ok := hookContentTypes [ name ]
return ok
}
2016-11-22 09:42:52 +03:00
// HookEvents is a set of web hook events
2015-08-28 18:36:13 +03:00
type HookEvents struct {
2018-05-16 17:01:55 +03:00
Create bool ` json:"create" `
Delete bool ` json:"delete" `
Fork bool ` json:"fork" `
Issues bool ` json:"issues" `
IssueComment bool ` json:"issue_comment" `
Push bool ` json:"push" `
PullRequest bool ` json:"pull_request" `
Repository bool ` json:"repository" `
Release bool ` json:"release" `
2015-08-28 18:36:13 +03:00
}
2014-06-08 12:45:34 +04:00
// HookEvent represents events that will delivery hook.
2014-05-06 04:52:25 +04:00
type HookEvent struct {
2019-09-09 08:48:21 +03:00
PushOnly bool ` json:"push_only" `
SendEverything bool ` json:"send_everything" `
ChooseEvents bool ` json:"choose_events" `
BranchFilter string ` json:"branch_filter" `
2015-08-28 18:36:13 +03:00
HookEvents ` json:"events" `
2014-05-06 04:52:25 +04:00
}
2016-11-22 09:42:52 +03:00
// HookStatus is the status of a web hook
2015-08-26 16:45:51 +03:00
type HookStatus int
2016-11-22 09:42:52 +03:00
// Possible statuses of a web hook
2015-08-26 16:45:51 +03:00
const (
2016-11-07 19:08:21 +03:00
HookStatusNone = iota
2016-11-07 18:37:32 +03:00
HookStatusSucceed
HookStatusFail
2015-08-26 16:45:51 +03:00
)
2014-06-08 12:45:34 +04:00
// Webhook represents a web hook object.
2014-05-06 04:52:25 +04:00
type Webhook struct {
2017-01-06 18:14:33 +03:00
ID int64 ` xorm:"pk autoincr" `
RepoID int64 ` xorm:"INDEX" `
OrgID int64 ` xorm:"INDEX" `
2015-08-26 16:45:51 +03:00
URL string ` xorm:"url TEXT" `
2019-03-26 03:08:55 +03:00
Signature string ` xorm:"TEXT" `
2019-05-05 21:09:02 +03:00
HTTPMethod string ` xorm:"http_method" `
2014-08-24 16:59:47 +04:00
ContentType HookContentType
Secret string ` xorm:"TEXT" `
Events string ` xorm:"TEXT" `
* HookEvent ` xorm:"-" `
2015-08-26 16:45:51 +03:00
IsSSL bool ` xorm:"is_ssl" `
2017-01-06 18:14:33 +03:00
IsActive bool ` xorm:"INDEX" `
2014-08-24 16:59:47 +04:00
HookTaskType HookTaskType
2015-08-26 16:45:51 +03:00
Meta string ` xorm:"TEXT" ` // store hook-specific attributes
LastStatus HookStatus // Last delivery status
2016-03-10 03:53:30 +03:00
2019-08-15 17:46:21 +03:00
CreatedUnix timeutil . TimeStamp ` xorm:"INDEX created" `
UpdatedUnix timeutil . TimeStamp ` xorm:"INDEX updated" `
2014-05-06 04:52:25 +04:00
}
2017-10-01 19:52:35 +03:00
// AfterLoad updates the webhook object upon setting a column
func ( w * Webhook ) AfterLoad ( ) {
w . HookEvent = & HookEvent { }
if err := json . Unmarshal ( [ ] byte ( w . Events ) , w . HookEvent ) ; err != nil {
2019-04-02 10:48:31 +03:00
log . Error ( "Unmarshal[%d]: %v" , w . ID , err )
2014-05-06 04:52:25 +04:00
}
}
2016-11-22 09:42:52 +03:00
// GetSlackHook returns slack metadata
2015-08-28 18:36:13 +03:00
func ( w * Webhook ) GetSlackHook ( ) * SlackMeta {
s := & SlackMeta { }
2014-08-24 16:59:47 +04:00
if err := json . Unmarshal ( [ ] byte ( w . Meta ) , s ) ; err != nil {
2019-04-02 10:48:31 +03:00
log . Error ( "webhook.GetSlackHook(%d): %v" , w . ID , err )
2014-08-24 16:59:47 +04:00
}
return s
}
2017-08-28 08:06:45 +03:00
// GetDiscordHook returns discord metadata
func ( w * Webhook ) GetDiscordHook ( ) * DiscordMeta {
s := & DiscordMeta { }
if err := json . Unmarshal ( [ ] byte ( w . Meta ) , s ) ; err != nil {
2019-04-02 10:48:31 +03:00
log . Error ( "webhook.GetDiscordHook(%d): %v" , w . ID , err )
2017-08-28 08:06:45 +03:00
}
return s
}
2019-04-19 05:45:02 +03:00
// GetTelegramHook returns telegram metadata
func ( w * Webhook ) GetTelegramHook ( ) * TelegramMeta {
s := & TelegramMeta { }
if err := json . Unmarshal ( [ ] byte ( w . Meta ) , s ) ; err != nil {
log . Error ( "webhook.GetTelegramHook(%d): %v" , w . ID , err )
}
return s
}
2015-08-27 18:06:14 +03:00
// History returns history of webhook by given conditions.
func ( w * Webhook ) History ( page int ) ( [ ] * HookTask , error ) {
return HookTasks ( w . ID , page )
}
2014-06-08 12:54:52 +04:00
// UpdateEvent handles conversion from HookEvent to Events.
2014-06-08 12:45:34 +04:00
func ( w * Webhook ) UpdateEvent ( ) error {
2014-05-06 05:36:08 +04:00
data , err := json . Marshal ( w . HookEvent )
2014-05-06 04:52:25 +04:00
w . Events = string ( data )
return err
}
2015-08-28 18:36:13 +03:00
// HasCreateEvent returns true if hook enabled create event.
func ( w * Webhook ) HasCreateEvent ( ) bool {
return w . SendEverything ||
( w . ChooseEvents && w . HookEvents . Create )
}
2018-05-16 17:01:55 +03:00
// HasDeleteEvent returns true if hook enabled delete event.
func ( w * Webhook ) HasDeleteEvent ( ) bool {
return w . SendEverything ||
( w . ChooseEvents && w . HookEvents . Delete )
}
// HasForkEvent returns true if hook enabled fork event.
func ( w * Webhook ) HasForkEvent ( ) bool {
return w . SendEverything ||
( w . ChooseEvents && w . HookEvents . Fork )
}
// HasIssuesEvent returns true if hook enabled issues event.
func ( w * Webhook ) HasIssuesEvent ( ) bool {
return w . SendEverything ||
( w . ChooseEvents && w . HookEvents . Issues )
}
// HasIssueCommentEvent returns true if hook enabled issue_comment event.
func ( w * Webhook ) HasIssueCommentEvent ( ) bool {
return w . SendEverything ||
( w . ChooseEvents && w . HookEvents . IssueComment )
}
2014-12-07 04:22:48 +03:00
// HasPushEvent returns true if hook enabled push event.
2014-05-06 19:50:31 +04:00
func ( w * Webhook ) HasPushEvent ( ) bool {
2015-08-28 18:36:13 +03:00
return w . PushOnly || w . SendEverything ||
( w . ChooseEvents && w . HookEvents . Push )
2014-05-06 19:50:31 +04:00
}
2016-08-14 13:32:24 +03:00
// HasPullRequestEvent returns true if hook enabled pull request event.
func ( w * Webhook ) HasPullRequestEvent ( ) bool {
return w . SendEverything ||
( w . ChooseEvents && w . HookEvents . PullRequest )
}
2018-05-16 17:01:55 +03:00
// HasReleaseEvent returns if hook enabled release event.
func ( w * Webhook ) HasReleaseEvent ( ) bool {
return w . SendEverything ||
( w . ChooseEvents && w . HookEvents . Release )
}
2017-09-03 11:20:24 +03:00
// HasRepositoryEvent returns if hook enabled repository event.
func ( w * Webhook ) HasRepositoryEvent ( ) bool {
return w . SendEverything ||
( w . ChooseEvents && w . HookEvents . Repository )
}
2018-05-16 17:01:55 +03:00
func ( w * Webhook ) eventCheckers ( ) [ ] struct {
has func ( ) bool
typ HookEventType
} {
return [ ] struct {
has func ( ) bool
typ HookEventType
} {
{ w . HasCreateEvent , HookEventCreate } ,
{ w . HasDeleteEvent , HookEventDelete } ,
{ w . HasForkEvent , HookEventFork } ,
{ w . HasPushEvent , HookEventPush } ,
{ w . HasIssuesEvent , HookEventIssues } ,
{ w . HasIssueCommentEvent , HookEventIssueComment } ,
{ w . HasPullRequestEvent , HookEventPullRequest } ,
{ w . HasRepositoryEvent , HookEventRepository } ,
{ w . HasReleaseEvent , HookEventRelease } ,
}
}
2016-11-22 09:42:52 +03:00
// EventsArray returns an array of hook events
2015-08-29 06:49:59 +03:00
func ( w * Webhook ) EventsArray ( ) [ ] string {
2018-05-16 17:01:55 +03:00
events := make ( [ ] string , 0 , 7 )
for _ , c := range w . eventCheckers ( ) {
if c . has ( ) {
events = append ( events , string ( c . typ ) )
}
2016-08-25 06:44:58 +03:00
}
2015-08-29 06:49:59 +03:00
return events
}
2019-09-09 08:48:21 +03:00
func ( w * Webhook ) checkBranch ( branch string ) bool {
if w . BranchFilter == "" || w . BranchFilter == "*" {
return true
}
g , err := glob . Compile ( w . BranchFilter )
if err != nil {
// should not really happen as BranchFilter is validated
log . Error ( "CheckBranch failed: %s" , err )
return false
}
return g . Match ( branch )
}
2014-06-08 12:45:34 +04:00
// CreateWebhook creates a new web hook.
2014-05-06 04:52:25 +04:00
func CreateWebhook ( w * Webhook ) error {
2019-03-19 05:33:20 +03:00
return createWebhook ( x , w )
}
func createWebhook ( e Engine , w * Webhook ) error {
_ , err := e . Insert ( w )
2014-05-06 04:52:25 +04:00
return err
}
2016-07-17 03:33:59 +03:00
// getWebhook uses argument bean as query condition,
// ID must be specified and do not assign unnecessary fields.
func getWebhook ( bean * Webhook ) ( * Webhook , error ) {
has , err := x . Get ( bean )
2014-05-06 05:36:08 +04:00
if err != nil {
return nil , err
} else if ! has {
2016-07-17 03:33:59 +03:00
return nil , ErrWebhookNotExist { bean . ID }
2014-05-06 05:36:08 +04:00
}
2016-07-17 03:33:59 +03:00
return bean , nil
}
2017-08-30 08:36:52 +03:00
// GetWebhookByID returns webhook of repository by given ID.
func GetWebhookByID ( id int64 ) ( * Webhook , error ) {
return getWebhook ( & Webhook {
ID : id ,
} )
}
2016-07-17 03:33:59 +03:00
// GetWebhookByRepoID returns webhook of repository by given ID.
func GetWebhookByRepoID ( repoID , id int64 ) ( * Webhook , error ) {
return getWebhook ( & Webhook {
ID : id ,
RepoID : repoID ,
} )
2014-05-06 05:36:08 +04:00
}
2016-07-15 20:02:55 +03:00
// GetWebhookByOrgID returns webhook of organization by given ID.
func GetWebhookByOrgID ( orgID , id int64 ) ( * Webhook , error ) {
2016-07-17 03:33:59 +03:00
return getWebhook ( & Webhook {
ID : id ,
OrgID : orgID ,
} )
2016-07-15 20:02:55 +03:00
}
2015-08-28 18:36:13 +03:00
// GetActiveWebhooksByRepoID returns all active webhooks of repository.
2016-08-25 02:05:56 +03:00
func GetActiveWebhooksByRepoID ( repoID int64 ) ( [ ] * Webhook , error ) {
2017-09-03 11:20:24 +03:00
return getActiveWebhooksByRepoID ( x , repoID )
}
func getActiveWebhooksByRepoID ( e Engine , repoID int64 ) ( [ ] * Webhook , error ) {
2016-08-25 02:05:56 +03:00
webhooks := make ( [ ] * Webhook , 0 , 5 )
2017-09-03 11:20:24 +03:00
return webhooks , e . Where ( "is_active=?" , true ) .
2017-01-25 13:37:35 +03:00
Find ( & webhooks , & Webhook { RepoID : repoID } )
2014-05-06 19:50:31 +04:00
}
2016-08-25 02:05:56 +03:00
// GetWebhooksByRepoID returns all webhooks of a repository.
func GetWebhooksByRepoID ( repoID int64 ) ( [ ] * Webhook , error ) {
webhooks := make ( [ ] * Webhook , 0 , 5 )
return webhooks , x . Find ( & webhooks , & Webhook { RepoID : repoID } )
2014-05-06 04:52:25 +04:00
}
2014-05-06 05:36:08 +04:00
2017-01-25 13:37:35 +03:00
// GetActiveWebhooksByOrgID returns all active webhooks for an organization.
func GetActiveWebhooksByOrgID ( orgID int64 ) ( ws [ ] * Webhook , err error ) {
2017-09-03 11:20:24 +03:00
return getActiveWebhooksByOrgID ( x , orgID )
}
func getActiveWebhooksByOrgID ( e Engine , orgID int64 ) ( ws [ ] * Webhook , err error ) {
err = e .
2017-01-25 13:37:35 +03:00
Where ( "org_id=?" , orgID ) .
And ( "is_active=?" , true ) .
Find ( & ws )
return ws , err
}
// GetWebhooksByOrgID returns all webhooks for an organization.
func GetWebhooksByOrgID ( orgID int64 ) ( ws [ ] * Webhook , err error ) {
err = x . Find ( & ws , & Webhook { OrgID : orgID } )
return ws , err
}
2019-03-19 05:33:20 +03:00
// GetDefaultWebhook returns admin-default webhook by given ID.
func GetDefaultWebhook ( id int64 ) ( * Webhook , error ) {
webhook := & Webhook { ID : id }
has , err := x .
Where ( "repo_id=? AND org_id=?" , 0 , 0 ) .
Get ( webhook )
if err != nil {
return nil , err
} else if ! has {
return nil , ErrWebhookNotExist { id }
}
return webhook , nil
}
// GetDefaultWebhooks returns all admin-default webhooks.
func GetDefaultWebhooks ( ) ( [ ] * Webhook , error ) {
return getDefaultWebhooks ( x )
}
func getDefaultWebhooks ( e Engine ) ( [ ] * Webhook , error ) {
webhooks := make ( [ ] * Webhook , 0 , 5 )
return webhooks , e .
Where ( "repo_id=? AND org_id=?" , 0 , 0 ) .
Find ( & webhooks )
}
2014-06-08 12:45:34 +04:00
// UpdateWebhook updates information of webhook.
func UpdateWebhook ( w * Webhook ) error {
2017-10-05 07:43:04 +03:00
_ , err := x . ID ( w . ID ) . AllCols ( ) . Update ( w )
2014-06-08 12:45:34 +04:00
return err
}
2017-08-30 08:36:52 +03:00
// UpdateWebhookLastStatus updates last status of webhook.
func UpdateWebhookLastStatus ( w * Webhook ) error {
_ , err := x . ID ( w . ID ) . Cols ( "last_status" ) . Update ( w )
return err
}
2016-07-17 03:33:59 +03:00
// deleteWebhook uses argument bean as query condition,
// ID must be specified and do not assign unnecessary fields.
func deleteWebhook ( bean * Webhook ) ( err error ) {
2015-08-26 16:45:51 +03:00
sess := x . NewSession ( )
2017-06-21 03:57:05 +03:00
defer sess . Close ( )
2015-08-26 16:45:51 +03:00
if err = sess . Begin ( ) ; err != nil {
return err
}
2017-01-14 05:14:48 +03:00
if count , err := sess . Delete ( bean ) ; err != nil {
2015-08-26 16:45:51 +03:00
return err
2017-01-14 05:14:48 +03:00
} else if count == 0 {
return ErrWebhookNotExist { ID : bean . ID }
2016-07-17 03:33:59 +03:00
} else if _ , err = sess . Delete ( & HookTask { HookID : bean . ID } ) ; err != nil {
2015-08-26 16:45:51 +03:00
return err
}
return sess . Commit ( )
2014-05-06 05:36:08 +04:00
}
2014-06-08 12:45:34 +04:00
2016-07-17 03:33:59 +03:00
// DeleteWebhookByRepoID deletes webhook of repository by given ID.
2016-07-23 15:24:44 +03:00
func DeleteWebhookByRepoID ( repoID , id int64 ) error {
2016-07-17 03:33:59 +03:00
return deleteWebhook ( & Webhook {
ID : id ,
RepoID : repoID ,
} )
}
// DeleteWebhookByOrgID deletes webhook of organization by given ID.
2016-07-23 15:24:44 +03:00
func DeleteWebhookByOrgID ( orgID , id int64 ) error {
2016-07-17 03:33:59 +03:00
return deleteWebhook ( & Webhook {
ID : id ,
OrgID : orgID ,
} )
}
2019-03-19 05:33:20 +03:00
// DeleteDefaultWebhook deletes an admin-default webhook by given ID.
func DeleteDefaultWebhook ( id int64 ) error {
sess := x . NewSession ( )
defer sess . Close ( )
if err := sess . Begin ( ) ; err != nil {
return err
}
count , err := sess .
Where ( "repo_id=? AND org_id=?" , 0 , 0 ) .
Delete ( & Webhook { ID : id } )
if err != nil {
return err
} else if count == 0 {
return ErrWebhookNotExist { ID : id }
}
if _ , err := sess . Delete ( & HookTask { HookID : id } ) ; err != nil {
return err
}
return sess . Commit ( )
}
// copyDefaultWebhooksToRepo creates copies of the default webhooks in a new repo
func copyDefaultWebhooksToRepo ( e Engine , repoID int64 ) error {
ws , err := getDefaultWebhooks ( e )
if err != nil {
return fmt . Errorf ( "GetDefaultWebhooks: %v" , err )
}
for _ , w := range ws {
w . ID = 0
w . RepoID = repoID
if err := createWebhook ( e , w ) ; err != nil {
return fmt . Errorf ( "CreateWebhook: %v" , err )
}
}
return nil
}
2014-06-08 12:45:34 +04:00
// ___ ___ __ ___________ __
// / | \ ____ ____ | | _\__ ___/____ _____| | __
// / ~ \/ _ \ / _ \| |/ / | | \__ \ / ___/ |/ /
// \ Y ( <_> | <_> ) < | | / __ \_\___ \| <
// \___|_ / \____/ \____/|__|_ \ |____| (____ /____ >__|_ \
// \/ \/ \/ \/ \/
2016-11-22 09:42:52 +03:00
// HookTaskType is the type of an hook task
2014-06-08 12:45:34 +04:00
type HookTaskType int
2016-11-22 09:42:52 +03:00
// Types of hook tasks
2014-06-08 12:45:34 +04:00
const (
2014-08-24 16:59:47 +04:00
GOGS HookTaskType = iota + 1
SLACK
2017-05-29 10:17:15 +03:00
GITEA
2017-08-28 08:06:45 +03:00
DISCORD
2017-11-21 07:26:43 +03:00
DINGTALK
2019-04-19 05:45:02 +03:00
TELEGRAM
2019-04-19 17:18:06 +03:00
MSTEAMS
2014-06-08 12:45:34 +04:00
)
2014-11-13 20:57:00 +03:00
var hookTaskTypes = map [ string ] HookTaskType {
2017-11-21 07:26:43 +03:00
"gitea" : GITEA ,
"gogs" : GOGS ,
"slack" : SLACK ,
"discord" : DISCORD ,
"dingtalk" : DINGTALK ,
2019-04-19 05:45:02 +03:00
"telegram" : TELEGRAM ,
2019-04-19 17:18:06 +03:00
"msteams" : MSTEAMS ,
2014-11-13 20:57:00 +03:00
}
// ToHookTaskType returns HookTaskType by given name.
func ToHookTaskType ( name string ) HookTaskType {
return hookTaskTypes [ name ]
}
2016-11-22 09:42:52 +03:00
// Name returns the name of an hook task type
2014-11-13 10:32:18 +03:00
func ( t HookTaskType ) Name ( ) string {
switch t {
2017-05-29 10:17:15 +03:00
case GITEA :
return "gitea"
2014-11-13 10:32:18 +03:00
case GOGS :
return "gogs"
case SLACK :
return "slack"
2017-08-28 08:06:45 +03:00
case DISCORD :
return "discord"
2017-11-21 07:26:43 +03:00
case DINGTALK :
return "dingtalk"
2019-04-19 05:45:02 +03:00
case TELEGRAM :
return "telegram"
2019-04-19 17:18:06 +03:00
case MSTEAMS :
return "msteams"
2014-11-13 10:32:18 +03:00
}
return ""
}
2014-11-13 20:57:00 +03:00
// IsValidHookTaskType returns true if given name is a valid hook task type.
func IsValidHookTaskType ( name string ) bool {
_ , ok := hookTaskTypes [ name ]
return ok
}
2016-11-22 09:42:52 +03:00
// HookEventType is the type of an hook event
2014-08-10 02:40:10 +04:00
type HookEventType string
2016-11-22 09:42:52 +03:00
// Types of hook events
2014-08-10 02:40:10 +04:00
const (
2018-12-27 21:04:30 +03:00
HookEventCreate HookEventType = "create"
HookEventDelete HookEventType = "delete"
HookEventFork HookEventType = "fork"
HookEventPush HookEventType = "push"
HookEventIssues HookEventType = "issues"
HookEventIssueComment HookEventType = "issue_comment"
HookEventPullRequest HookEventType = "pull_request"
HookEventRepository HookEventType = "repository"
HookEventRelease HookEventType = "release"
HookEventPullRequestApproved HookEventType = "pull_request_approved"
HookEventPullRequestRejected HookEventType = "pull_request_rejected"
HookEventPullRequestComment HookEventType = "pull_request_comment"
2014-08-10 02:40:10 +04:00
)
2015-08-27 18:06:14 +03:00
// HookRequest represents hook task request information.
type HookRequest struct {
Headers map [ string ] string ` json:"headers" `
}
// HookResponse represents hook task response information.
type HookResponse struct {
Status int ` json:"status" `
Headers map [ string ] string ` json:"headers" `
Body string ` json:"body" `
}
2014-06-08 12:54:52 +04:00
// HookTask represents a hook task.
2014-06-08 12:45:34 +04:00
type HookTask struct {
2015-08-27 18:06:14 +03:00
ID int64 ` xorm:"pk autoincr" `
RepoID int64 ` xorm:"INDEX" `
HookID int64
UUID string
Type HookTaskType
2016-01-27 03:38:07 +03:00
URL string ` xorm:"TEXT" `
2019-03-26 03:08:55 +03:00
Signature string ` xorm:"TEXT" `
2015-08-28 18:36:13 +03:00
api . Payloader ` xorm:"-" `
2015-08-27 18:06:14 +03:00
PayloadContent string ` xorm:"TEXT" `
2019-05-05 21:09:02 +03:00
HTTPMethod string ` xorm:"http_method" `
2015-08-27 18:06:14 +03:00
ContentType HookContentType
EventType HookEventType
IsSSL bool
IsDelivered bool
Delivered int64
DeliveredString string ` xorm:"-" `
// History info.
IsSucceed bool
RequestContent string ` xorm:"TEXT" `
RequestInfo * HookRequest ` xorm:"-" `
ResponseContent string ` xorm:"TEXT" `
ResponseInfo * HookResponse ` xorm:"-" `
}
2016-11-22 09:42:52 +03:00
// BeforeUpdate will be invoked by XORM before updating a record
// representing this object
2015-08-27 18:06:14 +03:00
func ( t * HookTask ) BeforeUpdate ( ) {
if t . RequestInfo != nil {
2016-11-22 09:42:52 +03:00
t . RequestContent = t . simpleMarshalJSON ( t . RequestInfo )
2015-08-27 18:06:14 +03:00
}
if t . ResponseInfo != nil {
2016-11-22 09:42:52 +03:00
t . ResponseContent = t . simpleMarshalJSON ( t . ResponseInfo )
2015-08-27 18:06:14 +03:00
}
}
2017-10-01 19:52:35 +03:00
// AfterLoad updates the webhook object upon setting a column
func ( t * HookTask ) AfterLoad ( ) {
t . DeliveredString = time . Unix ( 0 , t . Delivered ) . Format ( "2006-01-02 15:04:05 MST" )
if len ( t . RequestContent ) == 0 {
return
}
t . RequestInfo = & HookRequest { }
if err := json . Unmarshal ( [ ] byte ( t . RequestContent ) , t . RequestInfo ) ; err != nil {
2019-04-02 10:48:31 +03:00
log . Error ( "Unmarshal RequestContent[%d]: %v" , t . ID , err )
2018-05-23 09:12:02 +03:00
}
if len ( t . ResponseContent ) > 0 {
t . ResponseInfo = & HookResponse { }
if err := json . Unmarshal ( [ ] byte ( t . ResponseContent ) , t . ResponseInfo ) ; err != nil {
2019-04-02 10:48:31 +03:00
log . Error ( "Unmarshal ResponseContent[%d]: %v" , t . ID , err )
2018-05-23 09:12:02 +03:00
}
2015-08-27 18:06:14 +03:00
}
}
2016-11-22 09:42:52 +03:00
func ( t * HookTask ) simpleMarshalJSON ( v interface { } ) string {
2015-08-27 18:06:14 +03:00
p , err := json . Marshal ( v )
if err != nil {
2019-04-02 10:48:31 +03:00
log . Error ( "Marshal [%d]: %v" , t . ID , err )
2015-08-27 18:06:14 +03:00
}
return string ( p )
}
// HookTasks returns a list of hook tasks by given conditions.
func HookTasks ( hookID int64 , page int ) ( [ ] * HookTask , error ) {
tasks := make ( [ ] * HookTask , 0 , setting . Webhook . PagingNum )
2016-11-10 18:16:32 +03:00
return tasks , x .
Limit ( setting . Webhook . PagingNum , ( page - 1 ) * setting . Webhook . PagingNum ) .
Where ( "hook_id=?" , hookID ) .
Desc ( "id" ) .
Find ( & tasks )
2014-06-08 12:45:34 +04:00
}
// CreateHookTask creates a new hook task,
// it handles conversion from Payload to PayloadContent.
func CreateHookTask ( t * HookTask ) error {
2017-09-03 11:20:24 +03:00
return createHookTask ( x , t )
}
func createHookTask ( e Engine , t * HookTask ) error {
2015-08-28 18:36:13 +03:00
data , err := t . Payloader . JSONPayload ( )
2014-06-08 12:45:34 +04:00
if err != nil {
return err
}
2016-02-21 02:13:12 +03:00
t . UUID = gouuid . NewV4 ( ) . String ( )
2014-06-08 12:45:34 +04:00
t . PayloadContent = string ( data )
2017-09-03 11:20:24 +03:00
_ , err = e . Insert ( t )
2014-06-08 12:45:34 +04:00
return err
}
// UpdateHookTask updates information of hook task.
func UpdateHookTask ( t * HookTask ) error {
2017-10-05 07:43:04 +03:00
_ , err := x . ID ( t . ID ) . AllCols ( ) . Update ( t )
2014-06-08 12:45:34 +04:00
return err
}
2017-08-29 17:55:24 +03:00
// PrepareWebhook adds special webhook to task queue for given payload.
func PrepareWebhook ( w * Webhook , repo * Repository , event HookEventType , p api . Payloader ) error {
2017-09-03 11:20:24 +03:00
return prepareWebhook ( x , w , repo , event , p )
}
2019-09-09 08:48:21 +03:00
// getPayloadBranch returns branch for hook event, if applicable.
func getPayloadBranch ( p api . Payloader ) string {
switch pp := p . ( type ) {
case * api . CreatePayload :
if pp . RefType == "branch" {
return pp . Ref
}
case * api . DeletePayload :
if pp . RefType == "branch" {
return pp . Ref
}
case * api . PushPayload :
if strings . HasPrefix ( pp . Ref , git . BranchPrefix ) {
return pp . Ref [ len ( git . BranchPrefix ) : ]
}
}
return ""
}
2017-09-03 11:20:24 +03:00
func prepareWebhook ( e Engine , w * Webhook , repo * Repository , event HookEventType , p api . Payloader ) error {
2018-05-16 17:01:55 +03:00
for _ , e := range w . eventCheckers ( ) {
if event == e . typ {
if ! e . has ( ) {
return nil
}
2017-09-03 11:20:24 +03:00
}
2017-08-29 17:55:24 +03:00
}
2019-09-09 08:48:21 +03:00
// If payload has no associated branch (e.g. it's a new tag, issue, etc.),
// branch filter has no effect.
if branch := getPayloadBranch ( p ) ; branch != "" {
if ! w . checkBranch ( branch ) {
log . Info ( "Branch %q doesn't match branch filter %q, skipping" , branch , w . BranchFilter )
return nil
}
}
2017-08-29 17:55:24 +03:00
var payloader api . Payloader
var err error
// Use separate objects so modifications won't be made on payload on non-Gogs/Gitea type hooks.
switch w . HookTaskType {
case SLACK :
payloader , err = GetSlackPayload ( p , event , w . Meta )
if err != nil {
return fmt . Errorf ( "GetSlackPayload: %v" , err )
}
case DISCORD :
payloader , err = GetDiscordPayload ( p , event , w . Meta )
if err != nil {
return fmt . Errorf ( "GetDiscordPayload: %v" , err )
}
2017-11-21 07:26:43 +03:00
case DINGTALK :
payloader , err = GetDingtalkPayload ( p , event , w . Meta )
if err != nil {
return fmt . Errorf ( "GetDingtalkPayload: %v" , err )
}
2019-04-19 05:45:02 +03:00
case TELEGRAM :
payloader , err = GetTelegramPayload ( p , event , w . Meta )
if err != nil {
return fmt . Errorf ( "GetTelegramPayload: %v" , err )
}
2019-04-19 17:18:06 +03:00
case MSTEAMS :
payloader , err = GetMSTeamsPayload ( p , event , w . Meta )
if err != nil {
return fmt . Errorf ( "GetMSTeamsPayload: %v" , err )
}
2017-08-29 17:55:24 +03:00
default :
p . SetSecret ( w . Secret )
payloader = p
}
2019-03-26 03:08:55 +03:00
var signature string
if len ( w . Secret ) > 0 {
data , err := payloader . JSONPayload ( )
if err != nil {
2019-04-02 10:48:31 +03:00
log . Error ( "prepareWebhooks.JSONPayload: %v" , err )
2019-03-26 03:08:55 +03:00
}
sig := hmac . New ( sha256 . New , [ ] byte ( w . Secret ) )
2019-06-12 22:41:28 +03:00
_ , err = sig . Write ( data )
if err != nil {
log . Error ( "prepareWebhooks.sigWrite: %v" , err )
}
2019-03-26 03:08:55 +03:00
signature = hex . EncodeToString ( sig . Sum ( nil ) )
}
2017-09-03 11:20:24 +03:00
if err = createHookTask ( e , & HookTask {
2017-08-29 17:55:24 +03:00
RepoID : repo . ID ,
HookID : w . ID ,
Type : w . HookTaskType ,
URL : w . URL ,
2019-03-26 03:08:55 +03:00
Signature : signature ,
2017-08-29 17:55:24 +03:00
Payloader : payloader ,
2019-05-05 21:09:02 +03:00
HTTPMethod : w . HTTPMethod ,
2017-08-29 17:55:24 +03:00
ContentType : w . ContentType ,
EventType : event ,
IsSSL : w . IsSSL ,
} ) ; err != nil {
return fmt . Errorf ( "CreateHookTask: %v" , err )
}
return nil
}
2015-08-28 18:36:13 +03:00
// PrepareWebhooks adds new webhooks to task queue for given payload.
func PrepareWebhooks ( repo * Repository , event HookEventType , p api . Payloader ) error {
2017-09-03 11:20:24 +03:00
return prepareWebhooks ( x , repo , event , p )
}
func prepareWebhooks ( e Engine , repo * Repository , event HookEventType , p api . Payloader ) error {
ws , err := getActiveWebhooksByRepoID ( e , repo . ID )
2015-08-28 18:36:13 +03:00
if err != nil {
return fmt . Errorf ( "GetActiveWebhooksByRepoID: %v" , err )
}
// check if repo belongs to org and append additional webhooks
2017-09-03 11:20:24 +03:00
if repo . mustOwner ( e ) . IsOrganization ( ) {
2015-08-28 18:36:13 +03:00
// get hooks for org
2017-09-03 11:20:24 +03:00
orgHooks , err := getActiveWebhooksByOrgID ( e , repo . OwnerID )
2015-08-28 18:36:13 +03:00
if err != nil {
return fmt . Errorf ( "GetActiveWebhooksByOrgID: %v" , err )
}
2017-03-15 03:52:01 +03:00
ws = append ( ws , orgHooks ... )
2015-08-28 18:36:13 +03:00
}
if len ( ws ) == 0 {
return nil
}
for _ , w := range ws {
2017-09-03 11:20:24 +03:00
if err = prepareWebhook ( e , w , repo , event , p ) ; err != nil {
2017-08-29 17:55:24 +03:00
return err
2015-08-28 18:36:13 +03:00
}
}
return nil
}
2019-05-21 10:20:17 +03:00
func ( t * HookTask ) deliver ( ) error {
2015-08-27 18:06:14 +03:00
t . IsDelivered = true
2019-05-21 10:20:17 +03:00
var req * http . Request
var err error
2019-05-05 21:09:02 +03:00
2019-05-15 15:01:53 +03:00
switch t . HTTPMethod {
case "" :
log . Info ( "HTTP Method for webhook %d empty, setting to POST as default" , t . ID )
fallthrough
case http . MethodPost :
2019-05-05 21:09:02 +03:00
switch t . ContentType {
case ContentTypeJSON :
2019-05-21 10:20:17 +03:00
req , err = http . NewRequest ( "POST" , t . URL , strings . NewReader ( t . PayloadContent ) )
if err != nil {
return err
}
req . Header . Set ( "Content-Type" , "application/json" )
2019-05-05 21:09:02 +03:00
case ContentTypeForm :
2019-05-21 10:20:17 +03:00
var forms = url . Values {
"payload" : [ ] string { t . PayloadContent } ,
}
req , err = http . NewRequest ( "POST" , t . URL , strings . NewReader ( forms . Encode ( ) ) )
if err != nil {
return err
}
2019-05-05 21:09:02 +03:00
}
2019-05-15 15:01:53 +03:00
case http . MethodGet :
2019-05-21 10:20:17 +03:00
u , err := url . Parse ( t . URL )
if err != nil {
return err
}
vals := u . Query ( )
vals [ "payload" ] = [ ] string { t . PayloadContent }
u . RawQuery = vals . Encode ( )
req , err = http . NewRequest ( "GET" , u . String ( ) , nil )
if err != nil {
return err
}
2019-05-15 15:01:53 +03:00
default :
2019-05-21 10:20:17 +03:00
return fmt . Errorf ( "Invalid http method for webhook: [%d] %v" , t . ID , t . HTTPMethod )
2019-05-05 21:09:02 +03:00
}
2019-05-21 10:20:17 +03:00
req . Header . Add ( "X-Gitea-Delivery" , t . UUID )
req . Header . Add ( "X-Gitea-Event" , string ( t . EventType ) )
req . Header . Add ( "X-Gitea-Signature" , t . Signature )
req . Header . Add ( "X-Gogs-Delivery" , t . UUID )
req . Header . Add ( "X-Gogs-Event" , string ( t . EventType ) )
req . Header . Add ( "X-Gogs-Signature" , t . Signature )
req . Header [ "X-GitHub-Delivery" ] = [ ] string { t . UUID }
req . Header [ "X-GitHub-Event" ] = [ ] string { string ( t . EventType ) }
2014-06-08 12:45:34 +04:00
2015-08-27 18:06:14 +03:00
// Record delivery information.
2019-05-15 15:01:53 +03:00
t . RequestInfo = & HookRequest {
Headers : map [ string ] string { } ,
}
2019-05-21 10:20:17 +03:00
for k , vals := range req . Header {
2015-08-27 18:06:14 +03:00
t . RequestInfo . Headers [ k ] = strings . Join ( vals , "," )
}
2015-07-25 16:32:04 +03:00
2019-05-15 15:01:53 +03:00
t . ResponseInfo = & HookResponse {
Headers : map [ string ] string { } ,
}
2015-08-27 18:06:14 +03:00
defer func ( ) {
2016-07-23 15:24:44 +03:00
t . Delivered = time . Now ( ) . UnixNano ( )
2015-08-27 18:06:14 +03:00
if t . IsSucceed {
log . Trace ( "Hook delivered: %s" , t . UUID )
2015-12-05 21:50:43 +03:00
} else {
log . Trace ( "Hook delivery failed: %s" , t . UUID )
2015-07-25 16:32:04 +03:00
}
2015-08-27 18:06:14 +03:00
2018-05-23 09:12:02 +03:00
if err := UpdateHookTask ( t ) ; err != nil {
2019-04-02 10:48:31 +03:00
log . Error ( "UpdateHookTask [%d]: %v" , t . ID , err )
2018-05-23 09:12:02 +03:00
}
2015-08-27 18:06:14 +03:00
// Update webhook last delivery status.
2017-08-30 08:36:52 +03:00
w , err := GetWebhookByID ( t . HookID )
2015-08-27 18:06:14 +03:00
if err != nil {
2019-04-02 10:48:31 +03:00
log . Error ( "GetWebhookByID: %v" , err )
2015-08-27 18:06:14 +03:00
return
}
if t . IsSucceed {
2016-11-07 18:37:32 +03:00
w . LastStatus = HookStatusSucceed
2015-08-27 18:06:14 +03:00
} else {
2016-11-07 18:37:32 +03:00
w . LastStatus = HookStatusFail
2015-07-25 16:32:04 +03:00
}
2017-08-30 08:36:52 +03:00
if err = UpdateWebhookLastStatus ( w ) ; err != nil {
2019-04-02 10:48:31 +03:00
log . Error ( "UpdateWebhookLastStatus: %v" , err )
2015-08-27 18:06:14 +03:00
return
}
} ( )
2019-05-21 10:20:17 +03:00
resp , err := webhookHTTPClient . Do ( req )
2015-08-27 18:06:14 +03:00
if err != nil {
t . ResponseInfo . Body = fmt . Sprintf ( "Delivery: %v" , err )
2019-05-21 10:20:17 +03:00
return err
2015-07-25 16:32:04 +03:00
}
2015-08-27 18:06:14 +03:00
defer resp . Body . Close ( )
2014-08-10 02:40:10 +04:00
2015-08-27 18:06:14 +03:00
// Status code is 20x can be seen as succeed.
t . IsSucceed = resp . StatusCode / 100 == 2
t . ResponseInfo . Status = resp . StatusCode
for k , vals := range resp . Header {
t . ResponseInfo . Headers [ k ] = strings . Join ( vals , "," )
}
p , err := ioutil . ReadAll ( resp . Body )
if err != nil {
t . ResponseInfo . Body = fmt . Sprintf ( "read body: %s" , err )
2019-05-21 10:20:17 +03:00
return err
2015-08-27 18:06:14 +03:00
}
t . ResponseInfo . Body = string ( p )
2019-05-21 10:20:17 +03:00
return nil
2015-07-25 16:32:04 +03:00
}
2014-06-08 12:45:34 +04:00
2015-07-25 16:32:04 +03:00
// DeliverHooks checks and delivers undelivered hooks.
2015-10-24 10:36:47 +03:00
// TODO: shoot more hooks at same time.
2015-07-25 16:32:04 +03:00
func DeliverHooks ( ) {
tasks := make ( [ ] * HookTask , 0 , 10 )
2017-05-05 05:13:48 +03:00
err := x . Where ( "is_delivered=?" , false ) . Find ( & tasks )
if err != nil {
2019-04-02 10:48:31 +03:00
log . Error ( "DeliverHooks: %v" , err )
2017-05-05 05:13:48 +03:00
return
}
2014-09-13 02:58:24 +04:00
// Update hook task status.
for _ , t := range tasks {
2019-05-21 10:20:17 +03:00
if err = t . deliver ( ) ; err != nil {
log . Error ( "deliver: %v" , err )
}
2015-07-25 16:32:04 +03:00
}
// Start listening on new hook requests.
2018-01-29 03:26:01 +03:00
for repoIDStr := range HookQueue . Queue ( ) {
log . Trace ( "DeliverHooks [repo_id: %v]" , repoIDStr )
HookQueue . Remove ( repoIDStr )
repoID , err := com . StrTo ( repoIDStr ) . Int64 ( )
if err != nil {
2019-04-02 10:48:31 +03:00
log . Error ( "Invalid repo ID: %s" , repoIDStr )
2018-01-29 03:26:01 +03:00
continue
}
2015-07-25 16:32:04 +03:00
tasks = make ( [ ] * HookTask , 0 , 5 )
if err := x . Where ( "repo_id=? AND is_delivered=?" , repoID , false ) . Find ( & tasks ) ; err != nil {
2019-04-02 10:48:31 +03:00
log . Error ( "Get repository [%d] hook tasks: %v" , repoID , err )
2015-07-25 16:32:04 +03:00
continue
}
for _ , t := range tasks {
2019-05-21 10:20:17 +03:00
if err = t . deliver ( ) ; err != nil {
log . Error ( "deliver: %v" , err )
}
2014-09-13 02:58:24 +04:00
}
}
2014-06-08 12:45:34 +04:00
}
2015-07-25 16:32:04 +03:00
2019-05-21 10:20:17 +03:00
var webhookHTTPClient * http . Client
2016-11-22 09:42:52 +03:00
// InitDeliverHooks starts the hooks delivery thread
2015-07-25 16:32:04 +03:00
func InitDeliverHooks ( ) {
2019-05-21 10:20:17 +03:00
timeout := time . Duration ( setting . Webhook . DeliverTimeout ) * time . Second
webhookHTTPClient = & http . Client {
Transport : & http . Transport {
TLSClientConfig : & tls . Config { InsecureSkipVerify : setting . Webhook . SkipTLSVerify } ,
2019-09-09 19:21:07 +03:00
Proxy : http . ProxyFromEnvironment ,
2019-05-21 10:20:17 +03:00
Dial : func ( netw , addr string ) ( net . Conn , error ) {
conn , err := net . DialTimeout ( netw , addr , timeout )
if err != nil {
return nil , err
}
2019-06-12 22:41:28 +03:00
return conn , conn . SetDeadline ( time . Now ( ) . Add ( timeout ) )
2019-05-21 10:20:17 +03:00
} ,
} ,
}
2015-07-25 16:32:04 +03:00
go DeliverHooks ( )
}