2023-11-07 22:34:57 -05:00
|
|
|
package controllers
|
|
|
|
|
|
|
|
import (
|
2023-11-18 19:17:14 -05:00
|
|
|
"bytes"
|
2023-11-07 22:34:57 -05:00
|
|
|
"crypto/hmac"
|
|
|
|
"crypto/sha256"
|
|
|
|
"encoding/hex"
|
2023-11-23 09:05:44 -05:00
|
|
|
"errors"
|
|
|
|
"fmt"
|
2023-11-07 22:34:57 -05:00
|
|
|
"io"
|
|
|
|
|
2023-11-18 19:17:14 -05:00
|
|
|
"git.preston-baxter.com/Preston_PLB/capstone/webhook-service/vendors/pco/webhooks"
|
2023-11-07 22:34:57 -05:00
|
|
|
"github.com/gin-gonic/gin"
|
2023-11-18 19:17:14 -05:00
|
|
|
"github.com/google/jsonapi"
|
2023-11-07 22:34:57 -05:00
|
|
|
)
|
|
|
|
|
|
|
|
const PCO_VALIDATE_HEADER = "X-PCO-Webhooks-Authenticity"
|
|
|
|
|
|
|
|
func ValidatePcoWebhook(c *gin.Context) {
|
|
|
|
//get remote version from header
|
|
|
|
remoteDigestStr := c.GetHeader(PCO_VALIDATE_HEADER)
|
|
|
|
if remoteDigestStr == "" {
|
|
|
|
log.Warnf("Request was sent with no %s header. Rejecting", PCO_VALIDATE_HEADER)
|
|
|
|
c.AbortWithStatus(401)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
pcoSig := make([]byte, len(remoteDigestStr)/2)
|
|
|
|
_, err := hex.Decode(pcoSig, []byte(remoteDigestStr))
|
2023-11-18 19:17:14 -05:00
|
|
|
if err != nil {
|
|
|
|
log.WithError(err).Error("Failed to decode byte digest")
|
|
|
|
_ = c.AbortWithError(501, err)
|
|
|
|
return
|
|
|
|
}
|
2023-11-07 22:34:57 -05:00
|
|
|
|
2023-11-23 09:05:44 -05:00
|
|
|
//clone request body to harmlessly inspect the body
|
|
|
|
bodyCopy := bytes.NewBuffer([]byte{})
|
|
|
|
_, err = io.Copy(bodyCopy, c.Request.Body)
|
2023-11-07 22:34:57 -05:00
|
|
|
if err != nil {
|
2023-11-23 09:05:44 -05:00
|
|
|
log.WithError(err).Error("Failed to copy body while validating PCO webhook")
|
2023-11-18 19:17:14 -05:00
|
|
|
_ = c.AbortWithError(501, err)
|
2023-11-07 22:34:57 -05:00
|
|
|
return
|
|
|
|
}
|
2023-11-23 09:05:44 -05:00
|
|
|
body := bodyCopy.Bytes()
|
|
|
|
c.Request.Body = io.NopCloser(bytes.NewReader(body))
|
2023-11-07 22:34:57 -05:00
|
|
|
|
|
|
|
//Get secret
|
2023-11-18 19:17:14 -05:00
|
|
|
key, err := getAuthSecret(c, body)
|
|
|
|
if err != nil {
|
|
|
|
log.WithError(err).Error("Failed to find auth secret for event. It may not be setup")
|
|
|
|
_ = c.AbortWithError(501, err)
|
|
|
|
return
|
|
|
|
}
|
2023-11-07 22:34:57 -05:00
|
|
|
|
|
|
|
//Get HMAC
|
|
|
|
hmacSig := hmac.New(sha256.New, []byte(key))
|
|
|
|
hmacSig.Write(body)
|
|
|
|
|
|
|
|
if !hmac.Equal(hmacSig.Sum(nil), pcoSig) {
|
|
|
|
log.Warn("")
|
|
|
|
c.AbortWithStatus(401)
|
|
|
|
}
|
|
|
|
}
|
2023-11-18 19:17:14 -05:00
|
|
|
|
|
|
|
func getAuthSecret(c *gin.Context, body []byte) (string, error) {
|
|
|
|
userObjectId := userIdFromContext(c)
|
2023-11-23 09:05:44 -05:00
|
|
|
log.Debug(string(body))
|
2023-11-18 19:17:14 -05:00
|
|
|
|
2023-11-23 09:05:44 -05:00
|
|
|
//Pco is weird and sends a data array instead of an object. Yet there is only one event. Fun times
|
|
|
|
event, err := jsonapi.UnmarshalManyPayload[webhooks.EventDelivery](bytes.NewBuffer(body))
|
2023-11-18 19:17:14 -05:00
|
|
|
if err != nil {
|
2023-11-23 09:05:44 -05:00
|
|
|
return "", errors.Join(fmt.Errorf("Failed to unmarshall event delivery from PCO"), err)
|
2023-11-18 19:17:14 -05:00
|
|
|
}
|
|
|
|
|
2023-11-23 09:05:44 -05:00
|
|
|
if len(event) == 0 {
|
|
|
|
return "", fmt.Errorf("There are no events in the delivery. Something is wrong")
|
|
|
|
}
|
|
|
|
|
|
|
|
webhook, err := mongo.FindPcoSubscriptionForUser(*userObjectId, event[0].Name)
|
2023-11-18 19:17:14 -05:00
|
|
|
if err != nil {
|
2023-11-23 09:05:44 -05:00
|
|
|
return "", errors.Join(fmt.Errorf("Failed to find pco subscription for user: %s and event: %s", userObjectId.Hex(), event[0].Name), err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if webhook == nil {
|
|
|
|
return "", fmt.Errorf("Could not find subscription for user: %s and name %s", userObjectId.Hex(), event[0].Name)
|
2023-11-18 19:17:14 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
return webhook.Details.AuthenticitySecret, nil
|
|
|
|
}
|