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

     1  package gitlab
     2  
     3  import (
     4  	"crypto/sha512"
     5  	"crypto/subtle"
     6  	"encoding/json"
     7  	"errors"
     8  	"fmt"
     9  	"io"
    10  	"io/ioutil"
    11  	"net/http"
    12  )
    13  
    14  // parse errors
    15  var (
    16  	ErrEventNotSpecifiedToParse      = errors.New("no Event specified to parse")
    17  	ErrInvalidHTTPMethod             = errors.New("invalid HTTP Method")
    18  	ErrMissingGitLabEventHeader      = errors.New("missing X-Gitlab-Event Header")
    19  	ErrGitLabTokenVerificationFailed = errors.New("X-Gitlab-Token validation failed")
    20  	ErrEventNotFound                 = errors.New("event not defined to be parsed")
    21  	ErrParsingPayload                = errors.New("error parsing payload")
    22  	ErrParsingSystemPayload          = errors.New("error parsing system payload")
    23  	// ErrHMACVerificationFailed    = errors.New("HMAC verification failed")
    24  )
    25  
    26  // GitLab hook types
    27  const (
    28  	PushEvents               Event  = "Push Hook"
    29  	TagEvents                Event  = "Tag Push Hook"
    30  	IssuesEvents             Event  = "Issue Hook"
    31  	ConfidentialIssuesEvents Event  = "Confidential Issue Hook"
    32  	CommentEvents            Event  = "Note Hook"
    33  	ConfidentialCommentEvents Event = "Confidential Note Hook"
    34  	MergeRequestEvents       Event  = "Merge Request Hook"
    35  	WikiPageEvents           Event  = "Wiki Page Hook"
    36  	PipelineEvents           Event  = "Pipeline Hook"
    37  	BuildEvents              Event  = "Build Hook"
    38  	JobEvents                Event  = "Job Hook"
    39    DeploymentEvents         Event = "Deployment Hook"
    40  	SystemHookEvents         Event  = "System Hook"
    41  	objectPush               string = "push"
    42  	objectTag                string = "tag_push"
    43  	objectMergeRequest       string = "merge_request"
    44  	objectBuild              string = "build"
    45  	eventProjectCreate       string = "project_create"
    46  	eventProjectDestroy      string = "project_destroy"
    47  	eventProjectRename       string = "project_rename"
    48  	eventProjectTransfer     string = "project_transfer"
    49  	eventProjectUpdate       string = "project_update"
    50  	eventUserAddToTeam       string = "user_add_to_team"
    51  	eventUserRemoveFromTeam  string = "user_remove_from_team"
    52  	eventUserUpdateForTeam   string = "user_update_for_team"
    53  	eventUserCreate          string = "user_create"
    54  	eventUserDestroy         string = "user_destroy"
    55  	eventUserFailedLogin     string = "user_failed_login"
    56  	eventUserRename          string = "user_rename"
    57  	eventKeyCreate           string = "key_create"
    58  	eventKeyDestroy          string = "key_destroy"
    59  	eventGroupCreate         string = "group_create"
    60  	eventGroupDestroy        string = "group_destroy"
    61  	eventGroupRename         string = "group_rename"
    62  	eventUserAddToGroup      string = "user_add_to_group"
    63  	eventUserRemoveFromGroup string = "user_remove_from_group"
    64  	eventUserUpdateForGroup  string = "user_update_for_group"
    65  )
    66  
    67  // Option is a configuration option for the webhook
    68  type Option func(*Webhook) error
    69  
    70  // Options is a namespace var for configuration options
    71  var Options = WebhookOptions{}
    72  
    73  // WebhookOptions is a namespace for configuration option methods
    74  type WebhookOptions struct{}
    75  
    76  // Secret registers the GitLab secret
    77  func (WebhookOptions) Secret(secret string) Option {
    78  	return func(hook *Webhook) error {
    79  		// already convert here to prevent timing attack (conversion depends on secret)
    80  		hash := sha512.Sum512([]byte(secret))
    81  		hook.secretHash = hash[:]
    82  		return nil
    83  	}
    84  }
    85  
    86  // Webhook instance contains all methods needed to process events
    87  type Webhook struct {
    88  	secretHash []byte
    89  }
    90  
    91  // Event defines a GitLab hook event type by the X-Gitlab-Event Header
    92  type Event string
    93  
    94  // New creates and returns a WebHook instance denoted by the Provider type
    95  func New(options ...Option) (*Webhook, error) {
    96  	hook := new(Webhook)
    97  	for _, opt := range options {
    98  		if err := opt(hook); err != nil {
    99  			return nil, errors.New("Error applying Option")
   100  		}
   101  	}
   102  	return hook, nil
   103  }
   104  
   105  // Parse verifies and parses the events specified and returns the payload object or an error
   106  func (hook Webhook) Parse(r *http.Request, events ...Event) (interface{}, error) {
   107  	defer func() {
   108  		_, _ = io.Copy(ioutil.Discard, r.Body)
   109  		_ = r.Body.Close()
   110  	}()
   111  
   112  	if len(events) == 0 {
   113  		return nil, ErrEventNotSpecifiedToParse
   114  	}
   115  	if r.Method != http.MethodPost {
   116  		return nil, ErrInvalidHTTPMethod
   117  	}
   118  
   119  	// If we have a Secret set, we should check in constant time
   120  	if len(hook.secretHash) > 0 {
   121  		tokenHash := sha512.Sum512([]byte(r.Header.Get("X-Gitlab-Token")))
   122  		if subtle.ConstantTimeCompare(tokenHash[:], hook.secretHash[:]) == 0 {
   123  			return nil, ErrGitLabTokenVerificationFailed
   124  		}
   125  	}
   126  
   127  	event := r.Header.Get("X-Gitlab-Event")
   128  	if len(event) == 0 {
   129  		return nil, ErrMissingGitLabEventHeader
   130  	}
   131  
   132  	gitLabEvent := Event(event)
   133  
   134  	payload, err := ioutil.ReadAll(r.Body)
   135  	if err != nil || len(payload) == 0 {
   136  		return nil, ErrParsingPayload
   137  	}
   138  
   139  	return eventParsing(gitLabEvent, events, payload)
   140  }
   141  
   142  func eventParsing(gitLabEvent Event, events []Event, payload []byte) (interface{}, error) {
   143  
   144  	var found bool
   145  	for _, evt := range events {
   146  		if evt == gitLabEvent {
   147  			found = true
   148  			break
   149  		}
   150  	}
   151  	// event not defined to be parsed
   152  	if !found {
   153  		return nil, ErrEventNotFound
   154  	}
   155  
   156  	switch gitLabEvent {
   157  	case PushEvents:
   158  		var pl PushEventPayload
   159  		err := json.Unmarshal([]byte(payload), &pl)
   160  		return pl, err
   161  
   162  	case TagEvents:
   163  		var pl TagEventPayload
   164  		err := json.Unmarshal([]byte(payload), &pl)
   165  		return pl, err
   166  
   167  	case ConfidentialIssuesEvents:
   168  		var pl ConfidentialIssueEventPayload
   169  		err := json.Unmarshal([]byte(payload), &pl)
   170  		return pl, err
   171  
   172  	case IssuesEvents:
   173  		var pl IssueEventPayload
   174  		err := json.Unmarshal([]byte(payload), &pl)
   175  		return pl, err
   176  
   177  	case ConfidentialCommentEvents:
   178  		var pl ConfidentialCommentEventPayload
   179  		err := json.Unmarshal([]byte(payload), &pl)
   180  		return pl, err
   181  
   182  	case CommentEvents:
   183  		var pl CommentEventPayload
   184  		err := json.Unmarshal([]byte(payload), &pl)
   185  		return pl, err
   186  
   187  	case MergeRequestEvents:
   188  		var pl MergeRequestEventPayload
   189  		err := json.Unmarshal([]byte(payload), &pl)
   190  		return pl, err
   191  
   192  	case WikiPageEvents:
   193  		var pl WikiPageEventPayload
   194  		err := json.Unmarshal([]byte(payload), &pl)
   195  		return pl, err
   196  
   197  	case PipelineEvents:
   198  		var pl PipelineEventPayload
   199  		err := json.Unmarshal([]byte(payload), &pl)
   200  		return pl, err
   201  
   202  	case BuildEvents:
   203  		var pl BuildEventPayload
   204  		err := json.Unmarshal([]byte(payload), &pl)
   205  		return pl, err
   206  
   207  	case JobEvents:
   208  		var pl JobEventPayload
   209  		err := json.Unmarshal([]byte(payload), &pl)
   210  		if err != nil {
   211  			return nil, err
   212  		}
   213  		if pl.ObjectKind == objectBuild {
   214  			return eventParsing(BuildEvents, events, payload)
   215  		}
   216  		return pl, nil
   217  
   218  	case DeploymentEvents:
   219  		var pl DeploymentEventPayload
   220  		err := json.Unmarshal([]byte(payload), &pl)
   221  		if err != nil {
   222  			return nil, err
   223  		}
   224  		return pl, nil
   225  
   226  	case SystemHookEvents:
   227  		var pl SystemHookPayload
   228  		err := json.Unmarshal([]byte(payload), &pl)
   229  		if err != nil {
   230  			return nil, err
   231  		}
   232  
   233  		switch pl.ObjectKind {
   234  		case objectPush:
   235  			return eventParsing(PushEvents, events, payload)
   236  
   237  		case objectTag:
   238  			return eventParsing(TagEvents, events, payload)
   239  
   240  		case objectMergeRequest:
   241  			return eventParsing(MergeRequestEvents, events, payload)
   242  		default:
   243  			switch pl.EventName {
   244  			case objectPush:
   245  				return eventParsing(PushEvents, events, payload)
   246  
   247  			case objectTag:
   248  				return eventParsing(TagEvents, events, payload)
   249  
   250  			case objectMergeRequest:
   251  				return eventParsing(MergeRequestEvents, events, payload)
   252  
   253  			case eventProjectCreate:
   254  				var pl ProjectCreatedEventPayload
   255  				err := json.Unmarshal([]byte(payload), &pl)
   256  				return pl, err
   257  
   258  			case eventProjectDestroy:
   259  				var pl ProjectDestroyedEventPayload
   260  				err := json.Unmarshal([]byte(payload), &pl)
   261  				return pl, err
   262  
   263  			case eventProjectRename:
   264  				var pl ProjectRenamedEventPayload
   265  				err := json.Unmarshal([]byte(payload), &pl)
   266  				return pl, err
   267  
   268  			case eventProjectTransfer:
   269  				var pl ProjectTransferredEventPayload
   270  				err := json.Unmarshal([]byte(payload), &pl)
   271  				return pl, err
   272  
   273  			case eventProjectUpdate:
   274  				var pl ProjectUpdatedEventPayload
   275  				err := json.Unmarshal([]byte(payload), &pl)
   276  				return pl, err
   277  
   278  			case eventUserAddToTeam:
   279  				var pl TeamMemberAddedEventPayload
   280  				err := json.Unmarshal([]byte(payload), &pl)
   281  				return pl, err
   282  
   283  			case eventUserRemoveFromTeam:
   284  				var pl TeamMemberRemovedEventPayload
   285  				err := json.Unmarshal([]byte(payload), &pl)
   286  				return pl, err
   287  
   288  			case eventUserUpdateForTeam:
   289  				var pl TeamMemberUpdatedEventPayload
   290  				err := json.Unmarshal([]byte(payload), &pl)
   291  				return pl, err
   292  
   293  			case eventUserCreate:
   294  				var pl UserCreatedEventPayload
   295  				err := json.Unmarshal([]byte(payload), &pl)
   296  				return pl, err
   297  
   298  			case eventUserDestroy:
   299  				var pl UserRemovedEventPayload
   300  				err := json.Unmarshal([]byte(payload), &pl)
   301  				return pl, err
   302  
   303  			case eventUserFailedLogin:
   304  				var pl UserFailedLoginEventPayload
   305  				err := json.Unmarshal([]byte(payload), &pl)
   306  				return pl, err
   307  
   308  			case eventUserRename:
   309  				var pl UserRenamedEventPayload
   310  				err := json.Unmarshal([]byte(payload), &pl)
   311  				return pl, err
   312  
   313  			case eventKeyCreate:
   314  				var pl KeyAddedEventPayload
   315  				err := json.Unmarshal([]byte(payload), &pl)
   316  				return pl, err
   317  
   318  			case eventKeyDestroy:
   319  				var pl KeyRemovedEventPayload
   320  				err := json.Unmarshal([]byte(payload), &pl)
   321  				return pl, err
   322  
   323  			case eventGroupCreate:
   324  				var pl GroupCreatedEventPayload
   325  				err := json.Unmarshal([]byte(payload), &pl)
   326  				return pl, err
   327  
   328  			case eventGroupDestroy:
   329  				var pl GroupRemovedEventPayload
   330  				err := json.Unmarshal([]byte(payload), &pl)
   331  				return pl, err
   332  
   333  			case eventGroupRename:
   334  				var pl GroupRenamedEventPayload
   335  				err := json.Unmarshal([]byte(payload), &pl)
   336  				return pl, err
   337  
   338  			case eventUserAddToGroup:
   339  				var pl GroupMemberAddedEventPayload
   340  				err := json.Unmarshal([]byte(payload), &pl)
   341  				return pl, err
   342  
   343  			case eventUserRemoveFromGroup:
   344  				var pl GroupMemberRemovedEventPayload
   345  				err := json.Unmarshal([]byte(payload), &pl)
   346  				return pl, err
   347  
   348  			case eventUserUpdateForGroup:
   349  				var pl GroupMemberUpdatedEventPayload
   350  				err := json.Unmarshal([]byte(payload), &pl)
   351  				return pl, err
   352  
   353  			default:
   354  				return nil, fmt.Errorf("unknown system hook event %s", gitLabEvent)
   355  			}
   356  		}
   357  	default:
   358  		return nil, fmt.Errorf("unknown event %s", gitLabEvent)
   359  	}
   360  }