282 lines
8.9 KiB
Go
282 lines
8.9 KiB
Go
package github
|
|
|
|
import (
|
|
"crypto/hmac"
|
|
"crypto/sha1"
|
|
"encoding/hex"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"net/http"
|
|
|
|
"gopkg.in/go-playground/webhooks.v3"
|
|
)
|
|
|
|
// Webhook instance contains all methods needed to process events
|
|
type Webhook struct {
|
|
provider webhooks.Provider
|
|
secret string
|
|
eventFuncs map[Event]webhooks.ProcessPayloadFunc
|
|
}
|
|
|
|
// Config defines the configuration to create a new GitHub Webhook instance
|
|
type Config struct {
|
|
Secret string
|
|
}
|
|
|
|
// Event defines a GitHub hook event type
|
|
type Event string
|
|
|
|
// GitHub hook types
|
|
const (
|
|
CommitCommentEvent Event = "commit_comment"
|
|
CreateEvent Event = "create"
|
|
DeleteEvent Event = "delete"
|
|
DeploymentEvent Event = "deployment"
|
|
DeploymentStatusEvent Event = "deployment_status"
|
|
ForkEvent Event = "fork"
|
|
GollumEvent Event = "gollum"
|
|
IssueCommentEvent Event = "issue_comment"
|
|
IssuesEvent Event = "issues"
|
|
LabelEvent Event = "label"
|
|
MemberEvent Event = "member"
|
|
MembershipEvent Event = "membership"
|
|
MilestoneEvent Event = "milestone"
|
|
OrganizationEvent Event = "organization"
|
|
OrgBlockEvent Event = "org_block"
|
|
PageBuildEvent Event = "page_build"
|
|
ProjectCardEvent Event = "project_card"
|
|
ProjectColumnEvent Event = "project_column"
|
|
ProjectEvent Event = "project"
|
|
PublicEvent Event = "public"
|
|
PullRequestEvent Event = "pull_request"
|
|
PullRequestReviewEvent Event = "pull_request_review"
|
|
PullRequestReviewCommentEvent Event = "pull_request_review_comment"
|
|
PushEvent Event = "push"
|
|
ReleaseEvent Event = "release"
|
|
RepositoryEvent Event = "repository"
|
|
StatusEvent Event = "status"
|
|
TeamEvent Event = "team"
|
|
TeamAddEvent Event = "team_add"
|
|
WatchEvent Event = "watch"
|
|
)
|
|
|
|
// EventSubtype defines a GitHub Hook Event subtype
|
|
type EventSubtype string
|
|
|
|
// GitHub hook event subtypes
|
|
const (
|
|
NoSubtype EventSubtype = ""
|
|
BranchSubtype EventSubtype = "branch"
|
|
TagSubtype EventSubtype = "tag"
|
|
PullSubtype EventSubtype = "pull"
|
|
IssueSubtype EventSubtype = "issues"
|
|
)
|
|
|
|
// New creates and returns a WebHook instance denoted by the Provider type
|
|
func New(config *Config) *Webhook {
|
|
return &Webhook{
|
|
provider: webhooks.GitHub,
|
|
secret: config.Secret,
|
|
eventFuncs: map[Event]webhooks.ProcessPayloadFunc{},
|
|
}
|
|
}
|
|
|
|
// Provider returns the current hooks provider ID
|
|
func (hook Webhook) Provider() webhooks.Provider {
|
|
return hook.provider
|
|
}
|
|
|
|
// RegisterEvents registers the function to call when the specified event(s) are encountered
|
|
func (hook Webhook) RegisterEvents(fn webhooks.ProcessPayloadFunc, events ...Event) {
|
|
|
|
for _, event := range events {
|
|
hook.eventFuncs[event] = fn
|
|
}
|
|
}
|
|
|
|
// ParsePayload parses and verifies the payload and fires off the mapped function, if it exists.
|
|
func (hook Webhook) ParsePayload(w http.ResponseWriter, r *http.Request) {
|
|
webhooks.DefaultLog.Info("Parsing Payload...")
|
|
|
|
event := r.Header.Get("X-GitHub-Event")
|
|
if len(event) == 0 {
|
|
webhooks.DefaultLog.Error("Missing X-GitHub-Event Header")
|
|
http.Error(w, "400 Bad Request - Missing X-GitHub-Event Header", http.StatusBadRequest)
|
|
return
|
|
}
|
|
webhooks.DefaultLog.Debug(fmt.Sprintf("X-GitHub-Event:%s", event))
|
|
|
|
gitHubEvent := Event(event)
|
|
|
|
fn, ok := hook.eventFuncs[gitHubEvent]
|
|
// if no event registered
|
|
if !ok {
|
|
webhooks.DefaultLog.Info(fmt.Sprintf("Webhook Event %s not registered, it is recommended to setup only events in github that will be registered in the webhook to avoid unnecessary traffic and reduce potential attack vectors.", event))
|
|
return
|
|
}
|
|
|
|
payload, err := ioutil.ReadAll(r.Body)
|
|
if err != nil || len(payload) == 0 {
|
|
webhooks.DefaultLog.Error("Issue reading Payload")
|
|
http.Error(w, "Issue reading Payload", http.StatusInternalServerError)
|
|
return
|
|
}
|
|
webhooks.DefaultLog.Debug(fmt.Sprintf("Payload:%s", string(payload)))
|
|
|
|
// If we have a Secret set, we should check the MAC
|
|
if len(hook.secret) > 0 {
|
|
webhooks.DefaultLog.Info("Checking secret")
|
|
signature := r.Header.Get("X-Hub-Signature")
|
|
if len(signature) == 0 {
|
|
webhooks.DefaultLog.Error("Missing X-Hub-Signature required for HMAC verification")
|
|
http.Error(w, "403 Forbidden - Missing X-Hub-Signature required for HMAC verification", http.StatusForbidden)
|
|
return
|
|
}
|
|
webhooks.DefaultLog.Debug(fmt.Sprintf("X-Hub-Signature:%s", signature))
|
|
|
|
mac := hmac.New(sha1.New, []byte(hook.secret))
|
|
mac.Write(payload)
|
|
|
|
expectedMAC := hex.EncodeToString(mac.Sum(nil))
|
|
|
|
if !hmac.Equal([]byte(signature[5:]), []byte(expectedMAC)) {
|
|
webhooks.DefaultLog.Error("HMAC verification failed")
|
|
http.Error(w, "403 Forbidden - HMAC verification failed", http.StatusForbidden)
|
|
return
|
|
}
|
|
}
|
|
|
|
// Make headers available to ProcessPayloadFunc as a webhooks type
|
|
hd := webhooks.Header(r.Header)
|
|
|
|
switch gitHubEvent {
|
|
case CommitCommentEvent:
|
|
var cc CommitCommentPayload
|
|
json.Unmarshal([]byte(payload), &cc)
|
|
hook.runProcessPayloadFunc(fn, cc, hd)
|
|
case CreateEvent:
|
|
var c CreatePayload
|
|
json.Unmarshal([]byte(payload), &c)
|
|
hook.runProcessPayloadFunc(fn, c, hd)
|
|
case DeleteEvent:
|
|
var d DeletePayload
|
|
json.Unmarshal([]byte(payload), &d)
|
|
hook.runProcessPayloadFunc(fn, d, hd)
|
|
case DeploymentEvent:
|
|
var d DeploymentPayload
|
|
json.Unmarshal([]byte(payload), &d)
|
|
hook.runProcessPayloadFunc(fn, d, hd)
|
|
case DeploymentStatusEvent:
|
|
var d DeploymentStatusPayload
|
|
json.Unmarshal([]byte(payload), &d)
|
|
hook.runProcessPayloadFunc(fn, d, hd)
|
|
case ForkEvent:
|
|
var f ForkPayload
|
|
json.Unmarshal([]byte(payload), &f)
|
|
hook.runProcessPayloadFunc(fn, f, hd)
|
|
case GollumEvent:
|
|
var g GollumPayload
|
|
json.Unmarshal([]byte(payload), &g)
|
|
hook.runProcessPayloadFunc(fn, g, hd)
|
|
case IssueCommentEvent:
|
|
var i IssueCommentPayload
|
|
json.Unmarshal([]byte(payload), &i)
|
|
hook.runProcessPayloadFunc(fn, i, hd)
|
|
case IssuesEvent:
|
|
var i IssuesPayload
|
|
json.Unmarshal([]byte(payload), &i)
|
|
hook.runProcessPayloadFunc(fn, i, hd)
|
|
case LabelEvent:
|
|
var l LabelPayload
|
|
json.Unmarshal([]byte(payload), &l)
|
|
hook.runProcessPayloadFunc(fn, l, hd)
|
|
case MemberEvent:
|
|
var m MemberPayload
|
|
json.Unmarshal([]byte(payload), &m)
|
|
hook.runProcessPayloadFunc(fn, m, hd)
|
|
case MembershipEvent:
|
|
var m MembershipPayload
|
|
json.Unmarshal([]byte(payload), &m)
|
|
hook.runProcessPayloadFunc(fn, m, hd)
|
|
case MilestoneEvent:
|
|
var m MilestonePayload
|
|
json.Unmarshal([]byte(payload), &m)
|
|
hook.runProcessPayloadFunc(fn, m, hd)
|
|
case OrganizationEvent:
|
|
var o OrganizationPayload
|
|
json.Unmarshal([]byte(payload), &o)
|
|
hook.runProcessPayloadFunc(fn, o, hd)
|
|
case OrgBlockEvent:
|
|
var o OrgBlockPayload
|
|
json.Unmarshal([]byte(payload), &o)
|
|
hook.runProcessPayloadFunc(fn, o, hd)
|
|
case PageBuildEvent:
|
|
var p PageBuildPayload
|
|
json.Unmarshal([]byte(payload), &p)
|
|
hook.runProcessPayloadFunc(fn, p, hd)
|
|
case ProjectCardEvent:
|
|
var p ProjectCardPayload
|
|
json.Unmarshal([]byte(payload), &p)
|
|
hook.runProcessPayloadFunc(fn, p, hd)
|
|
case ProjectColumnEvent:
|
|
var p ProjectColumnPayload
|
|
json.Unmarshal([]byte(payload), &p)
|
|
hook.runProcessPayloadFunc(fn, p, hd)
|
|
case ProjectEvent:
|
|
var p ProjectPayload
|
|
json.Unmarshal([]byte(payload), &p)
|
|
hook.runProcessPayloadFunc(fn, p, hd)
|
|
case PublicEvent:
|
|
var p PublicPayload
|
|
json.Unmarshal([]byte(payload), &p)
|
|
hook.runProcessPayloadFunc(fn, p, hd)
|
|
case PullRequestEvent:
|
|
var p PullRequestPayload
|
|
json.Unmarshal([]byte(payload), &p)
|
|
hook.runProcessPayloadFunc(fn, p, hd)
|
|
case PullRequestReviewEvent:
|
|
var p PullRequestReviewPayload
|
|
json.Unmarshal([]byte(payload), &p)
|
|
hook.runProcessPayloadFunc(fn, p, hd)
|
|
case PullRequestReviewCommentEvent:
|
|
var p PullRequestReviewCommentPayload
|
|
json.Unmarshal([]byte(payload), &p)
|
|
hook.runProcessPayloadFunc(fn, p, hd)
|
|
case PushEvent:
|
|
var p PushPayload
|
|
json.Unmarshal([]byte(payload), &p)
|
|
hook.runProcessPayloadFunc(fn, p, hd)
|
|
case ReleaseEvent:
|
|
var r ReleasePayload
|
|
json.Unmarshal([]byte(payload), &r)
|
|
hook.runProcessPayloadFunc(fn, r, hd)
|
|
case RepositoryEvent:
|
|
var r RepositoryPayload
|
|
json.Unmarshal([]byte(payload), &r)
|
|
hook.runProcessPayloadFunc(fn, r, hd)
|
|
case StatusEvent:
|
|
var s StatusPayload
|
|
json.Unmarshal([]byte(payload), &s)
|
|
hook.runProcessPayloadFunc(fn, s, hd)
|
|
case TeamEvent:
|
|
var t TeamPayload
|
|
json.Unmarshal([]byte(payload), &t)
|
|
hook.runProcessPayloadFunc(fn, t, hd)
|
|
case TeamAddEvent:
|
|
var t TeamAddPayload
|
|
json.Unmarshal([]byte(payload), &t)
|
|
hook.runProcessPayloadFunc(fn, t, hd)
|
|
case WatchEvent:
|
|
var w WatchPayload
|
|
json.Unmarshal([]byte(payload), &w)
|
|
hook.runProcessPayloadFunc(fn, w, hd)
|
|
}
|
|
}
|
|
|
|
func (hook Webhook) runProcessPayloadFunc(fn webhooks.ProcessPayloadFunc, results interface{}, header webhooks.Header) {
|
|
go func(fn webhooks.ProcessPayloadFunc, results interface{}, header webhooks.Header) {
|
|
fn(results, header)
|
|
}(fn, results, header)
|
|
}
|