github.com/go-playground/webhooks/v6@v6.3.0/bitbucket-server/bitbucketserver.go (about)

     1  package bitbucketserver
     2  
     3  import (
     4  	"crypto/hmac"
     5  	"crypto/sha256"
     6  	"encoding/hex"
     7  	"encoding/json"
     8  	"errors"
     9  	"fmt"
    10  	"io"
    11  	"io/ioutil"
    12  	"net/http"
    13  )
    14  
    15  var (
    16  	ErrEventNotSpecifiedToParse  = errors.New("no Event specified to parse")
    17  	ErrInvalidHTTPMethod         = errors.New("invalid HTTP Method")
    18  	ErrMissingEventKeyHeader     = errors.New("missing X-Event-Key Header")
    19  	ErrMissingHubSignatureHeader = errors.New("missing X-Hub-Signature Header")
    20  	ErrEventNotFound             = errors.New("event not defined to be parsed")
    21  	ErrParsingPayload            = errors.New("error parsing payload")
    22  	ErrHMACVerificationFailed    = errors.New("HMAC verification failed")
    23  )
    24  
    25  type Event string
    26  
    27  const (
    28  	RepositoryReferenceChangedEvent Event = "repo:refs_changed"
    29  	RepositoryModifiedEvent         Event = "repo:modified"
    30  	RepositoryForkedEvent           Event = "repo:forked"
    31  	RepositoryCommentAddedEvent     Event = "repo:comment:added"
    32  	RepositoryCommentEditedEvent    Event = "repo:comment:edited"
    33  	RepositoryCommentDeletedEvent   Event = "repo:comment:deleted"
    34  
    35  	PullRequestOpenedEvent               Event = "pr:opened"
    36  	PullRequestFromReferenceUpdatedEvent Event = "pr:from_ref_updated"
    37  	PullRequestModifiedEvent             Event = "pr:modified"
    38  	PullRequestMergedEvent               Event = "pr:merged"
    39  	PullRequestDeclinedEvent             Event = "pr:declined"
    40  	PullRequestDeletedEvent              Event = "pr:deleted"
    41  
    42  	PullRequestReviewerUpdatedEvent    Event = "pr:reviewer:updated"
    43  	PullRequestReviewerApprovedEvent   Event = "pr:reviewer:approved"
    44  	PullRequestReviewerUnapprovedEvent Event = "pr:reviewer:unapproved"
    45  	PullRequestReviewerNeedsWorkEvent  Event = "pr:reviewer:needs_work"
    46  
    47  	PullRequestCommentAddedEvent   Event = "pr:comment:added"
    48  	PullRequestCommentEditedEvent  Event = "pr:comment:edited"
    49  	PullRequestCommentDeletedEvent Event = "pr:comment:deleted"
    50  
    51  	DiagnosticsPingEvent Event = "diagnostics:ping"
    52  )
    53  
    54  // Option is a configuration option for the webhook
    55  type Option func(*Webhook) error
    56  
    57  // Options is a namespace var for configuration options
    58  var Options = WebhookOptions{}
    59  
    60  // WebhookOptions is a namespace for configuration option methods
    61  type WebhookOptions struct{}
    62  
    63  // Secret registers the GitHub secret
    64  func (WebhookOptions) Secret(secret string) Option {
    65  	return func(hook *Webhook) error {
    66  		hook.secret = secret
    67  		return nil
    68  	}
    69  }
    70  
    71  // Webhook instance contains all methods needed to process events
    72  type Webhook struct {
    73  	secret string
    74  }
    75  
    76  // New creates and returns a WebHook instance denoted by the Provider type
    77  func New(options ...Option) (*Webhook, error) {
    78  	hook := new(Webhook)
    79  	for _, opt := range options {
    80  		if err := opt(hook); err != nil {
    81  			return nil, errors.New("Error applying Option")
    82  		}
    83  	}
    84  	return hook, nil
    85  }
    86  
    87  func (hook *Webhook) Parse(r *http.Request, events ...Event) (interface{}, error) {
    88  	defer func() {
    89  		_, _ = io.Copy(ioutil.Discard, r.Body)
    90  		_ = r.Body.Close()
    91  	}()
    92  
    93  	if len(events) == 0 {
    94  		return nil, ErrEventNotSpecifiedToParse
    95  	}
    96  
    97  	if r.Method != http.MethodPost {
    98  		return nil, ErrInvalidHTTPMethod
    99  	}
   100  
   101  	event := r.Header.Get("X-Event-Key")
   102  	if event == "" {
   103  		return nil, ErrMissingEventKeyHeader
   104  	}
   105  
   106  	bitbucketEvent := Event(event)
   107  
   108  	var found bool
   109  	for _, evt := range events {
   110  		if evt == bitbucketEvent {
   111  			found = true
   112  			break
   113  		}
   114  	}
   115  	// event not defined to be parsed
   116  	if !found {
   117  		return nil, ErrEventNotFound
   118  	}
   119  
   120  	if bitbucketEvent == DiagnosticsPingEvent {
   121  		return DiagnosticsPingPayload{}, nil
   122  	}
   123  
   124  	payload, err := ioutil.ReadAll(r.Body)
   125  	if err != nil || len(payload) == 0 {
   126  		return nil, ErrParsingPayload
   127  	}
   128  
   129  	if len(hook.secret) > 0 {
   130  		signature := r.Header.Get("X-Hub-Signature")
   131  		if len(signature) == 0 {
   132  			return nil, ErrMissingHubSignatureHeader
   133  		}
   134  		mac := hmac.New(sha256.New, []byte(hook.secret))
   135  		_, _ = mac.Write(payload)
   136  		expectedMAC := hex.EncodeToString(mac.Sum(nil))
   137  
   138  		if !hmac.Equal([]byte(signature[7:]), []byte(expectedMAC)) {
   139  			return nil, ErrHMACVerificationFailed
   140  		}
   141  	}
   142  
   143  	switch bitbucketEvent {
   144  	case RepositoryReferenceChangedEvent:
   145  		var pl RepositoryReferenceChangedPayload
   146  		err = json.Unmarshal([]byte(payload), &pl)
   147  		return pl, err
   148  	case RepositoryModifiedEvent:
   149  		var pl RepositoryModifiedPayload
   150  		err = json.Unmarshal([]byte(payload), &pl)
   151  		return pl, err
   152  	case RepositoryForkedEvent:
   153  		var pl RepositoryForkedPayload
   154  		err = json.Unmarshal([]byte(payload), &pl)
   155  		return pl, err
   156  	case RepositoryCommentAddedEvent:
   157  		var pl RepositoryCommentAddedPayload
   158  		err = json.Unmarshal([]byte(payload), &pl)
   159  		return pl, err
   160  	case RepositoryCommentEditedEvent:
   161  		var pl RepositoryCommentEditedPayload
   162  		err = json.Unmarshal([]byte(payload), &pl)
   163  		return pl, err
   164  	case RepositoryCommentDeletedEvent:
   165  		var pl RepositoryCommentDeletedPayload
   166  		err = json.Unmarshal([]byte(payload), &pl)
   167  		return pl, err
   168  	case PullRequestOpenedEvent:
   169  		var pl PullRequestOpenedPayload
   170  		err = json.Unmarshal([]byte(payload), &pl)
   171  		return pl, err
   172  	case PullRequestFromReferenceUpdatedEvent:
   173  		var pl PullRequestFromReferenceUpdatedPayload
   174  		err = json.Unmarshal([]byte(payload), &pl)
   175  		return pl, err
   176  	case PullRequestModifiedEvent:
   177  		var pl PullRequestModifiedPayload
   178  		err = json.Unmarshal([]byte(payload), &pl)
   179  		return pl, err
   180  	case PullRequestMergedEvent:
   181  		var pl PullRequestMergedPayload
   182  		err = json.Unmarshal([]byte(payload), &pl)
   183  		return pl, err
   184  	case PullRequestDeclinedEvent:
   185  		var pl PullRequestDeclinedPayload
   186  		err = json.Unmarshal([]byte(payload), &pl)
   187  		return pl, err
   188  	case PullRequestDeletedEvent:
   189  		var pl PullRequestDeletedPayload
   190  		err = json.Unmarshal([]byte(payload), &pl)
   191  		return pl, err
   192  	case PullRequestReviewerUpdatedEvent:
   193  		var pl PullRequestReviewerUpdatedPayload
   194  		err = json.Unmarshal([]byte(payload), &pl)
   195  		return pl, err
   196  	case PullRequestReviewerApprovedEvent:
   197  		var pl PullRequestReviewerApprovedPayload
   198  		err = json.Unmarshal([]byte(payload), &pl)
   199  		return pl, err
   200  	case PullRequestReviewerUnapprovedEvent:
   201  		var pl PullRequestReviewerUnapprovedPayload
   202  		err = json.Unmarshal([]byte(payload), &pl)
   203  		return pl, err
   204  	case PullRequestReviewerNeedsWorkEvent:
   205  		var pl PullRequestReviewerNeedsWorkPayload
   206  		err = json.Unmarshal([]byte(payload), &pl)
   207  		return pl, err
   208  	case PullRequestCommentAddedEvent:
   209  		var pl PullRequestCommentAddedPayload
   210  		err = json.Unmarshal([]byte(payload), &pl)
   211  		return pl, err
   212  	case PullRequestCommentEditedEvent:
   213  		var pl PullRequestCommentEditedPayload
   214  		err = json.Unmarshal([]byte(payload), &pl)
   215  		return pl, err
   216  	case PullRequestCommentDeletedEvent:
   217  		var pl PullRequestCommentDeletedPayload
   218  		err = json.Unmarshal([]byte(payload), &pl)
   219  		return pl, err
   220  	default:
   221  		return nil, fmt.Errorf("unknown event %s", bitbucketEvent)
   222  	}
   223  }