-
Notifications
You must be signed in to change notification settings - Fork 1.1k
/
Copy pathgitlab_request_parser_validator.go
107 lines (96 loc) · 3.54 KB
/
gitlab_request_parser_validator.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
// Copyright 2017 HootSuite Media Inc.
//
// Licensed under the Apache License, Version 2.0 (the License);
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an AS IS BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Modified hereafter by contributors to runatlantis/atlantis.
package events
import (
"crypto/subtle"
"encoding/json"
"fmt"
"io"
"net/http"
gitlab "gitlab.com/gitlab-org/api/client-go"
)
const secretHeader = "X-Gitlab-Token" // #nosec
//go:generate pegomock generate --package mocks -o mocks/mock_gitlab_request_parser_validator.go GitlabRequestParserValidator
// GitlabRequestParserValidator parses and validates GitLab requests.
type GitlabRequestParserValidator interface {
// ParseAndValidate validates that the request has a token header matching secret.
// If the secret does not match it returns an error.
// If secret is empty it does not check the token header.
// It then parses the request as a GitLab object depending on the header
// provided by GitLab identifying the webhook type. If the webhook type
// is not recognized it will return nil but will not return an error.
// Usage:
// event, err := GitlabRequestParserValidator.ParseAndValidate(r, secret)
// if err != nil {
// return
// }
// switch event := event.(type) {
// case gitlab.MergeCommentEvent:
// // handle
// case gitlab.MergeEvent:
// // handle
// default:
// // unsupported event
// }
ParseAndValidate(r *http.Request, secret []byte) (interface{}, error)
}
// DefaultGitlabRequestParserValidator parses and validates GitLab requests.
type DefaultGitlabRequestParserValidator struct{}
// ParseAndValidate returns the JSON payload of the request.
// See GitlabRequestParserValidator.ParseAndValidate().
func (d *DefaultGitlabRequestParserValidator) ParseAndValidate(r *http.Request, secret []byte) (interface{}, error) {
const mergeEventHeader = "Merge Request Hook"
const noteEventHeader = "Note Hook"
// Validate secret if specified.
headerSecret := r.Header.Get(secretHeader)
if len(secret) != 0 && subtle.ConstantTimeCompare(secret, []byte(headerSecret)) != 1 {
return nil, fmt.Errorf("header %s=%s did not match expected secret", secretHeader, headerSecret)
}
// Parse request into a gitlab object based on the object type specified
// in the gitlabHeader.
bytes, err := io.ReadAll(r.Body)
if err != nil {
return nil, err
}
switch r.Header.Get(gitlabHeader) {
case mergeEventHeader:
var m gitlab.MergeEvent
if err := json.Unmarshal(bytes, &m); err != nil {
return nil, err
}
return m, nil
case noteEventHeader:
// First, parse a small part of the json to determine if this is a
// comment on a merge request or a commit.
var subset struct {
ObjectAttributes struct {
NoteableType string `json:"noteable_type"`
} `json:"object_attributes"`
}
if err := json.Unmarshal(bytes, &subset); err != nil {
return nil, err
}
// We then parse into the correct comment event type.
switch subset.ObjectAttributes.NoteableType {
case "Commit":
var e gitlab.CommitCommentEvent
err := json.Unmarshal(bytes, &e)
return e, err
case "MergeRequest":
var e gitlab.MergeCommentEvent
err := json.Unmarshal(bytes, &e)
return e, err
}
}
return nil, nil
}