Skip to content

Commit acf48b9

Browse files
gooberDean Karn
authored and
Dean Karn
committed
Adds support for Bitbucket Server (go-playground#63)
1 parent a1051fd commit acf48b9

22 files changed

+4032
-1
lines changed

bitbucket-server/bitbucketserver.go

Lines changed: 218 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,218 @@
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+
PullRequestModifiedEvent Event = "pr:modified"
37+
PullRequestMergedEvent Event = "pr:merged"
38+
PullRequestDeclinedEvent Event = "pr:declined"
39+
PullRequestDeletedEvent Event = "pr:deleted"
40+
41+
PullRequestReviewerUpdatedEvent Event = "pr:reviewer:updated"
42+
PullRequestReviewerApprovedEvent Event = "pr:reviewer:approved"
43+
PullRequestReviewerUnapprovedEvent Event = "pr:reviewer:unapproved"
44+
PullRequestReviewerNeedsWorkEvent Event = "pr:reviewer:needs_work"
45+
46+
PullRequestCommentAddedEvent Event = "pr:comment:added"
47+
PullRequestCommentEditedEvent Event = "pr:comment:edited"
48+
PullRequestCommentDeletedEvent Event = "pr:comment:deleted"
49+
50+
DiagnosticsPingEvent Event = "diagnostics:ping"
51+
)
52+
53+
// Option is a configuration option for the webhook
54+
type Option func(*Webhook) error
55+
56+
// Options is a namespace var for configuration options
57+
var Options = WebhookOptions{}
58+
59+
// WebhookOptions is a namespace for configuration option methods
60+
type WebhookOptions struct{}
61+
62+
// Secret registers the GitHub secret
63+
func (WebhookOptions) Secret(secret string) Option {
64+
return func(hook *Webhook) error {
65+
hook.secret = secret
66+
return nil
67+
}
68+
}
69+
70+
// Webhook instance contains all methods needed to process events
71+
type Webhook struct {
72+
secret string
73+
}
74+
75+
// New creates and returns a WebHook instance denoted by the Provider type
76+
func New(options ...Option) (*Webhook, error) {
77+
hook := new(Webhook)
78+
for _, opt := range options {
79+
if err := opt(hook); err != nil {
80+
return nil, errors.New("Error applying Option")
81+
}
82+
}
83+
return hook, nil
84+
}
85+
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+
96+
if r.Method != http.MethodPost {
97+
return nil, ErrInvalidHTTPMethod
98+
}
99+
100+
event := r.Header.Get("X-Event-Key")
101+
if event == "" {
102+
return nil, ErrMissingEventKeyHeader
103+
}
104+
105+
bitbucketEvent := Event(event)
106+
107+
var found bool
108+
for _, evt := range events {
109+
if evt == bitbucketEvent {
110+
found = true
111+
break
112+
}
113+
}
114+
// event not defined to be parsed
115+
if !found {
116+
return nil, ErrEventNotFound
117+
}
118+
119+
if bitbucketEvent == DiagnosticsPingEvent {
120+
return DiagnosticsPingPayload{}, nil
121+
}
122+
123+
payload, err := ioutil.ReadAll(r.Body)
124+
if err != nil || len(payload) == 0 {
125+
return nil, ErrParsingPayload
126+
}
127+
128+
if len(hook.secret) > 0 {
129+
signature := r.Header.Get("X-Hub-Signature")
130+
if len(signature) == 0 {
131+
return nil, ErrMissingHubSignatureHeader
132+
}
133+
mac := hmac.New(sha256.New, []byte(hook.secret))
134+
_, _ = mac.Write(payload)
135+
expectedMAC := hex.EncodeToString(mac.Sum(nil))
136+
137+
if !hmac.Equal([]byte(signature[7:]), []byte(expectedMAC)) {
138+
return nil, ErrHMACVerificationFailed
139+
}
140+
}
141+
142+
switch bitbucketEvent {
143+
case RepositoryReferenceChangedEvent:
144+
var pl RepositoryReferenceChangedPayload
145+
err = json.Unmarshal([]byte(payload), &pl)
146+
return pl, err
147+
case RepositoryModifiedEvent:
148+
var pl RepositoryModifiedPayload
149+
err = json.Unmarshal([]byte(payload), &pl)
150+
return pl, err
151+
case RepositoryForkedEvent:
152+
var pl RepositoryForkedPayload
153+
err = json.Unmarshal([]byte(payload), &pl)
154+
return pl, err
155+
case RepositoryCommentAddedEvent:
156+
var pl RepositoryCommentAddedPayload
157+
err = json.Unmarshal([]byte(payload), &pl)
158+
return pl, err
159+
case RepositoryCommentEditedEvent:
160+
var pl RepositoryCommentEditedPayload
161+
err = json.Unmarshal([]byte(payload), &pl)
162+
return pl, err
163+
case RepositoryCommentDeletedEvent:
164+
var pl RepositoryCommentDeletedPayload
165+
err = json.Unmarshal([]byte(payload), &pl)
166+
return pl, err
167+
case PullRequestOpenedEvent:
168+
var pl PullRequestOpenedPayload
169+
err = json.Unmarshal([]byte(payload), &pl)
170+
return pl, err
171+
case PullRequestModifiedEvent:
172+
var pl PullRequestModifiedPayload
173+
err = json.Unmarshal([]byte(payload), &pl)
174+
return pl, err
175+
case PullRequestMergedEvent:
176+
var pl PullRequestMergedPayload
177+
err = json.Unmarshal([]byte(payload), &pl)
178+
return pl, err
179+
case PullRequestDeclinedEvent:
180+
var pl PullRequestDeclinedPayload
181+
err = json.Unmarshal([]byte(payload), &pl)
182+
return pl, err
183+
case PullRequestDeletedEvent:
184+
var pl PullRequestDeletedPayload
185+
err = json.Unmarshal([]byte(payload), &pl)
186+
return pl, err
187+
case PullRequestReviewerUpdatedEvent:
188+
var pl PullRequestReviewerUpdatedPayload
189+
err = json.Unmarshal([]byte(payload), &pl)
190+
return pl, err
191+
case PullRequestReviewerApprovedEvent:
192+
var pl PullRequestReviewerApprovedPayload
193+
err = json.Unmarshal([]byte(payload), &pl)
194+
return pl, err
195+
case PullRequestReviewerUnapprovedEvent:
196+
var pl PullRequestReviewerUnapprovedPayload
197+
err = json.Unmarshal([]byte(payload), &pl)
198+
return pl, err
199+
case PullRequestReviewerNeedsWorkEvent:
200+
var pl PullRequestReviewerNeedsWorkPayload
201+
err = json.Unmarshal([]byte(payload), &pl)
202+
return pl, err
203+
case PullRequestCommentAddedEvent:
204+
var pl PullRequestCommentAddedPayload
205+
err = json.Unmarshal([]byte(payload), &pl)
206+
return pl, err
207+
case PullRequestCommentEditedEvent:
208+
var pl PullRequestCommentEditedPayload
209+
err = json.Unmarshal([]byte(payload), &pl)
210+
return pl, err
211+
case PullRequestCommentDeletedEvent:
212+
var pl PullRequestCommentDeletedPayload
213+
err = json.Unmarshal([]byte(payload), &pl)
214+
return pl, err
215+
default:
216+
return nil, fmt.Errorf("unknown event %s", bitbucketEvent)
217+
}
218+
}

0 commit comments

Comments
 (0)