Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 43 additions & 3 deletions azuredevops/azuredevops.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,9 @@ import (

// parse errors
var (
ErrInvalidHTTPMethod = errors.New("invalid HTTP Method")
ErrParsingPayload = errors.New("error parsing payload")
ErrInvalidHTTPMethod = errors.New("invalid HTTP Method")
ErrParsingPayload = errors.New("error parsing payload")
ErrBasicAuthVerificationFailed = errors.New("basic auth verification failed")
)

// Event defines an Azure DevOps server hook event type
Expand All @@ -30,13 +31,38 @@ const (
GitPullRequestCommentEventType Event = "ms.vss-code.git-pullrequest-comment-event"
)

// Option is a configuration option for the webhook
type Option func(*Webhook) error

// Options is a namespace var for configuration options
var Options = WebhookOptions{}

// WebhookOptions is a namespace for configuration option methods
type WebhookOptions struct{}

// BasicAuth verifies payload using basic auth
func (WebhookOptions) BasicAuth(username, password string) Option {
return func(hook *Webhook) error {
hook.username = username
hook.password = password
return nil
}
}

// Webhook instance contains all methods needed to process events
type Webhook struct {
username string
password string
}

// New creates and returns a WebHook instance
func New() (*Webhook, error) {
func New(options ...Option) (*Webhook, error) {
hook := new(Webhook)
for _, opt := range options {
if err := opt(hook); err != nil {
return nil, errors.New("Error applying Option")
}
}
return hook, nil
}

Expand All @@ -47,6 +73,10 @@ func (hook Webhook) Parse(r *http.Request, events ...Event) (interface{}, error)
_ = r.Body.Close()
}()

if !hook.verifyBasicAuth(r) {
return nil, ErrBasicAuthVerificationFailed
}

if r.Method != http.MethodPost {
return nil, ErrInvalidHTTPMethod
}
Expand Down Expand Up @@ -83,3 +113,13 @@ func (hook Webhook) Parse(r *http.Request, events ...Event) (interface{}, error)
return nil, fmt.Errorf("unknown event %s", pl.EventType)
}
}

func (hook Webhook) verifyBasicAuth(r *http.Request) bool {
// skip validation if username or password was not provided
if hook.username == "" && hook.password == "" {
return true
}
username, password, ok := r.BasicAuth()

return ok && username == hook.username && password == hook.password
}
66 changes: 66 additions & 0 deletions azuredevops/azuredevops_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package azuredevops

import (
"bytes"
"fmt"
"github.com/stretchr/testify/assert"
"log"
"net/http"
"net/http/httptest"
Expand Down Expand Up @@ -117,3 +120,66 @@ func TestWebhooks(t *testing.T) {
})
}
}

func TestParseBasicAuth(t *testing.T) {
const validUser = "validUser"
const validPass = "pass123"
tests := []struct {
name string
webhookUser string
webhookPass string
reqUser string
reqPass string
expectedErr error
}{
{
name: "valid basic auth",
webhookUser: validUser,
webhookPass: validPass,
reqUser: validUser,
reqPass: validPass,
expectedErr: fmt.Errorf("unknown event "), // no event passed, so this is expected
},
{
name: "no basic auth provided",
expectedErr: fmt.Errorf("unknown event "), // no event passed, so this is expected
},
{
name: "invalid basic auth",
webhookUser: validUser,
webhookPass: validPass,
reqUser: "fakeUser",
reqPass: "fakePass",
expectedErr: ErrBasicAuthVerificationFailed,
},
}

for _, tt := range tests {
h := Webhook{
username: tt.webhookUser,
password: tt.webhookPass,
}
body := []byte(`{}`)
r, err := http.NewRequest(http.MethodPost, "", bytes.NewBuffer(body))
assert.NoError(t, err)
r.SetBasicAuth(tt.reqUser, tt.reqPass)

p, err := h.Parse(r)

assert.Equal(t, err, tt.expectedErr)
assert.Nil(t, p)
}
}

func TestBasicAuth(t *testing.T) {
const user = "user"
const pass = "pass123"

opt := Options.BasicAuth(user, pass)
h := &Webhook{}
err := opt(h)

assert.NoError(t, err)
assert.Equal(t, h.username, user)
assert.Equal(t, h.password, pass)
}
5 changes: 5 additions & 0 deletions github/github.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ const (
WorkflowJobEvent Event = "workflow_job"
WorkflowRunEvent Event = "workflow_run"
GitHubAppAuthorizationEvent Event = "github_app_authorization"
CodeScanningAlertEvent Event = "code_scanning_alert"
)

// EventSubtype defines a GitHub Hook Event subtype
Expand Down Expand Up @@ -353,6 +354,10 @@ func (hook Webhook) Parse(r *http.Request, events ...Event) (interface{}, error)
var pl GitHubAppAuthorizationPayload
err = json.Unmarshal([]byte(payload), &pl)
return pl, err
case CodeScanningAlertEvent:
var pl CodeScanningAlertPayload
err = json.Unmarshal([]byte(payload), &pl)
return pl, err
default:
return nil, fmt.Errorf("unknown event %s", gitHubEvent)
}
Expand Down
9 changes: 9 additions & 0 deletions github/github_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -555,6 +555,15 @@ func TestWebhooks(t *testing.T) {
"X-Github-Event": []string{"github_app_authorization"},
},
},
{
name: "CodeScanningAlertEvent",
event: CodeScanningAlertEvent,
typ: CodeScanningAlertPayload{},
filename: "../testdata/github/code_scanning_alert.json",
headers: http.Header{
"X-Github-Event": []string{"code_scanning_alert"},
},
},
}

for _, tt := range tests {
Expand Down
Loading
Loading