Skip to content

Commit c939be3

Browse files
authored
Add concurrency tests for locks (#3185)
1 parent bc84916 commit c939be3

File tree

5 files changed

+139
-2
lines changed

5 files changed

+139
-2
lines changed

src/Microsoft.IdentityModel.LoggingExtensions/Microsoft.IdentityModel.LoggingExtensions.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
<GenerateDocumentationFile>true</GenerateDocumentationFile>
99
<PackageId>Microsoft.IdentityModel.LoggingExtensions</PackageId>
1010
<PackageTags>.NET;Windows;Authentication;Identity;Extensions;Logging</PackageTags>
11-
<TargetFrameworks>netstandard2.0</TargetFrameworks>
11+
<TargetFrameworks>netstandard2.0;net9.0</TargetFrameworks>
1212
<Nullable>enable</Nullable>
1313
</PropertyGroup>
1414

test/Microsoft.IdentityModel.JsonWebTokens.Tests/JsonClaimSetTests.cs

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,11 @@
88
using System.Security.Claims;
99
using System.Text;
1010
using System.Text.Json;
11+
using System.Threading;
12+
using System.Threading.Tasks;
1113
using Microsoft.IdentityModel.TestUtils;
12-
using Microsoft.IdentityModel.Tokens.Json.Tests;
1314
using Microsoft.IdentityModel.Tokens;
15+
using Microsoft.IdentityModel.Tokens.Json.Tests;
1416
using Xunit;
1517

1618
namespace Microsoft.IdentityModel.JsonWebTokens.Tests
@@ -100,6 +102,44 @@ public void GetClaimAsType(JsonClaimSetTheoryData theoryData)
100102
TestUtilities.AssertFailIfErrors(context);
101103
}
102104

105+
// Tests a JsonClaimSet, to ensure the same List object is returned for concurrent calls to the Claims member.
106+
[Fact]
107+
public async Task ValidJsonClaimSet_ConcurrencyTest()
108+
{
109+
// Arrange
110+
var numThreads = 10;
111+
var barrier = new Barrier(numThreads);
112+
var jsonClaims = new Dictionary<string, object>
113+
{
114+
{ "claim1", "value1" },
115+
{ "claim2", "value2" }
116+
};
117+
var jsonClaimSet = new JsonClaimSet(jsonClaims);
118+
List<Claim>[] allClaims = new List<Claim>[numThreads];
119+
Task[] tasks = new Task[numThreads];
120+
121+
for (var i = 0; i < numThreads; i++)
122+
{
123+
var index = i;
124+
tasks[i] = (Task.Run(() =>
125+
{
126+
barrier.SignalAndWait();
127+
allClaims[index] = jsonClaimSet.Claims("claim1");
128+
}));
129+
}
130+
131+
// Act
132+
await Task.WhenAll(tasks);
133+
134+
// Assert
135+
Assert.All(allClaims, claims => Assert.NotNull(claims));
136+
var firstClaims = allClaims[0];
137+
for (var i = 1; i < numThreads; i++)
138+
{
139+
Assert.Same(firstClaims, allClaims[i]);
140+
}
141+
}
142+
103143
public static TheoryData<JsonClaimSetTheoryData> GetClaimAsTypeTheoryData()
104144
{
105145
var theoryData = new TheoryData<JsonClaimSetTheoryData>();

test/Microsoft.IdentityModel.Tokens.Tests/KeyWrapProviderTests.cs

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22
// Licensed under the MIT License.
33

44
using System;
5+
using System.Collections.Generic;
6+
using System.Threading;
7+
using System.Threading.Tasks;
58
using Microsoft.IdentityModel.TestUtils;
69
using Xunit;
710

@@ -280,6 +283,39 @@ public void UnwrapParameterCheck(KeyWrapTheoryData theoryData)
280283
TestUtilities.AssertFailIfErrors(context);
281284
}
282285

286+
// Tests that concurrent calls to WrapKey and UnwrapKey do not run into encrypt/decrypt lock contention issues or other race conditions.
287+
[Fact]
288+
public void WrapAndUnwrapKey_ConcurrencyTest()
289+
{
290+
// Arrange
291+
var numThreads = 10;
292+
var wrapBarrier = new Barrier(numThreads);
293+
var unwrapBarrier = new Barrier(numThreads);
294+
var barrierTimeoutInMs = 5000;
295+
var key = new SymmetricSecurityKey(new byte[32]);
296+
var provider = new SymmetricKeyWrapProvider(key, SecurityAlgorithms.Aes256KW);
297+
var tasks = new List<Task>(numThreads);
298+
299+
// Act and Assert
300+
for (int i = 0; i < numThreads; i++)
301+
{
302+
tasks.Add(Task.Run(() =>
303+
{
304+
// Wait for all threads to be ready before checking the WrapKey locks
305+
var keyBytes = new byte[32];
306+
wrapBarrier.SignalAndWait(barrierTimeoutInMs);
307+
var wrappedKey = provider.WrapKey(keyBytes);
308+
Assert.NotNull(wrappedKey);
309+
310+
// Wait for all threads to be ready before checking the UnwrapKey locks
311+
unwrapBarrier.SignalAndWait(barrierTimeoutInMs);
312+
var unwrappedKey = provider.UnwrapKey(wrappedKey);
313+
Assert.NotNull(unwrappedKey);
314+
}));
315+
}
316+
Task.WhenAll(tasks);
317+
}
318+
283319
public static TheoryData<KeyWrapTheoryData> UnwrapTheoryData()
284320
{
285321
var theoryData = new TheoryData<KeyWrapTheoryData>();

test/Microsoft.IdentityModel.Tokens.Tests/SecurityKeyTests.cs

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
// Licensed under the MIT License.
33

44
using System;
5+
using System.Threading;
6+
using System.Threading.Tasks;
57
using Microsoft.IdentityModel.TestUtils;
68
using Xunit;
79

@@ -210,6 +212,39 @@ public void CanComputeJwkThumbprint()
210212
Assert.False(new CustomSecurityKey().CanComputeJwkThumbprint(), "CustomSecurityKey shouldn't be able to compute JWK thumbprint if CanComputeJwkThumbprint() is not overriden.");
211213
}
212214

215+
// Tests a SecurityKey object, to ensure the InternalId is set exactly once when faced with concurrent calls.
216+
[Fact]
217+
public async Task InternalId_ConcurrencyTest()
218+
{
219+
// Arrange
220+
var numTasks = 10;
221+
var barrier = new Barrier(numTasks);
222+
var key = new CustomSecurityKey();
223+
string[] internalIds = new string[numTasks];
224+
Task[] tasks = new Task[numTasks];
225+
226+
for (int i = 0; i < numTasks; i++)
227+
{
228+
var index = i;
229+
tasks[i] = Task.Run(() =>
230+
{
231+
barrier.SignalAndWait();
232+
internalIds[index] = key.InternalId;
233+
});
234+
}
235+
236+
// Act
237+
await Task.WhenAll(tasks);
238+
239+
// Assert
240+
Assert.All(internalIds, id => Assert.NotNull(id));
241+
var firstId = internalIds[0];
242+
for (int i = 1; i < numTasks; i++)
243+
{
244+
Assert.Same(firstId, internalIds[i]);
245+
}
246+
}
247+
213248
public class SecurityKeyTheoryData : TheoryDataBase
214249
{
215250
public SecurityKey SecurityKey { get; set; }

test/Microsoft.IdentityModel.Tokens.Tests/TokenValidationResultTests.cs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
using System;
55
using System.Collections.Generic;
66
using System.Reflection;
7+
using System.Threading;
8+
using System.Threading.Tasks;
79
using Microsoft.IdentityModel.JsonWebTokens;
810
using Microsoft.IdentityModel.TestUtils;
911
using Xunit;
@@ -44,5 +46,29 @@ public void GetSets()
4446

4547
TestUtilities.AssertFailIfErrors("TokenValidationResultTests.GetSets", context.Errors);
4648
}
49+
50+
// Ensure setting the ClaimsIdentity object simultaneously doesn't cause lock contention or other concurrency issues.
51+
[Fact]
52+
public async Task ClaimsIdentity_ConcurrencyTest()
53+
{
54+
// Arrange
55+
var numThreads = 10;
56+
var barrier = new Barrier(numThreads);
57+
var result = new TokenValidationResult();
58+
var claimsIdentity = new CaseSensitiveClaimsIdentity(Default.PayloadClaims);
59+
Task[] tasks = new Task[numThreads];
60+
61+
for (int i = 0; i < numThreads; i++)
62+
{
63+
tasks[i] = Task.Run(() =>
64+
{
65+
barrier.SignalAndWait();
66+
result.ClaimsIdentity = claimsIdentity;
67+
});
68+
}
69+
70+
// Act and implicit Assert as any exception will cause the test to fail
71+
await Task.WhenAll(tasks);
72+
}
4773
}
4874
}

0 commit comments

Comments
 (0)