github.com/bluestoneag/bluephish@v0.1.0/webhook/webhook.go (about)

     1  package webhook
     2  
     3  import (
     4  	"bytes"
     5  	"crypto/hmac"
     6  	"crypto/sha256"
     7  	"encoding/hex"
     8  	"encoding/json"
     9  	"errors"
    10  	"fmt"
    11  	"net/http"
    12  	"time"
    13  
    14  	log "github.com/bluestoneag/bluephish/logger"
    15  )
    16  
    17  const (
    18  
    19  	// DefaultTimeoutSeconds is the number of seconds before a timeout occurs
    20  	// when sending a webhook
    21  	DefaultTimeoutSeconds = 10
    22  
    23  	// MinHTTPStatusErrorCode is the lower bound of HTTP status codes which
    24  	// indicate an error occurred
    25  	MinHTTPStatusErrorCode = 400
    26  
    27  	// SignatureHeader is the name of the HTTP header which contains the
    28  	// webhook signature
    29  	SignatureHeader = "X-Gophish-Signature"
    30  
    31  	// Sha256Prefix is the prefix that specifies the hashing algorithm used
    32  	// for the signature
    33  	Sha256Prefix = "sha256"
    34  )
    35  
    36  // Sender represents a type which can send webhooks to an EndPoint
    37  type Sender interface {
    38  	Send(endPoint EndPoint, data interface{}) error
    39  }
    40  
    41  type defaultSender struct {
    42  	client *http.Client
    43  }
    44  
    45  var senderInstance = &defaultSender{
    46  	client: &http.Client{
    47  		Timeout: time.Second * DefaultTimeoutSeconds,
    48  		CheckRedirect: func(req *http.Request, via []*http.Request) error {
    49  			return http.ErrUseLastResponse
    50  		},
    51  	},
    52  }
    53  
    54  // SetTransport sets the underlying transport for the default webhook client.
    55  func SetTransport(tr *http.Transport) {
    56  	senderInstance.client.Transport = tr
    57  }
    58  
    59  // EndPoint represents a URL to send the webhook to, as well as a secret used
    60  // to sign the event
    61  type EndPoint struct {
    62  	URL    string
    63  	Secret string
    64  }
    65  
    66  // Send sends data to a single EndPoint
    67  func Send(endPoint EndPoint, data interface{}) error {
    68  	return senderInstance.Send(endPoint, data)
    69  }
    70  
    71  // SendAll sends data to multiple EndPoints
    72  func SendAll(endPoints []EndPoint, data interface{}) {
    73  	for _, e := range endPoints {
    74  		go func(e EndPoint) {
    75  			senderInstance.Send(e, data)
    76  		}(e)
    77  	}
    78  }
    79  
    80  // Send contains the implementation of sending webhook to an EndPoint
    81  func (ds defaultSender) Send(endPoint EndPoint, data interface{}) error {
    82  	jsonData, err := json.Marshal(data)
    83  	if err != nil {
    84  		log.Error(err)
    85  		return err
    86  	}
    87  
    88  	req, err := http.NewRequest("POST", endPoint.URL, bytes.NewBuffer(jsonData))
    89  	if err != nil {
    90  		log.Error(err)
    91  		return err
    92  	}
    93  	signat, err := sign(endPoint.Secret, jsonData)
    94  	if err != nil {
    95  		log.Error(err)
    96  		return err
    97  	}
    98  	req.Header.Set(SignatureHeader, fmt.Sprintf("%s=%s", Sha256Prefix, signat))
    99  	req.Header.Set("Content-Type", "application/json")
   100  	resp, err := ds.client.Do(req)
   101  	if err != nil {
   102  		log.Error(err)
   103  		return err
   104  	}
   105  	defer resp.Body.Close()
   106  
   107  	if resp.StatusCode >= MinHTTPStatusErrorCode {
   108  		errMsg := fmt.Sprintf("http status of response: %s", resp.Status)
   109  		log.Error(errMsg)
   110  		return errors.New(errMsg)
   111  	}
   112  	return nil
   113  }
   114  
   115  func sign(secret string, data []byte) (string, error) {
   116  	hash1 := hmac.New(sha256.New, []byte(secret))
   117  	_, err := hash1.Write(data)
   118  	if err != nil {
   119  		return "", err
   120  	}
   121  	hexStr := hex.EncodeToString(hash1.Sum(nil))
   122  	return hexStr, nil
   123  }