Adds support for Bitbucket Server (#63)
This commit is contained in:
committed by
Dean Karn
parent
a1051fd871
commit
acf48b9638
@@ -0,0 +1,218 @@
|
||||
package bitbucketserver
|
||||
|
||||
import (
|
||||
"crypto/hmac"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrEventNotSpecifiedToParse = errors.New("no Event specified to parse")
|
||||
ErrInvalidHTTPMethod = errors.New("invalid HTTP Method")
|
||||
ErrMissingEventKeyHeader = errors.New("missing X-Event-Key Header")
|
||||
ErrMissingHubSignatureHeader = errors.New("missing X-Hub-Signature Header")
|
||||
ErrEventNotFound = errors.New("event not defined to be parsed")
|
||||
ErrParsingPayload = errors.New("error parsing payload")
|
||||
ErrHMACVerificationFailed = errors.New("HMAC verification failed")
|
||||
)
|
||||
|
||||
type Event string
|
||||
|
||||
const (
|
||||
RepositoryReferenceChangedEvent Event = "repo:refs_changed"
|
||||
RepositoryModifiedEvent Event = "repo:modified"
|
||||
RepositoryForkedEvent Event = "repo:forked"
|
||||
RepositoryCommentAddedEvent Event = "repo:comment:added"
|
||||
RepositoryCommentEditedEvent Event = "repo:comment:edited"
|
||||
RepositoryCommentDeletedEvent Event = "repo:comment:deleted"
|
||||
|
||||
PullRequestOpenedEvent Event = "pr:opened"
|
||||
PullRequestModifiedEvent Event = "pr:modified"
|
||||
PullRequestMergedEvent Event = "pr:merged"
|
||||
PullRequestDeclinedEvent Event = "pr:declined"
|
||||
PullRequestDeletedEvent Event = "pr:deleted"
|
||||
|
||||
PullRequestReviewerUpdatedEvent Event = "pr:reviewer:updated"
|
||||
PullRequestReviewerApprovedEvent Event = "pr:reviewer:approved"
|
||||
PullRequestReviewerUnapprovedEvent Event = "pr:reviewer:unapproved"
|
||||
PullRequestReviewerNeedsWorkEvent Event = "pr:reviewer:needs_work"
|
||||
|
||||
PullRequestCommentAddedEvent Event = "pr:comment:added"
|
||||
PullRequestCommentEditedEvent Event = "pr:comment:edited"
|
||||
PullRequestCommentDeletedEvent Event = "pr:comment:deleted"
|
||||
|
||||
DiagnosticsPingEvent Event = "diagnostics:ping"
|
||||
)
|
||||
|
||||
// Option is a configuration option for the webhook
|
||||
type Option func(*Webhook) error
|
||||
|
||||
// Options is a namespace var for configuration options
|
||||
var Options = WebhookOptions{}
|
||||
|
||||
// WebhookOptions is a namespace for configuration option methods
|
||||
type WebhookOptions struct{}
|
||||
|
||||
// Secret registers the GitHub secret
|
||||
func (WebhookOptions) Secret(secret string) Option {
|
||||
return func(hook *Webhook) error {
|
||||
hook.secret = secret
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// Webhook instance contains all methods needed to process events
|
||||
type Webhook struct {
|
||||
secret string
|
||||
}
|
||||
|
||||
// New creates and returns a WebHook instance denoted by the Provider type
|
||||
func New(options ...Option) (*Webhook, error) {
|
||||
hook := new(Webhook)
|
||||
for _, opt := range options {
|
||||
if err := opt(hook); err != nil {
|
||||
return nil, errors.New("Error applying Option")
|
||||
}
|
||||
}
|
||||
return hook, nil
|
||||
}
|
||||
|
||||
func (hook *Webhook) Parse(r *http.Request, events ...Event) (interface{}, error) {
|
||||
defer func() {
|
||||
_, _ = io.Copy(ioutil.Discard, r.Body)
|
||||
_ = r.Body.Close()
|
||||
}()
|
||||
|
||||
if len(events) == 0 {
|
||||
return nil, ErrEventNotSpecifiedToParse
|
||||
}
|
||||
|
||||
if r.Method != http.MethodPost {
|
||||
return nil, ErrInvalidHTTPMethod
|
||||
}
|
||||
|
||||
event := r.Header.Get("X-Event-Key")
|
||||
if event == "" {
|
||||
return nil, ErrMissingEventKeyHeader
|
||||
}
|
||||
|
||||
bitbucketEvent := Event(event)
|
||||
|
||||
var found bool
|
||||
for _, evt := range events {
|
||||
if evt == bitbucketEvent {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
// event not defined to be parsed
|
||||
if !found {
|
||||
return nil, ErrEventNotFound
|
||||
}
|
||||
|
||||
if bitbucketEvent == DiagnosticsPingEvent {
|
||||
return DiagnosticsPingPayload{}, nil
|
||||
}
|
||||
|
||||
payload, err := ioutil.ReadAll(r.Body)
|
||||
if err != nil || len(payload) == 0 {
|
||||
return nil, ErrParsingPayload
|
||||
}
|
||||
|
||||
if len(hook.secret) > 0 {
|
||||
signature := r.Header.Get("X-Hub-Signature")
|
||||
if len(signature) == 0 {
|
||||
return nil, ErrMissingHubSignatureHeader
|
||||
}
|
||||
mac := hmac.New(sha256.New, []byte(hook.secret))
|
||||
_, _ = mac.Write(payload)
|
||||
expectedMAC := hex.EncodeToString(mac.Sum(nil))
|
||||
|
||||
if !hmac.Equal([]byte(signature[7:]), []byte(expectedMAC)) {
|
||||
return nil, ErrHMACVerificationFailed
|
||||
}
|
||||
}
|
||||
|
||||
switch bitbucketEvent {
|
||||
case RepositoryReferenceChangedEvent:
|
||||
var pl RepositoryReferenceChangedPayload
|
||||
err = json.Unmarshal([]byte(payload), &pl)
|
||||
return pl, err
|
||||
case RepositoryModifiedEvent:
|
||||
var pl RepositoryModifiedPayload
|
||||
err = json.Unmarshal([]byte(payload), &pl)
|
||||
return pl, err
|
||||
case RepositoryForkedEvent:
|
||||
var pl RepositoryForkedPayload
|
||||
err = json.Unmarshal([]byte(payload), &pl)
|
||||
return pl, err
|
||||
case RepositoryCommentAddedEvent:
|
||||
var pl RepositoryCommentAddedPayload
|
||||
err = json.Unmarshal([]byte(payload), &pl)
|
||||
return pl, err
|
||||
case RepositoryCommentEditedEvent:
|
||||
var pl RepositoryCommentEditedPayload
|
||||
err = json.Unmarshal([]byte(payload), &pl)
|
||||
return pl, err
|
||||
case RepositoryCommentDeletedEvent:
|
||||
var pl RepositoryCommentDeletedPayload
|
||||
err = json.Unmarshal([]byte(payload), &pl)
|
||||
return pl, err
|
||||
case PullRequestOpenedEvent:
|
||||
var pl PullRequestOpenedPayload
|
||||
err = json.Unmarshal([]byte(payload), &pl)
|
||||
return pl, err
|
||||
case PullRequestModifiedEvent:
|
||||
var pl PullRequestModifiedPayload
|
||||
err = json.Unmarshal([]byte(payload), &pl)
|
||||
return pl, err
|
||||
case PullRequestMergedEvent:
|
||||
var pl PullRequestMergedPayload
|
||||
err = json.Unmarshal([]byte(payload), &pl)
|
||||
return pl, err
|
||||
case PullRequestDeclinedEvent:
|
||||
var pl PullRequestDeclinedPayload
|
||||
err = json.Unmarshal([]byte(payload), &pl)
|
||||
return pl, err
|
||||
case PullRequestDeletedEvent:
|
||||
var pl PullRequestDeletedPayload
|
||||
err = json.Unmarshal([]byte(payload), &pl)
|
||||
return pl, err
|
||||
case PullRequestReviewerUpdatedEvent:
|
||||
var pl PullRequestReviewerUpdatedPayload
|
||||
err = json.Unmarshal([]byte(payload), &pl)
|
||||
return pl, err
|
||||
case PullRequestReviewerApprovedEvent:
|
||||
var pl PullRequestReviewerApprovedPayload
|
||||
err = json.Unmarshal([]byte(payload), &pl)
|
||||
return pl, err
|
||||
case PullRequestReviewerUnapprovedEvent:
|
||||
var pl PullRequestReviewerUnapprovedPayload
|
||||
err = json.Unmarshal([]byte(payload), &pl)
|
||||
return pl, err
|
||||
case PullRequestReviewerNeedsWorkEvent:
|
||||
var pl PullRequestReviewerNeedsWorkPayload
|
||||
err = json.Unmarshal([]byte(payload), &pl)
|
||||
return pl, err
|
||||
case PullRequestCommentAddedEvent:
|
||||
var pl PullRequestCommentAddedPayload
|
||||
err = json.Unmarshal([]byte(payload), &pl)
|
||||
return pl, err
|
||||
case PullRequestCommentEditedEvent:
|
||||
var pl PullRequestCommentEditedPayload
|
||||
err = json.Unmarshal([]byte(payload), &pl)
|
||||
return pl, err
|
||||
case PullRequestCommentDeletedEvent:
|
||||
var pl PullRequestCommentDeletedPayload
|
||||
err = json.Unmarshal([]byte(payload), &pl)
|
||||
return pl, err
|
||||
default:
|
||||
return nil, fmt.Errorf("unknown event %s", bitbucketEvent)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user