github.com/go-playground/webhooks/v6@v6.3.0/gitea/gitea.go (about)

     1  package gitea
     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  // parse errors
    16  var (
    17  	ErrEventNotSpecifiedToParse    = errors.New("no Event specified to parse")
    18  	ErrInvalidHTTPMethod           = errors.New("invalid HTTP Method")
    19  	ErrMissingGiteaEventHeader     = errors.New("missing X-Gitea-Event Header")
    20  	ErrMissingGiteaSignatureHeader = errors.New("missing X-Gitea-Signature Header")
    21  	ErrEventNotFound               = errors.New("event not defined to be parsed")
    22  	ErrParsingPayload              = errors.New("error parsing payload")
    23  	ErrHMACVerificationFailed      = errors.New("HMAC verification failed")
    24  )
    25  
    26  // Gitea hook types
    27  // https://github.com/go-gitea/gitea/blob/bf7b083cfe47cc922090ce7922b89f7a5030a44d/models/webhook/hooktask.go#L31
    28  const (
    29  	CreateEvent               Event = "create"
    30  	DeleteEvent               Event = "delete"
    31  	ForkEvent                 Event = "fork"
    32  	IssuesEvent               Event = "issues"
    33  	IssueAssignEvent          Event = "issue_assign"
    34  	IssueLabelEvent           Event = "issue_label"
    35  	IssueMilestoneEvent       Event = "issue_milestone"
    36  	IssueCommentEvent         Event = "issue_comment"
    37  	PushEvent                 Event = "push"
    38  	PullRequestEvent          Event = "pull_request"
    39  	PullRequestAssignEvent    Event = "pull_request_assign"
    40  	PullRequestLabelEvent     Event = "pull_request_label"
    41  	PullRequestMilestoneEvent Event = "pull_request_milestone"
    42  	PullRequestCommentEvent   Event = "pull_request_comment"
    43  	PullRequestReviewEvent    Event = "pull_request_review"
    44  	PullRequestSyncEvent      Event = "pull_request_sync"
    45  	RepositoryEvent           Event = "repository"
    46  	ReleaseEvent              Event = "release"
    47  )
    48  
    49  // Option is a configuration option for the webhook
    50  type Option func(*Webhook) error
    51  
    52  // Options is a namespace var for configuration options
    53  var Options = WebhookOptions{}
    54  
    55  // WebhookOptions is a namespace for configuration option methods
    56  type WebhookOptions struct{}
    57  
    58  // Secret registers the GitLab secret
    59  func (WebhookOptions) Secret(secret string) Option {
    60  	return func(hook *Webhook) error {
    61  		hook.secret = secret
    62  		return nil
    63  	}
    64  }
    65  
    66  // Webhook instance contains all methods needed to process events
    67  type Webhook struct {
    68  	secret string
    69  }
    70  
    71  // Event defines a GitLab hook event type by the X-Gitlab-Event Header
    72  type Event string
    73  
    74  // New creates and returns a WebHook instance denoted by the Provider type
    75  func New(options ...Option) (*Webhook, error) {
    76  	hook := new(Webhook)
    77  	for _, opt := range options {
    78  		if err := opt(hook); err != nil {
    79  			return nil, errors.New("Error applying Option")
    80  		}
    81  	}
    82  	return hook, nil
    83  }
    84  
    85  // Parse verifies and parses the events specified and returns the payload object or an error
    86  func (hook Webhook) Parse(r *http.Request, events ...Event) (interface{}, error) {
    87  	defer func() {
    88  		_, _ = io.Copy(ioutil.Discard, r.Body)
    89  		_ = r.Body.Close()
    90  	}()
    91  
    92  	if len(events) == 0 {
    93  		return nil, ErrEventNotSpecifiedToParse
    94  	}
    95  	if r.Method != http.MethodPost {
    96  		return nil, ErrInvalidHTTPMethod
    97  	}
    98  
    99  	event := r.Header.Get("X-Gitea-Event")
   100  	if len(event) == 0 {
   101  		return nil, ErrMissingGiteaEventHeader
   102  	}
   103  
   104  	giteaEvent := Event(event)
   105  
   106  	var found bool
   107  	for _, evt := range events {
   108  		if evt == giteaEvent {
   109  			found = true
   110  			break
   111  		}
   112  	}
   113  	// event not defined to be parsed
   114  	if !found {
   115  		return nil, ErrEventNotFound
   116  	}
   117  
   118  	payload, err := ioutil.ReadAll(r.Body)
   119  	if err != nil || len(payload) == 0 {
   120  		return nil, ErrParsingPayload
   121  	}
   122  
   123  	// If we have a Secret set, we should check the MAC
   124  	if len(hook.secret) > 0 {
   125  		signature := r.Header.Get("X-Gitea-Signature")
   126  		if len(signature) == 0 {
   127  			return nil, ErrMissingGiteaSignatureHeader
   128  		}
   129  		sig256 := hmac.New(sha256.New, []byte(hook.secret))
   130  		_, _ = io.Writer(sig256).Write([]byte(payload))
   131  		expectedMAC := hex.EncodeToString(sig256.Sum(nil))
   132  
   133  		if !hmac.Equal([]byte(signature), []byte(expectedMAC)) {
   134  			return nil, ErrHMACVerificationFailed
   135  		}
   136  	}
   137  
   138  	// https://github.com/go-gitea/gitea/blob/33fca2b537d36cf998dd27425b2bb8ed5b0965f3/services/webhook/payloader.go#L27
   139  	switch giteaEvent {
   140  	case CreateEvent:
   141  		var pl CreatePayload
   142  		err = json.Unmarshal([]byte(payload), &pl)
   143  		return pl, err
   144  	case DeleteEvent:
   145  		var pl DeletePayload
   146  		err = json.Unmarshal([]byte(payload), &pl)
   147  		return pl, err
   148  	case ForkEvent:
   149  		var pl ForkPayload
   150  		err = json.Unmarshal([]byte(payload), &pl)
   151  		return pl, err
   152  	case PushEvent:
   153  		var pl PushPayload
   154  		err = json.Unmarshal([]byte(payload), &pl)
   155  		return pl, err
   156  	case IssuesEvent, IssueAssignEvent, IssueLabelEvent, IssueMilestoneEvent:
   157  		var pl IssuePayload
   158  		err = json.Unmarshal([]byte(payload), &pl)
   159  		return pl, err
   160  	case IssueCommentEvent, PullRequestCommentEvent:
   161  		var pl IssueCommentPayload
   162  		err = json.Unmarshal([]byte(payload), &pl)
   163  		return pl, err
   164  	case PullRequestEvent, PullRequestAssignEvent, PullRequestLabelEvent, PullRequestMilestoneEvent, PullRequestReviewEvent, PullRequestSyncEvent:
   165  		var pl PullRequestPayload
   166  		err = json.Unmarshal([]byte(payload), &pl)
   167  		return pl, err
   168  	case RepositoryEvent:
   169  		var pl RepositoryPayload
   170  		err = json.Unmarshal([]byte(payload), &pl)
   171  		return pl, err
   172  	case ReleaseEvent:
   173  		var pl ReleasePayload
   174  		err = json.Unmarshal([]byte(payload), &pl)
   175  		return pl, err
   176  	default:
   177  		return nil, fmt.Errorf("unknown event %s", giteaEvent)
   178  	}
   179  }