From 1665054fed306e492945c499f1c772ecf7b56b26 Mon Sep 17 00:00:00 2001 From: jar-stripe Date: Wed, 23 Oct 2024 17:02:44 -0700 Subject: [PATCH] Update webhook API version validation (#1940) replaced api version check in webhook/client with isCompatibleApiVersion, which will test if the release identifier of the webhook event matches the pinned version, or return false for any event api version that does not have a release identifier --- webhook/client.go | 16 ++++++++++- webhook/client_test.go | 63 +++++++++++++++++++++++++++++++++++++++++- 2 files changed, 77 insertions(+), 2 deletions(-) diff --git a/webhook/client.go b/webhook/client.go index e85232c623..27169f2a1f 100644 --- a/webhook/client.go +++ b/webhook/client.go @@ -187,6 +187,20 @@ type signedHeader struct { // Private functions // +func isCompatibleAPIVersion(eventApiVersion string) bool { + // If the event api version is from before we started adding + // a release train, there's no way its compatible with this + // version + if !strings.Contains(eventApiVersion, ".") { + return false + } + + // versions are yyyy-MM-dd.train + var eventReleaseTrain = strings.Split(eventApiVersion, ".")[1] + var currentReleaseTrain = strings.Split(stripe.APIVersion, ".")[1] + return eventReleaseTrain == currentReleaseTrain +} + func constructEvent(payload []byte, sigHeader string, secret string, options ConstructEventOptions) (stripe.Event, error) { e := stripe.Event{} @@ -203,7 +217,7 @@ func constructEvent(payload []byte, sigHeader string, secret string, options Con return e, fmt.Errorf("Failed to parse webhook body json: %s", err.Error()) } - if !options.IgnoreAPIVersionMismatch && e.APIVersion != stripe.APIVersion { + if !options.IgnoreAPIVersionMismatch && !isCompatibleAPIVersion(e.APIVersion) { return e, fmt.Errorf("Received event with API version %s, but stripe-go %s expects API version %s. We recommend that you create a WebhookEndpoint with this API version. Otherwise, you can disable this error by using `ConstructEventWithOptions(..., ConstructEventOptions{..., ignoreAPIVersionMismatch: true})` but be wary that objects may be incorrectly deserialized.", e.APIVersion, stripe.ClientVersion, stripe.APIVersion) } diff --git a/webhook/client_test.go b/webhook/client_test.go index fd91a08a75..accc5dd634 100644 --- a/webhook/client_test.go +++ b/webhook/client_test.go @@ -15,11 +15,25 @@ var testPayload = []byte(fmt.Sprintf(`{ "object": "event", "api_version": "%s" }`, stripe.APIVersion)) + +var testPayloadWithNewVersionInReleaseTrain = []byte(fmt.Sprintf(`{ + "id": "evt_test_webhook", + "object": "event", + "api_version": "%s" + }`, "2099-10-10."+strings.Split(stripe.APIVersion, ".")[1])) + var testPayloadWithAPIVersionMismatch = []byte(`{ "id": "evt_test_webhook", "object": "event", "api_version": "2020-01-01" }`) + +var testPayloadWithReleaseTrainVersionMismatch = []byte(`{ + "id": "evt_test_webhook", + "object": "event", + "api_version": "2099-10-10.the_larch" + }`) + var testSecret = "whsec_test_secret" func newSignedPayload(options ...func(*SignedPayload)) *SignedPayload { @@ -179,7 +193,39 @@ func TestTokenNew(t *testing.T) { } } -func TestConstructEvent_ErrorOnAPIVersionMismatch(t *testing.T) { +func TestConstructEvent_SuccessOnExpectedAPIVersion(t *testing.T) { + p := newSignedPayload(func(p *SignedPayload) { + p.Payload = testPayload + }) + + evt, err := ConstructEvent(p.Payload, p.Header, p.Secret) + + if err != nil { + t.Errorf("Unexpected error in ConstructEvent: %v", err) + } + + if evt.APIVersion != stripe.APIVersion { + t.Errorf("Expected API versions to match") + } +} + +func TestConstructEvent_SuccessOnNewAPIVersionInExpectedReleaseTrain(t *testing.T) { + p := newSignedPayload(func(p *SignedPayload) { + p.Payload = testPayloadWithNewVersionInReleaseTrain + }) + + evt, err := ConstructEvent(p.Payload, p.Header, p.Secret) + + if err != nil { + t.Errorf("Unexpected error in ConstructEvent: %v", err) + } + + expectedSuffix := "." + strings.Split(stripe.APIVersion, ".")[1] + if !strings.HasSuffix(evt.APIVersion, expectedSuffix) { + t.Errorf("Expected API release trains to match") + } +} +func TestConstructEvent_ErrorOnLegacyAPIVersionMismatch(t *testing.T) { p := newSignedPayload(func(p *SignedPayload) { p.Payload = testPayloadWithAPIVersionMismatch }) @@ -195,6 +241,21 @@ func TestConstructEvent_ErrorOnAPIVersionMismatch(t *testing.T) { } } +func TestConstructEvent_ErrorOnReleaseTrainMismatch(t *testing.T) { + p := newSignedPayload(func(p *SignedPayload) { + p.Payload = testPayloadWithReleaseTrainVersionMismatch + }) + + _, err := ConstructEvent(p.Payload, p.Header, p.Secret) + + if err == nil { + t.Errorf("Expected error due to API version mismatch.") + } + + if !strings.Contains(err.Error(), "Received event with API version") { + t.Errorf("Expected API version mismatch error but received %v", err) + } +} func TestConstructEventWithOptions_IgnoreAPIVersionMismatch(t *testing.T) { p := newSignedPayload(func(p *SignedPayload) {