From 993ec330d37fb01303029f1802a123658f334654 Mon Sep 17 00:00:00 2001 From: Dean Karn Date: Sat, 15 Jul 2017 12:04:34 -0700 Subject: [PATCH] Add customizable Logger interface + info,error and debugs --- README.md | 2 +- bitbucket/bitbucket.go | 15 +++++-- examples/custom-logger/main.go | 67 ++++++++++++++++++++++++++++++ examples/multiple-handlers/main.go | 2 - examples/single-handler/main.go | 2 - github/github.go | 15 +++++-- gitlab/gitlab.go | 13 ++++-- logger.go | 44 ++++++++++++++++++++ webhooks.go | 15 +++++-- webhooks_test.go | 2 +- 10 files changed, 158 insertions(+), 19 deletions(-) create mode 100644 examples/custom-logger/main.go create mode 100644 logger.go diff --git a/README.md b/README.md index 94bb05c..2f5581b 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ Library webhooks ================ -![Project status](https://img.shields.io/badge/version-3.1.1-green.svg) +![Project status](https://img.shields.io/badge/version-3.2.0-green.svg) [![Build Status](https://travis-ci.org/go-playground/webhooks.svg?branch=v3)](https://travis-ci.org/go-playground/webhooks) [![Coverage Status](https://coveralls.io/repos/go-playground/webhooks/badge.svg?branch=v3&service=github)](https://coveralls.io/github/go-playground/webhooks?branch=v3) [![Go Report Card](https://goreportcard.com/badge/go-playground/webhooks)](https://goreportcard.com/report/go-playground/webhooks) diff --git a/bitbucket/bitbucket.go b/bitbucket/bitbucket.go index 7629fb3..0374e11 100644 --- a/bitbucket/bitbucket.go +++ b/bitbucket/bitbucket.go @@ -2,6 +2,7 @@ package bitbucket import ( "encoding/json" + "fmt" "io/ioutil" "net/http" @@ -69,38 +70,46 @@ func (hook Webhook) RegisterEvents(fn webhooks.ProcessPayloadFunc, events ...Eve // ParsePayload parses and verifies the payload and fires off the mapped function, if it exists. func (hook Webhook) ParsePayload(w http.ResponseWriter, r *http.Request) { + webhooks.DefaultLog.Info("Parsing Payload...") uuid := r.Header.Get("X-Hook-UUID") if uuid == "" { + webhooks.DefaultLog.Error("Missing X-Hook-UUID Header") http.Error(w, "400 Bad Request - Missing X-Hook-UUID Header", http.StatusBadRequest) return } + webhooks.DefaultLog.Debug(fmt.Sprintf("X-Hook-UUID:%s", uuid)) if uuid != hook.uuid { - http.Error(w, "403 Forbidden - Missing X-Hook-UUID does not match", http.StatusForbidden) + webhooks.DefaultLog.Error(fmt.Sprintf("X-Hook-UUID does not match configured uuid of %s", hook.uuid)) + http.Error(w, "403 Forbidden - X-Hook-UUID does not match", http.StatusForbidden) return } event := r.Header.Get("X-Event-Key") if event == "" { + webhooks.DefaultLog.Error("Missing X-Event-Key Header") http.Error(w, "400 Bad Request - Missing X-Event-Key Header", http.StatusBadRequest) return } + webhooks.DefaultLog.Debug(fmt.Sprintf("X-Event-Key:%s", event)) bitbucketEvent := Event(event) fn, ok := hook.eventFuncs[bitbucketEvent] // if no event registered if !ok { + webhooks.DefaultLog.Info(fmt.Sprintf("Webhook Event %s not registered, it is recommended to setup only events in bitbucket that will be registered in the webhook to avoid unnecessary traffic and reduce potential attack vectors.", event)) return } payload, err := ioutil.ReadAll(r.Body) if err != nil || len(payload) == 0 { - http.Error(w, "Error reading Body", http.StatusInternalServerError) + webhooks.DefaultLog.Error("Issue reading Payload") + http.Error(w, "Issue reading Payload", http.StatusInternalServerError) return } - + webhooks.DefaultLog.Debug(fmt.Sprintf("Payload:%s", string(payload))) hd := webhooks.Header(r.Header) switch bitbucketEvent { diff --git a/examples/custom-logger/main.go b/examples/custom-logger/main.go new file mode 100644 index 0000000..9ebefe1 --- /dev/null +++ b/examples/custom-logger/main.go @@ -0,0 +1,67 @@ +package main + +import ( + "fmt" + "log" + "strconv" + + "gopkg.in/go-playground/webhooks.v3" + "gopkg.in/go-playground/webhooks.v3/github" +) + +const ( + path = "/webhooks" + port = 3016 +) + +type myLogger struct { + PrintDebugs bool +} + +func (l *myLogger) Info(msg string) { + log.Println(msg) +} + +func (l *myLogger) Error(msg string) { + log.Println(msg) +} + +func (l *myLogger) Debug(msg string) { + if !l.PrintDebugs { + return + } + log.Println(msg) +} + +func main() { + // webhooks.DefaultLog=webhooks.NewLogger(true) + // + // or override with your own + webhooks.DefaultLog = &myLogger{PrintDebugs: true} + + hook := github.New(&github.Config{Secret: "MyGitHubSuperSecretSecrect...?"}) + hook.RegisterEvents(HandleMultiple, github.ReleaseEvent, github.PullRequestEvent) // Add as many as you want + + err := webhooks.Run(hook, ":"+strconv.Itoa(port), path) + if err != nil { + fmt.Println(err) + } +} + +// HandleMultiple handles multiple GitHub events +func HandleMultiple(payload interface{}, header webhooks.Header) { + fmt.Println("Handling Payload..") + + switch payload.(type) { + + case github.ReleasePayload: + release := payload.(github.ReleasePayload) + // Do whatever you want from here... + fmt.Printf("%+v", release) + + case github.PullRequestPayload: + pullRequest := payload.(github.PullRequestPayload) + // Do whatever you want from here... + fmt.Printf("%+v", pullRequest) + } +} diff --git a/examples/multiple-handlers/main.go b/examples/multiple-handlers/main.go index 9c18641..ca40d9a 100644 --- a/examples/multiple-handlers/main.go +++ b/examples/multiple-handlers/main.go @@ -14,7 +14,6 @@ const ( ) func main() { - hook := github.New(&github.Config{Secret: "MyGitHubSuperSecretSecrect...?"}) hook.RegisterEvents(HandleRelease, github.ReleaseEvent) hook.RegisterEvents(HandlePullRequest, github.PullRequestEvent) @@ -27,7 +26,6 @@ func main() { // HandleRelease handles GitHub release events func HandleRelease(payload interface{}, header webhooks.Header) { - fmt.Println("Handling Release") pl := payload.(github.ReleasePayload) diff --git a/examples/single-handler/main.go b/examples/single-handler/main.go index dd93308..491a517 100644 --- a/examples/single-handler/main.go +++ b/examples/single-handler/main.go @@ -14,7 +14,6 @@ const ( ) func main() { - hook := github.New(&github.Config{Secret: "MyGitHubSuperSecretSecrect...?"}) hook.RegisterEvents(HandleMultiple, github.ReleaseEvent, github.PullRequestEvent) // Add as many as you want @@ -26,7 +25,6 @@ func main() { // HandleMultiple handles multiple GitHub events func HandleMultiple(payload interface{}, header webhooks.Header) { - fmt.Println("Handling Payload..") switch payload.(type) { diff --git a/github/github.go b/github/github.go index 828f8a3..2702d1f 100644 --- a/github/github.go +++ b/github/github.go @@ -5,6 +5,7 @@ import ( "crypto/sha1" "encoding/hex" "encoding/json" + "fmt" "io/ioutil" "net/http" @@ -96,36 +97,43 @@ func (hook Webhook) RegisterEvents(fn webhooks.ProcessPayloadFunc, events ...Eve // ParsePayload parses and verifies the payload and fires off the mapped function, if it exists. func (hook Webhook) ParsePayload(w http.ResponseWriter, r *http.Request) { + webhooks.DefaultLog.Info("Parsing Payload...") event := r.Header.Get("X-GitHub-Event") if len(event) == 0 { + webhooks.DefaultLog.Error("Missing X-GitHub-Event Header") http.Error(w, "400 Bad Request - Missing X-GitHub-Event Header", http.StatusBadRequest) return } + webhooks.DefaultLog.Debug(fmt.Sprintf("X-GitHub-Event:%s", event)) gitHubEvent := Event(event) fn, ok := hook.eventFuncs[gitHubEvent] // if no event registered if !ok { + webhooks.DefaultLog.Info(fmt.Sprintf("Webhook Event %s not registered, it is recommended to setup only events in github that will be registered in the webhook to avoid unnecessary traffic and reduce potential attack vectors.", event)) return } payload, err := ioutil.ReadAll(r.Body) if err != nil || len(payload) == 0 { - http.Error(w, "Error reading Body", http.StatusInternalServerError) + webhooks.DefaultLog.Error("Issue reading Payload") + http.Error(w, "Issue reading Payload", http.StatusInternalServerError) return } + webhooks.DefaultLog.Debug(fmt.Sprintf("Payload:%s", string(payload))) // If we have a Secret set, we should check the MAC if len(hook.secret) > 0 { - + webhooks.DefaultLog.Info("Checking secret") signature := r.Header.Get("X-Hub-Signature") - if len(signature) == 0 { + webhooks.DefaultLog.Error("Missing X-Hub-Signature required for HMAC verification") http.Error(w, "403 Forbidden - Missing X-Hub-Signature required for HMAC verification", http.StatusForbidden) return } + webhooks.DefaultLog.Debug(fmt.Sprintf("X-Hub-Signature:%s", signature)) mac := hmac.New(sha1.New, []byte(hook.secret)) mac.Write(payload) @@ -133,6 +141,7 @@ func (hook Webhook) ParsePayload(w http.ResponseWriter, r *http.Request) { expectedMAC := hex.EncodeToString(mac.Sum(nil)) if !hmac.Equal([]byte(signature[5:]), []byte(expectedMAC)) { + webhooks.DefaultLog.Error("HMAC verification failed") http.Error(w, "403 Forbidden - HMAC verification failed", http.StatusForbidden) return } diff --git a/gitlab/gitlab.go b/gitlab/gitlab.go index 09a8469..8b1ef08 100644 --- a/gitlab/gitlab.go +++ b/gitlab/gitlab.go @@ -2,6 +2,7 @@ package gitlab import ( "encoding/json" + "fmt" "io/ioutil" "net/http" @@ -59,33 +60,39 @@ func (hook Webhook) RegisterEvents(fn webhooks.ProcessPayloadFunc, events ...Eve // ParsePayload parses and verifies the payload and fires off the mapped function, if it exists. func (hook Webhook) ParsePayload(w http.ResponseWriter, r *http.Request) { + webhooks.DefaultLog.Info("Parsing Payload...") event := r.Header.Get("X-Gitlab-Event") if len(event) == 0 { + webhooks.DefaultLog.Error("Missing X-Gitlab-Event Header") http.Error(w, "400 Bad Request - Missing X-Gitlab-Event Header", http.StatusBadRequest) return } + webhooks.DefaultLog.Debug(fmt.Sprintf("X-Gitlab-Event:%s", event)) gitLabEvent := Event(event) fn, ok := hook.eventFuncs[gitLabEvent] // if no event registered if !ok { + webhooks.DefaultLog.Info(fmt.Sprintf("Webhook Event %s not registered, it is recommended to setup only events in gitlab that will be registered in the webhook to avoid unnecessary traffic and reduce potential attack vectors.", event)) return } payload, err := ioutil.ReadAll(r.Body) if err != nil || len(payload) == 0 { - http.Error(w, "Error reading Body", http.StatusInternalServerError) + webhooks.DefaultLog.Error("Issue reading Payload") + http.Error(w, "Error reading Payload", http.StatusInternalServerError) return } + webhooks.DefaultLog.Debug(fmt.Sprintf("Payload:%s", string(payload))) // If we have a Secret set, we should check the MAC if len(hook.secret) > 0 { - + webhooks.DefaultLog.Info("Checking secret") signature := r.Header.Get("X-Gitlab-Token") - if signature != hook.secret { + webhooks.DefaultLog.Error(fmt.Sprintf("Invalid X-Gitlab-Token of '%s'", signature)) http.Error(w, "403 Forbidden - Token missmatch", http.StatusForbidden) return } diff --git a/logger.go b/logger.go new file mode 100644 index 0000000..0d3d588 --- /dev/null +++ b/logger.go @@ -0,0 +1,44 @@ +package webhooks + +import "log" + +// DefaultLog contains the default logger for webhooks, and prints only info and error messages by default +// for debugs override DefaultLog or see NewLogger for creating one without debugs. +var DefaultLog Logger = new(logger) + +// Logger allows for customizable logging +type Logger interface { + // Info prints basic information. + Info(string) + // Error prints error information. + Error(string) + // Debug prints information usefull for debugging. + Debug(string) +} + +// NewLogger returns a new logger for use. +func NewLogger(debug bool) Logger { + return &logger{PrintDebugs: debug} +} + +type logger struct { + PrintDebugs bool +} + +// Info prints basic information. +func (l *logger) Info(msg string) { + log.Println("INFO:", msg) +} + +// v prints error information. +func (l *logger) Error(msg string) { + log.Println("ERROR:", msg) +} + +// Debug prints information usefull for debugging. +func (l *logger) Debug(msg string) { + if !l.PrintDebugs { + return + } + log.Println("DEBUG:", msg) +} diff --git a/webhooks.go b/webhooks.go index 4d1a965..7e2d483 100644 --- a/webhooks.go +++ b/webhooks.go @@ -1,6 +1,9 @@ package webhooks -import "net/http" +import ( + "fmt" + "net/http" +) // Header provides http.Header to minimize imports type Header http.Header @@ -57,9 +60,9 @@ func Run(hook Webhook, addr string, path string) error { path: path, includePathCheck: true, } - s := &http.Server{Addr: addr, Handler: srv} + DefaultLog.Info(fmt.Sprintf("Listening on addr: %s path: %s", addr, path)) return s.ListenAndServe() } @@ -73,7 +76,7 @@ func RunServer(s *http.Server, hook Webhook, path string) error { } s.Handler = srv - + DefaultLog.Info(fmt.Sprintf("Listening on addr: %s path: %s", s.Addr, path)) return s.ListenAndServe() } @@ -90,21 +93,25 @@ func RunTLSServer(s *http.Server, hook Webhook, path string) error { } s.Handler = srv - + DefaultLog.Info(fmt.Sprintf("Listening on addr: %s path: %s", s.Addr, path)) return s.ListenAndServeTLS("", "") } // ServeHTTP is the Handler for every posted WebHook Event func (s *server) ServeHTTP(w http.ResponseWriter, r *http.Request) { defer r.Body.Close() + DefaultLog.Info("Webhook received") if r.Method != "POST" { + DefaultLog.Error(fmt.Sprintf("405 Method not allowed, attempt made using Method: %s", r.Method)) http.Error(w, "405 Method not allowed", http.StatusMethodNotAllowed) return } + DefaultLog.Debug(fmt.Sprintf("Include path check: %t", s.includePathCheck)) if s.includePathCheck { if r.URL.Path != s.path { + DefaultLog.Error(fmt.Sprintf("404 Not found, POST made using path: %s, but expected %s", r.URL.Path, s.path)) http.Error(w, "404 Not found", http.StatusNotFound) return } diff --git a/webhooks_test.go b/webhooks_test.go index b6c06fc..fed998b 100644 --- a/webhooks_test.go +++ b/webhooks_test.go @@ -136,7 +136,7 @@ func TestRun(t *testing.T) { } func TestRunServer(t *testing.T) { - + DefaultLog = NewLogger(true) server := &http.Server{Addr: "127.0.0.1:3007", Handler: nil} go RunServer(server, fakeHook, "/webhooks") time.Sleep(5000)