Skip to content

Commit

Permalink
dynamically deserialize request log event payload request field (#705)
Browse files Browse the repository at this point in the history
* dynamically deserialize event payload request data

* rewind redundant rpc proto autogens

* address feedback
  • Loading branch information
suz-stripe authored Jul 21, 2021
1 parent b8415d0 commit 959df58
Show file tree
Hide file tree
Showing 5 changed files with 131 additions and 15 deletions.
48 changes: 48 additions & 0 deletions pkg/proxy/proxy.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"net/http"
"net/url"
"os"
"reflect"
"strconv"
"strings"
"time"
Expand Down Expand Up @@ -321,6 +322,15 @@ func (p *Proxy) processWebhookEvent(msg websocket.IncomingMessage) {
return
}

req, err := ExtractRequestData(evt.RequestData)

if err != nil {
p.cfg.Log.Debug("Received malformed event from Stripe, ignoring")
return
}

evt.Request = req

p.cfg.Log.WithFields(log.Fields{
"prefix": "proxy.Proxy.processWebhookEvent",
"webhook_id": webhookEvent.WebhookID,
Expand Down Expand Up @@ -517,6 +527,44 @@ func Init(cfg *Config) (*Proxy, error) {
return p, nil
}

// ExtractRequestData takes an interface with request data from a Stripe event payload
// and properly parses it into a StripeRequest struct before returning it
func ExtractRequestData(data interface{}) (StripeRequest, error) {
var req StripeRequest

reqDataValue := reflect.ValueOf(data)
// here we check the type of the RequestData as it could be either a string or a map
// this depends on which API version is in use when listening to events
// versions including and prior to 2017-05-25 present the request field as a string
switch reqDataValue.Kind() {
case reflect.String:
req = StripeRequest{
ID: data.(string),
IdempotencyKey: "",
}
case reflect.Map:
reqDataMap := reqDataValue.Interface().(map[string]interface{})

var id = ""
if rawID, ok := reqDataMap["id"]; ok && rawID != nil {
id = rawID.(string)
}

var idempotencyKey = ""
if rawKey, ok := reqDataMap["idempotency_key"]; ok && rawKey != nil {
idempotencyKey = rawKey.(string)
}

req = StripeRequest{
ID: id,
IdempotencyKey: idempotencyKey,
}
default:
return StripeRequest{}, errors.New("Received malformed event from Stripe")
}
return req, nil
}

//
// Private types
//
Expand Down
13 changes: 8 additions & 5 deletions pkg/proxy/stripeevent.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,25 @@ package proxy
import "fmt"

// StripeEvent is a representation of a Stripe `event` object
// we define RequestData as an interface for backwards compatibility
// Request will hold the deserialized request data
type StripeEvent struct {
Account string `json:"account"`
APIVersion string `json:"api_version"`
Created int `json:"created"`
Data map[string]interface{} `json:"data"`
ID string `json:"id"`
Livemode bool `json:"livemode"`
Request StripeRequestData `json:"request"`
PendingWebhooks int `json:"pending_webhooks"`
Type string `json:"type"`
RequestData interface{} `json:"request"`
Request StripeRequest
}

// StripeRequestData is a representation of the Request field in a Stripe `event` object
type StripeRequestData struct {
ID string `json:"id"`
IdempotencyKey string `json:"idempotency_key"`
// StripeRequest is a representation of the Request field in a Stripe `event` object
type StripeRequest struct {
ID string
IdempotencyKey string
}

// IsConnect return true or false if *StripeEvent is connect or not.
Expand Down
10 changes: 8 additions & 2 deletions pkg/rpcservice/events_resend.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,9 +62,15 @@ func (srv *RPCService) EventsResend(ctx context.Context, req *rpc.EventsResendRe
return nil, err
}

reqData, err := proxy.ExtractRequestData(evt.RequestData)

if err != nil {
return nil, err
}

request := rpc.StripeEvent_Request{
Id: evt.Request.ID,
IdempotencyKey: evt.Request.IdempotencyKey,
Id: reqData.ID,
IdempotencyKey: reqData.IdempotencyKey,
}

return &rpc.EventsResendResponse{
Expand Down
12 changes: 10 additions & 2 deletions pkg/rpcservice/listen.go
Original file line number Diff line number Diff line change
Expand Up @@ -183,10 +183,18 @@ func buildStripeEventResp(raw *proxy.StripeEvent) (*rpc.ListenResponse, error) {
if err != nil {
return nil, err
}

reqData, err := proxy.ExtractRequestData(raw.RequestData)

if err != nil {
return nil, err
}

request := rpc.StripeEvent_Request{
Id: raw.Request.ID,
IdempotencyKey: raw.Request.IdempotencyKey,
Id: reqData.ID,
IdempotencyKey: reqData.IdempotencyKey,
}

return &rpc.ListenResponse{
Content: &rpc.ListenResponse_StripeEvent{
StripeEvent: &rpc.StripeEvent{
Expand Down
63 changes: 57 additions & 6 deletions pkg/rpcservice/listen_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -110,9 +110,9 @@ func TestListenStreamsEvents(t *testing.T) {
"id": "cs_test_12345",
},
},
Request: proxy.StripeRequestData{
ID: "req_12345",
IdempotencyKey: "foo",
RequestData: map[string]interface{}{
"id": "req_12345",
"idempotency_key": "foo",
},
},
}
Expand Down Expand Up @@ -430,9 +430,9 @@ func TestBuildStripeEventResponseSucceeds(t *testing.T) {
"id": "cs_test_12345",
},
},
Request: proxy.StripeRequestData{
ID: "req_12345",
IdempotencyKey: "foo",
RequestData: map[string]interface{}{
"id": "req_12345",
"idempotency_key": "foo",
},
}

Expand Down Expand Up @@ -469,3 +469,54 @@ func TestBuildStripeEventResponseSucceeds(t *testing.T) {
assert.Nil(t, err)
assert.Equal(t, expected, actual)
}

func TestBuildLegacyStripeEventResponseSucceeds(t *testing.T) {
raw := &proxy.StripeEvent{
Account: "acct_12345",
APIVersion: "2017-04-06",
Created: 12345,
ID: "evt_12345",
Livemode: false,
PendingWebhooks: 2,
Type: "checkout.session.completed",
Data: map[string]interface{}{
"object": map[string]interface{}{
"id": "cs_test_12345",
},
},
RequestData: "req_12345",
}

expectedData, err := structpb.NewStruct(map[string]interface{}{
"object": map[string]interface{}{
"id": "cs_test_12345",
},
})
if err != nil {
t.Fatalf("Failed to create expected event data")
}

expected := &rpc.ListenResponse{
Content: &rpc.ListenResponse_StripeEvent{
StripeEvent: &rpc.StripeEvent{
Id: "evt_12345",
Account: "acct_12345",
ApiVersion: "2017-04-06",
Data: expectedData,
Type: "checkout.session.completed",
Created: 12345,
Livemode: false,
PendingWebhooks: 2,
Request: &rpc.StripeEvent_Request{
Id: "req_12345",
IdempotencyKey: "",
},
},
},
}

actual, err := buildStripeEventResp(raw)

assert.Nil(t, err)
assert.Equal(t, expected, actual)
}

0 comments on commit 959df58

Please sign in to comment.