From 75229eb3df20ed7c3155f80b7d2ab8d0f6ce543e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ianar=C3=A9=20S=C3=A9vi?= Date: Mon, 7 Sep 2026 12:55:46 +0200 Subject: [PATCH] :bug: fix paramter verifications in HMAC method --- src/Mindee/Parsing/BaseLocalResponse.cs | 16 ++++++------ .../V2/Parsing/ErrorResponseTest.cs | 4 ++- tests/Mindee.UnitTests/V2/Parsing/JobTest.cs | 22 ++++++++-------- .../V2/Parsing/LocalResponseTest.cs | 25 +++++++++++++------ 4 files changed, 39 insertions(+), 28 deletions(-) diff --git a/src/Mindee/Parsing/BaseLocalResponse.cs b/src/Mindee/Parsing/BaseLocalResponse.cs index 3ea897df..25d0f4ec 100644 --- a/src/Mindee/Parsing/BaseLocalResponse.cs +++ b/src/Mindee/Parsing/BaseLocalResponse.cs @@ -18,7 +18,7 @@ public abstract class BaseLocalResponse /// /// Load from a string. /// - /// Will be decoded as UTF-8. + /// Assumes UTF-8 encoding. protected BaseLocalResponse(string input) { if (input == null) @@ -31,7 +31,7 @@ protected BaseLocalResponse(string input) /// /// Load from a byte buffer. /// - /// Assumes UTF-8 encoding. + /// Will be decoded as UTF-8. protected BaseLocalResponse(byte[] input) { if (input == null) @@ -48,9 +48,7 @@ protected BaseLocalResponse(byte[] input) /// Load from a Stream. /// This method will not close the provided stream. /// - /// - /// Assumes UTF-8 encoding. - /// + /// Will be decoded as UTF-8. protected BaseLocalResponse(Stream input) { if (input == null) @@ -127,15 +125,17 @@ public string GetHmacSignature(string secretKey) /// The signature from the "X-Signature" HTTP header. public bool IsValidHmacSignature(string secretKey, string signature) { - if (string.IsNullOrEmpty(signature)) - { + if (string.IsNullOrWhiteSpace(secretKey) || string.IsNullOrWhiteSpace(signature)) return false; - } string expectedSignature = GetHmacSignature(secretKey); + if (string.IsNullOrWhiteSpace(expectedSignature)) + return false; byte[] expectedBytes = Encoding.UTF8.GetBytes(expectedSignature); byte[] actualBytes = Encoding.UTF8.GetBytes(signature.ToLower()); + if (expectedBytes.Length != actualBytes.Length) + return false; return FixedTimeEquals(expectedBytes, actualBytes); } diff --git a/tests/Mindee.UnitTests/V2/Parsing/ErrorResponseTest.cs b/tests/Mindee.UnitTests/V2/Parsing/ErrorResponseTest.cs index 04e16339..79cd6600 100644 --- a/tests/Mindee.UnitTests/V2/Parsing/ErrorResponseTest.cs +++ b/tests/Mindee.UnitTests/V2/Parsing/ErrorResponseTest.cs @@ -2,9 +2,11 @@ namespace Mindee.UnitTests.V2.Parsing { + [Trait("Category", "V2")] + [Trait("Category", "Error Response")] public class ErrorResponseTest { - [Fact] + [Fact(DisplayName = "should load and pretty print an error response")] public void RstOutput_mustBeValid() { var localResponse = new LocalResponse( diff --git a/tests/Mindee.UnitTests/V2/Parsing/JobTest.cs b/tests/Mindee.UnitTests/V2/Parsing/JobTest.cs index 0de73ec7..6ec29ada 100644 --- a/tests/Mindee.UnitTests/V2/Parsing/JobTest.cs +++ b/tests/Mindee.UnitTests/V2/Parsing/JobTest.cs @@ -3,10 +3,17 @@ namespace Mindee.UnitTests.V2.Parsing { [Trait("Category", "V2")] - [Trait("Category", "Job")] + [Trait("Category", "Job Response")] public class JobTest { - [Fact] + private static JobResponse GetJob(string path) + { + var localResponse = new LocalResponse( + File.ReadAllText(Constants.V2ResourcePath + path)); + return localResponse.DeserializeJobResponse(); + } + + [Fact(DisplayName = "should load when status is Processing")] public void OkProcessing_MustHaveValidProperties() { var response = GetJob("job/ok_processing.json"); @@ -21,7 +28,7 @@ public void OkProcessing_MustHaveValidProperties() Assert.Equal("Processing", response.Job.Status); } - [Fact] + [Fact(DisplayName = "should load when status is Processed")] public void OkProcessed_WebhooksOk_MustHaveValidProperties() { var response = GetJob("job/ok_processed_webhooks_ok.json"); @@ -45,7 +52,7 @@ public void OkProcessed_WebhooksOk_MustHaveValidProperties() Assert.Null(webhook.Error); } - [Fact] + [Fact(DisplayName = "should load with 422 error")] public void Error_422_MustHaveValidProperties() { var response = GetJob("job/fail_422.json"); @@ -61,12 +68,5 @@ public void Error_422_MustHaveValidProperties() Assert.Contains("must be a valid", error.Errors.First().Detail); Assert.Equal("Failed", response.Job.Status); } - - private static JobResponse GetJob(string path) - { - var localResponse = new LocalResponse( - File.ReadAllText(Constants.V2ResourcePath + path)); - return localResponse.DeserializeJobResponse(); - } } } diff --git a/tests/Mindee.UnitTests/V2/Parsing/LocalResponseTest.cs b/tests/Mindee.UnitTests/V2/Parsing/LocalResponseTest.cs index 84e26a56..9217281e 100644 --- a/tests/Mindee.UnitTests/V2/Parsing/LocalResponseTest.cs +++ b/tests/Mindee.UnitTests/V2/Parsing/LocalResponseTest.cs @@ -13,11 +13,20 @@ public class LocalResponseTest private const string DummySecretKey = "ogNjY44MhvKPGTtVsI8zG82JqWQa68woYQH"; private const string FilePath = "extraction/standard_field_types.json"; + /// + /// Asserts that a local response is valid. + /// private static void AssertLocalResponse(LocalResponse localResponse, string fileContent) { - Assert.False(localResponse.IsValidHmacSignature(DummySecretKey, "invalid signature")); Assert.Equal(Signature, localResponse.GetHmacSignature(DummySecretKey)); + + Assert.False(localResponse.IsValidHmacSignature(DummySecretKey, "invalid signature")); + Assert.False(localResponse.IsValidHmacSignature(DummySecretKey, null)); + Assert.False(localResponse.IsValidHmacSignature(null, Signature)); + Assert.False(localResponse.IsValidHmacSignature(null, null)); + Assert.False(localResponse.IsValidHmacSignature(DummySecretKey, "")); Assert.True(localResponse.IsValidHmacSignature(DummySecretKey, Signature)); + Assert.True(localResponse.IsValidHmacSignature(DummySecretKey, Signature.ToUpper())); var response = localResponse.DeserializeResponse(); @@ -35,7 +44,7 @@ private static void AssertLocalResponse(LocalResponse localResponse, string file } [Fact(DisplayName = "should load a response from a JSON string")] - public void JsonString_mustLoadValidLocalResponse() + public void ValidString_mustLoadValidLocalResponse() { string filePath = Path.Combine(Constants.V2ProductPath, FilePath); string fileContent = File.ReadAllText(filePath); @@ -45,7 +54,7 @@ public void JsonString_mustLoadValidLocalResponse() } [Fact(DisplayName = "should load a response from a buffer")] - public void Buffer_mustLoadValidLocalResponse() + public void ValidBuffer_mustLoadValidLocalResponse() { string filePath = Path.Combine(Constants.V2ProductPath, FilePath); var localResponse = new LocalResponse(File.ReadAllBytes(filePath)); @@ -54,7 +63,7 @@ public void Buffer_mustLoadValidLocalResponse() } [Fact(DisplayName = "should load a response from a JSON file")] - public void JsonFile_mustLoadValidLocalResponse() + public void ValidFile_mustLoadValidLocalResponse() { string filePath = Path.Combine(Constants.V2ProductPath, FilePath); var localResponse = new LocalResponse(new FileInfo(filePath)); @@ -63,7 +72,7 @@ public void JsonFile_mustLoadValidLocalResponse() } [Fact(DisplayName = "should load a response from a stream")] - public void Stream_mustLoadValidLocalResponse() + public void ValidStream_mustLoadValidLocalResponse() { string filePath = Path.Combine(Constants.V2ProductPath, FilePath); using (var stream = File.OpenRead(filePath)) @@ -78,7 +87,7 @@ public void Stream_mustLoadValidLocalResponse() } [Fact(DisplayName = "should raise an exception when given an invalid JSON string")] - public void InvalidJsonString_mustRaiseException() + public void InvalidString_mustRaiseException() { var localResponse = new LocalResponse("{invalid json"); @@ -88,7 +97,7 @@ public void InvalidJsonString_mustRaiseException() } [Fact(DisplayName = "should raise an exception when given an empty value")] - public void EmptyString_mustRaiseException() + public void EmptyValue_mustRaiseException() { Assert.Throws( () => new LocalResponse("") @@ -102,7 +111,7 @@ public void EmptyString_mustRaiseException() } [Fact(DisplayName = "should raise an exception when given a null value")] - public void Null_mustRaiseException() + public void NullValue_mustRaiseException() { Assert.Throws( () => new LocalResponse((string?)null)