mirror of https://github.com/gophish/gophish
106 lines
2.6 KiB
Go
106 lines
2.6 KiB
Go
|
package webhook
|
||
|
|
||
|
import (
|
||
|
"bytes"
|
||
|
"crypto/hmac"
|
||
|
"crypto/sha256"
|
||
|
"encoding/hex"
|
||
|
"encoding/json"
|
||
|
"errors"
|
||
|
"fmt"
|
||
|
"net/http"
|
||
|
"time"
|
||
|
|
||
|
log "github.com/gophish/gophish/logger"
|
||
|
)
|
||
|
|
||
|
const (
|
||
|
|
||
|
// DefaultTimeoutSeconds is amount of seconds of timeout used by HTTP sender
|
||
|
DefaultTimeoutSeconds = 10
|
||
|
|
||
|
// MinHTTPStatusErrorCode is the lowest number of an HTTP response which indicates an error
|
||
|
MinHTTPStatusErrorCode = 400
|
||
|
|
||
|
// SignatureHeader is the name of an HTTP header used to which contains signature of a webhook
|
||
|
SignatureHeader = "X-Gophish-Signature"
|
||
|
|
||
|
// Sha256Prefix is the prefix that specifies the hashing algorithm used for signature
|
||
|
Sha256Prefix = "sha256"
|
||
|
)
|
||
|
|
||
|
// Sender defines behaviour of an entity by which webhook is sent
|
||
|
type Sender interface {
|
||
|
Send(endPoint EndPoint, data interface{}) error
|
||
|
}
|
||
|
|
||
|
type defaultSender struct {
|
||
|
client *http.Client
|
||
|
}
|
||
|
|
||
|
var senderInstance = &defaultSender{
|
||
|
client: &http.Client{
|
||
|
Timeout: time.Second * DefaultTimeoutSeconds,
|
||
|
CheckRedirect: func(req *http.Request, via []*http.Request) error {
|
||
|
return http.ErrUseLastResponse
|
||
|
},
|
||
|
},
|
||
|
}
|
||
|
|
||
|
// EndPoint represents and end point to send a webhook to: url and secret by which payload is signed
|
||
|
type EndPoint struct {
|
||
|
URL string
|
||
|
Secret string
|
||
|
}
|
||
|
|
||
|
// Send sends data to a single EndPoint
|
||
|
func Send(endPoint EndPoint, data interface{}) error {
|
||
|
return senderInstance.Send(endPoint, data)
|
||
|
}
|
||
|
|
||
|
// SendAll sends data to each of the EndPoints
|
||
|
func SendAll(endPoints []EndPoint, data interface{}) {
|
||
|
for _, ept := range endPoints {
|
||
|
go func(ept1 EndPoint) {
|
||
|
senderInstance.Send(ept1, data)
|
||
|
}(EndPoint{URL: ept.URL, Secret: ept.Secret})
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Send contains the implementation of sending webhook to an EndPoint
|
||
|
func (ds defaultSender) Send(endPoint EndPoint, data interface{}) error {
|
||
|
jsonData, err := json.Marshal(data)
|
||
|
if err != nil {
|
||
|
log.Error(err)
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
req, err := http.NewRequest("POST", endPoint.URL, bytes.NewBuffer(jsonData))
|
||
|
signat, err := sign(endPoint.Secret, jsonData)
|
||
|
req.Header.Set(SignatureHeader, fmt.Sprintf("%s=%s", Sha256Prefix, signat))
|
||
|
req.Header.Set("Content-Type", "application/json")
|
||
|
resp, err := ds.client.Do(req)
|
||
|
if err != nil {
|
||
|
log.Error(err)
|
||
|
return err
|
||
|
}
|
||
|
defer resp.Body.Close()
|
||
|
|
||
|
if resp.StatusCode >= MinHTTPStatusErrorCode {
|
||
|
errMsg := fmt.Sprintf("http status of response: %s", resp.Status)
|
||
|
log.Error(errMsg)
|
||
|
return errors.New(errMsg)
|
||
|
}
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
func sign(secret string, data []byte) (string, error) {
|
||
|
hash1 := hmac.New(sha256.New, []byte(secret))
|
||
|
_, err := hash1.Write(data)
|
||
|
if err != nil {
|
||
|
return "", err
|
||
|
}
|
||
|
hexStr := hex.EncodeToString(hash1.Sum(nil))
|
||
|
return hexStr, nil
|
||
|
}
|