-
Notifications
You must be signed in to change notification settings - Fork 0
/
bitbucketserver.go
152 lines (137 loc) · 5.13 KB
/
bitbucketserver.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
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
package drivers
import (
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"net/http"
"strings"
"github.com/rancher/rancher/pkg/pipeline/remote/bitbucketserver"
"github.com/rancher/rancher/pkg/pipeline/remote/model"
"github.com/rancher/rancher/pkg/pipeline/utils"
"github.com/rancher/rancher/pkg/ref"
"github.com/rancher/types/apis/project.cattle.io/v3"
)
const (
BitbucketServerWebhookHeader = "X-Request-Id"
bitbucketServerEventHeader = "X-Event-Key"
bitbucketServerPushEvent = "repo:refs_changed"
bitbucketServerPrCreatedEvent = "pr:opened"
bitbucketServerPrUpdatedEvent = "pr:modified"
bitbucketServerStateOpen = "OPEN"
)
type BitbucketServerDriver struct {
PipelineLister v3.PipelineLister
PipelineExecutions v3.PipelineExecutionInterface
SourceCodeCredentials v3.SourceCodeCredentialInterface
SourceCodeCredentialLister v3.SourceCodeCredentialLister
}
func (b BitbucketServerDriver) Execute(req *http.Request) (int, error) {
var signature string
if signature = req.Header.Get(githubSignatureHeader); len(signature) == 0 {
return http.StatusUnprocessableEntity, errors.New("webhook missing signature")
}
event := req.Header.Get(bitbucketServerEventHeader)
if event != bitbucketServerPushEvent && event != bitbucketServerPrCreatedEvent && event != bitbucketServerPrUpdatedEvent {
return http.StatusUnprocessableEntity, fmt.Errorf("not trigger for event:%s", event)
}
pipelineID := req.URL.Query().Get("pipelineId")
ns, name := ref.Parse(pipelineID)
pipeline, err := b.PipelineLister.Get(ns, name)
if err != nil {
return http.StatusInternalServerError, err
}
body, err := ioutil.ReadAll(req.Body)
if err != nil {
return http.StatusUnprocessableEntity, err
}
if match := verifyBitbucketServerWebhookSignature([]byte(pipeline.Status.Token), signature, body); !match {
return http.StatusUnprocessableEntity, errors.New("invalid signature")
}
info := &model.BuildInfo{}
if event == bitbucketServerPushEvent {
info, err = parseBitbucketServerPushPayload(body)
if err != nil {
return http.StatusUnprocessableEntity, err
}
} else if event == bitbucketServerPrCreatedEvent || event == bitbucketServerPrUpdatedEvent {
info, err = parseBitbucketServerPullRequestPayload(body)
if err != nil {
return http.StatusUnprocessableEntity, err
}
}
return validateAndGeneratePipelineExecution(b.PipelineExecutions, b.SourceCodeCredentials, b.SourceCodeCredentialLister, info, pipeline)
}
func parseBitbucketServerPushPayload(raw []byte) (*model.BuildInfo, error) {
info := &model.BuildInfo{}
payload := bitbucketserver.PushEventPayload{}
if err := json.Unmarshal(raw, &payload); err != nil {
return nil, err
}
info.TriggerType = utils.TriggerTypeWebhook
if len(payload.Changes) > 0 {
change := payload.Changes[0]
info.Commit = change.ToHash
info.Ref = change.RefID
//info.Message = change.New.Target.Message
info.Author = payload.Actor.Name
if len(payload.Actor.Links.Self) > 0 {
info.AvatarURL = payload.Actor.Links.Self[0].Href + "/avatar.png"
}
if strings.HasPrefix(change.RefID, RefsTagPrefix) {
//git tag is triggered as a push event
info.Event = utils.WebhookEventTag
info.Branch = strings.TrimPrefix(change.RefID, RefsTagPrefix)
if change.Type != "ADD" {
return nil, fmt.Errorf("filter '%s' changes for tag event", change.Type)
}
} else {
info.Event = utils.WebhookEventPush
info.Branch = strings.TrimPrefix(change.RefID, RefsBranchPrefix)
if change.Type != "UPDATE" && change.Type != "ADD" {
return nil, fmt.Errorf("filter '%s' changes for push event", change.Type)
}
}
}
return info, nil
}
func parseBitbucketServerPullRequestPayload(raw []byte) (*model.BuildInfo, error) {
info := &model.BuildInfo{}
payload := bitbucketserver.PullRequestEventPayload{}
if err := json.Unmarshal(raw, &payload); err != nil {
return nil, err
}
if payload.PullRequest.State != bitbucketServerStateOpen {
return nil, fmt.Errorf("no trigger for closed pull requests")
}
info.TriggerType = utils.TriggerTypeWebhook
info.Event = utils.WebhookEventPullRequest
info.Branch = payload.PullRequest.ToRef.DisplayID
info.Ref = fmt.Sprintf("refs/pull-requests/%d/from", payload.PullRequest.ID)
if len(payload.PullRequest.Links.Self) > 0 {
info.HTMLLink = payload.PullRequest.Links.Self[0].Href
}
info.Title = payload.PullRequest.Title
info.Message = payload.PullRequest.Title
info.Commit = payload.PullRequest.FromRef.LatestCommit
info.Author = payload.PullRequest.Author.User.Name
if len(payload.PullRequest.Author.User.Links.Self) > 0 {
info.AvatarURL = payload.PullRequest.Author.User.Links.Self[0].Href + "/avatar.png"
}
return info, nil
}
func verifyBitbucketServerWebhookSignature(secret []byte, signature string, body []byte) bool {
const signaturePrefix = "sha256="
const signatureLength = 71 // len(SignaturePrefix) + len(hex(sha1))
if len(signature) != signatureLength || !strings.HasPrefix(signature, signaturePrefix) {
return false
}
actual := make([]byte, 32)
hex.Decode(actual, []byte(signature[7:]))
computed := hmac.New(sha256.New, secret)
computed.Write(body)
return hmac.Equal(computed.Sum(nil), actual)
}