Skip to content

Commit

Permalink
Update webhook API version validation (#1940)
Browse files Browse the repository at this point in the history
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
  • Loading branch information
jar-stripe authored Oct 24, 2024
1 parent be11c5f commit 1665054
Show file tree
Hide file tree
Showing 2 changed files with 77 additions and 2 deletions.
16 changes: 15 additions & 1 deletion webhook/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -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{}

Expand All @@ -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)
}

Expand Down
63 changes: 62 additions & 1 deletion webhook/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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
})
Expand All @@ -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) {
Expand Down

0 comments on commit 1665054

Please sign in to comment.