Refactor to remove helpers package

This commit removes the "helpers" package by moving functions from the
package into the other packages that use them.

CheckPayloadSignature() and ExtractParamater() are simply moved to the
"hook" package.  I'm not sure of the usefulness of having these
functions exported, but I left them allow for now.

ValuesToMap() is moved to the "main" webhook package and renamed to
valuesToMap().

Tests were moved into the "hook" package since we only test
ExtractParameter() right now.

This commit closes adnanh/webhook#12.
This commit is contained in:
Cameron Moore 2015-03-20 08:55:42 -05:00
parent d8a21582a3
commit 7dd55f5232
4 changed files with 77 additions and 84 deletions

View file

@ -1,75 +0,0 @@
package helpers
import (
"crypto/hmac"
"crypto/sha1"
"encoding/hex"
"fmt"
"reflect"
"strconv"
"strings"
)
// CheckPayloadSignature calculates and verifies SHA1 signature of the given payload
func CheckPayloadSignature(payload []byte, secret string, signature string) (string, bool) {
if strings.HasPrefix(signature, "sha1=") {
signature = signature[5:]
}
mac := hmac.New(sha1.New, []byte(secret))
mac.Write(payload)
expectedMAC := hex.EncodeToString(mac.Sum(nil))
return expectedMAC, hmac.Equal([]byte(signature), []byte(expectedMAC))
}
// ValuesToMap converts map[string][]string to a map[string]string object
func ValuesToMap(values map[string][]string) map[string]interface{} {
ret := make(map[string]interface{})
for key, value := range values {
if len(value) > 0 {
ret[key] = value[0]
}
}
return ret
}
// ExtractParameter extracts value from interface{} based on the passed string
func ExtractParameter(s string, params interface{}) (string, bool) {
if params == nil {
return "", false
}
if paramsValue := reflect.ValueOf(params); paramsValue.Kind() == reflect.Slice {
if paramsValueSliceLength := paramsValue.Len(); paramsValueSliceLength > 0 {
if p := strings.SplitN(s, ".", 2); len(p) > 1 {
index, err := strconv.ParseInt(p[0], 10, 64)
if err != nil {
return "", false
} else if paramsValueSliceLength <= int(index) {
return "", false
}
return ExtractParameter(p[1], params.([]interface{})[index])
}
}
return "", false
}
if p := strings.SplitN(s, ".", 2); len(p) > 1 {
if pValue, ok := params.(map[string]interface{})[p[0]]; ok {
return ExtractParameter(p[1], pValue)
}
} else {
if pValue, ok := params.(map[string]interface{})[p[0]]; ok {
return fmt.Sprintf("%v", pValue), true
}
}
return "", false
}

View file

@ -1,12 +1,17 @@
package hook
import (
"crypto/hmac"
"crypto/sha1"
"encoding/hex"
"encoding/json"
"fmt"
"io/ioutil"
"log"
"reflect"
"regexp"
"github.com/adnanh/webhook/helpers"
"strconv"
"strings"
)
// Constants used to specify the parameter source
@ -16,6 +21,57 @@ const (
SourcePayload string = "payload"
)
// CheckPayloadSignature calculates and verifies SHA1 signature of the given payload
func CheckPayloadSignature(payload []byte, secret string, signature string) (string, bool) {
if strings.HasPrefix(signature, "sha1=") {
signature = signature[5:]
}
mac := hmac.New(sha1.New, []byte(secret))
mac.Write(payload)
expectedMAC := hex.EncodeToString(mac.Sum(nil))
return expectedMAC, hmac.Equal([]byte(signature), []byte(expectedMAC))
}
// ExtractParameter extracts value from interface{} based on the passed string
func ExtractParameter(s string, params interface{}) (string, bool) {
if params == nil {
return "", false
}
if paramsValue := reflect.ValueOf(params); paramsValue.Kind() == reflect.Slice {
if paramsValueSliceLength := paramsValue.Len(); paramsValueSliceLength > 0 {
if p := strings.SplitN(s, ".", 2); len(p) > 1 {
index, err := strconv.ParseInt(p[0], 10, 64)
if err != nil {
return "", false
} else if paramsValueSliceLength <= int(index) {
return "", false
}
return ExtractParameter(p[1], params.([]interface{})[index])
}
}
return "", false
}
if p := strings.SplitN(s, ".", 2); len(p) > 1 {
if pValue, ok := params.(map[string]interface{})[p[0]]; ok {
return ExtractParameter(p[1], pValue)
}
} else {
if pValue, ok := params.(map[string]interface{})[p[0]]; ok {
return fmt.Sprintf("%v", pValue), true
}
}
return "", false
}
// Argument type specifies the parameter key name and the source it should
// be extracted from
type Argument struct {
@ -38,7 +94,7 @@ func (ha *Argument) Get(headers, query, payload *map[string]interface{}) (string
}
if source != nil {
return helpers.ExtractParameter(ha.Name, *source)
return ExtractParameter(ha.Name, *source)
}
return "", false
@ -201,7 +257,7 @@ func (r MatchRule) Evaluate(headers, query, payload *map[string]interface{}, bod
}
return ok
case MatchHashSHA1:
expected, ok := helpers.CheckPayloadSignature(*body, r.Secret, arg)
expected, ok := CheckPayloadSignature(*body, r.Secret, arg)
if !ok {
log.Printf("payload signature mismatch, expected %s got %s", expected, arg)
}

View file

@ -1,4 +1,4 @@
package helpers
package hook
import "testing"

View file

@ -12,7 +12,6 @@ import (
"os/exec"
"strings"
"github.com/adnanh/webhook/helpers"
"github.com/adnanh/webhook/hook"
"github.com/codegangsta/negroni"
@ -143,10 +142,10 @@ func hookHandler(w http.ResponseWriter, r *http.Request) {
}
// parse headers
headers := helpers.ValuesToMap(r.Header)
headers := valuesToMap(r.Header)
// parse query variables
query := helpers.ValuesToMap(r.URL.Query())
query := valuesToMap(r.URL.Query())
// parse body
var payload map[string]interface{}
@ -167,7 +166,7 @@ func hookHandler(w http.ResponseWriter, r *http.Request) {
if err != nil {
log.Printf("error parsing form payload %+v\n", err)
} else {
payload = helpers.ValuesToMap(fd)
payload = valuesToMap(fd)
}
}
@ -236,3 +235,16 @@ func watchForFileChange() {
}
}
}
// valuesToMap converts map[string][]string to a map[string]string object
func valuesToMap(values map[string][]string) map[string]interface{} {
ret := make(map[string]interface{})
for key, value := range values {
if len(value) > 0 {
ret[key] = value[0]
}
}
return ret
}