diff --git a/src/Stripe.net/Infrastructure/Public/StripeClient.cs b/src/Stripe.net/Infrastructure/Public/StripeClient.cs index 3fcac1a9a2..a9626414ff 100644 --- a/src/Stripe.net/Infrastructure/Public/StripeClient.cs +++ b/src/Stripe.net/Infrastructure/Public/StripeClient.cs @@ -7,6 +7,7 @@ namespace Stripe using System.Threading; using System.Threading.Tasks; using Newtonsoft.Json; + using Newtonsoft.Json.Linq; using Stripe.V2.Core; /// @@ -228,6 +229,15 @@ public EventNotification ParseEventNotification( { EventUtility.ValidateSignature(json, stripeSignatureHeader, secret, tolerance, DateTimeOffset.UtcNow.ToUnixTimeSeconds()); + var parsed = JObject.Parse(json); + var objectValue = (string)parsed["object"]; + if (objectValue == "event") + { + throw new ArgumentException( + "You passed a webhook payload to ParseEventNotification, which expects " + + "a thin event notification. Use EventUtility.ConstructEvent instead."); + } + return EventNotification.FromJson(json, this); } diff --git a/src/Stripe.net/Services/Events/EventUtility.cs b/src/Stripe.net/Services/Events/EventUtility.cs index 27e807227a..76bf855015 100644 --- a/src/Stripe.net/Services/Events/EventUtility.cs +++ b/src/Stripe.net/Services/Events/EventUtility.cs @@ -65,6 +65,17 @@ public static bool IsCompatibleApiVersion(string eventApiVersion) /// public static Event ParseEvent(string json, bool throwOnApiVersionMismatch = true) { + using (var doc = System.Text.Json.JsonDocument.Parse(json)) + { + if (doc.RootElement.TryGetProperty("object", out var objectProp) && + objectProp.GetString() == "v2.core.event") + { + throw new ArgumentException( + "You passed a thin event notification to ConstructEvent, which expects " + + "a webhook payload. Use StripeClient.ParseEventNotification instead."); + } + } + var stripeEvent = System.Text.Json.JsonSerializer.Deserialize( json, StripeConfiguration.SerializerOptions); diff --git a/src/StripeTests/Events/V2/EventTest.cs b/src/StripeTests/Events/V2/EventTest.cs index d46bb2228a..8f35cdcf06 100644 --- a/src/StripeTests/Events/V2/EventTest.cs +++ b/src/StripeTests/Events/V2/EventTest.cs @@ -24,7 +24,7 @@ public class EventTest : BaseStripeTest private static string v2UnknownEventPayload = @"{ ""id"": ""evt_234"", - ""object"": ""event"", + ""object"": ""v2.core.event"", ""type"": ""this.event.doesnt.exist"", ""created"": ""2022-02-15T00:27:45.330Z"", ""livemode"": true, @@ -45,7 +45,7 @@ public class EventTest : BaseStripeTest private static string v2KnownEventNoRelatedObjectPayload = @"{ ""id"": ""evt_234"", - ""object"": ""event"", + ""object"": ""v2.core.event"", ""type"": ""v1.billing.meter.no_meter_found"", ""created"": ""2022-02-15T00:27:45.330Z"", ""livemode"": true, @@ -54,7 +54,7 @@ public class EventTest : BaseStripeTest private static string v2KnownEventPayload = @"{ ""id"": ""evt_234"", - ""object"": ""event"", + ""object"": ""v2.core.event"", ""type"": ""v1.billing.meter.error_report_triggered"", ""created"": ""2022-02-15T00:27:45.330Z"", ""context"": ""context 123"", @@ -70,7 +70,7 @@ public class EventTest : BaseStripeTest private static string v2KnownEventWithDataPayload = @"{ ""id"": ""evt_234"", - ""object"": ""event"", + ""object"": ""v2.core.event"", ""type"": ""v1.billing.meter.error_report_triggered"", ""created"": ""2022-02-15T00:27:45.330Z"", ""context"": ""context 123"", @@ -106,7 +106,7 @@ public class EventTest : BaseStripeTest private static string v2KnownEventLivemodeFalsePayload = @"{ ""id"": ""evt_234"", - ""object"": ""event"", + ""object"": ""v2.core.event"", ""type"": ""v1.billing.meter.no_meter_found"", ""created"": ""2022-02-15T00:27:45.330Z"", ""livemode"": false, @@ -115,7 +115,7 @@ public class EventTest : BaseStripeTest private static string v2KnownEventWithReasonPayload = @"{ ""id"": ""evt_234"", - ""object"": ""event"", + ""object"": ""v2.core.event"", ""type"": ""v1.billing.meter.no_meter_found"", ""created"": ""2022-02-15T00:27:45.330Z"", ""livemode"": true, @@ -290,13 +290,34 @@ public void ParseEventNotificationWithInvalidSignature() Assert.Matches("header format is unexpected", exception.Message); } + [Fact] + public void RejectV1PayloadInParseEventNotification() + { + var v1Payload = @"{ + ""id"": ""evt_123"", + ""object"": ""event"", + ""type"": ""customer.created"", + ""api_version"": ""2017-05-25"", + ""created"": 1533204620, + ""livemode"": false + }"; + + var exception = Assert.Throws(() => + this.stripeClient.ParseEventNotification( + v1Payload, + GenerateSigHeader(v1Payload), + WebhookSecret)); + + Assert.Contains("EventUtility.ConstructEvent", exception.Message); + } + [Fact] public void ParseUnknownEventDirectly() { var stripeEvent = JsonUtils.DeserializeObject(v2UnknownEventPayload); Assert.NotNull(stripeEvent); Assert.Equal("evt_234", stripeEvent.Id); - Assert.Equal("event", stripeEvent.Object); + Assert.Equal("v2.core.event", stripeEvent.Object); Assert.Equal("this.event.doesnt.exist", stripeEvent.Type); Assert.Equal(new DateTime(2022, 2, 15, 0, 27, 45, 330, DateTimeKind.Utc), stripeEvent.Created); Assert.Null(stripeEvent.Requestor); diff --git a/src/StripeTests/Infrastructure/Public/StripeClientTest.cs b/src/StripeTests/Infrastructure/Public/StripeClientTest.cs index 68fc99ba0a..dceb8500b2 100644 --- a/src/StripeTests/Infrastructure/Public/StripeClientTest.cs +++ b/src/StripeTests/Infrastructure/Public/StripeClientTest.cs @@ -236,7 +236,7 @@ await this.stripeClient.RawRequestAsync( public void ConstructEventNotification() { string payload = @"{ - ""object"": ""event"", + ""object"": ""v2.core.event"", ""type"": ""unknown"", ""data"": {}, ""relatedObject"": { diff --git a/src/StripeTests/Services/Events/EventUtilityTest.cs b/src/StripeTests/Services/Events/EventUtilityTest.cs index f327e46d16..56a9ce84c5 100644 --- a/src/StripeTests/Services/Events/EventUtilityTest.cs +++ b/src/StripeTests/Services/Events/EventUtilityTest.cs @@ -1,5 +1,6 @@ namespace StripeTests { + using System; using Stripe; using Xunit; @@ -152,6 +153,44 @@ public void ValidateSignatureHandlesIncorrectHeaderValues(string headerValue) EventUtility.ValidateSignature("{}", headerValue, string.Empty)); } + [Fact] + public void RejectV2PayloadInParseEvent() + { + var v2Payload = @"{ + ""id"": ""evt_234"", + ""object"": ""v2.core.event"", + ""type"": ""v1.billing.meter.error_report_triggered"", + ""created"": ""2022-02-15T00:27:45.330Z"", + ""livemode"": true + }"; + + var exception = Assert.Throws(() => + EventUtility.ParseEvent(v2Payload, throwOnApiVersionMismatch: false)); + + Assert.Contains("StripeClient.ParseEventNotification", exception.Message); + } + + [Fact] + public void RejectV2PayloadInConstructEvent() + { + var v2Payload = @"{ + ""id"": ""evt_234"", + ""object"": ""v2.core.event"", + ""type"": ""v1.billing.meter.error_report_triggered"", + ""created"": ""2022-02-15T00:27:45.330Z"", + ""livemode"": true + }"; + + var timestamp = DateTimeOffset.UtcNow.ToUnixTimeSeconds(); + var signature = EventUtility.ComputeSignature(this.secret, timestamp.ToString(), v2Payload); + var sigHeader = $"t={timestamp},v1={signature}"; + + var exception = Assert.Throws(() => + EventUtility.ConstructEvent(v2Payload, sigHeader, this.secret, throwOnApiVersionMismatch: false)); + + Assert.Contains("StripeClient.ParseEventNotification", exception.Message); + } + [Theory] [InlineData("2024-2-31.acacia", "1999-03-31", false)] [InlineData("2024-2-31.acacia", "2025-03-31.basil", false)]