Skip to content

Add concurrency tests for locks #3185

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Apr 3, 2025
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<PackageId>Microsoft.IdentityModel.LoggingExtensions</PackageId>
<PackageTags>.NET;Windows;Authentication;Identity;Extensions;Logging</PackageTags>
<TargetFrameworks>netstandard2.0</TargetFrameworks>
<TargetFrameworks>netstandard2.0;net9.0</TargetFrameworks>
<Nullable>enable</Nullable>
</PropertyGroup>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,18 @@
// Licensed under the MIT License.

using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Globalization;
using System.Reflection;
using System.Security.Claims;
using System.Text;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.IdentityModel.TestUtils;
using Microsoft.IdentityModel.Tokens.Json.Tests;
using Microsoft.IdentityModel.Tokens;
using Microsoft.IdentityModel.Tokens.Json.Tests;
using Xunit;

namespace Microsoft.IdentityModel.JsonWebTokens.Tests
Expand Down Expand Up @@ -100,6 +103,43 @@ public void GetClaimAsType(JsonClaimSetTheoryData theoryData)
TestUtilities.AssertFailIfErrors(context);
}

// Tests a JsonClaimSet, to ensure the same List object is returned for concurrent calls to the Claims member.
[Fact]
public void ValidJsonClaimSet_ConcurrencyTest()
{
// Arrange
var numThreads = 10;
var barrier = new Barrier(numThreads);
var jsonClaims = new Dictionary<string, object>
{
{ "claim1", "value1" },
{ "claim2", "value2" }
};
var jsonClaimSet = new JsonClaimSet(jsonClaims);
ConcurrentBag<List<Claim>> allClaims = new();
List<Action> actions = new List<Action>();

for (int i = 0; i < numThreads; i++)
{
actions.Add(() =>
{
barrier.SignalAndWait();
allClaims.Add(jsonClaimSet.Claims("claim1"));
});
}

// Act
Parallel.Invoke(actions.ToArray());

// Assert
Assert.Equal(numThreads, allClaims.Count);
Assert.True(allClaims.TryTake(out List<Claim> controlClaims));
foreach (var claims in allClaims)
{
Assert.Same(controlClaims, claims);
}
}

public static TheoryData<JsonClaimSetTheoryData> GetClaimAsTypeTheoryData()
{
var theoryData = new TheoryData<JsonClaimSetTheoryData>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@
// Licensed under the MIT License.

using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.IdentityModel.TestUtils;
using Xunit;

Expand Down Expand Up @@ -280,6 +283,33 @@ public void UnwrapParameterCheck(KeyWrapTheoryData theoryData)
TestUtilities.AssertFailIfErrors(context);
}

// Tests that concurrent calls to WrapKey and UnwrapKey do not run into encrypt/decrypt lock contention issues or race conditions.
[Fact]
public void WrapAndUnwrapKey_ConcurrencyTest()
{
// Arrange
var numThreads = 10;
var barrier = new Barrier(numThreads);
var barrierTimeoutInMs = 5000;
var key = new SymmetricSecurityKey(new byte[32]);
var provider = new SymmetricKeyWrapProvider(key, SecurityAlgorithms.Aes256KW);
var actions = new List<Action>();

// Act and Assert
for (int i = 0; i < numThreads; i++)
{
actions.Add(() =>
{
barrier.SignalAndWait(barrierTimeoutInMs);
var wrappedKey = provider.WrapKey(new byte[32]);
Assert.NotNull(wrappedKey);
var unwrappedKey = provider.UnwrapKey(wrappedKey);
Assert.NotNull(unwrappedKey);
});
}
Parallel.Invoke(actions.ToArray());
}

public static TheoryData<KeyWrapTheoryData> UnwrapTheoryData()
{
var theoryData = new TheoryData<KeyWrapTheoryData>();
Expand Down
36 changes: 36 additions & 0 deletions test/Microsoft.IdentityModel.Tokens.Tests/SecurityKeyTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@
// Licensed under the MIT License.

using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.IdentityModel.TestUtils;
using Xunit;

Expand Down Expand Up @@ -210,6 +214,38 @@ public void CanComputeJwkThumbprint()
Assert.False(new CustomSecurityKey().CanComputeJwkThumbprint(), "CustomSecurityKey shouldn't be able to compute JWK thumbprint if CanComputeJwkThumbprint() is not overriden.");
}

// Tests a SecurityKey object, to ensure the InternalId is set exactly once when faced with concurrent calls.
[Fact]
public void InternalId_ConcurrencyTest()
{
// Arrange
var numThreads = 10;
var barrier = new Barrier(numThreads);
var key = new CustomSecurityKey();
ConcurrentBag<string> internalIds = new();
List<Action> actions = [];

for (int i = 0; i < numThreads; i++)
{
actions.Add(() =>
{
barrier.SignalAndWait();
internalIds.Add(key.InternalId);
});
}

// Act
Parallel.Invoke(actions.ToArray());

// Assert
Assert.Equal(numThreads, internalIds.Count);
Assert.True(internalIds.TryTake(out var internalId));
foreach (var id in internalIds)
{
Assert.Same(internalId, id);
}
}

public class SecurityKeyTheoryData : TheoryDataBase
{
public SecurityKey SecurityKey { get; set; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,12 @@
// Licensed under the MIT License.

using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Reflection;
using System.Security.Claims;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.IdentityModel.JsonWebTokens;
using Microsoft.IdentityModel.TestUtils;
using Xunit;
Expand Down Expand Up @@ -44,5 +48,37 @@ public void GetSets()

TestUtilities.AssertFailIfErrors("TokenValidationResultTests.GetSets", context.Errors);
}

// Ensure the same ClaimsIdentity object is returned when concurrent calls made to TokenValidationResult.
[Fact]
public void ClaimsIdentity_ConcurrencyTest()
{
// Arrange
var numThreads = 10;
var barrier = new Barrier(numThreads);
var result = new TokenValidationResult();
ConcurrentBag<ClaimsIdentity> allClaimsIdentity = new();
List<Action> actions = [];

for (int i = 0; i < numThreads; i++)
{
actions.Add(() =>
{
barrier.SignalAndWait();
allClaimsIdentity.Add(result.ClaimsIdentity);
});
}

// Act
Parallel.Invoke(actions.ToArray());

// Assert
Assert.Equal(numThreads, allClaimsIdentity.Count);
Assert.True(allClaimsIdentity.TryTake(out var controlClaimsIdentity));
foreach (var claimsIdentity in allClaimsIdentity)
{
Assert.Same(controlClaimsIdentity, claimsIdentity);
}
}
}
}
Loading