Skip to content

Commit

Permalink
Cherry pick Cache context switches (#2724) to 7x (#2726)
Browse files Browse the repository at this point in the history
* Cache context switches (#2724)

* Cache context switches.

* Refactor switch reset in tests.

* Rename.

* Refactor to reset switches in one method.

* Fix tests.

* Fix tests.

* Add other context switches.

* Fix.

* Rename.

* reset state in CreateClaimsIdentity_ReturnsClaimsIdentity_ByDefault

---------

Co-authored-by: Keegan Caruso <keegancaruso@microsoft.com>
  • Loading branch information
pmaytak and Keegan Caruso authored Jul 17, 2024
1 parent 8405847 commit a75d614
Show file tree
Hide file tree
Showing 12 changed files with 108 additions and 73 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -609,7 +609,7 @@ internal static string GetStringClaimValueType(string str)

internal static string GetStringClaimValueType(string str, string claimType)
{
if (!string.IsNullOrEmpty(claimType) && !JsonSerializerPrimitives.TryAllStringClaimsAsDateTime() && JsonSerializerPrimitives.IsKnownToNotBeDateTime(claimType))
if (!string.IsNullOrEmpty(claimType) && !AppContextSwitches.TryAllStringClaimsAsDateTime && JsonSerializerPrimitives.IsKnownToNotBeDateTime(claimType))
return ClaimValueTypes.String;

if (DateTime.TryParse(str, out DateTime dateTimeValue))
Expand Down
62 changes: 61 additions & 1 deletion src/Microsoft.IdentityModel.Tokens/AppContextSwitches.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,66 @@ internal static class AppContextSwitches
/// </summary>
internal const string UseCaseSensitiveClaimsIdentityTypeSwitch = "Microsoft.IdentityModel.Tokens.UseCaseSensitiveClaimsIdentity";

internal static bool UseCaseSensitiveClaimsIdentityType() => (AppContext.TryGetSwitch(UseCaseSensitiveClaimsIdentityTypeSwitch, out bool useCaseSensitiveClaimsIdentityType) && useCaseSensitiveClaimsIdentityType);
private static bool? _useCaseSensitiveClaimsIdentityType;

internal static bool UseCaseSensitiveClaimsIdentityType => _useCaseSensitiveClaimsIdentityType ??= (AppContext.TryGetSwitch(UseCaseSensitiveClaimsIdentityTypeSwitch, out bool useCaseSensitiveClaimsIdentityType) && useCaseSensitiveClaimsIdentityType);

/// <summary>
/// When validating the issuer signing key, specifies whether to fail if the 'tid' claim is missing.
/// </summary>
internal const string DoNotFailOnMissingTidSwitch = "Switch.Microsoft.IdentityModel.DontFailOnMissingTidValidateIssuerSigning";

private static bool? _doNotFailOnMissingTid;

internal static bool DoNotFailOnMissingTid => _doNotFailOnMissingTid ??= (AppContext.TryGetSwitch(DoNotFailOnMissingTidSwitch, out bool doNotFailOnMissingTid) && doNotFailOnMissingTid);

/// <summary>
/// When reading claims from the token, specifies whether to try to convert all string claims to DateTime.
/// Some claims are known not to be DateTime, so conversion is skipped.
/// </summary>
internal const string TryAllStringClaimsAsDateTimeSwitch = "Switch.Microsoft.IdentityModel.TryAllStringClaimsAsDateTime";

private static bool? _tryAllStringClaimsAsDateTime;

internal static bool TryAllStringClaimsAsDateTime => _tryAllStringClaimsAsDateTime ??= (AppContext.TryGetSwitch(TryAllStringClaimsAsDateTimeSwitch, out bool tryAsDateTime) && tryAsDateTime);

/// <summary>
/// Controls whether to validate the length of the authentication tag when decrypting a token.
/// </summary>
internal const string SkipValidationOfAuthenticationTagLengthSwitch = "Switch.Microsoft.IdentityModel.SkipAuthenticationTagLengthValidation";

private static bool? _skipValidationOfAuthenticationTagLength;

internal static bool ShouldValidateAuthenticationTagLength => _skipValidationOfAuthenticationTagLength ??= !(AppContext.TryGetSwitch(SkipValidationOfAuthenticationTagLengthSwitch, out bool skipValidation) && skipValidation);

/// <summary>
/// Controls whether to use the short name for the RSA OAEP key wrap algorithm.
/// </summary>
internal const string UseShortNameForRsaOaepKeySwitch = "Switch.Microsoft.IdentityModel.UseShortNameForRsaOaepKey";

private static bool? _useShortNameForRsaOaepKey;

internal static bool ShouldUseShortNameForRsaOaepKey => _useShortNameForRsaOaepKey ??= AppContext.TryGetSwitch(UseShortNameForRsaOaepKeySwitch, out var useKeyWrap) && useKeyWrap;

/// <summary>
/// Used for testing to reset all switches to its default value.
/// </summary>
internal static void ResetAllSwitches()
{
_useCaseSensitiveClaimsIdentityType = null;
AppContext.SetSwitch(UseCaseSensitiveClaimsIdentityTypeSwitch, false);

_doNotFailOnMissingTid = null;
AppContext.SetSwitch(DoNotFailOnMissingTidSwitch, false);

_tryAllStringClaimsAsDateTime = null;
AppContext.SetSwitch(TryAllStringClaimsAsDateTimeSwitch, false);

_skipValidationOfAuthenticationTagLength = null;
AppContext.SetSwitch(SkipValidationOfAuthenticationTagLengthSwitch, false);

_useShortNameForRsaOaepKey = null;
AppContext.SetSwitch(UseShortNameForRsaOaepKeySwitch, false);
}
}
}
6 changes: 3 additions & 3 deletions src/Microsoft.IdentityModel.Tokens/ClaimsIdentityFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,23 +13,23 @@ internal static class ClaimsIdentityFactory
{
internal static ClaimsIdentity Create(IEnumerable<Claim> claims)
{
if (AppContextSwitches.UseCaseSensitiveClaimsIdentityType())
if (AppContextSwitches.UseCaseSensitiveClaimsIdentityType)
return new CaseSensitiveClaimsIdentity(claims);

return new ClaimsIdentity(claims);
}

internal static ClaimsIdentity Create(IEnumerable<Claim> claims, string authenticationType)
{
if (AppContextSwitches.UseCaseSensitiveClaimsIdentityType())
if (AppContextSwitches.UseCaseSensitiveClaimsIdentityType)
return new CaseSensitiveClaimsIdentity(claims, authenticationType);

return new ClaimsIdentity(claims, authenticationType);
}

internal static ClaimsIdentity Create(string authenticationType, string nameType, string roleType, SecurityToken securityToken)
{
if (AppContextSwitches.UseCaseSensitiveClaimsIdentityType())
if (AppContextSwitches.UseCaseSensitiveClaimsIdentityType)
return new CaseSensitiveClaimsIdentity(authenticationType: authenticationType, nameType: nameType, roleType: roleType)
{
SecurityToken = securityToken,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@ private struct AuthenticatedKeys
private DecryptionDelegate DecryptFunction;
private EncryptionDelegate EncryptFunction;
private const string _className = "Microsoft.IdentityModel.Tokens.AuthenticatedEncryptionProvider";
internal const string _skipValidationOfAuthenticationTagLength = "Switch.Microsoft.IdentityModel.SkipAuthenticationTagLengthValidation";

/// <summary>
/// Initializes a new instance of the <see cref="AuthenticatedEncryptionProvider"/> class used for encryption and decryption.
Expand Down Expand Up @@ -167,7 +166,7 @@ private AuthenticatedEncryptionResult EncryptWithAesCbc(byte[] plaintext, byte[]
private byte[] DecryptWithAesCbc(byte[] ciphertext, byte[] authenticatedData, byte[] iv, byte[] authenticationTag)
{
// Verify authentication Tag
if (ShouldValidateAuthenticationTagLength()
if (AppContextSwitches.ShouldValidateAuthenticationTagLength
&& SymmetricSignatureProvider.ExpectedSignatureSizeInBytes.TryGetValue(Algorithm, out int expectedTagLength)
&& expectedTagLength != authenticationTag.Length)
throw LogHelper.LogExceptionMessage(new SecurityTokenDecryptionFailedException(
Expand Down Expand Up @@ -197,11 +196,6 @@ private byte[] DecryptWithAesCbc(byte[] ciphertext, byte[] authenticatedData, by
}
}

private static bool ShouldValidateAuthenticationTagLength()
{
return !(AppContext.TryGetSwitch(_skipValidationOfAuthenticationTagLength, out bool skipValidation) && skipValidation);
}

private AuthenticatedKeys CreateAuthenticatedKeys()
{
ValidateKeySize(Key, Algorithm);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ internal static object CreateObjectFromJsonElement(JsonElement jsonElement, int

if (jsonElement.ValueKind == JsonValueKind.String)
{
if (!string.IsNullOrEmpty(claimType) && !TryAllStringClaimsAsDateTime() && IsKnownToNotBeDateTime(claimType))
if (!string.IsNullOrEmpty(claimType) && !AppContextSwitches.TryAllStringClaimsAsDateTime && IsKnownToNotBeDateTime(claimType))
return jsonElement.GetString();

if (DateTime.TryParse(jsonElement.GetString(), CultureInfo.InvariantCulture, DateTimeStyles.RoundtripKind, out DateTime dateTime))
Expand Down Expand Up @@ -706,13 +706,6 @@ internal static string ReadStringOrNumberAsString(ref Utf8JsonReader reader, str
return retVal;
}

internal const string TryToCreateDateTimeClaimsSwitch = "Switch.Microsoft.IdentityModel.TryAllStringClaimsAsDateTime";

public static bool TryAllStringClaimsAsDateTime()
{
return (AppContext.TryGetSwitch(TryToCreateDateTimeClaimsSwitch, out bool tryAsDateTime) && tryAsDateTime);
}

/// <summary>
/// This is a non-exhaustive list of claim types that are not expected to be DateTime values
/// sourced from expected Entra V1 and V2 claims, OpenID Connect claims, and a selection of
Expand Down Expand Up @@ -834,7 +827,7 @@ internal static object ReadStringAsObject(ref Utf8JsonReader reader, string prop

string originalString = reader.GetString();

if (!TryAllStringClaimsAsDateTime() && IsKnownToNotBeDateTime(propertyName))
if (!AppContextSwitches.TryAllStringClaimsAsDateTime && IsKnownToNotBeDateTime(propertyName))
{
reader.Read();
return originalString;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,6 @@ namespace Microsoft.IdentityModel.Tokens
/// </summary>
public class X509EncryptingCredentials : EncryptingCredentials
{
internal const string _useShortNameForRsaOaepKey = "Switch.Microsoft.IdentityModel.UseShortNameForRsaOaepKey";

/// <summary>
/// Designed to construct <see cref="EncryptingCredentials"/> based on a x509 certificate.
/// </summary>
Expand Down Expand Up @@ -53,12 +51,7 @@ public X509Certificate2 Certificate

private static string GetEncryptionAlgorithm()
{
return ShouldUseShortNameForRsaOaepKey() ? SecurityAlgorithms.RsaOAEP : SecurityAlgorithms.DefaultAsymmetricKeyWrapAlgorithm;
}

private static bool ShouldUseShortNameForRsaOaepKey()
{
return AppContext.TryGetSwitch(_useShortNameForRsaOaepKey, out var useKeyWrap) && useKeyWrap;
return AppContextSwitches.ShouldUseShortNameForRsaOaepKey ? SecurityAlgorithms.RsaOAEP : SecurityAlgorithms.DefaultAsymmetricKeyWrapAlgorithm;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -45,13 +45,6 @@ public static void EnableAadSigningKeyIssuerValidation(this TokenValidationParam
};
}

internal const string DontFailOnMissingTidSwitch = "Switch.Microsoft.IdentityModel.DontFailOnMissingTidValidateIssuerSigning";

private static bool DontFailOnMissingTid()
{
return (AppContext.TryGetSwitch(DontFailOnMissingTidSwitch, out bool dontFailOnMissingTid) && dontFailOnMissingTid);
}

/// <summary>
/// Validates the issuer signing key.
/// </summary>
Expand Down Expand Up @@ -81,7 +74,7 @@ internal static bool ValidateIssuerSigningKey(SecurityKey securityKey, SecurityT
var tenantIdFromToken = GetTid(securityToken);
if (string.IsNullOrEmpty(tenantIdFromToken))
{
if (DontFailOnMissingTid())
if (AppContextSwitches.DoNotFailOnMissingTid)
return true;

throw LogHelper.LogExceptionMessage(new SecurityTokenInvalidIssuerException(LogMessages.IDX40009));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ public void CreateClaimsIdentity_ReturnsClaimsIdentity_ByDefault()
handler.MapInboundClaims = true;
actualClaimsIdentity = handler.CreateClaimsIdentityInternal(jsonWebToken, tokenValidationParameters, Default.Issuer);
Assert.IsType<ClaimsIdentity>(actualClaimsIdentity);

AppContextSwitches.ResetAllSwitches();
}

[Fact]
Expand Down Expand Up @@ -61,7 +63,7 @@ public void CreateClaimsIdentity_ReturnsCaseSensitiveClaimsIdentity_WithAppConte
Assert.IsType<CaseSensitiveClaimsIdentity>(actualClaimsIdentity);
Assert.NotNull(((CaseSensitiveClaimsIdentity)actualClaimsIdentity).SecurityToken);

AppContext.SetSwitch(AppContextSwitches.UseCaseSensitiveClaimsIdentityTypeSwitch, false);
AppContextSwitches.ResetAllSwitches();
}

private class DerivedJsonWebTokenHandler : JsonWebTokenHandler
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4192,9 +4192,11 @@ public static TheoryData<CreateTokenTheoryData> IncludeSecurityTokenOnFailureTes
[Theory, MemberData(nameof(ValidateAuthenticationTagLengthTheoryData))]
public void ValidateTokenAsync_ModifiedAuthNTag(CreateTokenTheoryData theoryData)
{
// arrange
AppContext.SetSwitch(AuthenticatedEncryptionProvider._skipValidationOfAuthenticationTagLength, theoryData.EnableAppContextSwitch);
var payload = new JObject()
try
{
// arrange
AppContext.SetSwitch(AppContextSwitches.SkipValidationOfAuthenticationTagLengthSwitch, theoryData.EnableAppContextSwitch);
var payload = new JObject()
{
{ JwtRegisteredClaimNames.Email, "Bob@contoso.com" },
{ JwtRegisteredClaimNames.GivenName, "Bob" },
Expand All @@ -4205,24 +4207,29 @@ public void ValidateTokenAsync_ModifiedAuthNTag(CreateTokenTheoryData theoryData
{ JwtRegisteredClaimNames.Exp, EpochTime.GetIntDate(DateTime.Now.AddDays(1)).ToString() },
}.ToString();

var jsonWebTokenHandler = new JsonWebTokenHandler();
var signingCredentials = Default.SymmetricSigningCredentials;
var jsonWebTokenHandler = new JsonWebTokenHandler();
var signingCredentials = Default.SymmetricSigningCredentials;

if (SupportedAlgorithms.IsAesGcm(theoryData.Algorithm))
{
theoryData.EncryptingCredentials.CryptoProviderFactory = new CryptoProviderFactoryForGcm();
}
if (SupportedAlgorithms.IsAesGcm(theoryData.Algorithm))
{
theoryData.EncryptingCredentials.CryptoProviderFactory = new CryptoProviderFactoryForGcm();
}

var jwe = jsonWebTokenHandler.CreateToken(payload, signingCredentials, theoryData.EncryptingCredentials);
var jweWithExtraCharacters = jwe + "_cannoli_hunts_truffles_";
var jwe = jsonWebTokenHandler.CreateToken(payload, signingCredentials, theoryData.EncryptingCredentials);
var jweWithExtraCharacters = jwe + "_cannoli_hunts_truffles_";

// act
// calling ValidateTokenAsync.Result to prevent tests from sharing app context switch property
// normally, we would want to await ValidateTokenAsync().ConfigureAwait(false)
var tokenValidationResult = jsonWebTokenHandler.ValidateTokenAsync(jweWithExtraCharacters, theoryData.ValidationParameters).Result;
// act
// calling ValidateTokenAsync.Result to prevent tests from sharing app context switch property
// normally, we would want to await ValidateTokenAsync().ConfigureAwait(false)
var tokenValidationResult = jsonWebTokenHandler.ValidateTokenAsync(jweWithExtraCharacters, theoryData.ValidationParameters).Result;

// assert
Assert.Equal(theoryData.IsValid, tokenValidationResult.IsValid);
// assert
Assert.Equal(theoryData.IsValid, tokenValidationResult.IsValid);
}
finally
{
AppContextSwitches.ResetAllSwitches();
}
}

public static TheoryData<CreateTokenTheoryData> ValidateAuthenticationTagLengthTheoryData()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ public void Create_FromTokenValidationParameters_ReturnsCorrectClaimsIdentity(bo
Assert.IsType<ClaimsIdentity>(actualClaimsIdentity);
}

AppContext.SetSwitch(AppContextSwitches.UseCaseSensitiveClaimsIdentityTypeSwitch, false);
AppContextSwitches.ResetAllSwitches();
}

[Theory]
Expand Down
Loading

0 comments on commit a75d614

Please sign in to comment.