2016-12-06 23:36:28 -05:00
// Copyright 2016 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 utils
import (
2020-12-12 23:33:19 +08:00
"fmt"
2018-05-16 22:01:55 +08:00
"net/http"
2018-09-10 15:31:08 +01:00
"strings"
2018-05-16 22:01:55 +08:00
2016-12-06 23:36:28 -05:00
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/context"
2019-11-10 12:41:51 +08:00
"code.gitea.io/gitea/modules/convert"
2021-07-25 00:03:58 +08:00
"code.gitea.io/gitea/modules/json"
2019-05-11 18:21:34 +08:00
api "code.gitea.io/gitea/modules/structs"
2020-12-25 09:59:32 +00:00
"code.gitea.io/gitea/modules/util"
2018-09-10 15:31:08 +01:00
"code.gitea.io/gitea/routers/utils"
2020-12-08 18:41:14 +08:00
"code.gitea.io/gitea/services/webhook"
2016-12-06 23:36:28 -05:00
)
// GetOrgHook get an organization's webhook. If there is an error, write to
// `ctx` accordingly and return the error
func GetOrgHook ( ctx * context . APIContext , orgID , hookID int64 ) ( * models . Webhook , error ) {
w , err := models . GetWebhookByOrgID ( orgID , hookID )
if err != nil {
if models . IsErrWebhookNotExist ( err ) {
2019-03-18 21:29:43 -05:00
ctx . NotFound ( )
2016-12-06 23:36:28 -05:00
} else {
2019-12-20 18:07:12 +01:00
ctx . Error ( http . StatusInternalServerError , "GetWebhookByOrgID" , err )
2016-12-06 23:36:28 -05:00
}
return nil , err
}
return w , nil
}
// GetRepoHook get a repo's webhook. If there is an error, write to `ctx`
// accordingly and return the error
func GetRepoHook ( ctx * context . APIContext , repoID , hookID int64 ) ( * models . Webhook , error ) {
w , err := models . GetWebhookByRepoID ( repoID , hookID )
if err != nil {
if models . IsErrWebhookNotExist ( err ) {
2019-03-18 21:29:43 -05:00
ctx . NotFound ( )
2016-12-06 23:36:28 -05:00
} else {
2019-12-20 18:07:12 +01:00
ctx . Error ( http . StatusInternalServerError , "GetWebhookByID" , err )
2016-12-06 23:36:28 -05:00
}
return nil , err
}
return w , nil
}
// CheckCreateHookOption check if a CreateHookOption form is valid. If invalid,
// write the appropriate error to `ctx`. Return whether the form is valid
func CheckCreateHookOption ( ctx * context . APIContext , form * api . CreateHookOption ) bool {
2020-12-08 18:41:14 +08:00
if ! webhook . IsValidHookTaskType ( form . Type ) {
2020-12-12 23:33:19 +08:00
ctx . Error ( http . StatusUnprocessableEntity , "" , fmt . Sprintf ( "Invalid hook type: %s" , form . Type ) )
2016-12-06 23:36:28 -05:00
return false
}
for _ , name := range [ ] string { "url" , "content_type" } {
if _ , ok := form . Config [ name ] ; ! ok {
2019-12-20 18:07:12 +01:00
ctx . Error ( http . StatusUnprocessableEntity , "" , "Missing config option: " + name )
2016-12-06 23:36:28 -05:00
return false
}
}
if ! models . IsValidHookContentType ( form . Config [ "content_type" ] ) {
2019-12-20 18:07:12 +01:00
ctx . Error ( http . StatusUnprocessableEntity , "" , "Invalid content type" )
2016-12-06 23:36:28 -05:00
return false
}
return true
}
// AddOrgHook add a hook to an organization. Writes to `ctx` accordingly
func AddOrgHook ( ctx * context . APIContext , form * api . CreateHookOption ) {
org := ctx . Org . Organization
hook , ok := addHook ( ctx , form , org . ID , 0 )
if ok {
2017-11-19 23:00:53 -08:00
ctx . JSON ( http . StatusCreated , convert . ToHook ( org . HomeLink ( ) , hook ) )
2016-12-06 23:36:28 -05:00
}
}
// AddRepoHook add a hook to a repo. Writes to `ctx` accordingly
func AddRepoHook ( ctx * context . APIContext , form * api . CreateHookOption ) {
repo := ctx . Repo
hook , ok := addHook ( ctx , form , 0 , repo . Repository . ID )
if ok {
2017-11-19 23:00:53 -08:00
ctx . JSON ( http . StatusCreated , convert . ToHook ( repo . RepoLink , hook ) )
2016-12-06 23:36:28 -05:00
}
}
2020-03-05 23:10:48 -06:00
func issuesHook ( events [ ] string , event string ) bool {
2020-12-25 09:59:32 +00:00
return util . IsStringInSlice ( event , events , true ) || util . IsStringInSlice ( string ( models . HookEventIssues ) , events , true )
2020-03-05 23:10:48 -06:00
}
func pullHook ( events [ ] string , event string ) bool {
2020-12-25 09:59:32 +00:00
return util . IsStringInSlice ( event , events , true ) || util . IsStringInSlice ( string ( models . HookEventPullRequest ) , events , true )
2020-03-05 23:10:48 -06:00
}
2016-12-06 23:36:28 -05:00
// addHook add the hook specified by `form`, `orgID` and `repoID`. If there is
// an error, write to `ctx` accordingly. Return (webhook, ok)
func addHook ( ctx * context . APIContext , form * api . CreateHookOption , orgID , repoID int64 ) ( * models . Webhook , bool ) {
if len ( form . Events ) == 0 {
form . Events = [ ] string { "push" }
}
w := & models . Webhook {
OrgID : orgID ,
RepoID : repoID ,
URL : form . Config [ "url" ] ,
ContentType : models . ToHookContentType ( form . Config [ "content_type" ] ) ,
Secret : form . Config [ "secret" ] ,
2019-05-15 08:01:53 -04:00
HTTPMethod : "POST" ,
2016-12-06 23:36:28 -05:00
HookEvent : & models . HookEvent {
ChooseEvents : true ,
HookEvents : models . HookEvents {
2020-12-25 09:59:32 +00:00
Create : util . IsStringInSlice ( string ( models . HookEventCreate ) , form . Events , true ) ,
Delete : util . IsStringInSlice ( string ( models . HookEventDelete ) , form . Events , true ) ,
Fork : util . IsStringInSlice ( string ( models . HookEventFork ) , form . Events , true ) ,
2020-03-05 23:10:48 -06:00
Issues : issuesHook ( form . Events , "issues_only" ) ,
IssueAssign : issuesHook ( form . Events , string ( models . HookEventIssueAssign ) ) ,
IssueLabel : issuesHook ( form . Events , string ( models . HookEventIssueLabel ) ) ,
IssueMilestone : issuesHook ( form . Events , string ( models . HookEventIssueMilestone ) ) ,
IssueComment : issuesHook ( form . Events , string ( models . HookEventIssueComment ) ) ,
2020-12-25 09:59:32 +00:00
Push : util . IsStringInSlice ( string ( models . HookEventPush ) , form . Events , true ) ,
2020-03-05 23:10:48 -06:00
PullRequest : pullHook ( form . Events , "pull_request_only" ) ,
PullRequestAssign : pullHook ( form . Events , string ( models . HookEventPullRequestAssign ) ) ,
PullRequestLabel : pullHook ( form . Events , string ( models . HookEventPullRequestLabel ) ) ,
PullRequestMilestone : pullHook ( form . Events , string ( models . HookEventPullRequestMilestone ) ) ,
PullRequestComment : pullHook ( form . Events , string ( models . HookEventPullRequestComment ) ) ,
PullRequestReview : pullHook ( form . Events , "pull_request_review" ) ,
PullRequestSync : pullHook ( form . Events , string ( models . HookEventPullRequestSync ) ) ,
2020-12-25 09:59:32 +00:00
Repository : util . IsStringInSlice ( string ( models . HookEventRepository ) , form . Events , true ) ,
Release : util . IsStringInSlice ( string ( models . HookEventRelease ) , form . Events , true ) ,
2016-12-06 23:36:28 -05:00
} ,
2019-09-09 08:48:21 +03:00
BranchFilter : form . BranchFilter ,
2016-12-06 23:36:28 -05:00
} ,
2020-12-10 01:20:13 +08:00
IsActive : form . Active ,
2021-06-27 21:21:09 +02:00
Type : models . HookType ( form . Type ) ,
2016-12-06 23:36:28 -05:00
}
2020-12-10 01:20:13 +08:00
if w . Type == models . SLACK {
2016-12-06 23:36:28 -05:00
channel , ok := form . Config [ "channel" ]
if ! ok {
2019-12-20 18:07:12 +01:00
ctx . Error ( http . StatusUnprocessableEntity , "" , "Missing config option: channel" )
2016-12-06 23:36:28 -05:00
return nil , false
}
2018-09-10 15:31:08 +01:00
if ! utils . IsValidSlackChannel ( channel ) {
2019-12-20 18:07:12 +01:00
ctx . Error ( http . StatusBadRequest , "" , "Invalid slack channel name" )
2018-09-10 15:31:08 +01:00
return nil , false
}
2019-11-04 06:13:25 +08:00
meta , err := json . Marshal ( & webhook . SlackMeta {
2018-09-10 15:31:08 +01:00
Channel : strings . TrimSpace ( channel ) ,
2016-12-06 23:36:28 -05:00
Username : form . Config [ "username" ] ,
IconURL : form . Config [ "icon_url" ] ,
Color : form . Config [ "color" ] ,
} )
if err != nil {
2019-12-20 18:07:12 +01:00
ctx . Error ( http . StatusInternalServerError , "slack: JSON marshal failed" , err )
2016-12-06 23:36:28 -05:00
return nil , false
}
w . Meta = string ( meta )
}
if err := w . UpdateEvent ( ) ; err != nil {
2019-12-20 18:07:12 +01:00
ctx . Error ( http . StatusInternalServerError , "UpdateEvent" , err )
2016-12-06 23:36:28 -05:00
return nil , false
} else if err := models . CreateWebhook ( w ) ; err != nil {
2019-12-20 18:07:12 +01:00
ctx . Error ( http . StatusInternalServerError , "CreateWebhook" , err )
2016-12-06 23:36:28 -05:00
return nil , false
}
return w , true
}
// EditOrgHook edit webhook `w` according to `form`. Writes to `ctx` accordingly
func EditOrgHook ( ctx * context . APIContext , form * api . EditHookOption , hookID int64 ) {
org := ctx . Org . Organization
hook , err := GetOrgHook ( ctx , org . ID , hookID )
if err != nil {
return
}
if ! editHook ( ctx , form , hook ) {
return
}
updated , err := GetOrgHook ( ctx , org . ID , hookID )
if err != nil {
return
}
2019-12-20 18:07:12 +01:00
ctx . JSON ( http . StatusOK , convert . ToHook ( org . HomeLink ( ) , updated ) )
2016-12-06 23:36:28 -05:00
}
// EditRepoHook edit webhook `w` according to `form`. Writes to `ctx` accordingly
func EditRepoHook ( ctx * context . APIContext , form * api . EditHookOption , hookID int64 ) {
repo := ctx . Repo
hook , err := GetRepoHook ( ctx , repo . Repository . ID , hookID )
if err != nil {
return
}
if ! editHook ( ctx , form , hook ) {
return
}
updated , err := GetRepoHook ( ctx , repo . Repository . ID , hookID )
if err != nil {
return
}
2019-12-20 18:07:12 +01:00
ctx . JSON ( http . StatusOK , convert . ToHook ( repo . RepoLink , updated ) )
2016-12-06 23:36:28 -05:00
}
// editHook edit the webhook `w` according to `form`. If an error occurs, write
// to `ctx` accordingly and return the error. Return whether successful
func editHook ( ctx * context . APIContext , form * api . EditHookOption , w * models . Webhook ) bool {
if form . Config != nil {
if url , ok := form . Config [ "url" ] ; ok {
w . URL = url
}
if ct , ok := form . Config [ "content_type" ] ; ok {
if ! models . IsValidHookContentType ( ct ) {
2019-12-20 18:07:12 +01:00
ctx . Error ( http . StatusUnprocessableEntity , "" , "Invalid content type" )
2016-12-06 23:36:28 -05:00
return false
}
w . ContentType = models . ToHookContentType ( ct )
}
2020-12-10 01:20:13 +08:00
if w . Type == models . SLACK {
2016-12-06 23:36:28 -05:00
if channel , ok := form . Config [ "channel" ] ; ok {
2019-11-04 06:13:25 +08:00
meta , err := json . Marshal ( & webhook . SlackMeta {
2016-12-06 23:36:28 -05:00
Channel : channel ,
Username : form . Config [ "username" ] ,
IconURL : form . Config [ "icon_url" ] ,
Color : form . Config [ "color" ] ,
} )
if err != nil {
2019-12-20 18:07:12 +01:00
ctx . Error ( http . StatusInternalServerError , "slack: JSON marshal failed" , err )
2016-12-06 23:36:28 -05:00
return false
}
w . Meta = string ( meta )
}
}
}
// Update events
if len ( form . Events ) == 0 {
form . Events = [ ] string { "push" }
}
w . PushOnly = false
w . SendEverything = false
w . ChooseEvents = true
2020-12-25 09:59:32 +00:00
w . Create = util . IsStringInSlice ( string ( models . HookEventCreate ) , form . Events , true )
w . Push = util . IsStringInSlice ( string ( models . HookEventPush ) , form . Events , true )
w . PullRequest = util . IsStringInSlice ( string ( models . HookEventPullRequest ) , form . Events , true )
w . Create = util . IsStringInSlice ( string ( models . HookEventCreate ) , form . Events , true )
w . Delete = util . IsStringInSlice ( string ( models . HookEventDelete ) , form . Events , true )
w . Fork = util . IsStringInSlice ( string ( models . HookEventFork ) , form . Events , true )
w . Issues = util . IsStringInSlice ( string ( models . HookEventIssues ) , form . Events , true )
w . IssueComment = util . IsStringInSlice ( string ( models . HookEventIssueComment ) , form . Events , true )
w . Push = util . IsStringInSlice ( string ( models . HookEventPush ) , form . Events , true )
w . PullRequest = util . IsStringInSlice ( string ( models . HookEventPullRequest ) , form . Events , true )
w . Repository = util . IsStringInSlice ( string ( models . HookEventRepository ) , form . Events , true )
w . Release = util . IsStringInSlice ( string ( models . HookEventRelease ) , form . Events , true )
2019-09-09 08:48:21 +03:00
w . BranchFilter = form . BranchFilter
2018-05-16 22:01:55 +08:00
2016-12-06 23:36:28 -05:00
if err := w . UpdateEvent ( ) ; err != nil {
2019-12-20 18:07:12 +01:00
ctx . Error ( http . StatusInternalServerError , "UpdateEvent" , err )
2016-12-06 23:36:28 -05:00
return false
}
if form . Active != nil {
w . IsActive = * form . Active
}
if err := models . UpdateWebhook ( w ) ; err != nil {
2019-12-20 18:07:12 +01:00
ctx . Error ( http . StatusInternalServerError , "UpdateWebhook" , err )
2016-12-06 23:36:28 -05:00
return false
}
return true
}