From e67b25be77532af9ba405670b34b4d263d505fde Mon Sep 17 00:00:00 2001 From: msbw2 Date: Mon, 22 Jul 2024 16:14:18 -0700 Subject: [PATCH] Fix string comparison with differing timestamps (#2739) --- .../IdentityComparer.cs | 37 +++++++++++++++++-- .../IdentityComparerTests.cs | 10 +++++ 2 files changed, 43 insertions(+), 4 deletions(-) diff --git a/test/Microsoft.IdentityModel.TestUtils/IdentityComparer.cs b/test/Microsoft.IdentityModel.TestUtils/IdentityComparer.cs index 47b868d8a4..501275d644 100644 --- a/test/Microsoft.IdentityModel.TestUtils/IdentityComparer.cs +++ b/test/Microsoft.IdentityModel.TestUtils/IdentityComparer.cs @@ -19,6 +19,7 @@ using System.Security.Cryptography; using System.Security.Cryptography.X509Certificates; using System.Text.Json; +using System.Text.RegularExpressions; using Microsoft.IdentityModel.JsonWebTokens; using Microsoft.IdentityModel.Protocols; using Microsoft.IdentityModel.Protocols.OpenIdConnect; @@ -1601,6 +1602,8 @@ public static bool AreStringsEqual(object object1, object object2, CompareContex return AreStringsEqual(object1, object2, "str1", "str2", context); } + private static readonly Regex timestampRegex = new Regex(@"\d{1,2}:\d{2}:\d{2}", RegexOptions.Compiled); + public static bool AreStringsEqual(object object1, object object2, string name1, string name2, CompareContext context) { var localContext = new CompareContext(context); @@ -1624,10 +1627,36 @@ public static bool AreStringsEqual(object object1, object object2, string name1, if (!string.Equals(str1, str2, context.StringComparison)) { - localContext.Diffs.Add($"'{name1}' != '{name2}', StringComparison: '{context.StringComparison}'"); - localContext.Diffs.Add($"'{str1}'"); - localContext.Diffs.Add($"!="); - localContext.Diffs.Add($"'{str2}'"); + // Try to find timestamps in the strings which might differ due to skew and compare them with epsilon. + MatchCollection match1 = timestampRegex.Matches(str1); + MatchCollection match2 = timestampRegex.Matches(str2); + int matched = 0; + // Check that at least one timestamp matched and both strings matched the same number of timestamps. + if (match1.Count != 0 && match1.Count == match2.Count) + { + for (int i = 0; i < match1.Count; i++) + { + string time1 = match1[i].Value; + string time2 = match2[i].Value; + // If the matches match string-wise exactly or are parseable DateTimes and are equal within epsilon count as matched. + if (time1 == time2 || + (DateTime.TryParse(time1, out DateTime datetime1) && + DateTime.TryParse(time2, out DateTime datetime2) && + AreDatesEqualWithEpsilon(datetime1, datetime2, 1))) + { + matched++; + } + } + } + + // If no fancy timestamp comparison happened or some timestamps didn't match add the diff. + if (matched == 0 || matched != match1.Count) + { + localContext.Diffs.Add($"'{name1}' != '{name2}', StringComparison: '{context.StringComparison}'"); + localContext.Diffs.Add($"'{str1}'"); + localContext.Diffs.Add($"!="); + localContext.Diffs.Add($"'{str2}'"); + } } return context.Merge(localContext); diff --git a/test/Microsoft.IdentityModel.Tokens.Tests/IdentityComparerTests.cs b/test/Microsoft.IdentityModel.Tokens.Tests/IdentityComparerTests.cs index ce17447c1c..15b0d16fb8 100644 --- a/test/Microsoft.IdentityModel.Tokens.Tests/IdentityComparerTests.cs +++ b/test/Microsoft.IdentityModel.Tokens.Tests/IdentityComparerTests.cs @@ -580,6 +580,16 @@ public void CompareStrings() Assert.True(context.Diffs[3] == $"'{string2}'"); } + [Fact] + public void CompareStringsWithTimestamps() + { + TestUtilities.WriteHeader($"{this}.{nameof(CompareStringsWithTimestamps)}", true); + var context = new CompareContext($"{this}.{nameof(CompareStringsWithTimestamps)}"); + DateTime now = DateTime.UtcNow; + IdentityComparer.AreEqual($"{now:HH:mm:ss} {now.AddSeconds(1):HH:mm:ss}", $"{now.AddSeconds(1):HH:mm:ss} {now:HH:mm:ss}", context); + Assert.Empty(context.Diffs); + } + [Fact] public void CompareSymmetricSecurityKeys() {