Skip to content

Commit 2fe5d09

Browse files
authored
feat: add azure basic auth verification (#191)
1 parent e24c5f0 commit 2fe5d09

File tree

2 files changed

+109
-3
lines changed

2 files changed

+109
-3
lines changed

azuredevops/azuredevops.go

+43-3
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,9 @@ import (
1313

1414
// parse errors
1515
var (
16-
ErrInvalidHTTPMethod = errors.New("invalid HTTP Method")
17-
ErrParsingPayload = errors.New("error parsing payload")
16+
ErrInvalidHTTPMethod = errors.New("invalid HTTP Method")
17+
ErrParsingPayload = errors.New("error parsing payload")
18+
ErrBasicAuthVerificationFailed = errors.New("basic auth verification failed")
1819
)
1920

2021
// Event defines an Azure DevOps server hook event type
@@ -29,13 +30,38 @@ const (
2930
GitPushEventType Event = "git.push"
3031
)
3132

33+
// Option is a configuration option for the webhook
34+
type Option func(*Webhook) error
35+
36+
// Options is a namespace var for configuration options
37+
var Options = WebhookOptions{}
38+
39+
// WebhookOptions is a namespace for configuration option methods
40+
type WebhookOptions struct{}
41+
42+
// BasicAuth verifies payload using basic auth
43+
func (WebhookOptions) BasicAuth(username, password string) Option {
44+
return func(hook *Webhook) error {
45+
hook.username = username
46+
hook.password = password
47+
return nil
48+
}
49+
}
50+
3251
// Webhook instance contains all methods needed to process events
3352
type Webhook struct {
53+
username string
54+
password string
3455
}
3556

3657
// New creates and returns a WebHook instance
37-
func New() (*Webhook, error) {
58+
func New(options ...Option) (*Webhook, error) {
3859
hook := new(Webhook)
60+
for _, opt := range options {
61+
if err := opt(hook); err != nil {
62+
return nil, errors.New("Error applying Option")
63+
}
64+
}
3965
return hook, nil
4066
}
4167

@@ -46,6 +72,10 @@ func (hook Webhook) Parse(r *http.Request, events ...Event) (interface{}, error)
4672
_ = r.Body.Close()
4773
}()
4874

75+
if !hook.verifyBasicAuth(r) {
76+
return nil, ErrBasicAuthVerificationFailed
77+
}
78+
4979
if r.Method != http.MethodPost {
5080
return nil, ErrInvalidHTTPMethod
5181
}
@@ -78,3 +108,13 @@ func (hook Webhook) Parse(r *http.Request, events ...Event) (interface{}, error)
78108
return nil, fmt.Errorf("unknown event %s", pl.EventType)
79109
}
80110
}
111+
112+
func (hook Webhook) verifyBasicAuth(r *http.Request) bool {
113+
// skip validation if username or password was not provided
114+
if hook.username == "" && hook.password == "" {
115+
return true
116+
}
117+
username, password, ok := r.BasicAuth()
118+
119+
return ok && username == hook.username && password == hook.password
120+
}

azuredevops/azuredevops_test.go

+66
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
package azuredevops
22

33
import (
4+
"bytes"
5+
"fmt"
6+
"github.com/stretchr/testify/assert"
47
"log"
58
"net/http"
69
"net/http/httptest"
@@ -117,3 +120,66 @@ func TestWebhooks(t *testing.T) {
117120
})
118121
}
119122
}
123+
124+
func TestParseBasicAuth(t *testing.T) {
125+
const validUser = "validUser"
126+
const validPass = "pass123"
127+
tests := []struct {
128+
name string
129+
webhookUser string
130+
webhookPass string
131+
reqUser string
132+
reqPass string
133+
expectedErr error
134+
}{
135+
{
136+
name: "valid basic auth",
137+
webhookUser: validUser,
138+
webhookPass: validPass,
139+
reqUser: validUser,
140+
reqPass: validPass,
141+
expectedErr: fmt.Errorf("unknown event "), // no event passed, so this is expected
142+
},
143+
{
144+
name: "no basic auth provided",
145+
expectedErr: fmt.Errorf("unknown event "), // no event passed, so this is expected
146+
},
147+
{
148+
name: "invalid basic auth",
149+
webhookUser: validUser,
150+
webhookPass: validPass,
151+
reqUser: "fakeUser",
152+
reqPass: "fakePass",
153+
expectedErr: ErrBasicAuthVerificationFailed,
154+
},
155+
}
156+
157+
for _, tt := range tests {
158+
h := Webhook{
159+
username: tt.webhookUser,
160+
password: tt.webhookPass,
161+
}
162+
body := []byte(`{}`)
163+
r, err := http.NewRequest(http.MethodPost, "", bytes.NewBuffer(body))
164+
assert.NoError(t, err)
165+
r.SetBasicAuth(tt.reqUser, tt.reqPass)
166+
167+
p, err := h.Parse(r)
168+
169+
assert.Equal(t, err, tt.expectedErr)
170+
assert.Nil(t, p)
171+
}
172+
}
173+
174+
func TestBasicAuth(t *testing.T) {
175+
const user = "user"
176+
const pass = "pass123"
177+
178+
opt := Options.BasicAuth(user, pass)
179+
h := &Webhook{}
180+
err := opt(h)
181+
182+
assert.NoError(t, err)
183+
assert.Equal(t, h.username, user)
184+
assert.Equal(t, h.password, pass)
185+
}

0 commit comments

Comments
 (0)